diff --git a/.env.example b/.env.example index 351ad4e..761e195 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,10 @@ ANTHROPIC_API_KEY= OPENAI_API_KEY= # chat-latest follows ChatGPT Instant. gpt-* uses OpenAI; claude-* uses Anthropic. +# vendor/model names (e.g. deepseek/deepseek-chat) use an OpenAI-compatible +# gateway: set OPENAI_BASE_URL (e.g. https://openrouter.ai/api/v1) and put the +# gateway key in OPENAI_API_KEY. Server keys only; browser keys use OpenAI. +# OPENAI_BASE_URL= # Model access/availability must be verified with your provider. # Blank or unsupported model prefixes fail at startup. # Keys are optional for BYOK-only hosting; visitors enter both keys in the app. diff --git a/apps/agent/src/model.py b/apps/agent/src/model.py index 33725ab..38d0a65 100644 --- a/apps/agent/src/model.py +++ b/apps/agent/src/model.py @@ -21,11 +21,18 @@ def build_model(*, allow_unconfigured: bool = False) -> BaseChatModel: model_name = os.environ.get("LLM_MODEL", DEFAULT_MODEL).strip() - is_openai = model_name == "chat-latest" or model_name.startswith("gpt-") + # vendor/model names (e.g. deepseek/deepseek-chat) target an OpenAI-compatible + # gateway such as OpenRouter; set OPENAI_BASE_URL and OPENAI_API_KEY for it. + is_openai = ( + model_name == "chat-latest" + or model_name.startswith("gpt-") + or "/" in model_name + ) if not is_openai and not model_name.startswith("claude-"): raise ValueError( - "LLM_MODEL must name a supported provider: claude-* (Anthropic) " - "or gpt-*/chat-latest (OpenAI). Unset LLM_MODEL to use the default." + "LLM_MODEL must name a supported provider: claude-* (Anthropic), " + "gpt-*/chat-latest (OpenAI), or vendor/model with OPENAI_BASE_URL. " + "Unset LLM_MODEL to use the default." ) key_name = "OPENAI_API_KEY" if is_openai else "ANTHROPIC_API_KEY" if not os.environ.get(key_name, "").strip(): diff --git a/apps/agent/tests/test_model.py b/apps/agent/tests/test_model.py index b9fc29f..c530e67 100644 --- a/apps/agent/tests/test_model.py +++ b/apps/agent/tests/test_model.py @@ -84,3 +84,12 @@ def test_missing_provider_key_has_actionable_error(monkeypatch, model, key, valu monkeypatch.setenv(key, value) with pytest.raises(ValueError, match=key): build_model() + + +def test_vendor_model_names_route_to_openai_compatible_gateway(monkeypatch): + monkeypatch.setenv("LLM_MODEL", "deepseek/deepseek-chat") + monkeypatch.setenv("OPENAI_BASE_URL", "https://openrouter.ai/api/v1") + model = build_model() + assert isinstance(model, ChatOpenAI) + assert model.model_name == "deepseek/deepseek-chat" + assert str(model.root_client.base_url).startswith("https://openrouter.ai/api/v1") diff --git a/docs/deployment.md b/docs/deployment.md index 4dbb8a9..b069160 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -15,7 +15,7 @@ For shared credentials, the blueprint and Python factory default to `LLM_MODEL=c | Variable | Service | Purpose | | ----------------------------------------- | ------------- | ----------------------------------------------------------------------------------- | -| `LLM_MODEL` | Agent | `claude-*` routes to Anthropic; `gpt-*` routes to OpenAI | +| `LLM_MODEL` | Agent | `claude-*` routes to Anthropic; `gpt-*` to OpenAI; `vendor/model` to `OPENAI_BASE_URL` | | `ANTHROPIC_API_KEY` | Agent | Required when selecting Anthropic | | `OPENAI_API_KEY` | Agent | Required when selecting OpenAI | | `LANGSMITH_API_KEY` | As configured | Optional tracing configuration | diff --git a/docs/getting-started.md b/docs/getting-started.md index 73fa6fc..41af946 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -25,7 +25,7 @@ TYPESAFE_API_KEY=your-typesafe-key JEV_MODEL=jev-latest ``` -The model factory accepts `claude-*` names through Anthropic and `chat-latest` or `gpt-*` names through OpenAI. To override the default, set a model available to your account in `LLM_MODEL` and supply its provider key. Shared mode requires the selected answer provider’s key and the Jev key; BYOK requests supply their own pair. Unset `LLM_MODEL` uses the local default; an empty value or unsupported prefix fails with a configuration error. +The model factory accepts `claude-*` names through Anthropic and `chat-latest` or `gpt-*` names through OpenAI. `vendor/model` names, such as `deepseek/deepseek-chat`, go through an OpenAI-compatible gateway such as OpenRouter: set `OPENAI_BASE_URL=https://openrouter.ai/api/v1` and put the gateway key in `OPENAI_API_KEY`. Browser-supplied keys always use OpenAI. To override the default, set a model available to your account in `LLM_MODEL` and supply its provider key. Shared mode requires the selected answer provider’s key and the Jev key; BYOK requests supply their own pair. Unset `LLM_MODEL` uses the local default; an empty value or unsupported prefix fails with a configuration error. Check model availability and access with a real request. This repository does not guarantee that every model name works or implement a separate native GPT-6 API. Never put shared provider keys in public frontend environment variables. See [BYOK behavior](../README.md#use-your-own-api-keys).