回到你在第1章写的第一个Agent。它的核心循环是:
调用LLM → 如果返回工具调用 → 执行工具 → 把结果放回去 → 再调用LLM
这个循环看起来简单,但它隐含了一个深刻的问题:LLM在每一步都做了什么?
当用户问"北京今天多少度?这个数字乘以3再加5等于多少?"时,LLM的决策过程是:
第1步:我需要知道北京的温度 → 调用get_weather
第2步:温度是26,我需要计算26×3+5 → 调用calculate
第3步:结果是83 → 回答用户
每一步都包含两个动作:先推理("我需要做什么"),再行动(调用工具)。这种"推理-行动"交替进行的模式,就是ReAct的核心思想。
ReAct(Reasoning + Acting)是2022年由Google研究团队提出的一种Agent推理模式。它的核心主张是:让LLM在每一步都显式地说出它的推理过程,然后基于这个推理采取行动。
在ReAct模式下,Agent的每一步循环是这样的:
思考(Thought):我现在需要做什么?我知道什么?我不知道什么?
行动(Action):基于思考,我决定调用哪个工具,参数是什么?
观察(Observation):工具返回了什么结果?
(然后开始下一轮思考...)
这与你之前实现的循环本质相同,但有一个关键区别:"思考"从隐式变成了显式。
在之前的实现中,LLM的"思考"是隐式的——它直接输出工具调用请求,你不会看到它为什么选择这个工具。而在ReAct模式中,LLM被要求先输出一段推理文本,解释它的思考过程,然后再输出行动。
你可能会问:让LLM把思考过程写出来,有什么实际价值?它不只是多消耗了一些token吗?
价值一:更好的决策质量。
多个研究发现,当LLM被要求"先思考再行动"时,它的决策质量会提升。这类似于人类解决问题的方式——说出声来(或写下来)的思考,比默默在脑中想更清晰、更严谨。
一个具体的例子:
没有显式推理的Agent:
用户:帮我查一下下周三有没有时间开会
Agent直接调用:query_calendar(date="2026-09-09")
有显式推理的Agent:
思考:用户想知道下周三是否有空。今天是2026-09-07(周日),下周三应该是2026-09-09。我需要查询那天的日历。
行动:query_calendar(date="2026-09-09")
观察:2026-09-09 09:30 一对一沟通
思考:那天上午9:30有会。用户问"有没有时间",我需要判断空闲时段。9:30有会,但下午看起来是空的。我应该告诉用户上午有会,下午有空。
回答:下周三上午9:30有一对一沟通,下午时间空闲。
后者不仅给出了回答,还解释了判断依据("下午看起来是空的"),这让你能验证它的推理是否正确。
价值二:可调试性。
当Agent出错时,显式的思考过程是最有价值的调试信息。你可以看到Agent在哪一步推理出了偏差——是日期计算错了?是忽略了某个日历事件?还是对"有空"的理解有误?
没有显式推理,你只能看到"Agent调用了某个工具,最终给出了某个回答",中间的决策过程是黑盒。
价值三:防止"跳跃式"错误。
LLM有时会跳过必要的中间步骤,直接给出结论。这在简单任务中没问题,但在复杂任务中会导致错误。要求显式推理,相当于强迫LLM放慢速度,逐步推进。
最简单的方式:在系统提示词中要求LLM遵循ReAct格式:
REACT_SYSTEM_PROMPT = """
你是一个具备工具调用能力的智能助手。在解决任务时,请遵循以下模式:
思考(Thought):分析当前情况,判断下一步该做什么。考虑你已经知道什么、还需要知道什么。
行动(Action):如果需要调用工具,输出工具调用。如果已经可以回答,直接输出最终答案。
观察(Observation):工具执行后,你会看到结果。基于结果继续思考。
重要规则:
1. 每次只执行一个行动
2. 在调用工具前,先输出你的思考
3. 如果工具返回错误,思考如何调整策略
4. 当你确信已经完成任务时,直接输出最终答案
"""
但这种方式有一个问题:LLM的"思考"和"行动"是混合在同一个响应中的。 你需要在解析时区分哪些是思考文本、哪些是工具调用。
在OpenAI的API中,当LLM调用工具时,响应中的content字段可以包含思考文本,tool_calls字段包含工具调用。两者可以在同一个响应中共存:
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tool_schemas,
)
message = response.choices[0].message
# 思考文本在content中
thought = message.content # "我需要先查询日历..."
# 行动在tool_calls中
tool_calls = message.tool_calls # [{"name": "query_calendar", ...}]
更工程化的做法:在Harness的循环中显式地处理"思考"和"行动"两个阶段:
class ReActRuntime:
"""实现ReAct模式的Agent运行时"""
def __init__(self, llm_client, tool_registry, memory,
max_iterations: int = 10,
verbose: bool = True):
self.client = llm_client
self.tools = tool_registry
self.memory = memory
self.max_iterations = max_iterations
self.verbose = verbose
# 记录完整的推理轨迹
self.reasoning_trace = []
def run(self, user_input: str) -> str:
"""执行ReAct循环"""
self.memory.initialize(user_input)
for iteration in range(self.max_iterations):
if self.verbose:
print(f"\n{'='*40}")
print(f"第{iteration + 1}轮")
print(f"{'='*40}")
# 阶段1:思考+行动(LLM调用)
response = self._call_llm()
message = response.choices[0].message
# 记录思考
if message.content:
self._record_thought(iteration, message.content)
# 检查是否有工具调用
if message.tool_calls:
# 阶段2:行动(执行工具)
self.memory.add_llm_message(message)
for tool_call in message.tool_calls:
action = {
"tool": tool_call.function.name,
"arguments": json.loads(tool_call.function.arguments),
}
self._record_action(iteration, action)
# 执行工具
result = self.tools.execute(
action["tool"], action["arguments"]
)
# 阶段3:观察(记录工具结果)
self._record_observation(iteration, result.content)
# 将结果加入记忆
self.memory.add_tool_result(result)
else:
# 没有工具调用,直接返回最终答案
final_answer = message.content or ""
self._record_final_answer(iteration, final_answer)
return final_answer
return "达到最大迭代次数,未能完成任务。"
def _call_llm(self):
"""调用LLM"""
return self.client.chat.completions.create(
model="gpt-4o-mini",
messages=self.memory.get_context(),
tools=self.tools.get_schemas(),
temperature=0.0,
)
def _record_thought(self, iteration: int, thought: str):
"""记录思考"""
entry = {"iteration": iteration, "type": "thought", "content": thought}
self.reasoning_trace.append(entry)
if self.verbose:
print(f"思考:{thought}")
def _record_action(self, iteration: int, action: dict):
"""记录行动"""
entry = {"iteration": iteration, "type": "action", "content": action}
self.reasoning_trace.append(entry)
if self.verbose:
print(f"行动:{action['tool']}({action['arguments']})")
def _record_observation(self, iteration: int, observation: str):
"""记录观察"""
entry = {"iteration": iteration, "type": "observation", "content": observation}
self.reasoning_trace.append(entry)
if self.verbose:
print(f"观察:{observation[:200]}")
def _record_final_answer(self, iteration: int, answer: str):
"""记录最终答案"""
entry = {"iteration": iteration, "type": "final_answer", "content": answer}
self.reasoning_trace.append(entry)
if self.verbose:
print(f"最终答案:{answer}")
def get_reasoning_trace(self) -> list:
"""获取完整的推理轨迹"""
return self.reasoning_trace
运行示例
用户:帮我看看下周有没有空参加一个2小时的会议
========================================
第1轮
========================================
思考:用户想知道下周是否有空参加2小时的会议。我需要先查询下周的日历,看看有哪些空闲时段。下周是2026-09-08到2026-09-12。我应该查询这些日期的日程。
行动:query_calendar({"start_date": "2026-09-08", "end_date": "2026-09-12"})
观察:2026-09-08: 14:00周会;2026-09-09: 09:30一对一沟通;其他日期无安排
========================================
第2轮
========================================
思考:根据日历结果,9月8日下午2点有周会,9月9日上午9:30有一对一沟通。其他时间没有安排。用户需要2小时的空闲时段。9月10日、11日、12日整天都空闲。9月8日下午2点后有周会占用,9月9日上午9:30有一对一沟通占用。我应该告诉用户哪些时段可以安排。
最终答案:下周的日程如下:9月8日下午2点有周会,9月9日上午9:30有一对一沟通。其余时间(包括9月10-12日全天)都是空闲的,可以安排2小时的会议。
在之前的实现中,LLM直接从用户输入中提取工具参数。在ReAct模式下,LLM会先推理参数应该是什么,再输出工具调用。这个额外的推理步骤能提高参数提取的准确率。
当工具返回错误时,ReAct模式的"思考"步骤让LLM有机会明确地分析错误原因并调整策略:
思考:工具返回"未找到订单ORD123"。可能是订单号有误。让我尝试用其他信息查询,或者询问用户确认订单号。
行动:search_orders({"keyword": "最近的订单"})
观察:找到3个最近的订单...
对于需要多步完成的任务,ReAct的显式推理让每一步的目的更清晰:
思考:用户想比较两个产品再决定买哪个。我需要先查询两个产品的信息,然后对比,最后给用户提供决策依据。
行动:get_product_info({"product_id": "A001"})
观察:产品A:价格999元,评分4.5,特点...
思考:已经获取了产品A的信息。现在需要产品B的信息。
行动:get_product_info({"product_id": "B002"})
观察:产品B:价格1299元,评分4.8,特点...
思考:两个产品的信息都已获取。A便宜300元但评分稍低,B贵但评分更高。我应该列出对比,让用户自己做决定。
最终答案:以下是两个产品的对比...
局限一:额外token消耗。每轮循环中的"思考"文本会消耗额外的token。如果思考过于冗长,会显著增加成本。
应对:在提示词中要求思考保持简洁("用一句话说明你的思考")。
局限二:思考可能出错,错误会被放大。如果LLM在思考中得出了错误的结论(比如日期算错了),后续的行动都建立在错误之上。
应对:思考不是"免检"的——它在后续循环中会接受工具结果的检验。如果行动后观察到的结果与思考的预期不符,LLM有机会修正。
局限三:不适用于所有场景。对于非常简单的任务(如"计算2+3"),显式推理是浪费。
应对:根据任务的复杂度决定是否启用ReAct模式。简单任务用直接模式(LLM直接回答),复杂任务用ReAct模式。
你可能已经注意到:你在前面章节构建的Harness,本质上已经是ReAct模式的。
"感知"对应ReAct的"观察"
"思考"对应ReAct的"思考"
"行动"对应ReAct的"行动"
区别只在于:你的Harness没有显式地记录和展示"思考"这一步。 LLM的内部推理是隐式的,你只看到它输出的工具调用。
本节的价值在于:让隐式的推理变显式。 通过在循环中加入"思考"的记录和展示,你获得了:
更透明的决策过程
更丰富的调试信息
更好的LLM决策质量(当你在提示词中要求它"先思考"时)
ReAct模式是Agent推理的最基础也最重要的模式。它的核心是:推理与行动交替进行,显式的推理引导行动,行动的结果修正推理。
实现ReAct不需要特殊的框架或复杂的代码——它只需要两件事:
在系统提示词中要求LLM"先思考,再行动"
在Harness循环中记录和展示思考、行动、观察的完整序列
你现在已经具备了实现ReAct的所有基础。在下一节中,我们将讨论规划能力——如何让Agent不只走一步看一步,而是能够在执行前制定完整的计划。
评论专区
评论加载中...登录后即可发表评论