From 4841d901af0ea4c29edc499427fe1188b33741bb Mon Sep 17 00:00:00 2001 From: iomgaa Date: Fri, 31 Jul 2026 07:48:35 -0400 Subject: [PATCH] feat: add cached prompt tokens and reported model to response types --- src/polygateway/types.py | 11 +++++++++++ tests/unit/test_types.py | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/polygateway/types.py b/src/polygateway/types.py index b267076..6b8980a 100644 --- a/src/polygateway/types.py +++ b/src/polygateway/types.py @@ -30,12 +30,20 @@ class LLMResponse: ttft_ms: float | None max_inter_token_ms: float | None cache_hit: bool + """**PolyGateway 自身响应缓存**命中(未产生网关调用);与供应商侧 prompt + cache 无关,后者见 `cached_prompt_tokens`。""" call_id: str # —— 库新增(只增不删,必带默认值;迁移兼容硬约束)—— source_name: str = "" cost: float | None = None usage_source: str = "measured" structured_data: Any | None = None + cached_prompt_tokens: int | None = None + """供应商 prompt cache 命中的输入 token 数(issue #3);None = 该源未上报, + 与"上报了但是 0"(真实零命中)区分——两者对下游的处置不同。""" + model_reported: str | None = None + """API 响应体里的 model 字段;None = 未上报。与 `model`(配置别名)可能 + 分叉——供应商把别名指向新权重时,实验复现必须认这个串。""" @dataclass(frozen=True) @@ -82,6 +90,9 @@ class TransportResult: ttft_ms: float | None max_inter_token_ms: float | None raw: dict[str, Any] + # —— 可观测字段(issue #3;带默认值,非 OpenAI 兼容的 transport 可不填)—— + cached_prompt_tokens: int | None = None + model_reported: str | None = None @dataclass(frozen=True) diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 3965e81..f377268 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -49,6 +49,29 @@ class TestLLMResponse: assert resp.usage_source == "measured" assert resp.structured_data is None + def test_observability_fields_default_to_none(self): + """issue #3: None = 该源未上报,与"上报了但是 0"区分(0 是真实零命中)。""" + resp = LLMResponse("c", "t", "m", "p", 1, 2, 3, None, None, False, "cid") + assert resp.cached_prompt_tokens is None + assert resp.model_reported is None + filled = LLMResponse( + "c", + "t", + "m", + "p", + 1, + 2, + 3, + None, + None, + False, + "cid", + cached_prompt_tokens=0, + model_reported="MiniMax-Text-01-250321", + ) + assert filled.cached_prompt_tokens == 0 # 真实零命中,不得与 None 混同 + assert filled.model_reported == "MiniMax-Text-01-250321" + def test_frozen(self): resp = LLMResponse("c", "t", "m", "p", 1, 2, 3, None, None, False, "cid") with pytest.raises(dataclasses.FrozenInstanceError): @@ -222,6 +245,8 @@ class TestAuxTypes: raw={"id": "x"}, ) assert s.raw["id"] == "x" + # issue #3: 新字段带默认值,不填也能构造(OCR 等其他 transport 零改动) + assert s.cached_prompt_tokens is None and s.model_reported is None class TestOcrTypes: