Files
devplacepy/tests/unit/services/openai_gateway/gateway.py
T
retoorandClaude Sonnet 5 569f1dcc64 Add OpenCode Zen support, model health/stats dashboard, and gateway fallback fixes
AI gateway:
- Add a generic, admin-selectable `client_profile` field on gateway_providers
  (e.g. "opencode") so a provider needing special request headers (OpenCode
  Zen's client-identity spoofing) is configured like any other provider, not
  hardcoded by name.
- Track per-(provider, model) reliability/speed/latency health in memory,
  seeded from the existing gateway_usage_ledger at startup - purely
  observational, never influences routing.
- New Stats tab on /admin/gateway: request volume, latency, per-model
  breakdowns, and reliability weight, charted with a vendored Chart.js and
  devplace's own theme tokens.
- Record which model a failed request actually fell back to
  (fallback_used_route), surfaced in the Recent Failures table.
- Stop excluding context_length errors from fallback, and skip a primary
  attempt outright when its known context window is already too small for
  the estimated request size, going straight to the fallback.
- gateway_usage_ledger's provider/fallback_used_route columns and indexes
  are ensured centrally in database/schema.py's init_db(), the single point
  of truth for this table's schema.
- Non-OpenAI upstream routing and client-model passthrough; trust only the
  upstream's own X-Gateway-Model header for served-model attribution.

Devii agent:
- Fix a real lockup: plan/verify tools could be individually disabled via
  the admin tool toggles while still being required by the protocol gate,
  permanently bricking any task that needed tools. They can no longer be
  disabled, and the gate now also checks the tool is actually offered.
- Fix compaction being silently calibrated for a 1M-token model while
  running a much smaller one: context budget is now percentage-based and
  the summarizer's own request is sized to fit the real model.
- Give a specific, actionable retry message when plan()'s own arguments get
  cut off by the output limit, and tighten its schema to discourage
  overlong plans.

Other:
- Backup service: offload completed backups to a remote Hetzner Storage Box.
- Container manager: fix orphan blob leaks from sync races, add a two-phase
  plan/execute `system prune` CLI command.
- Admin gateway UI: replace the JS-rendered model/provider tables with
  server-rendered forms and pages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhmEkvutuwtzFVcLbTrhdo
2026-09-09 07:38:24 +02:00

1945 lines
65 KiB
Python

# retoor <retoor@molodetz.nl>
import json
from starlette.requests import Request
from tests.conftest import BASE_URL, run_async
from devplacepy.database import get_table, set_setting
from devplacepy.utils import generate_uid
import devplacepy.services.openai_gateway.gateway as gwmod
from devplacepy.services.openai_gateway import GatewayService
class FakeResp_openai_gateway:
def __init__(
self,
status=200,
payload=None,
ctype="application/json",
content=b"",
extra_headers=None,
):
self.status_code = status
self._payload = payload
self.text = json.dumps(payload) if payload is not None else ""
self.headers = {"content-type": ctype, **(extra_headers or {})}
self.content = content
def json(self):
if self._payload is None:
raise ValueError("no json")
return self._payload
async def aread(self):
return self.content or self.text.encode()
async def aclose(self):
pass
async def aiter_lines(self):
payload = self._payload or {}
chunk_id = payload.get("id", "x")
model = payload.get("model", "")
try:
content = payload["choices"][0]["message"].get("content") or ""
except (KeyError, IndexError, TypeError):
content = ""
try:
reasoning = payload["choices"][0]["message"].get("reasoning") or ""
except (KeyError, IndexError, TypeError):
reasoning = ""
def frame(delta=None, finish=None, usage=None):
body = {"id": chunk_id, "object": "chat.completion.chunk", "model": model}
if usage is not None:
body["choices"] = []
body["usage"] = usage
else:
body["choices"] = [{"index": 0, "delta": delta or {}, "finish_reason": finish}]
return f"data: {json.dumps(body)}"
yield frame({"role": "assistant"})
for i in range(0, len(reasoning), 5):
yield frame({"reasoning": reasoning[i : i + 5]})
for i in range(0, len(content), 5):
yield frame({"content": content[i : i + 5]})
yield frame({}, finish="stop")
if payload.get("usage"):
yield frame(usage=payload["usage"])
yield "data: [DONE]"
class FakeRequest:
def __init__(self, method, url, json_body):
self.method = method
self.url = url
self.json_body = json_body
self.extensions = {}
class FakeClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": body.get("model"),
"choices": [{"message": {"content": "hi there"}}],
}
)
async def post(self, url, headers=None, json=None, timeout=None):
self.calls.append((url, json))
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": json.get("model"),
"choices": [{"message": {"content": "hi there"}}],
}
)
async def request(self, method, url, headers=None, content=None):
return FakeResp_openai_gateway(payload={"ok": True})
async def aclose(self):
pass
def _make_request_openai_gateway(headers=None, cookies=None):
headers = headers or {}
raw = [(k.lower().encode(), v.encode()) for k, v in headers.items()]
if cookies:
raw.append(
(b"cookie", "; ".join(f"{k}={v}" for k, v in cookies.items()).encode())
)
return Request(
{
"type": "http",
"method": "POST",
"path": "/openai/v1/chat/completions",
"query_string": b"",
"headers": raw,
"state": {},
}
)
def _make_admin_openai_gateway(role="Admin"):
username = f"gw_{generate_uid()[:8]}"
api_key = generate_uid()
get_table("users").insert(
{
"uid": generate_uid(),
"username": username,
"terms_version": "1",
"email": f"{username}@t.dev",
"api_key": api_key,
"role": role,
"is_active": True,
}
)
return username, api_key
def _config(page, **fields):
page.request.post(f"{BASE_URL}/admin/services/openai/config", form=fields)
def test_model_is_forced(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"model": "gpt-4", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "test"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "deepseek-chat"
def test_model_route_overrides_upstream(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
routing.provider_store.set(
routing.ProviderIn(
name="gwroute",
base_url="https://routed.example/v1/chat/completions",
api_key="routed-key",
)
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="custom-pro",
provider="gwroute",
target_model="vendor/pro",
kind="chat",
price_output_per_m=9.0,
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
rt = svc.runtime()
run_async(
rt.handle_chat(
{"model": "custom-pro", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "test"),
"test",
"default",
)
)
url, body = rt._client.calls[-1]
assert str(url) == "https://routed.example/v1/chat/completions"
assert body["model"] == "vendor/pro"
finally:
routing.model_store.remove("custom-pro")
routing.provider_store.remove("gwroute")
def test_vision_rewrites_image_to_text(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = True
cfg["gateway_vision_key"] = "" # no key -> placeholder text, still rewrites to str
rt = svc.runtime()
msgs = [
{
"role": "user",
"content": [
{"type": "text", "text": "what is this"},
{"type": "image_url", "image_url": {"url": "http://x/y.png"}},
],
}
]
run_async(rt.handle_chat({"messages": msgs}, cfg, ("guest", "test"), "test", "default"))
sent = rt._client.calls[-1][1]["messages"][0]["content"]
assert isinstance(sent, str) and "vision" in sent.lower()
def test_streaming_emits_sse(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}], "stream": True},
cfg,
("guest", "test"),
"test",
"default",
)
)
async def drain():
out = []
async for chunk in resp.body_iterator:
out.append(chunk if isinstance(chunk, str) else chunk.decode())
return "".join(out)
body = run_async(drain())
assert "chat.completion.chunk" in body
assert "[DONE]" in body
def test_authorize_static_access_key(local_db):
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "topsecret")
try:
svc = GatewayService()
assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": "topsecret"})) is True
assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": "wrong"})) is False
finally:
set_setting("gateway_access_key", "")
set_setting("gateway_require_auth", "1")
def test_authorize_admin_and_user_toggles(local_db):
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "")
set_setting("gateway_allow_admins", "1")
set_setting("gateway_allow_users", "0")
try:
_, admin_key = _make_admin_openai_gateway(role="Admin")
_, member_key = _make_admin_openai_gateway(role="Member")
svc = GatewayService()
assert (
svc.authorize(_make_request_openai_gateway(headers={"Authorization": f"Bearer {admin_key}"}))
is True
)
assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": member_key})) is False
set_setting("gateway_allow_users", "1")
assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": member_key})) is True
finally:
set_setting("gateway_allow_admins", "1")
set_setting("gateway_allow_users", "1")
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "")
def test_authorize_require_auth_off_is_open(local_db):
set_setting("gateway_require_auth", "0")
try:
svc = GatewayService()
assert svc.authorize(_make_request_openai_gateway()) is True
finally:
set_setting("gateway_require_auth", "1")
class FakeEmbedClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"object": "list",
"model": body.get("model"),
"data": [{"object": "embedding", "index": 0, "embedding": [0.1, 0.2]}],
"usage": {"prompt_tokens": 5, "total_tokens": 5},
}
)
async def aclose(self):
pass
def test_embeddings_remaps_alias_to_configured_model(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = False
cfg["gateway_embed_enabled"] = True
cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
rt = svc.runtime()
run_async(
rt.handle_embeddings(
{"model": "molodetz~embed", "input": "hello"},
cfg,
("guest", "test"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "qwen/qwen3-embedding-8b"
def test_embeddings_success_records_ledger(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_embed_enabled"] = True
cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
rt = svc.runtime()
before = rt.embed_calls
resp = run_async(
rt.handle_embeddings(
{"model": "molodetz~embed", "input": "hello"},
cfg,
("guest", "ledger_probe"),
"test",
"default",
)
)
assert resp.status_code == 200
payload = json.loads(bytes(resp.body).decode())
assert payload["data"][0]["embedding"] == [0.1, 0.2]
assert rt.embed_calls == before + 1
row = get_table("gateway_usage_ledger").find_one(owner_id="ledger_probe")
assert row is not None
assert row["backend"] == "embed"
assert row["endpoint"] == "embeddings"
assert row["requested_model"] == "molodetz~embed"
assert row["model"] == "qwen/qwen3-embedding-8b"
assert row["success"] == 1
def test_embeddings_disabled_returns_503(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_embed_enabled"] = False
rt = svc.runtime()
resp = run_async(
rt.handle_embeddings(
{"input": "hello"}, cfg, ("guest", "test"), "test", "default"
)
)
assert resp.status_code == 503
class FakeImageClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"created": 1,
"model": body.get("model"),
"data": [{"b64_json": "aGVsbG8="}],
"usage": {"cost": 0.05},
}
)
async def aclose(self):
pass
def test_images_remaps_alias_to_configured_model(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeImageClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = False
cfg["gateway_image_enabled"] = True
cfg["gateway_image_model"] = "black-forest-labs/flux.2-pro"
rt = svc.runtime()
run_async(
rt.handle_images(
{
"model": "molodetz-img-small",
"prompt": "award emblem",
"size": "512x512",
},
cfg,
("guest", "test"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "black-forest-labs/flux.2-pro"
def test_image_route_overrides_upstream(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeImageClient_openai_gateway)
routing.provider_store.set(
routing.ProviderIn(
name="gwimg",
base_url="https://routed.example/v1/chat/completions",
api_key="img-key",
)
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="custom-img",
provider="gwimg",
target_model="vendor/flux-pro",
kind="image",
price_input_per_m=0.06,
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
run_async(
rt.handle_images(
{
"model": "custom-img",
"prompt": "trophy",
"response_format": "b64_json",
},
cfg,
("guest", "test"),
"test",
"default",
)
)
url, body = rt._client.calls[-1]
assert str(url) == "https://routed.example/v1/images"
assert body["model"] == "vendor/flux-pro"
finally:
routing.model_store.remove("custom-img")
routing.provider_store.remove("gwimg")
def test_images_success_records_ledger(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeImageClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_image_enabled"] = True
cfg["gateway_image_model"] = "black-forest-labs/flux.2-pro"
rt = svc.runtime()
before = rt.image_calls
resp = run_async(
rt.handle_images(
{"model": "molodetz-img-small", "prompt": "badge"},
cfg,
("guest", "img_ledger"),
"test",
"default",
)
)
assert resp.status_code == 200
payload = json.loads(bytes(resp.body).decode())
assert payload["data"][0]["b64_json"] == "aGVsbG8="
assert rt.image_calls == before + 1
row = get_table("gateway_usage_ledger").find_one(owner_id="img_ledger")
assert row is not None
assert row["backend"] == "image"
assert row["endpoint"] == "images/generations"
assert row["requested_model"] == "molodetz-img-small"
assert row["model"] == "black-forest-labs/flux.2-pro"
assert row["success"] == 1
assert float(row["cost_usd"]) == 0.05
def test_images_disabled_returns_503(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeImageClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_image_enabled"] = False
rt = svc.runtime()
resp = run_async(
rt.handle_images(
{"prompt": "badge"}, cfg, ("guest", "test"), "test", "default"
)
)
assert resp.status_code == 503
def test_compute_cost_embed_branch():
from devplacepy.services.openai_gateway.usage import (
Pricing,
compute_cost,
normalize_usage,
)
pricing = Pricing(
chat_cache_hit_per_m=0.0,
chat_cache_miss_per_m=0.0,
chat_output_per_m=0.0,
vision_input_per_m=0.0,
vision_output_per_m=0.0,
embed_input_per_m=0.01,
)
usage = {"prompt_tokens": 1_000_000, "total_tokens": 1_000_000}
norm = normalize_usage(usage)
total, input_cost, output_cost, native = compute_cost(
usage, norm, pricing, "embed"
)
assert native is False
assert output_cost == 0.0
assert abs(total - 0.01) < 1e-9
assert abs(input_cost - 0.01) < 1e-9
def test_app_reference_stored_in_ledger_for_chat(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "ref_test"}]},
cfg,
("guest", "ref_probe"),
"test-ua",
"my-custom-app",
)
)
row = get_table("gateway_usage_ledger").find_one(owner_id="ref_probe")
assert row is not None
assert row["app_reference"] == "my-custom-app"
assert row["backend"] == "chat"
def test_app_reference_defaults_when_not_passed(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "default_test"}]},
cfg,
("guest", "default_probe"),
"test-ua",
"default",
)
)
row = get_table("gateway_usage_ledger").find_one(owner_id="default_probe")
assert row is not None
assert row["app_reference"] == "default"
def test_app_reference_stored_in_ledger_for_embeddings(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_embed_enabled"] = True
cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
rt = svc.runtime()
run_async(
rt.handle_embeddings(
{"input": "hello"},
cfg,
("guest", "embed_ref"),
"test-ua",
"devplace-embed-test-v-1-0-0",
)
)
row = get_table("gateway_usage_ledger").find_one(owner_id="embed_ref")
assert row is not None
assert row["app_reference"] == "devplace-embed-test-v-1-0-0"
assert row["backend"] == "embed"
def test_app_reference_stored_in_ledger_for_images(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeImageClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_image_enabled"] = True
cfg["gateway_image_model"] = "black-forest-labs/flux.2-pro"
rt = svc.runtime()
run_async(
rt.handle_images(
{"prompt": "ref badge"},
cfg,
("guest", "img_ref"),
"test-ua",
"devplace-image-test-v-1-0-0",
)
)
row = get_table("gateway_usage_ledger").find_one(owner_id="img_ref")
assert row is not None
assert row["app_reference"] == "devplace-image-test-v-1-0-0"
assert row["backend"] == "image"
def test_app_reference_column_exists(local_db):
db = get_table("gateway_usage_ledger").db
if "gateway_usage_ledger" not in db.tables:
return
rows = list(db.query("PRAGMA table_info('gateway_usage_ledger')"))
column_names = [r["name"] for r in rows]
assert "app_reference" in column_names
def test_chat_unknown_model_falls_back_to_default(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = False
cfg["gateway_model"] = "deepseek-v4-flash"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"model": "nonexistent-model-v99", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "fallback_test_chat"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "deepseek-v4-flash"
class FakeFallbackClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
model = body.get("model")
if model == "primary-target":
return FakeResp_openai_gateway(status=500, payload={"error": "boom"})
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": model,
"choices": [{"message": {"content": "fallback ok"}}],
}
)
async def aclose(self):
pass
class FakeAlwaysFailClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
return FakeResp_openai_gateway(status=500, payload={"error": "boom"})
async def aclose(self):
pass
class FakeBadRequestClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
return FakeResp_openai_gateway(
status=400, payload={"error": {"message": "invalid request: bad param"}}
)
async def aclose(self):
pass
def test_chat_does_not_fall_back_on_an_unrecoverable_bad_request(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeBadRequestClient_openai_gateway)
routing.model_store.set(
routing.ModelRouteIn(source_model="fb-badreq-backup", target_model="backup-target")
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="fb-badreq-primary",
target_model="primary-target",
fallback_model="fb-badreq-backup",
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
response = run_async(
rt.handle_chat(
{"model": "fb-badreq-primary", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "no_fallback_on_bad_request"),
"test",
"default",
)
)
assert response.status_code == 400
assert len(rt._client.calls) == 1
finally:
routing.model_store.remove("fb-badreq-primary")
routing.model_store.remove("fb-badreq-backup")
def test_chat_falls_back_when_the_primary_model_fails(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeFallbackClient_openai_gateway)
routing.model_store.set(
routing.ModelRouteIn(source_model="fb-backup-route", target_model="backup-target")
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="fb-primary-route",
target_model="primary-target",
fallback_model="fb-backup-route",
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
response = run_async(
rt.handle_chat(
{"model": "fb-primary-route", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "fallback_chat_success"),
"test",
"default",
)
)
assert response.status_code == 200
assert rt._client.calls[0][1]["model"] == "primary-target"
assert rt._client.calls[-1][1]["model"] == "backup-target"
row = get_table("gateway_usage_ledger").find_one(owner_id="fallback_chat_success")
assert row is not None
assert row["requested_model"] == "fb-primary-route"
assert row["model"] == "backup-target"
assert row["success"] == 1
finally:
routing.model_store.remove("fb-primary-route")
routing.model_store.remove("fb-backup-route")
class FakeContextLengthClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
model = body.get("model")
if model == "ctx-primary-target":
return FakeResp_openai_gateway(
status=400,
payload={"error": {"message": "maximum context length exceeded"}},
)
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": model,
"choices": [{"message": {"content": "fallback ok"}}],
}
)
async def aclose(self):
pass
def test_chat_falls_back_on_a_context_length_error(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeContextLengthClient_openai_gateway)
routing.model_store.set(
routing.ModelRouteIn(source_model="ctx-backup-route", target_model="ctx-backup-target")
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="ctx-primary-route",
target_model="ctx-primary-target",
fallback_model="ctx-backup-route",
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
response = run_async(
rt.handle_chat(
{"model": "ctx-primary-route", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "context_length_fallback_success"),
"test",
"default",
)
)
assert response.status_code == 200
assert rt._client.calls[0][1]["model"] == "ctx-primary-target"
assert rt._client.calls[-1][1]["model"] == "ctx-backup-target"
row = get_table("gateway_usage_ledger").find_one(
owner_id="context_length_fallback_success"
)
assert row["success"] == 1
assert row["fallback_used_route"] == "ctx-backup-route"
finally:
routing.model_store.remove("ctx-primary-route")
routing.model_store.remove("ctx-backup-route")
def test_chat_skips_a_too_small_primary_and_goes_straight_to_fallback(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeContextLengthClient_openai_gateway)
routing.model_store.set(
routing.ModelRouteIn(
source_model="ctx-precheck-backup",
target_model="ctx-backup-target",
context_window=1_000_000,
)
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="ctx-precheck-primary",
target_model="ctx-primary-target",
context_window=50,
fallback_model="ctx-precheck-backup",
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
long_message = "word " * 2000
response = run_async(
rt.handle_chat(
{
"model": "ctx-precheck-primary",
"messages": [{"role": "user", "content": long_message}],
},
cfg,
("guest", "context_precheck_skips_primary"),
"test",
"default",
)
)
assert response.status_code == 200
# Only the fallback was ever called - the primary was known too small.
assert len(rt._client.calls) == 1
assert rt._client.calls[0][1]["model"] == "ctx-backup-target"
row = get_table("gateway_usage_ledger").find_one(
owner_id="context_precheck_skips_primary"
)
assert row["fallback_used_route"] == "ctx-precheck-backup"
finally:
routing.model_store.remove("ctx-precheck-primary")
routing.model_store.remove("ctx-precheck-backup")
def test_chat_falls_back_via_molodetz_when_the_client_sends_an_unrouted_model_name(
local_db, monkeypatch
):
from devplacepy.services.openai_gateway import routing
original = routing.model_store.get("molodetz")
routing.model_store.set(
routing.ModelRouteIn(source_model="fb-molodetz-backup", target_model="backup-target")
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="molodetz",
target_model="primary-target",
fallback_model="fb-molodetz-backup",
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
cfg["gateway_force_model"] = False
base_default_model = cfg["gateway_model"]
class FakeUnroutedPrimaryFailsClient:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
model = body.get("model")
if model == base_default_model:
return FakeResp_openai_gateway(
status=402, payload={"error": {"message": "Insufficient Balance"}}
)
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": model,
"choices": [{"message": {"content": "fallback ok"}}],
}
)
async def aclose(self):
pass
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeUnroutedPrimaryFailsClient)
rt = svc.runtime()
# The client sends a model name that matches no configured route at all
# (as a caller like dpc does, which is never told the exact "molodetz"
# routing alias) - the primary call falls through to the flat/global
# default model (no route overlay applies), but a failure there must
# still consult molodetz's own configured fallback, not silently skip
# it just because the client's literal string never matched anything.
response = run_async(
rt.handle_chat(
{
"model": "some-client-side-model-name",
"messages": [{"role": "user", "content": "hi"}],
},
cfg,
("guest", "fallback_chat_unrouted_client_model"),
"test",
"default",
)
)
assert response.status_code == 200
assert rt._client.calls[0][1]["model"] == base_default_model
assert rt._client.calls[-1][1]["model"] == "backup-target"
row = get_table("gateway_usage_ledger").find_one(
owner_id="fallback_chat_unrouted_client_model"
)
assert row is not None
assert row["requested_model"] == "some-client-side-model-name"
assert row["model"] == "backup-target"
assert row["success"] == 1
finally:
routing.model_store.remove("fb-molodetz-backup")
if original is not None:
routing.model_store.set(
routing.ModelRouteIn(
**{
field: getattr(original, field)
for field in routing.ModelRouteIn.model_fields
}
)
)
else:
routing.model_store.remove("molodetz")
def test_chat_returns_the_fallback_failure_when_both_models_fail(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeAlwaysFailClient_openai_gateway)
routing.model_store.set(
routing.ModelRouteIn(source_model="fb-backup-route2", target_model="backup-target2")
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="fb-primary-route2",
target_model="primary-target2",
fallback_model="fb-backup-route2",
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
response = run_async(
rt.handle_chat(
{"model": "fb-primary-route2", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "fallback_chat_both_fail"),
"test",
"default",
)
)
assert response.status_code == 500
assert len(rt._client.calls) == 2
assert rt._client.calls[0][1]["model"] == "primary-target2"
assert rt._client.calls[1][1]["model"] == "backup-target2"
finally:
routing.model_store.remove("fb-primary-route2")
routing.model_store.remove("fb-backup-route2")
def test_chat_without_a_fallback_never_makes_a_second_call(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeAlwaysFailClient_openai_gateway)
routing.model_store.set(
routing.ModelRouteIn(source_model="fb-no-fallback", target_model="no-fallback-target")
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
run_async(
rt.handle_chat(
{"model": "fb-no-fallback", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "no_fallback_configured"),
"test",
"default",
)
)
assert len(rt._client.calls) == 1
finally:
routing.model_store.remove("fb-no-fallback")
def test_embeddings_unknown_model_falls_back_to_default(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = False
cfg["gateway_embed_enabled"] = True
cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
rt = svc.runtime()
run_async(
rt.handle_embeddings(
{"model": "nonexistent-embed-model", "input": "hello"},
cfg,
("guest", "fallback_test_embed"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "qwen/qwen3-embedding-8b"
def test_images_unknown_model_falls_back_to_default(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeImageClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = False
cfg["gateway_image_enabled"] = True
cfg["gateway_image_model"] = "black-forest-labs/flux.2-pro"
rt = svc.runtime()
run_async(
rt.handle_images(
{"model": "nonexistent-image-model", "prompt": "test badge"},
cfg,
("guest", "fallback_test_img"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "black-forest-labs/flux.2-pro"
class FakeClientWithUsage_openai_gateway(FakeClient_openai_gateway):
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": body.get("model"),
"choices": [{"message": {"content": "hi there"}}],
"usage": {"prompt_tokens": 7, "completion_tokens": 3, "total_tokens": 10},
}
)
def test_ollama_stream_does_not_send_stream_options(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_upstream_url"] = "http://127.0.0.1:11434/api/chat"
rt = svc.runtime()
run_async(
rt.handle_chat(
{
"messages": [{"role": "user", "content": "hi"}],
"stream": True,
"stream_options": {"include_usage": True},
},
cfg,
("guest", "ollama_stream_opts"),
"test",
"default",
)
)
upstream_payload = rt._client.calls[-1][1]
assert upstream_payload["stream"] is True
assert "stream_options" not in upstream_payload
assert upstream_payload["think"] is False
def test_ollama_explicit_dialect_overrides_url(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_upstream_url"] = "https://ai.example.com/v1/chat/completions"
cfg["gateway_thinking_dialect"] = "ollama"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "ollama_dialect_override"),
"test",
"default",
)
)
body = rt._client.calls[-1][1]
assert body["think"] is False
assert "thinking" not in body
def test_client_model_allowed_when_allow_client_model_on(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_allow_client_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"model": "llama3.2", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "client_model"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "llama3.2"
def test_molodetz_alias_still_remapped_with_allow_client_model(local_db, monkeypatch):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
original = routing.model_store.get("molodetz")
routing.model_store.remove("molodetz")
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_allow_client_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"model": "molodetz", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "molodetz_alias"),
"test",
"default",
)
)
assert rt._client.calls[-1][1]["model"] == "deepseek-chat"
finally:
if original is not None:
routing.model_store.set(
routing.ModelRouteIn(
**{
field: getattr(original, field)
for field in routing.ModelRouteIn.model_fields
}
)
)
def test_stream_forwarded_to_upstream_with_forced_include_usage(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
run_async(
rt.handle_chat(
{
"messages": [{"role": "user", "content": "hi"}],
"stream": True,
"stream_options": {"include_usage": False},
},
cfg,
("guest", "test"),
"test",
"default",
)
)
upstream_payload = rt._client.calls[-1][1]
assert upstream_payload["stream"] is True
assert upstream_payload["stream_options"] == {"include_usage": True}
assert upstream_payload["thinking"] == {"type": "disabled"}
assert "think" not in upstream_payload
assert "reasoning" not in upstream_payload
def test_non_stream_has_no_stream_options_upstream(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
run_async(
rt.handle_chat(
{
"messages": [{"role": "user", "content": "hi"}],
"stream_options": {"include_usage": True},
},
cfg,
("guest", "test"),
"test",
"default",
)
)
upstream_payload = rt._client.calls[-1][1]
assert "stream_options" not in upstream_payload
assert upstream_payload["stream"] is False
def test_thinking_disabled_by_default(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "test"),
"test",
"default",
)
)
body = rt._client.calls[-1][1]
assert body["thinking"] == {"type": "disabled"}
def test_client_think_true_enables_upstream_thinking(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
run_async(
rt.handle_chat(
{
"messages": [{"role": "user", "content": "hi"}],
"think": True,
},
cfg,
("guest", "test"),
"test",
"default",
)
)
body = rt._client.calls[-1][1]
assert body["thinking"] == {"type": "enabled"}
assert "think" not in body
def test_openrouter_upstream_gets_reasoning_none(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_upstream_url"] = "https://openrouter.ai/api/v1/chat/completions"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "test"),
"test",
"default",
)
)
body = rt._client.calls[-1][1]
assert body["reasoning"] == {"effort": "none"}
assert "thinking" not in body
def test_include_usage_emits_usage_chunk(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClientWithUsage_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{
"messages": [{"role": "user", "content": "hi"}],
"stream": True,
"stream_options": {"include_usage": True},
},
cfg,
("guest", "test"),
"test",
"default",
)
)
async def drain():
out = []
async for chunk in resp.body_iterator:
out.append(chunk if isinstance(chunk, str) else chunk.decode())
return "".join(out)
body = run_async(drain())
assert '"usage"' in body
assert '"total_tokens": 10' in body
assert "[DONE]" in body
def test_no_usage_chunk_without_include_usage(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClientWithUsage_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}], "stream": True},
cfg,
("guest", "test"),
"test",
"default",
)
)
async def drain():
out = []
async for chunk in resp.body_iterator:
out.append(chunk if isinstance(chunk, str) else chunk.decode())
return "".join(out)
body = run_async(drain())
assert '"usage"' not in body
assert "[DONE]" in body
def test_stream_headers_carry_no_cost_or_token_data(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClientWithUsage_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}], "stream": True},
cfg,
("guest", "test"),
"test",
"my-app",
)
)
assert resp.headers["X-Gateway-Model"] == cfg["gateway_model"]
assert resp.headers["X-Gateway-Backend"] == "chat"
assert resp.headers["X-App-Reference"] == "my-app"
assert "x-gateway-cost-usd" not in {k.lower() for k in resp.headers}
assert "x-gateway-total-tokens" not in {k.lower() for k in resp.headers}
def test_stream_records_ttft_and_inter_token_in_ledger(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClientWithUsage_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{
"messages": [{"role": "user", "content": "hi"}],
"stream": True,
"stream_options": {"include_usage": True},
},
cfg,
("guest", "ttft_probe"),
"test",
"default",
)
)
async def drain():
async for _ in resp.body_iterator:
pass
run_async(drain())
row = get_table("gateway_usage_ledger").find_one(owner_id="ttft_probe")
assert row is not None
assert row["backend"] == "chat"
assert row["stream_requested"] == 1
assert row["success"] == 1
assert row["total_tokens"] == 10
assert row["ttft_ms"] is not None and row["ttft_ms"] >= 0.0
assert row["inter_token_ms"] is not None and row["inter_token_ms"] >= 0.0
class FakeClientReasoning_openai_gateway(FakeClient_openai_gateway):
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": body.get("model"),
"choices": [{"message": {"content": "x", "reasoning": "aaaaa"}}],
"usage": {"prompt_tokens": 7, "completion_tokens": 3, "total_tokens": 10},
}
)
def test_ollama_reasoning_delta_is_counted_as_a_content_chunk(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClientReasoning_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{
"messages": [{"role": "user", "content": "hi"}],
"stream": True,
"stream_options": {"include_usage": True},
},
cfg,
("guest", "ollama_reasoning_probe"),
"test",
"default",
)
)
async def drain():
async for _ in resp.body_iterator:
pass
run_async(drain())
row = get_table("gateway_usage_ledger").find_one(owner_id="ollama_reasoning_probe")
assert row is not None
assert row["inter_token_ms"] is not None and row["inter_token_ms"] >= 0.0
def test_stream_client_disconnect_records_failure_and_closes_upstream(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}], "stream": True},
cfg,
("guest", "disconnect_probe"),
"test",
"default",
)
)
async def partial_then_close():
agen = resp.body_iterator
await agen.__anext__()
await agen.aclose()
run_async(partial_then_close())
row = get_table("gateway_usage_ledger").find_one(owner_id="disconnect_probe")
assert row is not None
assert row["success"] == 0
assert row["error_category"] == "client_disconnected"
def test_models_endpoint_publishes_molodetz(local_db):
from devplacepy.services.openai_gateway import routing
routing.seed_default_deepseek_routes()
try:
svc = GatewayService()
resp = svc._models_response()
payload = json.loads(resp.body.decode())
ids = {m["id"] for m in payload["data"]}
assert payload["object"] == "list"
assert {"molodetz", "molodetz-pro"} <= ids
for model in payload["data"]:
assert model["object"] == "model"
assert model["owned_by"] == "molodetz"
finally:
routing.model_store.remove("molodetz")
routing.model_store.remove("molodetz-pro")
# Upstream header collision policy: only X-Gateway-Model is ever trusted and
# forwarded from an upstream that happens to speak our own X-Gateway-* header
# convention (e.g. another DevPlace-style gateway). Every other figure
# (cost/tokens/latency/context/app-reference) must remain ours regardless of
# what an upstream claims. See usage.upstream_reported_model / CLAUDE.md.
class FakeClientUpstreamModelHeader_openai_gateway(FakeClient_openai_gateway):
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": body.get("model"),
"choices": [{"message": {"content": "hi there"}}],
},
extra_headers={"X-Gateway-Model": "upstream/served-model-x"},
)
class FakeClientHostileModelHeader_openai_gateway(FakeClient_openai_gateway):
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": body.get("model"),
"choices": [{"message": {"content": "hi there"}}],
},
extra_headers={"X-Gateway-Model": "evil\r\nX-Injected: true"},
)
class FakeEmbedClientUpstreamModelHeader_openai_gateway(FakeEmbedClient_openai_gateway):
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"object": "list",
"model": body.get("model"),
"data": [{"object": "embedding", "index": 0, "embedding": [0.1, 0.2]}],
"usage": {"prompt_tokens": 5, "total_tokens": 5},
},
extra_headers={"X-Gateway-Model": "upstream/embed-served"},
)
class FakeImageClientUpstreamModelHeader_openai_gateway(FakeImageClient_openai_gateway):
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
return FakeResp_openai_gateway(
payload={
"created": 1,
"model": body.get("model"),
"data": [{"b64_json": "aGVsbG8="}],
"usage": {"cost": 0.05},
},
extra_headers={"X-Gateway-Model": "upstream/image-served"},
)
class FakePassthroughClientUpstreamModelHeader_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
return FakeResp_openai_gateway(
payload={"ok": True},
extra_headers={"X-Gateway-Model": "upstream/passthrough-served"},
)
async def aclose(self):
pass
def test_upstream_x_gateway_model_header_is_forwarded_for_chat(local_db, monkeypatch):
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakeClientUpstreamModelHeader_openai_gateway
)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "upstream_model_chat"),
"test",
"default",
)
)
assert resp.headers["X-Gateway-Model"] == "upstream/served-model-x"
row = get_table("gateway_usage_ledger").find_one(owner_id="upstream_model_chat")
assert row is not None
assert row["model"] == "deepseek-chat"
def test_upstream_x_gateway_model_header_is_forwarded_for_streaming_chat(
local_db, monkeypatch
):
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakeClientUpstreamModelHeader_openai_gateway
)
svc = GatewayService()
cfg = svc.effective_config()
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}], "stream": True},
cfg,
("guest", "upstream_model_stream"),
"test",
"default",
)
)
assert resp.headers["X-Gateway-Model"] == "upstream/served-model-x"
assert resp.headers["X-Gateway-Backend"] == "chat"
async def drain():
async for _ in resp.body_iterator:
pass
run_async(drain())
def test_upstream_x_gateway_model_header_is_forwarded_for_embeddings(
local_db, monkeypatch
):
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakeEmbedClientUpstreamModelHeader_openai_gateway
)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_embed_enabled"] = True
cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
rt = svc.runtime()
resp = run_async(
rt.handle_embeddings(
{"input": "hello"},
cfg,
("guest", "upstream_model_embed"),
"test",
"default",
)
)
assert resp.headers["X-Gateway-Model"] == "upstream/embed-served"
row = get_table("gateway_usage_ledger").find_one(owner_id="upstream_model_embed")
assert row is not None
assert row["model"] == "qwen/qwen3-embedding-8b"
def test_upstream_x_gateway_model_header_is_forwarded_for_images(local_db, monkeypatch):
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakeImageClientUpstreamModelHeader_openai_gateway
)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_image_enabled"] = True
cfg["gateway_image_model"] = "black-forest-labs/flux.2-pro"
rt = svc.runtime()
resp = run_async(
rt.handle_images(
{"prompt": "badge"},
cfg,
("guest", "upstream_model_img"),
"test",
"default",
)
)
assert resp.headers["X-Gateway-Model"] == "upstream/image-served"
row = get_table("gateway_usage_ledger").find_one(owner_id="upstream_model_img")
assert row is not None
assert row["model"] == "black-forest-labs/flux.2-pro"
def test_upstream_x_gateway_model_header_is_forwarded_for_passthrough(
local_db, monkeypatch
):
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakePassthroughClientUpstreamModelHeader_openai_gateway
)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
resp = run_async(
rt.handle_passthrough(
"POST",
"responses",
"application/json",
b"{}",
cfg,
("guest", "upstream_model_passthrough"),
"test",
"default",
)
)
assert resp.headers["X-Gateway-Model"] == "upstream/passthrough-served"
row = get_table("gateway_usage_ledger").find_one(
owner_id="upstream_model_passthrough"
)
assert row is not None
assert row["model"] == "deepseek-chat"
def test_no_upstream_model_header_keeps_our_own_model(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "no_upstream_header"),
"test",
"default",
)
)
assert resp.headers["X-Gateway-Model"] == "deepseek-chat"
class FakeEmbeddedErrorClient_openai_gateway:
"""Mimics OpenRouter's documented behavior of answering 200 OK with the
failure embedded in the JSON body (openrouter.ai/docs/api-reference/errors)
instead of a non-2xx status."""
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
body = request.json_body or {}
model = body.get("model")
if model == "primary-target":
return FakeResp_openai_gateway(
status=200,
payload={"error": {"message": "no provider available", "code": 502}},
)
return FakeResp_openai_gateway(
payload={
"id": "x",
"model": model,
"choices": [{"message": {"content": "fallback ok"}}],
}
)
async def aclose(self):
pass
def test_chat_falls_back_when_upstream_returns_200_with_an_embedded_error(
local_db, monkeypatch
):
from devplacepy.services.openai_gateway import routing
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakeEmbeddedErrorClient_openai_gateway
)
routing.model_store.set(
routing.ModelRouteIn(source_model="eb-backup-route", target_model="backup-target")
)
routing.model_store.set(
routing.ModelRouteIn(
source_model="eb-primary-route",
target_model="primary-target",
fallback_model="eb-backup-route",
)
)
try:
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
response = run_async(
rt.handle_chat(
{"model": "eb-primary-route", "messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "embedded_error_chat"),
"test",
"default",
)
)
assert response.status_code == 200
assert rt._client.calls[0][1]["model"] == "primary-target"
assert rt._client.calls[-1][1]["model"] == "backup-target"
row = get_table("gateway_usage_ledger").find_one(owner_id="embedded_error_chat")
assert row is not None
assert row["requested_model"] == "eb-primary-route"
assert row["model"] == "backup-target"
assert row["success"] == 1
finally:
routing.model_store.remove("eb-primary-route")
routing.model_store.remove("eb-backup-route")
class FakeAlwaysEmbeddedErrorClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
def build_request(self, method, url, headers=None, json=None, content=None):
return FakeRequest(method, url, json)
async def send(self, request, stream=False):
self.calls.append((request.url, request.json_body))
return FakeResp_openai_gateway(
status=200,
payload={"error": {"message": "no provider available", "code": 502}},
)
async def aclose(self):
pass
def test_chat_reports_502_when_upstream_returns_200_with_an_embedded_error_and_no_fallback(
local_db, monkeypatch
):
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakeAlwaysEmbeddedErrorClient_openai_gateway
)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_vision_enabled"] = False
cfg["gateway_max_retries"] = 0
rt = svc.runtime()
response = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "embedded_error_no_fallback"),
"test",
"default",
)
)
assert response.status_code == 502
row = get_table("gateway_usage_ledger").find_one(owner_id="embedded_error_no_fallback")
assert row is not None
assert row["success"] == 0
assert row["error_category"] == "upstream_error"
def test_ollama_dialect_sends_reasoning_effort_alongside_think(local_db, monkeypatch):
# Ollama's native /api/chat honors a boolean `think`, but its
# OpenAI-compatible /v1/chat/completions layer (what this gateway
# actually calls) does not - it maps reasoning_effort/reasoning
# instead (github.com/ollama/ollama issues #15288, #15293, #14820).
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_upstream_url"] = "http://127.0.0.1:11434/v1/chat/completions"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "ollama_reasoning_effort"),
"test",
"default",
)
)
body = rt._client.calls[-1][1]
assert body["think"] is False
assert body["reasoning_effort"] == "none"
def test_hostile_upstream_model_header_is_ignored_end_to_end(local_db, monkeypatch):
monkeypatch.setattr(
gwmod.httpx, "AsyncClient", FakeClientHostileModelHeader_openai_gateway
)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_force_model"] = True
cfg["gateway_model"] = "deepseek-chat"
rt = svc.runtime()
resp = run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}]},
cfg,
("guest", "hostile_header"),
"test",
"default",
)
)
assert resp.headers["X-Gateway-Model"] == "deepseek-chat"