Skip to content

Commit e54c1ff

Browse files
jerelvelardeclaude
andcommitted
Add OpenAI fallback: gpt-* LLM_MODEL names route to ChatOpenAI
Production safety net for the Fable 5 cutover: flipping LLM_MODEL to a gpt-* name in the Render dashboard falls back to OpenAI without a code change. OPENAI_API_KEY re-declared in render.yaml (sync: false) so the existing dashboard value survives blueprint sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 30be769 commit e54c1ff

7 files changed

Lines changed: 233 additions & 8 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ ANTHROPIC_API_KEY=
22

33
# Claude model — strong models are required for reliable UI generation
44
# Recommended: claude-fable-5 (default), claude-opus-4-6
5-
# To use a different provider, swap the chat model in apps/agent/src/model.py
5+
# Fallback: gpt-* names route to OpenAI (requires OPENAI_API_KEY)
66
LLM_MODEL=claude-fable-5
77

88
# Rate limiting (per IP) — disabled by default

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ make dev # Start all services
3232
> | `claude-fable-5` | Default |
3333
> | `claude-opus-4-6` | Strong alternative |
3434
>
35-
> To use a different provider, swap the chat model in `apps/agent/src/model.py` (see [docs/bring-to-your-app.md](docs/bring-to-your-app.md)). Smaller or weaker models will produce broken layouts, missing interactivity, or incomplete visualizations.
35+
> Setting `LLM_MODEL` to a `gpt-*` name routes to OpenAI instead (requires `OPENAI_API_KEY`). For other providers, swap the chat model in `apps/agent/src/model.py` (see [docs/bring-to-your-app.md](docs/bring-to-your-app.md)). Smaller or weaker models will produce broken layouts, missing interactivity, or incomplete visualizations.
3636
3737
- **App**: http://localhost:3000
3838
- **Agent**: http://localhost:8123

apps/agent/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies = [
1515
"langchain-mcp-adapters>=0.2.1",
1616
"deepagents>=0.1.0",
1717
"langchain-anthropic>=1.4.0",
18+
"langchain-openai>=1.3.0",
1819
]
1920

2021
[dependency-groups]

apps/agent/src/model.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44

55
from langchain_anthropic import ChatAnthropic
6+
from langchain_core.language_models.chat_models import BaseChatModel
67

78
DEFAULT_MODEL = "claude-fable-5"
89

@@ -13,8 +14,16 @@
1314
MAX_TOKENS = 64000
1415

1516

16-
def build_model() -> ChatAnthropic:
17+
def build_model() -> BaseChatModel:
18+
model_name = os.environ.get("LLM_MODEL", DEFAULT_MODEL)
19+
if model_name.startswith("gpt-"):
20+
# Production fallback: gpt-* names route to OpenAI so LLM_MODEL can be
21+
# flipped in the deploy dashboard without a code change. No max_tokens
22+
# override here — OpenAI's default matches pre-migration behavior.
23+
from langchain_openai import ChatOpenAI
24+
25+
return ChatOpenAI(model=model_name)
1726
return ChatAnthropic(
18-
model=os.environ.get("LLM_MODEL", DEFAULT_MODEL),
27+
model=model_name,
1928
max_tokens=MAX_TOKENS,
2029
)

apps/agent/tests/test_model.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
"""The agent runs on Anthropic Claude — Fable 5 by default, LLM_MODEL overridable."""
1+
"""The agent runs on Anthropic Claude — Fable 5 by default, LLM_MODEL overridable.
2+
gpt-* model names route to ChatOpenAI as a production fallback."""
23

34
import os
45

56
import pytest
67

78
os.environ.setdefault("ANTHROPIC_API_KEY", "test-key")
9+
os.environ.setdefault("OPENAI_API_KEY", "test-key")
810

911
from langchain_anthropic import ChatAnthropic
12+
from langchain_openai import ChatOpenAI
1013

1114
from src.model import build_model
1215

@@ -23,6 +26,20 @@ def test_llm_model_env_override(monkeypatch: pytest.MonkeyPatch):
2326
assert model.model == "claude-opus-4-6"
2427

2528

29+
def test_gpt_model_names_route_to_openai_fallback(monkeypatch: pytest.MonkeyPatch):
30+
# Production fallback: flipping LLM_MODEL to a gpt-* name in the Render
31+
# dashboard must work without a code change.
32+
monkeypatch.setenv("LLM_MODEL", "gpt-5.4-2026-03-05")
33+
model = build_model()
34+
assert isinstance(model, ChatOpenAI)
35+
assert model.model_name == "gpt-5.4-2026-03-05"
36+
37+
38+
def test_claude_model_names_route_to_anthropic(monkeypatch: pytest.MonkeyPatch):
39+
monkeypatch.setenv("LLM_MODEL", "claude-opus-4-6")
40+
assert isinstance(build_model(), ChatAnthropic)
41+
42+
2643
def test_max_tokens_fits_full_widget_generation():
2744
# langchain-anthropic's default (4096) truncates generateSandboxedUi args
2845
# mid-stream: css+html arrive but jsFunctions/jsExpressions are cut off, so

0 commit comments

Comments
 (0)