Spaces:
Running
Running
towards-ai-deploy commited on
Commit ·
ae1e375
1
Parent(s): 0c08d1a
Deploy tai-helper ea13623
Browse files- .env.example +5 -0
- README.md +10 -0
- tai_helper/llm.py +17 -0
- tai_helper/settings.py +10 -0
- tests/test_llm.py +63 -0
.env.example
CHANGED
|
@@ -11,6 +11,11 @@ HELPER_SITE_WIDE_HOSTS=towardsai.com,www.towardsai.com
|
|
| 11 |
HELPER_PRIMARY_BASE_URL=https://openrouter.ai/api/v1
|
| 12 |
HELPER_PRIMARY_API_KEY=
|
| 13 |
HELPER_PRIMARY_MODEL=deepseek/deepseek-v4-flash
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# Backup model: Gemini 3.7 Flash, on Google's API rather than OpenRouter so the
|
| 16 |
# fallback does not share a gateway with the primary.
|
|
|
|
| 11 |
HELPER_PRIMARY_BASE_URL=https://openrouter.ai/api/v1
|
| 12 |
HELPER_PRIMARY_API_KEY=
|
| 13 |
HELPER_PRIMARY_MODEL=deepseek/deepseek-v4-flash
|
| 14 |
+
# OpenRouter serves one model id from many independent hosts at different
|
| 15 |
+
# quantisations, and they are NOT interchangeable for extractive grounding.
|
| 16 |
+
# Unpinned routing sent every request to one host that answered 0/6 on the
|
| 17 |
+
# main starter prompt. Preference order, first entry wins; fallbacks stay on.
|
| 18 |
+
HELPER_OPENROUTER_PROVIDER_ORDER=novita
|
| 19 |
|
| 20 |
# Backup model: Gemini 3.7 Flash, on Google's API rather than OpenRouter so the
|
| 21 |
# fallback does not share a gateway with the primary.
|
README.md
CHANGED
|
@@ -49,6 +49,16 @@ out of balance and took the helper down, while OpenRouter draws on a shared
|
|
| 49 |
balance that is kept topped up. `HELPER_PRIMARY_MODEL` can name any OpenRouter
|
| 50 |
model without code changes.
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
Gemini 3.7 Flash is the fallback, deliberately kept on Google's own API rather
|
| 53 |
than routed through OpenRouter, so the backup does not share a gateway with the
|
| 54 |
primary. It only runs when the primary fails and it passes the grounding
|
|
|
|
| 49 |
balance that is kept topped up. `HELPER_PRIMARY_MODEL` can name any OpenRouter
|
| 50 |
model without code changes.
|
| 51 |
|
| 52 |
+
**Pin the OpenRouter host.** OpenRouter serves one model id from many
|
| 53 |
+
independent providers at different quantisations, and they are not
|
| 54 |
+
interchangeable for this workload. Measured on identical grounding prompts, one
|
| 55 |
+
host answered 3/3 while another answered 0/3, and the host the default routing
|
| 56 |
+
kept choosing returned `not_found` on the main starter prompt every single time
|
| 57 |
+
— 0/6 in production, 6/6 once pinned. `HELPER_OPENROUTER_PROVIDER_ORDER` sets
|
| 58 |
+
the preference order; `require_parameters` additionally drops hosts that would
|
| 59 |
+
ignore `response_format` and answer with prose. Fallbacks stay enabled, so one
|
| 60 |
+
host going down degrades quality rather than taking the helper offline.
|
| 61 |
+
|
| 62 |
Gemini 3.7 Flash is the fallback, deliberately kept on Google's own API rather
|
| 63 |
than routed through OpenRouter, so the backup does not share a gateway with the
|
| 64 |
primary. It only runs when the primary fails and it passes the grounding
|
tai_helper/llm.py
CHANGED
|
@@ -638,6 +638,22 @@ def _reasoning_control() -> dict[str, Any]:
|
|
| 638 |
return {"reasoning": {"enabled": settings.primary_reasoning_enabled}}
|
| 639 |
|
| 640 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
def _generate_primary_answer(prompt: str) -> LLMResult:
|
| 642 |
if not settings.primary_api_key:
|
| 643 |
raise RuntimeError("HELPER_PRIMARY_API_KEY is not configured")
|
|
@@ -652,6 +668,7 @@ def _generate_primary_answer(prompt: str) -> LLMResult:
|
|
| 652 |
{"role": "user", "content": prompt},
|
| 653 |
],
|
| 654 |
**_reasoning_control(),
|
|
|
|
| 655 |
"response_format": {"type": "json_object"},
|
| 656 |
"temperature": DEFAULT_TEMPERATURE,
|
| 657 |
"max_tokens": settings.max_output_tokens,
|
|
|
|
| 638 |
return {"reasoning": {"enabled": settings.primary_reasoning_enabled}}
|
| 639 |
|
| 640 |
|
| 641 |
+
def _provider_routing() -> dict[str, Any]:
|
| 642 |
+
"""Constrain which OpenRouter host serves the request.
|
| 643 |
+
|
| 644 |
+
``require_parameters`` drops hosts that do not honour what is sent, so a
|
| 645 |
+
host that ignores ``response_format`` cannot answer with prose that then
|
| 646 |
+
fails to parse. The preference order puts a host measured as reliable for
|
| 647 |
+
extractive grounding first, with fallbacks left on so a single host being
|
| 648 |
+
down degrades quality instead of taking the helper offline.
|
| 649 |
+
"""
|
| 650 |
+
|
| 651 |
+
routing: dict[str, Any] = {"require_parameters": True, "allow_fallbacks": True}
|
| 652 |
+
if settings.openrouter_provider_order:
|
| 653 |
+
routing["order"] = list(settings.openrouter_provider_order)
|
| 654 |
+
return {"provider": routing}
|
| 655 |
+
|
| 656 |
+
|
| 657 |
def _generate_primary_answer(prompt: str) -> LLMResult:
|
| 658 |
if not settings.primary_api_key:
|
| 659 |
raise RuntimeError("HELPER_PRIMARY_API_KEY is not configured")
|
|
|
|
| 668 |
{"role": "user", "content": prompt},
|
| 669 |
],
|
| 670 |
**_reasoning_control(),
|
| 671 |
+
**_provider_routing(),
|
| 672 |
"response_format": {"type": "json_object"},
|
| 673 |
"temperature": DEFAULT_TEMPERATURE,
|
| 674 |
"max_tokens": settings.max_output_tokens,
|
tai_helper/settings.py
CHANGED
|
@@ -103,6 +103,16 @@ class Settings:
|
|
| 103 |
or DEFAULT_PRIMARY_MODEL
|
| 104 |
)
|
| 105 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
gemini_api_key: str = field(
|
| 107 |
default_factory=lambda: os.getenv("GEMINI_API_KEY", "").strip()
|
| 108 |
)
|
|
|
|
| 103 |
or DEFAULT_PRIMARY_MODEL
|
| 104 |
)
|
| 105 |
)
|
| 106 |
+
# OpenRouter serves one model id from many independent hosts at different
|
| 107 |
+
# quantisations, and they are not interchangeable for extractive grounding:
|
| 108 |
+
# measured on the same prompts, one host answered 3/3 while another
|
| 109 |
+
# answered 0/3 and a third returned not_found every time. Left unpinned,
|
| 110 |
+
# routing silently picks one and answer quality swings with it.
|
| 111 |
+
openrouter_provider_order: tuple[str, ...] = field(
|
| 112 |
+
default_factory=lambda: _csv_env(
|
| 113 |
+
"HELPER_OPENROUTER_PROVIDER_ORDER", "novita"
|
| 114 |
+
)
|
| 115 |
+
)
|
| 116 |
gemini_api_key: str = field(
|
| 117 |
default_factory=lambda: os.getenv("GEMINI_API_KEY", "").strip()
|
| 118 |
)
|
tests/test_llm.py
CHANGED
|
@@ -260,3 +260,66 @@ def test_openrouter_style_disables_reasoning_with_openrouter_parameter(
|
|
| 260 |
assert captured["reasoning"] == {"enabled": False}
|
| 261 |
assert "thinking" not in captured
|
| 262 |
assert captured["model"] == "deepseek/deepseek-v4-flash"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
assert captured["reasoning"] == {"enabled": False}
|
| 261 |
assert "thinking" not in captured
|
| 262 |
assert captured["model"] == "deepseek/deepseek-v4-flash"
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def test_openrouter_request_pins_provider_routing(monkeypatch) -> None:
|
| 266 |
+
"""One OpenRouter model id is served by many independent hosts.
|
| 267 |
+
|
| 268 |
+
They are not interchangeable for extractive grounding: on identical prompts
|
| 269 |
+
one host answered 3/3 while another answered 0/3, and the host the default
|
| 270 |
+
routing kept selecting returned not_found every time. require_parameters
|
| 271 |
+
also drops hosts that would ignore response_format and reply with prose.
|
| 272 |
+
"""
|
| 273 |
+
|
| 274 |
+
captured: dict[str, object] = {}
|
| 275 |
+
|
| 276 |
+
def fake_post(_url, **kwargs):
|
| 277 |
+
captured.update(kwargs["json"])
|
| 278 |
+
return FakeOpenRouterResponse(
|
| 279 |
+
200, {"choices": [{"message": {"content": "{}"}}]}, ""
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
monkeypatch.setattr(
|
| 283 |
+
llm,
|
| 284 |
+
"settings",
|
| 285 |
+
Settings(
|
| 286 |
+
primary_api_key="openrouter-key",
|
| 287 |
+
openrouter_provider_order=("novita", "alibaba"),
|
| 288 |
+
),
|
| 289 |
+
)
|
| 290 |
+
monkeypatch.setattr(llm.requests, "post", fake_post)
|
| 291 |
+
|
| 292 |
+
llm.generate_answer("Visitor prompt")
|
| 293 |
+
|
| 294 |
+
assert captured["provider"] == {
|
| 295 |
+
"require_parameters": True,
|
| 296 |
+
"allow_fallbacks": True,
|
| 297 |
+
"order": ["novita", "alibaba"],
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def test_openrouter_routing_omits_order_when_unset(monkeypatch) -> None:
|
| 302 |
+
"""An empty order must not pin routing to nothing and 404 the request."""
|
| 303 |
+
|
| 304 |
+
captured: dict[str, object] = {}
|
| 305 |
+
|
| 306 |
+
def fake_post(_url, **kwargs):
|
| 307 |
+
captured.update(kwargs["json"])
|
| 308 |
+
return FakeOpenRouterResponse(
|
| 309 |
+
200, {"choices": [{"message": {"content": "{}"}}]}, ""
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
monkeypatch.setattr(
|
| 313 |
+
llm,
|
| 314 |
+
"settings",
|
| 315 |
+
Settings(primary_api_key="openrouter-key", openrouter_provider_order=()),
|
| 316 |
+
)
|
| 317 |
+
monkeypatch.setattr(llm.requests, "post", fake_post)
|
| 318 |
+
|
| 319 |
+
llm.generate_answer("Visitor prompt")
|
| 320 |
+
|
| 321 |
+
assert captured["provider"] == {
|
| 322 |
+
"require_parameters": True,
|
| 323 |
+
"allow_fallbacks": True,
|
| 324 |
+
}
|
| 325 |
+
assert "order" not in captured["provider"]
|