Răsfoiți Sursa

fix(langgraph): 修复分析图 checkpointer 配置缺失、chat LLM 空响应兜底、pytest loop scope,并更新 LLM 网关配置

zxyj-langgraph 2 zile în urmă
părinte
comite
eeb1472733

+ 3 - 3
cfc-langgraph/.env

@@ -1,7 +1,7 @@
 # LLM
-LLM_API_KEY=sk-test-local
-LLM_BASE_URL=https://api.deepseek.com/v1
-LLM_MODEL=deepseek-chat
+LLM_API_KEY=ah-213f366ec0eb7cea46f91afd5b8e0d61fdfde1729684894a5b61add735e8ab12
+LLM_BASE_URL=http://axonhub.iwintrue.com/v1
+LLM_MODEL=agnes-3.0-flash
 
 # Embedding
 EMBEDDING_API_KEY=${LLM_API_KEY}

+ 6 - 1
cfc-langgraph/app/api/analyze.py

@@ -32,6 +32,11 @@ async def analyze(req: AnalyzeRequest):
     graph = get_graph()
     import uuid
     trace_id = str(uuid.uuid4())
+    # MemorySaver/AsyncSqliteSaver 均要求 config 提供 thread_id,缺省会抛
+    # "Checkpointer requires one or more of the following 'configurable' keys"
+    config = {
+        "configurable": {"thread_id": f"analyze_{req.user_id}_{trace_id}"},
+    }
     result = await graph.ainvoke({
         "report_id": req.report_id,
         "user_id": req.user_id,
@@ -41,5 +46,5 @@ async def analyze(req: AnalyzeRequest):
         "dimension_scores": None,
         "analysis": None,
         "recommendations": [],
-    })
+    }, config)
     return AnalyzeResponse(analysis=result.get("analysis", ""), trace_id=trace_id)

+ 8 - 0
cfc-langgraph/app/graphs/chat_graph.py

@@ -153,6 +153,14 @@ def create_chat_graph():
         response = await llm_with_tools.ainvoke(messages)
         answer = response.content
 
+        # B-117 兜底:部分网关/模型在 bind_tools 下偶发返回空 content(finish_reason
+        # 为 tool_calls 但未携带可用 tool_call,或直接空文本)。此时改用无 tools 的 llm
+        # 重试一次,避免 auto-tag 等纯文本任务拿到空 answer。
+        if not answer and not response.response_metadata.get("tool_calls"):
+            logger.warning("bind_tools 调用返回空 content,改用无工具 llm 重试")
+            response = await llm.ainvoke(messages)
+            answer = response.content
+
         # 提取任务
         tasks = await agent.extract_tasks(answer, state["user_id"], state["conversation_id"])
 

+ 5 - 0
cfc-langgraph/pyproject.toml

@@ -41,3 +41,8 @@ ignore = ["E501"]
 # session/auto 默认 loop scope 方能初始化 checkpointer 并无缝复用于全部测试。
 asyncio_mode = "auto"
 asyncio_default_fixture_loop_scope = "session"
+# B-117 修复:仅设 fixture loop scope 不够;测试函数本身默认 function scope,
+# 与 session-scoped checkpointer 跨 loop,触发 "Lock bound to different event loop"。
+# 将测试 loop 也提升到 session,让所有 async 测试共享同一事件循环,避免 AsyncSqliteSaver
+# 内部 Condition/Lock 绑定失效。
+asyncio_default_test_loop_scope = "session"