在构建基于 LangChain Agent 的应用时,遇到了一个令人头疼的问题: Agent 陷入无限循环。在我的一个项目中,Agent 不仅循环,还不断抛出 Invalid Format: Missing 'Action:' after 'Thought:'
的错误。通过深入 LangChain 源码和对 LLM 输出的调试,我最终锁定了问题的根源:Qwen LLM 对 ReAct 提示词的匹配不严格。
🧐 一:发现问题与初步诊断
我的 Agent 使用了 LangChain 的 ReAct 框架,并配置了自定义工具,同时使用了 agent_executor.stream()
进行流式输出。
初始现象
Agent 在执行完一个工具调用并收到 Observation
后,没有继续下一步的 Action
,而是陷入循环,并在日志中反复出现以下错误:
1 | Action: _Exception |
并且,这个错误信息被错误地注入到 messages
历史中,加剧了循环。如下:
1 | {'steps': [AgentStep(action=AgentAction(tool='_Exception', tool_input="Invalid Format: Missing 'Action:' after 'Thought:'", log='此处省略我的工具输出内容,以上为全部用户列表。部分用户未 |
初步怀疑点
1.
Memory 污染(循环的直接原因): 中间步骤(
Observation
或_Exception
)被错误地当作HumanMessage
写入了对话历史。2.
Tool异常导致Langchain异常:Tool抛出异常,Langchain错误的将Tool输出作为HumanMessage。
3.
LLM 格式化失败(错误的根源): LLM 的输出不符合 ReAct 格式。
我决定从根源入手,确认 LLM 的输出到底出了什么问题。
🛠️ 二:深入源码定位解析失败点
逐个排除,首先停用Memory,在停用后发现问题依旧
为了找出 Agent 解析器在哪里“卡住”了,我定位到 LangChain 负责 ReAct 解析的核心文件:langchain/agents/output_parsers/react_single_input.py
中的 ReActSingleInputOutputParser
类。
核心代码分析
在 parse
方法中,解析逻辑是按顺序检查 LLM 输出 text
的格式:
1.
检查
**Action**
+**Action Input**
(首要匹配):Python
1
2
3
4regex = r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
action_match = re.search(regex, text, re.DOTALL)
if action_match:
# 成功解析 Action,返回 AgentAction2.
检查
**Final Answer**
:Python
1
2if includes_answer:
# 成功解析 Final Answer,返回 AgentFinish3.
检查是否存在
**Action**
标记 (我的异常点):Python
1
2
3
4
5
6
7
8if not re.search(r"Action\s*\d*\s*:[\s]*(.*?)", text, re.DOTALL):
msg = f"Could not parse LLM output: `{text}`"
raise OutputParserException(
msg,
observation=MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE, # <-- 触发了这个错误信息
llm_output=text,
send_to_llm=True,
)
调试发现
通过调试,我发现当工具返回长篇 Markdown 表格数据作为 Observation
后,LLM 的下一段输出(即进入 parse(self, text: str)
的 text
变量)内容如下:
1 | '【我的工具返回的数据】\n\n以上是系统中的用户列表,共20位用户。更多用户信息可进一步查询。' |
而正常的应该类似如下:
1 | Thought: 从 `xxx` 表中未查询到有效数据,而从 `yyy` 表中已获取到用户信息。因此可以确认当前系统中的用户主要存储在 `yyy` 表中。\n\nFinal Answer: 当前系统中存在的用户如下表 |
从以上对比可以看出,错误响应的信息如下:
•
没有
**Thought:**
标记。也没有Final Answer标记。
由于 text
变量中既没有 Action:
也没有 Final Answer:
,它直接掉入了第三个检查,导致了 Missing 'Action:' after 'Thought:'
异常。
真相大白: Qwen 大模型 在处理完复杂的 Observation
后,认为任务已经完成,直接给出了总结性的自然语言回答,没有严格遵循 ReAct 提示词要求在答案前加上 **Final Answer:**
标记。
✅ 三:解决方案——加固提示词(Prompt Engineering)
既然问题是 LLM 的输出格式不严格,那么解决办法就是加固 ReAct 框架的提示词,强制 Qwen LLM 严格遵守输出要求。
关键调整点
在 Agent 的 System Prompt 或 Instruction 部分,我对格式要求进行了以下修改,增加了清晰的措辞、分隔符和强调:
1.
明确格式要求(尤其针对 Final Answer): 将原来Langchain官方给出的React提示词进行调整,要求”输出必须严格按照如下格式”
2.
检查Instruction部分的提示词,确保没有任何提示词与格式要求混淆
示例 Prompt 片段(强调部分):
Markdown
1 | Answer the following questions as best you can. You have access to the following tools: |
📢经过以上调整,问题缓解,多次尝试暂无出现类似的错误。
结论
通过将 LLM 的行为偏差与 LangChain 解析器的源码逻辑相结合,成功定位并解决了 Agent 循环和解析失败的问题。
核心经验是:
1.
不要相信 LLM 会自然遵循格式。 即使提供了示例,也必须通过强有力的措辞和格式化在 Prompt 中进行约束。
2.
调试 Agent 循环,一定要从 LLM 的原始输出(
**log**
字段)开始,结合OutputParser
的源码,才能真正理解 LLM 在哪个环节“失控”了。
在调整提示词后,Qwen LLM 的输出格式变得稳定,Agent 成功地从工具返回的数据跳转到 Final Answer:
步骤,流程顺畅,循环问题也随之消失。希望这段经验能帮助遇到类似 Agent 问题的开发者!🙏