Add generic OpenAI-compatible provider for cheap/local model support

This commit is contained in:
Himanshu Dongre 2026-04-13 16:56:34 +05:30
parent 318812d527
commit 5f5e15c7aa
4 changed files with 56 additions and 1 deletions

View file

@ -27,5 +27,13 @@ DATABASE_URL=postgresql://smriti:smriti@localhost:5432/smriti
# Default model for background intelligence (extraction, draft, review)
OPENAI_MODEL=gpt-4o-mini
# Generic OpenAI-compatible provider (Ollama, LM Studio, vLLM, Together, etc.)
# Set the API URL to use a local or cheap model instead of OpenAI/Anthropic.
# API key is optional — local servers like Ollama don't require one.
# Then set background_intelligence.provider to "generic" in providers.yaml.
# SMRITI_GENERIC_API_URL=http://localhost:11434/v1
# SMRITI_GENERIC_MODEL=llama3.1:8b
# SMRITI_GENERIC_API_KEY=
# App
DEBUG=false

View file

@ -184,6 +184,12 @@ cp .env.example .env
# Leave keys commented out to use mock mode (no real LLM calls).
# .env is the primary config path. config/providers.yaml is optional
# and gitignored — you do not need it for a standard setup.
#
# Using Ollama or another local model? Set these instead:
# SMRITI_GENERIC_API_URL=http://localhost:11434/v1
# SMRITI_GENERIC_MODEL=llama3.1:8b
# Then set background_intelligence.provider to "generic" in
# config/providers.yaml. See .env.example for details.
docker compose up -d postgres # start the database
make setup # backend venv + deps + migrations + CLI + frontend

View file

@ -65,6 +65,7 @@ class AppProviderConfig:
openai: ProviderConfig = field(default_factory=ProviderConfig)
anthropic: ProviderConfig = field(default_factory=ProviderConfig)
openrouter: ProviderConfig = field(default_factory=ProviderConfig)
generic: ProviderConfig = field(default_factory=ProviderConfig)
chat: ChatConfig = field(default_factory=ChatConfig)
background: BackgroundConfig = field(default_factory=BackgroundConfig)
@ -166,6 +167,29 @@ def load_config() -> AppProviderConfig:
base_url_default="https://openrouter.ai/api/v1",
)
# Generic OpenAI-compatible provider (Ollama, LM Studio, vLLM, Together,
# Groq, etc.). Enabled when base_url is set — api_key is optional because
# local servers like Ollama don't require one.
g = providers_raw.get("generic", {})
generic_base_url = (
os.environ.get("SMRITI_GENERIC_API_URL")
or g.get("base_url", "")
)
generic_api_key = (
os.environ.get("SMRITI_GENERIC_API_KEY")
or g.get("api_key", "")
)
generic_model = (
os.environ.get("SMRITI_GENERIC_MODEL")
or g.get("default_model", "")
)
generic = ProviderConfig(
enabled=bool(generic_base_url),
api_key=generic_api_key or "not-required",
default_model=generic_model,
base_url=generic_base_url,
)
chat = ChatConfig(
default_provider=os.environ.get("SMRITI_DEFAULT_PROVIDER")
or chat_raw.get("default_provider", "openrouter"),
@ -183,6 +207,7 @@ def load_config() -> AppProviderConfig:
openai=openai,
anthropic=anthropic,
openrouter=openrouter,
generic=generic,
chat=chat,
background=background,
)
@ -248,7 +273,7 @@ def providers_status() -> dict[str, dict]:
"status_label": "Ready" if getattr(cfg, name).enabled else "Disabled",
"default_model": getattr(cfg, name).default_model,
}
for name in ("openai", "anthropic", "openrouter")
for name in ("openai", "anthropic", "openrouter", "generic")
}
# Background intelligence uses one of the above providers. Report
# its ACTUAL key/enabled/configured state instead of hardcoding True,

View file

@ -108,6 +108,22 @@ def get_adapter(provider: str, allow_mock: bool = False) -> ProviderAdapter:
except ImportError as e:
raise ProviderNotConfiguredError(str(e))
if p == "generic":
# Generic OpenAI-compatible provider (Ollama, LM Studio, vLLM, etc.).
# Reuses OpenAIAdapter with the user-configured base_url. The api_key
# may be "not-required" for local servers — OpenAI SDK accepts any
# non-empty string.
if not cfg.base_url:
raise ProviderNotConfiguredError(
"Generic provider requires SMRITI_GENERIC_API_URL or "
"providers.yaml generic.base_url to be set."
)
try:
from app.providers.openai_adapter import OpenAIAdapter
return OpenAIAdapter(api_key=cfg.api_key, base_url=cfg.base_url)
except ImportError as e:
raise ProviderNotConfiguredError(str(e))
raise ProviderNotConfiguredError(f"Unknown provider: {provider}")