From f733c13dd1b184eb2edddd8f02bdc8722b7b989d Mon Sep 17 00:00:00 2001 From: iomgaa Date: Thu, 9 Jul 2026 00:12:38 -0400 Subject: [PATCH] =?UTF-8?q?fix(llm):=20call=5Fid=20=E7=A7=BB=E5=85=A5?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E5=BE=AA=E7=8E=AF=EF=BC=8C=E6=AF=8F=20attemp?= =?UTF-8?q?t=20=E7=8B=AC=E7=AB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 消除重试时遥测主键冲突的根因。每次 attempt 独立记录, parent_call_id 不受影响(循环外固定),更利于事后诊断重试轨迹。 Co-Authored-By: Claude Opus 4.6 (1M context) --- adapters/llm.py | 13 ++++++------- tests/unit/test_governed_llm.py | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) 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