diff --git a/src/polygateway/transports/openai_compat.py b/src/polygateway/transports/openai_compat.py index 52993df..80f6615 100644 --- a/src/polygateway/transports/openai_compat.py +++ b/src/polygateway/transports/openai_compat.py @@ -138,12 +138,13 @@ def _strip_think(content: str) -> tuple[str, str]: return _THINK_PATTERN.sub("", content).strip(), match.group(1).strip() -def _resolve_usage(usage: dict[str, Any], source: SourceConfig) -> tuple[int, int, str]: +def _resolve_usage(usage: dict[str, Any]) -> tuple[int, int, str]: """usage 帧读取;缺失/非法记 0/0 并标 unavailable(est_tokens 解耦设计 §3.2 #3)。 不再拿 `est_tokens` 兜底: 它按 CHS 定义是"最坏情形上界",拿上界当实测值 只会系统性高估账单;宁可把用量记成显式的"不可得"(cost 随之为 NULL), - 让缺口可被统计,也不编一个看似有效的数字。 + 让缺口可被统计,也不编一个看似有效的数字。用量口径自此不依赖源配置, + 故不再收 `SourceConfig`。 """ prompt, completion = usage.get("prompt_tokens"), usage.get("completion_tokens") if isinstance(prompt, int) and isinstance(completion, int) and prompt + completion > 0: @@ -151,15 +152,13 @@ def _resolve_usage(usage: dict[str, Any], source: SourceConfig) -> tuple[int, in return 0, 0, "unavailable" -def _resolve_stream_usage( - sink: dict[str, Any], salvaged: bool, source: SourceConfig -) -> tuple[int, int, str]: +def _resolve_stream_usage(sink: dict[str, Any], salvaged: bool) -> tuple[int, int, str]: """流式用量口径: 打捞路径把 measured 降级为 estimated,unavailable 原样保留。 前置条件不可省(解耦设计 §3.2 #4): usage 帧本就缺失时 `0/0` 会被洗成 `estimated`,进而按 token 换算出一个假的 `0.0` 成本。 """ - prompt, completion, usage_source = _resolve_usage(sink.get("usage") or {}, source) + prompt, completion, usage_source = _resolve_usage(sink.get("usage") or {}) if salvaged and usage_source == "measured": # 收到 usage 帧但流被截断: 数字真实、可信度降级(M1 设计 §6) usage_source = "estimated" @@ -188,7 +187,7 @@ def _extract_vectors( return vectors -def _resolve_embedding_usage(data: dict[str, Any], source: SourceConfig) -> tuple[int, str]: +def _resolve_embedding_usage(data: dict[str, Any]) -> tuple[int, str]: """usage 读取;缺失/非法记 0 并标 unavailable(与 chat 同口径,设计 §3.2 #3)。""" prompt = (data.get("usage") or {}).get("prompt_tokens") if isinstance(prompt, int) and prompt > 0: @@ -206,7 +205,7 @@ def _parse_embedding_payload( except json.JSONDecodeError as exc: raise ResultInvalidError(f"{source.name} embedding 响应非 JSON: {exc}", **ctx) from exc vectors = _extract_vectors(data, source, expected_count, ctx) - prompt_tokens, usage_source = _resolve_embedding_usage(data, source) + prompt_tokens, usage_source = _resolve_embedding_usage(data) return EmbeddingTransportResult( vectors=vectors, dim=len(vectors[0]), @@ -351,7 +350,7 @@ class OpenAICompatTransport: salvaged = self._check_done(sink, content_parts, thinking_parts, source) content, thinking = self._finalize_text(content_parts, thinking_parts, profile) self._reject_empty_completion(content, source) - prompt, completion, usage_source = _resolve_stream_usage(sink, salvaged, source) + prompt, completion, usage_source = _resolve_stream_usage(sink, salvaged) return TransportResult( content=content, thinking=thinking, @@ -433,7 +432,7 @@ class OpenAICompatTransport: [message.get("content") or ""], [message.get("reasoning_content") or ""], profile ) self._reject_empty_completion(content, source) - prompt, completion, usage_source = _resolve_usage(body.get("usage") or {}, source) + prompt, completion, usage_source = _resolve_usage(body.get("usage") or {}) return TransportResult( content=content, thinking=thinking, diff --git a/tests/unit/test_usage_source_domain.py b/tests/unit/test_usage_source_domain.py index 366aee6..eda73d7 100644 --- a/tests/unit/test_usage_source_domain.py +++ b/tests/unit/test_usage_source_domain.py @@ -80,7 +80,7 @@ class _MemoryRecorder: ], ) def test_resolve_usage_stays_in_domain(usage): - assert _resolve_usage(usage, _src())[2] in USAGE_SOURCES + assert _resolve_usage(usage)[2] in USAGE_SOURCES @pytest.mark.parametrize( @@ -95,7 +95,7 @@ def test_resolve_usage_stays_in_domain(usage): ], ) def test_resolve_embedding_usage_stays_in_domain(data): - assert _resolve_embedding_usage(data, _src())[1] in USAGE_SOURCES + assert _resolve_embedding_usage(data)[1] in USAGE_SOURCES def _sse(*frames, done):