Wave 1 item 1.2 of plans/token-waste-waves.md. Three provider shapes collapsed into two stored outcomes, so "the provider said zero" was indistinguishable from "the provider said nothing": details present, cached_tokens: 0 -> stored 0 details present, no cached count -> stored NULL no details block at all -> stored NULL That distinction is the entire question the item exists to settle. If the field is omitted on a full cache miss, every cache rate computed from the rows that carry it is conditioned on a hit having occurred and is biased upward -- which would make the 0.924 figure on pruned turns meaningless. `cached_tokens_source TEXT` records which shape produced the row: 'reported' | 'details_no_count' | 'no_details'. Additive, idempotent ALTER alongside the existing one, NULL on existing rows -- which is honest, since a row written before the column cannot say, and inferring one would fabricate exactly the evidence being gathered. Invariant: cached_prompt_tokens IS NOT NULL exactly when source = 'reported'. A JSON null under an existing cached_tokens key groups with 'details_no_count', not with a zero; no value is invented anywhere. Two bugs fell out of writing it: - `prompt_tokens_details: null` called .get on None and raised AttributeError inside the streamed finally block. - cached tokens were read ONLY from the explicit `usage` argument, so every buffered call site (non-streaming dispatch, eval_proficiency, seed_energy) dropped the count even when the provider reported it. It now resolves its source the same way cost already did. Measured read-only on the live DB before writing this: the 12.8% NeuralWatt coverage in the plan is not sparse reporting. Of 425 post-restart NULL rows, 420 are seed_reference sweeps; real dispatch traffic carries a count on 282 of 287 rows, and 24 of those are an explicit 0. So the provider does report zeros, and the upward-bias worry is much smaller than the plan assumed -- but the column is what makes that checkable rather than argued. 7 new tests: the three shapes at parse level, JSON null, a non-dict details block, the buffered payload path, and the three shapes asserted distinct as STORED rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRQXz5SYZYVWscxS1QqF6U
675 lines
25 KiB
Python
675 lines
25 KiB
Python
"""Multi-provider dispatch edge cases.
|
|
|
|
Exercises the dispatcher where two configured providers diverge in telemetry
|
|
shape and in per-provider refusal state. Nothing here calls real providers.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sqlite3
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from starlette.testclient import TestClient
|
|
|
|
import dispatcher
|
|
import session_cache
|
|
from config import DispatchProvider
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SCHEMA_SQL = (ROOT / "config" / "schema.sql").read_text()
|
|
|
|
CHEAP = "cheap-model"
|
|
DEAR = "dear-model"
|
|
|
|
# Stream lines carrying OpenRouter's keepalive comment *instead* of energy/cost.
|
|
OPENROUTER_STREAM_LINES = [
|
|
'data: {"id":"chatcmpl-or-1","choices":[{"delta":{"content":"hel"}}]}',
|
|
"",
|
|
': OPENROUTER PROCESSING',
|
|
'data: {"id":"chatcmpl-or-1","choices":[{"delta":{"content":"lo"},'
|
|
'"finish_reason":"stop"}],"usage":{"prompt_tokens":5,"completion_tokens":2}}',
|
|
"",
|
|
"data: [DONE]",
|
|
"",
|
|
]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clean_state(monkeypatch):
|
|
"""Isolate the module-level refusal map between tests."""
|
|
session_cache.clear()
|
|
dispatcher._provider_refusal_since.clear()
|
|
yield
|
|
dispatcher._provider_refusal_since.clear()
|
|
session_cache.clear()
|
|
|
|
|
|
@pytest.fixture
|
|
def router(tmp_path, monkeypatch):
|
|
"""A chat-completions fixture with two providers in the catalog."""
|
|
db_path = tmp_path / "test.db"
|
|
conn = sqlite3.connect(db_path)
|
|
conn.executescript(SCHEMA_SQL)
|
|
# Seed a cheap model under the default (neuralwatt) provider.
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO models (
|
|
model_id, provider, base_model_id, tier, context_window,
|
|
effective_context_window, max_output_tokens,
|
|
cost_per_1m_prompt, cost_per_1m_completion,
|
|
supports_vision, supports_json_mode,
|
|
latency_class, reasoning_mode, context_variant,
|
|
access_level, availability, last_updated
|
|
) VALUES (?, ?, ?, 2, 262128, 192500, 16384, ?, ?,
|
|
1, 1, 'standard', 'default', 'full', 'public', 'active',
|
|
'2026-08-22T00:00:00+00:00')
|
|
""",
|
|
(CHEAP, "neuralwatt", CHEAP, 0.30 / 3, 0.30),
|
|
)
|
|
# Seed an OpenRouter-only frontier model.
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO models (
|
|
model_id, provider, base_model_id, tier, context_window,
|
|
effective_context_window, max_output_tokens,
|
|
cost_per_1m_prompt, cost_per_1m_completion,
|
|
supports_vision, supports_json_mode,
|
|
latency_class, reasoning_mode, context_variant,
|
|
access_level, availability, last_updated
|
|
) VALUES (?, 'openrouter', ?, 3, 262128, 192500, 16384, 0.10, 20.0,
|
|
1, 1, 'standard', 'default', 'full', 'public', 'active',
|
|
'2026-08-22T00:00:00+00:00')
|
|
""",
|
|
("openai/gpt-6-astra", "openai/gpt-6-astra"),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
monkeypatch.setattr(dispatcher.cfg.database, "path", str(db_path))
|
|
monkeypatch.setattr(dispatcher.cfg.verification, "local_llm_enabled", False)
|
|
monkeypatch.setattr(dispatcher.cfg.local_vision, "enabled", False)
|
|
monkeypatch.setattr(dispatcher.cfg.session_cache, "enabled", False)
|
|
monkeypatch.setattr(dispatcher.cfg.exploration, "epsilon", 0.0)
|
|
monkeypatch.setenv("NEURALWATT_API_KEY", "test-key")
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
|
|
|
calls = []
|
|
|
|
def fake_post(url, headers=None, json=None, stream=False, timeout=None):
|
|
calls.append({"url": url, "body": json, "stream": stream})
|
|
if stream:
|
|
return _FakeResponse(lines=OPENROUTER_STREAM_LINES)
|
|
return _FakeResponse(_completion(json["model"]))
|
|
|
|
monkeypatch.setattr(dispatcher.requests, "post", fake_post)
|
|
yield TestClient(dispatcher.app), calls, db_path
|
|
|
|
|
|
class _FakeResponse:
|
|
"""Minimal requests.Response stand-in."""
|
|
|
|
def __init__(self, payload=None, *, status_code=200, lines=None):
|
|
self.status_code = status_code
|
|
self._payload = payload or {}
|
|
self._lines = lines or []
|
|
self.text = json.dumps(self._payload)
|
|
self.headers = {}
|
|
self.request = None
|
|
|
|
def json(self):
|
|
return self._payload
|
|
|
|
@property
|
|
def encoding(self):
|
|
"""The charset requests would pick, by requests' own rule.
|
|
|
|
Derived rather than asserted, so this fake tracks requests instead of
|
|
restating a belief about it. The default is a charset-less
|
|
``text/event-stream`` on purpose: that is what OpenRouter actually
|
|
sends, and `get_encoding_from_headers` answers ISO-8859-1 for any
|
|
``text/*`` without a charset. Decoding a UTF-8 stream with that and
|
|
re-encoding it produced mojibake on every streamed non-ASCII
|
|
character -- so the hostile case is the DEFAULT here, and a fake can
|
|
no longer make a broken passthrough look correct.
|
|
"""
|
|
from requests.structures import CaseInsensitiveDict
|
|
from requests.utils import get_encoding_from_headers
|
|
|
|
headers = CaseInsensitiveDict(
|
|
getattr(self, "headers", None) or {"Content-Type": "text/event-stream"}
|
|
)
|
|
return get_encoding_from_headers(headers) or "utf-8"
|
|
|
|
def iter_lines(self, decode_unicode=False):
|
|
# Faithful to requests in both modes: BYTES unless decode_unicode is
|
|
# set, and when it is set the charset comes from Content-Type.
|
|
#
|
|
# The `wire` line is the load-bearing one. Whatever a test wrote into
|
|
# `lines`, what a provider actually puts on the wire is UTF-8 bytes,
|
|
# so that is what gets decoded. An earlier version of this fake passed
|
|
# str lines straight through under decode_unicode, which modelled a
|
|
# stream that had ALREADY been decoded correctly -- and a fake that
|
|
# hands back the right answer cannot reproduce a charset bug. It let
|
|
# the whole streaming suite pass against a proxy that was mangling
|
|
# every non-ASCII character.
|
|
for line in self._lines:
|
|
wire = line.encode("utf-8") if isinstance(line, str) else line
|
|
yield wire.decode(self.encoding) if decode_unicode else wire
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
|
|
def _completion(model):
|
|
return {
|
|
"id": "chatcmpl-test-1",
|
|
"model": model,
|
|
"choices": [
|
|
{
|
|
"message": {"role": "assistant", "content": "hello there"},
|
|
"finish_reason": "stop",
|
|
}
|
|
],
|
|
"usage": {"prompt_tokens": 31, "completion_tokens": 12},
|
|
}
|
|
|
|
|
|
def _energy_row(db_path):
|
|
conn = sqlite3.connect(db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
row = conn.execute(
|
|
"SELECT model_id, energy_kwh, cost_usd FROM energy_observations ORDER BY id DESC LIMIT 1"
|
|
).fetchone()
|
|
conn.close()
|
|
return row
|
|
|
|
|
|
def test_openrouter_streaming_ignores_keepalive_comments_and_logs_null_telemetry(router):
|
|
"""A provider with has_energy_telemetry=false can emit `: OPENROUTER ...`
|
|
comments without crashing the stream or logging fake telemetry.
|
|
"""
|
|
client, calls, db_path = router
|
|
# Wire the openrouter provider config (already in config.yaml, but ensure
|
|
# the test fixture has it for clarity).
|
|
dispatcher.cfg.dispatch_providers["openrouter"] = DispatchProvider(
|
|
base_url="https://openrouter.ai/api/v1",
|
|
api_key_env="OPENROUTER_API_KEY",
|
|
has_energy_telemetry=False,
|
|
enabled=True,
|
|
)
|
|
|
|
resp = client.post(
|
|
"/v1/chat/completions",
|
|
json={
|
|
"model": "openai/gpt-6-astra",
|
|
"messages": [{"role": "user", "content": "hi"}],
|
|
"stream": True,
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
assert resp.headers["content-type"].startswith("text/event-stream")
|
|
# The keepalive comment must reach the client unchanged.
|
|
assert ": OPENROUTER PROCESSING" in resp.text
|
|
# No crash on the SSE framing.
|
|
assert "data: [DONE]" in resp.text
|
|
|
|
row = _energy_row(db_path)
|
|
assert row is not None
|
|
assert row["model_id"] == "openai/gpt-6-astra"
|
|
assert row["energy_kwh"] is None
|
|
assert row["cost_usd"] is None
|
|
|
|
|
|
def test_provider_refusal_is_isolated_between_providers(router, monkeypatch):
|
|
"""A refusal on neuralwatt must skip the cloud classifier only when the
|
|
default provider is neuralwatt; a request whose default provider is
|
|
openrouter must still be allowed to use the cloud classifier.
|
|
"""
|
|
client, calls, db_path = router
|
|
|
|
# Configure a cloud classifier so the cascade has something to skip.
|
|
from types import SimpleNamespace
|
|
|
|
cloud_called = {"neuralwatt": False, "openrouter": False}
|
|
|
|
def fake_cloud_classify(*args, **kwargs):
|
|
cloud_called[dispatcher.cfg.dispatch_settings.default_provider] = True
|
|
return {
|
|
"task_category": "reasoning_math",
|
|
"task_tier": 3,
|
|
"required_context_tokens": 100,
|
|
"confidence": 0.8,
|
|
}
|
|
|
|
monkeypatch.setattr(
|
|
dispatcher.cfg.classifier,
|
|
"cloud_fallback",
|
|
SimpleNamespace(
|
|
base_url="https://cloud.example/v1",
|
|
model="cloud-classifier",
|
|
timeout_seconds=2,
|
|
api_key_env=None,
|
|
max_output_tokens=1024,
|
|
),
|
|
)
|
|
monkeypatch.setattr(dispatcher, "_cloud_classifier_client", lambda cf: object())
|
|
monkeypatch.setattr(session_cache, "classify_one", fake_cloud_classify)
|
|
# Ensure classify() degrades so the cascade reaches the cloud step.
|
|
monkeypatch.setattr(dispatcher, "classify", lambda task, context: dispatcher.Classification(
|
|
task_category="general_chat",
|
|
task_tier=2,
|
|
required_context_tokens=10,
|
|
confidence=0.0,
|
|
source="fallback",
|
|
))
|
|
|
|
# Record a refusal for neuralwatt.
|
|
dispatcher._record_provider_refusal("neuralwatt")
|
|
|
|
# With default_provider=neuralwatt, the cloud step should be skipped.
|
|
monkeypatch.setattr(dispatcher.cfg.dispatch_settings, "default_provider", "neuralwatt")
|
|
assert dispatcher._classify_cascade("sess-nw", "sys", "user") is None
|
|
assert cloud_called["neuralwatt"] is False
|
|
|
|
# With default_provider=openrouter, the refusal on neuralwatt must not apply.
|
|
monkeypatch.setattr(dispatcher.cfg.dispatch_settings, "default_provider", "openrouter")
|
|
result = dispatcher._classify_cascade("sess-or", "sys", "user")
|
|
assert result is not None
|
|
assert result.source == "classifier_cloud"
|
|
assert cloud_called["openrouter"] is True
|
|
|
|
|
|
def test_extract_telemetry_empty_payload_has_no_energy_or_cost():
|
|
"""An empty payload yields an all-None Telemetry rather than an error."""
|
|
telemetry = dispatcher.extract_telemetry({})
|
|
assert telemetry.energy_kwh is None
|
|
assert telemetry.cost_usd is None
|
|
|
|
|
|
def test_sniff_telemetry_line_ignores_foreign_comments():
|
|
"""SSE comment lines that do not carry energy/cost (e.g. OpenRouter
|
|
keepalives) are deliberately ignored."""
|
|
assert dispatcher._sniff_telemetry_line(": OPENROUTER PROCESSING") is None
|
|
|
|
|
|
def test_sniff_telemetry_line_parses_energy_comment():
|
|
"""A `: energy {...}` SSE comment line is parsed into a truthy
|
|
(word, payload) tuple."""
|
|
parsed = dispatcher._sniff_telemetry_line(": energy {}")
|
|
assert parsed is not None
|
|
word, payload = parsed
|
|
assert word == "energy"
|
|
assert payload == {}
|
|
|
|
|
|
# --- WI-1e: cost capture ------------------------------------------------
|
|
|
|
|
|
def _stream_lines_with_usage_cost():
|
|
"""SSE stream whose final chunk carries `usage.cost` (OpenRouter shape:
|
|
the provider reports its bill inside the usage block, not in a top-level
|
|
`cost` comment, and has has_energy_telemetry=false so comment lines are
|
|
stripped)."""
|
|
return [
|
|
'data: {"id":"chatcmpl-or-1","choices":[{"delta":{"content":"hel"}}]}',
|
|
"",
|
|
'data: {"id":"chatcmpl-or-1","choices":[{"delta":{"content":"lo"},'
|
|
'"finish_reason":"stop"}],"usage":{"prompt_tokens":5,'
|
|
'"completion_tokens":2,"cost":0.005}}',
|
|
"",
|
|
"data: [DONE]",
|
|
"",
|
|
]
|
|
|
|
|
|
def _openrouter_provider():
|
|
return DispatchProvider(
|
|
base_url="https://openrouter.ai/api/v1",
|
|
api_key_env="OPENROUTER_API_KEY",
|
|
has_energy_telemetry=False,
|
|
reports_cost_in_usage=True,
|
|
enabled=True,
|
|
)
|
|
|
|
|
|
def _neuralwatt_provider():
|
|
return DispatchProvider(
|
|
base_url="https://api.neuralwatt.com/v1",
|
|
api_key_env="NEURALWATT_API_KEY",
|
|
has_energy_telemetry=True,
|
|
reports_cost_in_usage=False,
|
|
enabled=True,
|
|
)
|
|
|
|
|
|
def test_dispatch_neuralwatt_flag_off_omits_usage_include(router):
|
|
"""Case 1: reports_cost_in_usage=false -> upstream_body carries no `usage`
|
|
include key on the wire."""
|
|
client, calls, db_path = router
|
|
dispatcher.cfg.dispatch_providers["neuralwatt"] = _neuralwatt_provider()
|
|
|
|
resp = client.post(
|
|
"/v1/chat/completions",
|
|
json={
|
|
"model": CHEAP,
|
|
"messages": [{"role": "user", "content": "hi"}],
|
|
"stream": True,
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
assert "data: [DONE]" in resp.text
|
|
body = calls[0]["body"]
|
|
assert "usage" not in body
|
|
|
|
|
|
def test_dispatch_openrouter_flag_on_injects_usage_include(router):
|
|
"""Case 2: reports_cost_in_usage=true -> upstream_body carries
|
|
`usage.include=true` on the wire."""
|
|
client, calls, db_path = router
|
|
dispatcher.cfg.dispatch_providers["openrouter"] = _openrouter_provider()
|
|
|
|
resp = client.post(
|
|
"/v1/chat/completions",
|
|
json={
|
|
"model": "openai/gpt-6-astra",
|
|
"messages": [{"role": "user", "content": "hi"}],
|
|
"stream": True,
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
assert "data: [DONE]" in resp.text
|
|
body = calls[0]["body"]
|
|
assert body.get("usage") == {"include": True}
|
|
|
|
|
|
def test_extract_telemetry_cost_from_usage_buffered():
|
|
"""Case 3: flag on, buffered — cost_usd comes from usage.cost when the
|
|
payload carries no cost.request_cost_usd."""
|
|
telemetry = dispatcher.extract_telemetry({}, usage={"cost": 0.005})
|
|
assert telemetry.cost_usd == 0.005
|
|
|
|
|
|
def test_extract_telemetry_cost_payload_wins_over_usage():
|
|
"""Case 5: cost.request_cost_usd takes precedence over usage.cost when
|
|
both blocks are present."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{"cost": {"request_cost_usd": 0.01}}, usage={"cost": 0.005}
|
|
)
|
|
assert telemetry.cost_usd == 0.01
|
|
|
|
|
|
def test_extract_telemetry_streamed_cost_from_usage(router, monkeypatch):
|
|
"""Case 4: the streamed path logs non-null cost_usd when usage.cost is
|
|
present in the streamed finally block.
|
|
|
|
This is the trap the whole fix exists for: comment lines carrying cost are
|
|
stripped, so the cost must survive via the explicit `usage` parameter
|
|
rather than re-derivation from the (stripped) payload.
|
|
"""
|
|
client, calls, db_path = router
|
|
dispatcher.cfg.dispatch_providers["openrouter"] = _openrouter_provider()
|
|
|
|
def fake_post(url, headers=None, json=None, stream=False, timeout=None):
|
|
calls.append({"url": url, "body": json, "stream": stream})
|
|
if stream:
|
|
return _FakeResponse(lines=_stream_lines_with_usage_cost())
|
|
return _FakeResponse(_completion(json["model"]))
|
|
|
|
monkeypatch.setattr(dispatcher.requests, "post", fake_post)
|
|
|
|
resp = client.post(
|
|
"/v1/chat/completions",
|
|
json={
|
|
"model": "openai/gpt-6-astra",
|
|
"messages": [{"role": "user", "content": "hi"}],
|
|
"stream": True,
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
assert "data: [DONE]" in resp.text
|
|
row = _energy_row(db_path)
|
|
assert row is not None
|
|
assert row["cost_usd"] == 0.005
|
|
|
|
|
|
def test_extract_telemetry_empty_payload_cost_none():
|
|
"""Case 7: extract_telemetry({}) yields cost_usd=None."""
|
|
telemetry = dispatcher.extract_telemetry({})
|
|
assert telemetry.cost_usd is None
|
|
|
|
|
|
def test_extract_telemetry_cached_prompt_tokens_from_usage():
|
|
"""Case 8: cached_prompt_tokens read from usage.prompt_tokens_details
|
|
even when the payload carries nothing else."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{}, usage={"prompt_tokens_details": {"cached_tokens": 42}}
|
|
)
|
|
assert telemetry.cached_prompt_tokens == 42
|
|
|
|
|
|
# --- 1.1: the accounting opt-in reaches every dispatch path -----------------
|
|
|
|
|
|
def test_buffered_openrouter_carries_usage_include(router):
|
|
"""The NON-streaming path must carry the opt-in too.
|
|
|
|
It builds its own `attempt_body` from the client body rather than from
|
|
`upstream_body`, so the opt-in set alongside `stream_options` never
|
|
reached it: every buffered OpenRouter dispatch asked for no accounting and
|
|
logged NULL cost and NULL cached tokens.
|
|
"""
|
|
client, calls, db_path = router
|
|
dispatcher.cfg.dispatch_providers["openrouter"] = _openrouter_provider()
|
|
|
|
resp = client.post(
|
|
"/v1/chat/completions",
|
|
json={
|
|
"model": "openai/gpt-6-astra",
|
|
"messages": [{"role": "user", "content": "hi"}],
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
body = calls[-1]["body"]
|
|
assert calls[-1]["stream"] is False
|
|
assert body.get("usage") == {"include": True}
|
|
|
|
|
|
def test_buffered_neuralwatt_omits_usage_include(router):
|
|
"""The other half of the same rule: a provider with the flag off must not
|
|
have an opt-in invented for it on the buffered path either."""
|
|
client, calls, db_path = router
|
|
dispatcher.cfg.dispatch_providers["neuralwatt"] = _neuralwatt_provider()
|
|
|
|
resp = client.post(
|
|
"/v1/chat/completions",
|
|
json={"model": CHEAP, "messages": [{"role": "user", "content": "hi"}]},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
assert "usage" not in calls[-1]["body"]
|
|
|
|
|
|
def test_client_supplied_usage_block_is_not_overwritten():
|
|
"""setdefault, not assignment: a client that sent its own `usage` block
|
|
asked for something specific and keeps it."""
|
|
dispatcher.cfg.dispatch_providers["openrouter"] = _openrouter_provider()
|
|
body = dispatcher._apply_provider_usage_opts(
|
|
{"usage": {"include": False}}, "openrouter"
|
|
)
|
|
assert body["usage"] == {"include": False}
|
|
|
|
|
|
def test_unknown_provider_name_adds_nothing():
|
|
"""A provider name with no config entry is not a crash and not an opt-in."""
|
|
assert dispatcher._apply_provider_usage_opts({}, "no-such-provider") == {}
|
|
|
|
|
|
def test_streaming_failover_uses_the_candidates_flag_not_the_first_choice(
|
|
router, monkeypatch
|
|
):
|
|
"""Failover crosses providers, and the opt-in belongs to whoever is being
|
|
posted to.
|
|
|
|
`upstream_body` was built once from the initially-selected provider, so a
|
|
failover onto OpenRouter posted a body with no accounting opt-in -- the
|
|
exact requests most likely to need one, since they are the retries.
|
|
"""
|
|
client, calls, db_path = router
|
|
dispatcher.cfg.dispatch_providers["neuralwatt"] = _neuralwatt_provider()
|
|
dispatcher.cfg.dispatch_providers["openrouter"] = _openrouter_provider()
|
|
monkeypatch.setattr(dispatcher.cfg.circuit_breaker, "enabled", False)
|
|
|
|
stream_calls = []
|
|
|
|
def fake_post(url, headers=None, json=None, stream=False, timeout=None):
|
|
calls.append({"url": url, "body": json, "stream": stream})
|
|
if not stream:
|
|
return _FakeResponse(_completion(json["model"]))
|
|
stream_calls.append(json)
|
|
# First candidate fails, so the router fails over to the runner-up,
|
|
# which the fixture places on the other provider.
|
|
if len(stream_calls) == 1:
|
|
return _FakeResponse({"error": "nope"}, status_code=500)
|
|
return _FakeResponse(lines=_stream_lines_with_usage_cost())
|
|
|
|
monkeypatch.setattr(dispatcher.requests, "post", fake_post)
|
|
|
|
resp = client.post(
|
|
"/v1/chat/completions",
|
|
json={
|
|
"model": "auto",
|
|
"messages": [{"role": "user", "content": "refactor this"}],
|
|
"stream": True,
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
assert len(stream_calls) == 2, "expected a failover onto the second provider"
|
|
first, second = stream_calls
|
|
# The two candidates sit on different providers, so exactly one body
|
|
# carries the opt-in -- and which one is decided by the candidate, not by
|
|
# the request's first choice.
|
|
assert first["model"] != second["model"]
|
|
by_provider = {
|
|
CHEAP: "neuralwatt", "openai/gpt-6-astra": "openrouter",
|
|
}
|
|
for sent in (first, second):
|
|
wants = by_provider[sent["model"]] == "openrouter"
|
|
assert ("usage" in sent) is wants, (
|
|
f"{sent['model']} on {by_provider[sent['model']]} "
|
|
f"{'lost' if wants else 'gained'} the accounting opt-in"
|
|
)
|
|
|
|
|
|
# --- 1.2: an absent cached_tokens is distinguishable from a reported zero ---
|
|
#
|
|
# Three provider shapes used to collapse into two stored outcomes, so "the
|
|
# provider said zero" could not be told from "the provider said nothing". The
|
|
# difference decides whether every cache rate computed from the rows that DO
|
|
# carry a count is conditioned on a hit having occurred.
|
|
|
|
|
|
def _cached_cell(db_path):
|
|
conn = sqlite3.connect(db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
row = conn.execute(
|
|
"SELECT cached_prompt_tokens, cached_tokens_source "
|
|
"FROM energy_observations ORDER BY id DESC LIMIT 1"
|
|
).fetchone()
|
|
conn.close()
|
|
return (row["cached_prompt_tokens"], row["cached_tokens_source"])
|
|
|
|
|
|
def test_cached_tokens_reported_zero_is_a_measurement():
|
|
"""(a) prompt_tokens_details present, cached_tokens: 0. An explicit zero
|
|
is the provider measuring a full cache miss, and must survive as 0."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{}, usage={"prompt_tokens_details": {"cached_tokens": 0}}
|
|
)
|
|
assert telemetry.cached_prompt_tokens == 0
|
|
assert telemetry.cached_tokens_source == "reported"
|
|
|
|
|
|
def test_cached_tokens_details_without_the_key_is_not_a_zero():
|
|
"""(b) prompt_tokens_details present, cached_tokens absent. The provider
|
|
described the prompt and said nothing about caching -- which is not a
|
|
measurement of zero, so no value is invented."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{}, usage={"prompt_tokens_details": {"audio_tokens": 0}}
|
|
)
|
|
assert telemetry.cached_prompt_tokens is None
|
|
assert telemetry.cached_tokens_source == "details_no_count"
|
|
|
|
|
|
def test_cached_tokens_no_details_block_at_all():
|
|
"""(c) no prompt_tokens_details. Distinct from (b): a provider that never
|
|
reports the breakdown is a different fact from one that reported a
|
|
breakdown with no cached count in it."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{}, usage={"prompt_tokens": 100, "completion_tokens": 5}
|
|
)
|
|
assert telemetry.cached_prompt_tokens is None
|
|
assert telemetry.cached_tokens_source == "no_details"
|
|
|
|
|
|
def test_cached_tokens_json_null_groups_with_no_count():
|
|
"""A JSON null under an existing key is the provider producing the slot
|
|
and declining to fill it. It groups with (b), never with a zero, which
|
|
keeps the invariant that a non-NULL count means source='reported'."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{}, usage={"prompt_tokens_details": {"cached_tokens": None}}
|
|
)
|
|
assert telemetry.cached_prompt_tokens is None
|
|
assert telemetry.cached_tokens_source == "details_no_count"
|
|
|
|
|
|
def test_cached_tokens_details_not_a_dict_does_not_raise():
|
|
"""`prompt_tokens_details: null` reached `.get` on None and raised
|
|
AttributeError inside the streamed finally block."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{}, usage={"prompt_tokens_details": None}
|
|
)
|
|
assert telemetry.cached_prompt_tokens is None
|
|
assert telemetry.cached_tokens_source == "no_details"
|
|
|
|
|
|
def test_cached_tokens_read_from_a_buffered_payloads_own_usage():
|
|
"""The buffered call sites pass a payload and no explicit `usage`, so the
|
|
count was dropped for every one of them. Cost already resolved its source
|
|
this way; cached tokens now do too."""
|
|
telemetry = dispatcher.extract_telemetry(
|
|
{"usage": {"prompt_tokens_details": {"cached_tokens": 7}}}
|
|
)
|
|
assert telemetry.cached_prompt_tokens == 7
|
|
assert telemetry.cached_tokens_source == "reported"
|
|
|
|
|
|
def test_three_cached_shapes_store_three_distinct_outcomes(router):
|
|
"""The point of the whole item: (a), (b) and (c) must be distinguishable
|
|
in the DB, not just in the parse."""
|
|
client, calls, db_path = router
|
|
shapes = {
|
|
"reported_zero": {"prompt_tokens_details": {"cached_tokens": 0}},
|
|
"details_no_count": {"prompt_tokens_details": {"audio_tokens": 0}},
|
|
"no_details": {"prompt_tokens": 100},
|
|
}
|
|
stored = {}
|
|
for name, usage in shapes.items():
|
|
dispatcher.log_observation(
|
|
CHEAP, "neuralwatt", "coding_general", f"req-{name}",
|
|
prompt_tokens=100, completion_tokens=5,
|
|
telemetry=dispatcher.extract_telemetry({}, usage=usage),
|
|
)
|
|
stored[name] = _cached_cell(db_path)
|
|
|
|
assert stored["reported_zero"] == (0, "reported")
|
|
assert stored["details_no_count"] == (None, "details_no_count")
|
|
assert stored["no_details"] == (None, "no_details")
|
|
assert len(set(stored.values())) == 3
|