feat: carry the new observability fields through retry and cache
This commit is contained in:
@@ -436,6 +436,8 @@ class RetryMW:
|
|||||||
source_name=source.name,
|
source_name=source.name,
|
||||||
cost=None,
|
cost=None,
|
||||||
usage_source=result.usage_source,
|
usage_source=result.usage_source,
|
||||||
|
cached_prompt_tokens=result.cached_prompt_tokens,
|
||||||
|
model_reported=result.model_reported,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _settle_and_release(self, permit: Permit, actual: int) -> None:
|
async def _settle_and_release(self, permit: Permit, actual: int) -> None:
|
||||||
|
|||||||
@@ -150,6 +150,49 @@ class TestCacheFlow:
|
|||||||
assert terminal.calls == 2
|
assert terminal.calls == 2
|
||||||
|
|
||||||
|
|
||||||
|
class TestObservabilityFieldsOnHit:
|
||||||
|
"""issue #3 决策 B1: 命中行原样回放,与 model/prompt_tokens 同一口径。"""
|
||||||
|
|
||||||
|
async def test_fields_replayed_on_hit(self):
|
||||||
|
backend = InMemoryCache()
|
||||||
|
mw = _mw(backend)
|
||||||
|
terminal = _Terminal(
|
||||||
|
_resp(cached_prompt_tokens=64, model_reported="MiniMax-Text-01-250321")
|
||||||
|
)
|
||||||
|
await mw(ChatRequest(messages=_MSGS), terminal)
|
||||||
|
hit = await mw(ChatRequest(messages=_MSGS), terminal)
|
||||||
|
assert hit.cache_hit is True
|
||||||
|
assert hit.cached_prompt_tokens == 64
|
||||||
|
assert hit.model_reported == "MiniMax-Text-01-250321"
|
||||||
|
|
||||||
|
async def test_legacy_cache_entry_without_new_keys_rehydrates(self):
|
||||||
|
"""旧格式条目(无这两个键)必须照常重建为 None,不得抛异常回源。"""
|
||||||
|
backend = InMemoryCache()
|
||||||
|
mw = _mw(backend)
|
||||||
|
key = build_cache_key("m", _MSGS, "proj", None)
|
||||||
|
legacy = {
|
||||||
|
"content": "legacy",
|
||||||
|
"thinking": "",
|
||||||
|
"model": "m",
|
||||||
|
"provider": "p",
|
||||||
|
"prompt_tokens": 1,
|
||||||
|
"completion_tokens": 2,
|
||||||
|
"latency_ms": 30,
|
||||||
|
"ttft_ms": 5.0,
|
||||||
|
"max_inter_token_ms": 2.0,
|
||||||
|
"cache_hit": False,
|
||||||
|
"call_id": "orig",
|
||||||
|
"source_name": "s1",
|
||||||
|
"cost": None,
|
||||||
|
"usage_source": "measured",
|
||||||
|
}
|
||||||
|
await backend.set(key, json.dumps(legacy), ttl_s=100)
|
||||||
|
terminal = _Terminal(_resp())
|
||||||
|
hit = await mw(ChatRequest(messages=_MSGS), terminal)
|
||||||
|
assert hit.content == "legacy" and terminal.calls == 0 # 真的走了缓存
|
||||||
|
assert hit.cached_prompt_tokens is None and hit.model_reported is None
|
||||||
|
|
||||||
|
|
||||||
class _BrokenBackend:
|
class _BrokenBackend:
|
||||||
async def get(self, key):
|
async def get(self, key):
|
||||||
raise ConnectionError("redis down")
|
raise ConnectionError("redis down")
|
||||||
|
|||||||
@@ -194,6 +194,35 @@ class TestSuccessPath:
|
|||||||
assert (await limiter.source_stats("a")).tpm_used == 16
|
assert (await limiter.source_stats("a")).tpm_used == 16
|
||||||
|
|
||||||
|
|
||||||
|
class TestObservabilityPassthrough:
|
||||||
|
"""issue #3: transport 采到的两个可观测字段必须原样上浮到 LLMResponse。"""
|
||||||
|
|
||||||
|
async def test_fields_reach_the_response(self):
|
||||||
|
result = TransportResult(
|
||||||
|
content="ok",
|
||||||
|
thinking="",
|
||||||
|
prompt_tokens=10,
|
||||||
|
completion_tokens=5,
|
||||||
|
usage_source="measured",
|
||||||
|
ttft_ms=12.0,
|
||||||
|
max_inter_token_ms=3.0,
|
||||||
|
raw={},
|
||||||
|
cached_prompt_tokens=64,
|
||||||
|
model_reported="MiniMax-Text-01-250321",
|
||||||
|
)
|
||||||
|
mw, *_ = _harness([_src("a")], [result])
|
||||||
|
resp = await mw(_REQ)
|
||||||
|
assert resp.cached_prompt_tokens == 64
|
||||||
|
assert resp.model_reported == "MiniMax-Text-01-250321"
|
||||||
|
# model 仍是配置别名: 真实版本是旁证,不顶替溯源主字段
|
||||||
|
assert resp.model == "m"
|
||||||
|
|
||||||
|
async def test_absent_fields_stay_none(self):
|
||||||
|
mw, *_ = _harness([_src("a")], [_ok()])
|
||||||
|
resp = await mw(_REQ)
|
||||||
|
assert resp.cached_prompt_tokens is None and resp.model_reported is None
|
||||||
|
|
||||||
|
|
||||||
class TestRetryAndFailover:
|
class TestRetryAndFailover:
|
||||||
async def test_transient_switches_source_then_succeeds(self):
|
async def test_transient_switches_source_then_succeeds(self):
|
||||||
mw, _, _, transport, sleep, _ = _harness(
|
mw, _, _, transport, sleep, _ = _harness(
|
||||||
|
|||||||
Reference in New Issue
Block a user