docs: fold the Codex review into the issue #7 design and register it

Pins the ARCHITECTURE section 6.1 revision to land before or with the
implementation, since the new scope reason contradicts the current single
source of truth. Documents why SourceNotConfiguredError may sit outside
the four-way classification: that rule governs transport-translated call
failures, and the GatewayUnavailableError family already lives outside it.

Also collapses the ten per-field response ternaries in emit_attempt into
an _AttemptUsage view. They all expressed the same decision and pushed the
method to cyclomatic complexity C, which blocked the commit gate.
This commit is contained in:
2026-08-06 03:45:48 -04:00
parent 2f5abb6a55
commit b1109e9fe9
6 changed files with 132 additions and 19 deletions
+50 -10
View File
@@ -12,6 +12,7 @@ import asyncio
import json
import time
import uuid
from dataclasses import dataclass
from typing import TYPE_CHECKING
from loguru import logger
@@ -28,6 +29,44 @@ if TYPE_CHECKING:
from polygateway.types import ChatRequest, LLMResponse, SourceConfig
@dataclass(frozen=True)
class _AttemptUsage:
"""一次尝试的用量视图;默认值即"失败尝试"档(无用量可言,记 0 并标 unavailable)。
存在的理由是把 `emit_attempt` 里逐字段重复的 `X if response else Y` 收敛为
一处判定——十处三元把该方法推到圈复杂度 C,而它们表达的是同一件事。
"""
response_text: str = ""
thinking: str = ""
prompt_tokens: int = 0
completion_tokens: int = 0
usage_source: str = "unavailable"
ttft_ms: float | None = None
max_inter_token_ms: float | None = None
cached_prompt_tokens: int | None = None
model_reported: str | None = None
reasoning_tokens: int | None = None
@classmethod
def of(cls, response: LLMResponse | None) -> _AttemptUsage:
"""从响应取用量;`None`(失败尝试)返回全默认视图。"""
if response is None:
return cls()
return cls(
response_text=response.content,
thinking=response.thinking,
prompt_tokens=response.prompt_tokens,
completion_tokens=response.completion_tokens,
usage_source=response.usage_source,
ttft_ms=response.ttft_ms,
max_inter_token_ms=response.max_inter_token_ms,
cached_prompt_tokens=response.cached_prompt_tokens,
model_reported=response.model_reported,
reasoning_tokens=response.reasoning_tokens,
)
class TelemetryEmitter:
"""从请求与结果组装 21 字段并写入 recorder;一切写失败降级 warning。"""
@@ -46,25 +85,26 @@ class TelemetryEmitter:
error: str | None,
) -> None:
"""逐次尝试记录(RetryMW 调用);失败尝试无用量可言,记 0 并标 unavailable。"""
usage = _AttemptUsage.of(response)
await self._record(
request=request,
call_id=call_id,
model=source.model,
provider=source.provider,
source_name=source.name,
response_text=response.content if response else "",
thinking=response.thinking if response else "",
prompt_tokens=response.prompt_tokens if response else 0,
completion_tokens=response.completion_tokens if response else 0,
usage_source=response.usage_source if response else "unavailable",
response_text=usage.response_text,
thinking=usage.thinking,
prompt_tokens=usage.prompt_tokens,
completion_tokens=usage.completion_tokens,
usage_source=usage.usage_source,
latency_ms=latency_ms,
ttft_ms=response.ttft_ms if response else None,
max_inter_token_ms=response.max_inter_token_ms if response else None,
ttft_ms=usage.ttft_ms,
max_inter_token_ms=usage.max_inter_token_ms,
cache_hit=False,
error=error,
cached_prompt_tokens=response.cached_prompt_tokens if response else None,
model_reported=response.model_reported if response else None,
reasoning_tokens=response.reasoning_tokens if response else None,
cached_prompt_tokens=usage.cached_prompt_tokens,
model_reported=usage.model_reported,
reasoning_tokens=usage.reasoning_tokens,
# 唯一有"生效源"的入口,故是唯一能并上 extra_body 的(设计决策 D)
sampling=canonical_sampling_json(merge_sampling(source.extra_body, request.sampling)),
)