GitHub周趋势2026W25 | Headroom 压缩 95% Token、NVIDIA 开源 AI Agent 安全扫描器、…
2026-07-28
2026-07-31 0
ReAct 循环中陷入"工具调用死循环"的重点在于把前置条件、操作顺序和容易误判的地方分清楚。
Agent 在 ReAct 循环中陷入"工具调用死循环"——反复调用同一个搜索工具 8 次,每次只是换个近义词,最终因为 token 耗尽而崩溃,却从未给出用户想要的答案。

某 SaaS 公司为内部客服搭建了一个"合同条款查询 Agent"。产品经理给它配了一个 search_contract 工具,用来在企业知识库中检索合同模板。
上线第三天,一个销售同事问了句:
Agent 开始工作了:
第 1 轮:search_contract(query="SaaS 订阅合同 自动续费") → 返回 5 条结果第 2 轮:search_contract(query="SaaS 订阅协议 自动续约") → 返回 3 条结果第 3 轮:search_contract(query="软件即服务 合同 续费条款") → 返回 4 条结果第 4 轮:search_contract(query="自动续费条款 SaaS 协议") → 返回 2 条结果第 5 轮:search_contract(query="SaaS 合同 续约 自动") → 返回 3 条结果第 6 轮:search_contract(query="续费 条款") → 返回 6 条结果第 7 轮:search_contract(query="自动续费") → 返回 4 条结果第 8 轮:search_contract(query="续费") → 返回 7 条结果
然后,上下文窗口爆了。Agent 崩溃,返回 Token limit exceeded。用户等了 45 秒,什么都没得到。
产品经理怒了:"这智能体怕不是个智障吧?搜了 8 次同一个东西!"
在开始拆解之前,先看看你脑子里有没有这些"直觉陷阱":
| 盲区 | 你的直觉 | 实际情况 |
|---|---|---|
| "给它更多工具就好了" | 工具越多,能力越强 | 工具多了模型反而更"贪心",总想再试一个 |
| "把 max_iterations 调大" | 多跑几轮总能出结果 | 每一轮都在消耗 token,且模型不知道"何时该停" |
| "写更好的 prompt 就行" | 提示词是银弹 | Prompt 只能影响意图,不能阻止模型"上瘾" |
| "这是一个 bug" | 这是代码逻辑错了 | 这是涌现行为——模型在 ReAct 框架下自然产生的策略退化 |
| "换个更强的模型" | GPT-5 就不会这样 | 更强的模型可能搜得更"花哨",但停不下来的问题依然存在 |
核心误区:你以为 Agent 是"聪明但偶尔犯错",其实它在 ReAct 循环里更像一个"只知道锤子的人,看什么都是钉子"。
用 LangChain 搭建一个极简复现场景:
from langchain.agents import AgentExecutor, create_react_agentfrom langchain_community.tools import DuckDuckGoSearchRunfrom langchain_openai import ChatOpenAIfrom langchain.prompts import PromptTemplate# 只给一个搜索工具search = DuckDuckGoSearchRun()tools = [search]llm = ChatOpenAI(model="gpt-4o", temperature=0)# ReAct prompt(简化版)prompt = PromptTemplate.from_template("""You have access to: {tools}Use this format:Question: the input questionThought: think about what to doAction: the tool to useAction Input: the input to the actionObservation: the result... (repeat Thought/Action/Observation)Thought: I now know the final answerFinal Answer: the final answerQuestion: {input}{agent_scratchpad}""")agent = create_react_agent(llm, tools, prompt)executor = AgentExecutor(agent=agent,tools=tools,max_iterations=10, # ← 危险!给足了"弹药"verbose=True,handle_parsing_errors=True)# 触发死循环的典型问题result = executor.invoke({"input": "2024年中国新能源汽车出口数据是多少?"})
你会发现 Agent 的行为模式:
让我们用两个比喻来理解:
比喻 1:超市找酱油
你去超市买酱油。正常人是:走到调味料区 → 看到酱油 → 拿一瓶 → 结账走人。
但 Agent 的行为是:
Agent 的问题不是"找不到",而是不知道什么时候已经找到了。
比喻 2:侦探查案
ReAct 框架就像一个侦探:观察现场(Observation)→ 推理(Thought)→ 采取行动(Action)。
好侦探知道:证据够了,可以结案了。 烂侦探(我们的 Agent)会:反复勘察同一个现场,每次都认为"可能还有遗漏的线索"。
技术根因:
| 层面 | 问题 |
|---|---|
| Prompt 层 | ReAct 模板没有"收敛信号"——没有明确告诉模型什么情况下该输出 Final Answer |
| 工具设计层 | 每次搜索结果都是"新信息",模型无法判断"我已经搜够了" |
| 执行框架层 | max_iterations 是硬上限,不是智能停止条件 |
| 模型认知层 | LLM 天生有"信息贪"——给定更多轮次,它倾向于填满 |
在 Prompt 中加入明确的"收敛信号":
convergence_prompt = PromptTemplate.from_template("""You are a precise research assistant. Follow these rules STRICTLY:1. You may use tools to gather information.2. AFTER each tool call, ask yourself: "Do I have enough to answer the user?"3. If the answer is YES → IMMEDIATELY output Final Answer. Do NOT search again.4. You may search AT MOST 3 times. After 3 searches, you MUST give the best answeryou can with whatever information you have.5. If the first search result already contains the answer, DO NOT search again.Just use it.Available tools: {tools}Format:Question: {input}Thought: ...Action: ...Observation: ...... (max 3 rounds)Thought: I have enough informationFinal Answer: ...{agent_scratchpad}""")
优点:零额外成本,立即可用 缺点:依赖模型遵循指令的能力,小模型可能无视
在每次工具调用后,计算"这次搜索到底有没有带来新信息":
import hashlibfrom difflib import SequenceMatcherclassGatedAgentExecutor:""" 在工具调用之间插入"信息增益检查",如果新一轮搜索 没有显著新信息,直接终止并返回已有结果。 """def__init__(self, similarity_threshold=0.7, max_stagnant_rounds=2):self.similarity_threshold = similarity_thresholdself.max_stagnant_rounds = max_stagnant_roundsself.previous_observations = []self.stagnant_count = 0defhas_new_information(self, new_observation: str) ->bool:""" 判断新搜索结果是否带来了实质性新信息。 用最长公共子序列的倒数作为"新信息量"的度量。 """ifnot self.previous_observations:self.previous_observations.append(new_observation)returnTrue# 合并之前所有观察结果combined_previous = " ".join(self.previous_observations)similarity = SequenceMatcher(None, combined_previous, new_observation).ratio()if similarity > self.similarity_threshold:self.stagnant_count += 1print(f"⚠️信息重复度 {similarity:.0%}, 停滞计数: {self.stagnant_count}")returnFalse# 有新信息,重置停滞计数self.stagnant_count = 0self.previous_observations.append(new_observation)returnTruedefshould_terminate(self, current_observation: str, accumulated_results: list) ->bool:"""综合判断是否应该终止搜索"""# 条件1:信息停滞(连续 N 轮无新信息)if self.stagnant_count >= self.max_stagnant_rounds:print("? 信息停滞,终止搜索")returnTrue# 条件2:已积累足够多的结果(搜索结果已经丰富)iflen(accumulated_results) >= 5:print("? 已积累足够信息,终止搜索")returnTrue# 条件3:单次搜索结果已经包含了明确的答案(简单启发式)answer_indicators = ["是的", "不,", "根据", "第一条", "答案是", "具体来说"]ifany(indicator in current_observation for indicator in answer_indicators):print("? 当前结果已包含明确答案,终止搜索")returnTruereturnFalse# === 集成到 LangChain AgentExecutor ===from langchain.agents import AgentExecutorfrom langchain.callbacks.base import BaseCallbackHandlerclassConvergenceCallback(BaseCallbackHandler):"""在 Agent 每轮迭代后进行检查"""def__init__(self, gate: GatedAgentExecutor):self.gate = gateself.accumulated = []self.force_stop = Falsedefon_agent_action(self, action, kwargs):"""工具调用前记录"""passdefon_tool_end(self, output, kwargs):"""工具返回后检查"""self.accumulated.append(output)ifnot self.gate.has_new_information(output):# 标记需要干预passif self.gate.should_terminate(output, self.accumulated):self.force_stop = True
优点:精确控制,不依赖模型自觉 缺点:实现复杂,相似度阈值需要针对具体场景调参
利用模型的 Structured Output / JSON Mode 能力,强制每轮输出包含"是否继续"的决策:
from langchain_core.pydantic_v1 import BaseModel, Fieldfrom typing importLiteral, OptionalclassAgentDecision(BaseModel):"""Agent 每轮的决策结构"""reasoning: str = Field(description="当前轮次的推理过程")decision: Literal["search", "answer"] = Field(description="继续搜索还是给出最终答案" )search_query: Optional[str] = Field(description="如果要搜索,具体搜什么" )final_answer: Optional[str] = Field(description="如果决定回答,答案是什么" )confidence: int = Field(description="对当前信息的信心程度,1-10",ge=1, le=10 )# 使用结构化输出llm_with_structure = ChatOpenAI(model="gpt-4o").with_structured_output(AgentDecision)classStructuredReActAgent:""" 每轮都要求模型做出显式的"搜 vs 答"的抉择, 并且当 confidence >= 7 时自动终止。 """defrun(self, query: str, max_rounds: int = 5):context = f"用户问题: {query}n当前已获取信息: 暂无"for round_num inrange(1, max_rounds + 1):decision: AgentDecision = llm_with_structure.invoke(f"{context}nn请决定: 继续搜索还是给出最终答案?" )print(f"第 {round_num} 轮: decision={decision.decision}, "f"confidence={decision.confidence}")if decision.decision == "answer"and decision.confidence >= 7:return decision.final_answerif decision.decision == "search":# 执行搜索,更新 contextresult = self.search(decision.search_query)context += f"n搜索结果 [{decision.search_query}]: {result}"# 最后一轮强制输出if round_num == max_rounds:return llm_with_structure.invoke(f"{context}nn这是最后一轮,你必须给出最终答案。" ).final_answer
优点:最优雅,把"何时停止"变成了一个明确的分类决策 缺点:需要模型支持 structured output,老旧模型不适用
| 维度 | A:收敛 Prompt | B:信息增益门控 | C:结构化输出 |
|---|---|---|---|
| 实现复杂度 | ⭐ | ⭐⭐⭐ | ⭐⭐ |
| 对小模型友好度 | ⭐⭐ | ⭐⭐⭐ | ⭐ |
| 不依赖模型自觉 | ❌ | ✅ | 部分 |
| 额外 token 消耗 | 几乎为零 | 中(相似度计算) | 低 |
| 可控性 | 低 | 高 | 最高 |
| 适用场景 | 原型验证 | 生产高可靠 | 前沿探索 |
推荐策略:先用 A 快速验证,上线前切到 B 或 C(取决于你的模型是否支持 structured output)。
max_iterations 不是安全网,是死亡线。 把它设到 10,Agent 就会真的跑满 10 轮。建议设为 3~5,并在第 N-1 轮 Prompt 中注入"你必须现在回答"的指令。description 里加类似 "If you don't find the answer, try a different query" 的提示——这等于在教模型"上瘾"。max_tokens=500 做摘要)。现在给你一个动手练习:
场景:把上面的方案 B(信息增益门控)改造一下,让它不是简单地比较"文本相似度",而是用一个轻量级本地模型(比如 Ollama 上的 qwen2.5:3b)来判断"这一轮搜索结果是否带来了新信息"。
提示:
# 把 difflib.SequenceMatcher 替换成一个 LLM 调用def has_new_information_with_llm(previous_results: list[str], new_result: str) -> bool:prompt = f""" 你是一个信息评估助手。判断以下新搜索结果是否包含之前结果中没有的新信息。 【之前的结果汇总】{' '.join(previous_results[-3:])}# 只看最近 3 轮 【新的搜索结果】{new_result} 只回答 YES 或 NO。YES 表示有新信息,NO 表示信息重复。 """# 你的代码:调用本地 Ollama 模型pass
思考题: