diff --git a/pipeline_service/agent_loop_v2.py b/pipeline_service/agent_loop_v2.py index 6bc4e3f..550c2db 100644 --- a/pipeline_service/agent_loop_v2.py +++ b/pipeline_service/agent_loop_v2.py @@ -99,6 +99,15 @@ class AgentExecutor: #(实测上游抖动窗口内 utility 调用连烧 15 分钟才放弃,会话全程无响应)。 _UTILITY_TIMEOUT = 60 + # 续跑门禁(2026-09-11 商机产线实测):模型输出纯文本「意图句」就结束 turn、 + # 不调工具 → 循环误判为最终答案终止会话,用户拿到半截话(实测连续 4 个会话 + # 在 tool_call_count 7~11 时这样夭折,报告/总结从未产出)。 + # 门禁条件:已有工作推进(tool_call_count>0) 且回复短于 _CONTINUE_GATE_MIN_CHARS + #(真结论/报告远长于此)且催促未达上限 → 注入续跑指令继续循环; + # 达上限后尊重模型终止(防死循环)。 + _CONTINUE_GATE_MIN_CHARS = 400 + _CONTINUE_GATE_MAX = 2 + def __init__( self, config, # AgentConfig (from pipeline_core) @@ -175,6 +184,7 @@ class AgentExecutor: self._started_at = time.time() self._turn_count = 0 self._tool_call_count = 0 + self._continue_nudges = 0 # Step 0: 初始化(解析 pipeline_id / skill_loader 等,slash 命令也需要) await self._init_components() @@ -422,6 +432,30 @@ class AgentExecutor: # (历史版本会在 tool_call_count==0 时用硬编码关键词强制路由, # 那会剥夺 LLM 诚实降级/能力自省的能力,把 agent 变成路由器。) final_reply = act.get("message", "") + + # 续跑门禁(2026-09-11):已有工作推进但回复是短意图句 + #("我再拉一次数据…")= 模型提前结束 turn 而非真结论。 + # 注入续跑指令继续循环,不让会话夭折在半截话上。 + if (self._tool_call_count > 0 + and len(final_reply) < self._CONTINUE_GATE_MIN_CHARS + and self._continue_nudges < self._CONTINUE_GATE_MAX): + self._continue_nudges += 1 + logger.warning( + f"run: 续跑门禁 turn={turn+1} 短回复({len(final_reply)}字)" + f" tool_calls={self._tool_call_count} 催促{self._continue_nudges}次") + yield json.dumps({ + "type": "progress", + "message": "检测到未完成的中途回复,继续推进任务…\n", + }, ensure_ascii=False) + "\n" + self._msgs.append({"role": "assistant", "content": final_reply}) + self._msgs.append({"role": "user", "content": ( + "【续跑指令】你刚才的回复是中途进度句,不是最终答案," + "任务尚未完成(用户要求的产出还没交付)。" + "请继续执行:要么调用工具推进下一步," + "要么直接输出完整的最终结论/报告(含全部要求的分析项)。" + "不要只说下一步打算做什么。")}) + continue + yield json.dumps({ "type": "reply", "message": final_reply, }, ensure_ascii=False) + "\n"