diff --git a/adapters/llm.py b/adapters/llm.py index 5bb2793..3e9223a 100644 --- a/adapters/llm.py +++ b/adapters/llm.py @@ -295,12 +295,10 @@ class GovernedLLMClient: if self._breaker.is_open(self._provider, time.monotonic()): raise CircuitOpenError(f"熔断器已开启,拒绝调用 provider={self._provider}") - # ② call_id 生成 - call_id = str(uuid4()) - - # ③ 缓存查询(cache 为 None 时跳过) + # ② 缓存查询(cache 为 None 时跳过)— call_id 在缓存路径独立生成 cached = await self._cache.get(self._model, messages) if self._cache is not None else None if cached is not None: + cache_call_id = str(uuid4()) response = LLMResponse( content=cached.content, thinking=cached.thinking, @@ -312,10 +310,10 @@ class GovernedLLMClient: ttft_ms=None, max_inter_token_ms=None, cache_hit=True, - call_id=call_id, + call_id=cache_call_id, ) await self._telemetry.record_llm_call( - call_id=call_id, + call_id=cache_call_id, parent_call_id=parent_call_id, session_id=session_id, model_name=self._model, @@ -333,9 +331,10 @@ class GovernedLLMClient: ) return response - # ④ 重试循环 + 流式消费 + # ③ 重试循环 + 流式消费(每次 attempt 独立 call_id) last_exc: Exception | None = None for attempt in range(self._max_retries): + call_id = str(uuid4()) attempt_start = time.monotonic() try: content, thinking_text, ttft_ms, max_itoken_ms, usage = await self._call_streaming( diff --git a/tests/unit/test_governed_llm.py b/tests/unit/test_governed_llm.py index 0f3146a..923b6ec 100644 --- a/tests/unit/test_governed_llm.py +++ b/tests/unit/test_governed_llm.py @@ -197,9 +197,9 @@ async def test_transient_error_retries_and_records_telemetry(): success_calls = [c for c in telemetry.calls if c.get("error") is None] assert len(success_calls) == 1 - # 所有遥测记录应使用同一个 call_id(Important 2 修复验证) + # 每次 attempt 应使用独立的 call_id(根因修复:防遥测主键冲突) call_ids = {c["call_id"] for c in telemetry.calls} - assert len(call_ids) == 1 + assert len(call_ids) == 3 # 2 次失败 + 1 次成功 = 3 个独立 call_id @pytest.mark.asyncio