构建ReAct智能体需要理解其核心控制流程和交互模式。手动构建这个智能体,能够让您对操作机制、提示词设计的复杂性以及编排思-行-观察循环所面临的难点有全面的理解,这些内容通常在更高级的框架中被抽象化。我们的目标不是复刻一个功能全面的库,而是实现ReAct的核心逻辑。我们假设您可以使用LLM API(如OpenAI的GPT模型、Anthropic的Claude或自托管模型),并熟练掌握Python以进行API调用和处理响应。自定义ReAct智能体的核心构成部分实现一个ReAct智能体需要几个相互连接的构成部分:LLM接口: 一个函数或类,用于处理与您所选大型语言模型的通信。它接收一个格式化的提示字符串,并返回模型的文本生成结果。API调用的错误处理(如超时、速率限制)对于实现一个可用系统非常重要。工具集: 智能体可使用的一系列工具。每种工具需要:一个独有的名称(例如,search,calculator)。清晰的描述,解释其功能、预期的输入格式和输出。该描述有助于LLM明确何时及如何使用该工具。实际的实现(例如,一个Python函数),用于执行工具的逻辑。提示模板: ReAct智能体的核心。此模板规范LLM的输入,引导其推理过程。它通常包含:最初的问题或任务。定义ReAct格式(思、行、观察)的指令。可用工具的描述。一个“暂存区”,智能体在此记录其先前步骤的思、行和观察序列。当前步骤思考过程的占位符。响应解析器: 用于解析LLM输出字符串的逻辑。它需要可靠地识别和提取:Thought:智能体的下一步推理。Action:要使用的工具及其输入(例如,Action: search[query: recent advancements in LLM agents])。Final Answer:当智能体认为任务完成时的最终回复。处理LLM输出格式的变化和潜在的解析失败,需要周全的设计(例如,使用正则表达式,或者在LLM支持的情况下,请求结构化输出如JSON)。执行循环: 主控制流程,迭代地编排ReAct过程:使用模板、当前问题、工具描述和累积的暂存区内容来格式化提示。将提示发送给LLM。解析LLM的响应。如果检测到Final Answer,则终止并返回答案。如果解析出Action:识别工具名称和输入。使用提供的输入执行相应的工具函数。处理潜在的执行错误。将工具的输出(或错误消息)格式化为Observation。将Thought、Action和Observation追加到暂存区。继续下一次迭代。设置终止条件(例如,最大步骤数)以防止无限循环。ReAct循环流程示例该交互遵循一种明确的模式,通常可视化为一个循环。digraph ReAct_Loop { rankdir=LR; node [shape=box, style=rounded, fontname="Arial", fontsize=10, margin=0.2]; edge [fontname="Arial", fontsize=9]; Start [label="开始\n(用户问题)", shape=ellipse, style=filled, fillcolor="#a5d8ff"]; FormatPrompt [label="格式化提示\n(问题 + 工具 + 暂存区)"]; LLM [label="LLM调用", shape=cylinder, style=filled, fillcolor="#ffec99"]; ParseResponse [label="解析响应\n(思,行/最终答案)"]; IsFinal [label="最终答案?", shape=diamond, style=filled, fillcolor="#ffc9c9"]; ExecuteTool [label="执行工具\n(行动输入 -> 工具函数)"]; FormatObs [label="格式化观察\n(工具输出/错误)"]; UpdateScratchpad [label="更新暂存区\n(思,行,观察)"]; End [label="结束\n(返回最终答案)", shape=ellipse, style=filled, fillcolor="#b2f2bb"]; Start -> FormatPrompt; FormatPrompt -> LLM; LLM -> ParseResponse; ParseResponse -> IsFinal; IsFinal -> End [label=" 是"]; IsFinal -> ExecuteTool [label=" 否 (行动)"]; ExecuteTool -> FormatObs; FormatObs -> UpdateScratchpad; UpdateScratchpad -> FormatPrompt [label="下一次迭代"]; }ReAct智能体通过格式化提示、调用LLM、解析响应、根据行动可能执行工具、将结果格式化为观察,并循环直到获得最终答案,从而在其暂存区中迭代地构建上下文。Python简化实现概述我们来概述Python伪代码中的核心循环结构。这主要侧重于流程,而非具体的API或解析细节。import re # 用于基本解析示例 # 假设llm_call(prompt)存在并返回LLM文本响应 # 假设tools = { "tool_name": {"description": "...", "function": callable} } 存在 def execute_react_agent(question, tools, llm_call, max_steps=10): """ 执行ReAct智能体循环 """ scratchpad = "" # 存储思-行-观察历史 tool_descriptions = "\n".join([f"- {name}: {details['description']}" for name, details in tools.items()]) for step in range(max_steps): # 1. 格式化提示 prompt = f"""你是一个使用ReAct框架来回答问题的助手。 可用工具: {tool_descriptions} 请使用以下格式: 思: 你的推理步骤。 行: 要采取的行动,应该是[{', '.join(tools.keys())}]中的一个或'Final Answer'。工具请使用Action: tool_name[input]格式。 观察: 行动的结果。 ... (此思/行/观察循环重复) 问题: {question} {scratchpad}思:""" # 提示LLM以思考开始 # 2. LLM调用 response = llm_call(prompt).strip() # 立即将LLM的思考过程追加到暂存区 scratchpad += f"思: {response}\n" print(f"--- 步骤 {step+1} ---") print(f"思: {response}") # 3. 解析响应 (简化示例) action_match = re.search(r"Action: (.*?)(?:\[(.*?)\])?$", response, re.MULTILINE) final_answer_match = re.search(r"Final Answer: (.*)", response, re.MULTILINE | re.DOTALL) if final_answer_match: # 4a. 检测到最终答案 final_answer = final_answer_match.group(1).strip() print(f"最终答案: {final_answer}") return final_answer if action_match: action_name = action_match.group(1).strip() action_input = action_match.group(2).strip() if action_match.group(2) else "" scratchpad += f"Action: {action_name}[{action_input}]\n" print(f"行: {action_name}[{action_input}]") if action_name in tools: # 5. 执行工具 try: tool_function = tools[action_name]["function"] observation = tool_function(action_input) except Exception as e: observation = f"执行工具 {action_name} 出错:{e}" # 6. 格式化观察并更新暂存区 observation_str = str(observation) # 确保它是字符串 scratchpad += f"观察: {observation_str}\n" print(f"观察: {observation_str}") else: scratchpad += "Observation: 指定了未知工具。\n" print("指定了未知工具。") else: # 处理LLM未输出有效Action或Final Answer的情况 scratchpad += "Observation: 响应格式无效。停止。\n" print("响应格式无效。停止。") return "智能体因响应格式无效而失败。" return "智能体在达到最大步骤数后停止。" # 示例用法(需要定义llm_call和tools) # result = execute_react_agent("2加2是多少?", my_tools, my_llm_call) # print(f"\n最终结果: {result}")实现考量提示工程: 您的提示模板的结构和措辞非常重要。关于所需格式(思、行、观察)的清晰指令、简洁的工具描述以及有效的示例(如果使用少量样本提示),都会显著影响性能。迭代优化通常是必需的。解析可靠性: LLM输出可能不一致。仅依赖简单的字符串分割或正则表达式可能不够稳定。如果可行,考虑让LLM输出结构化数据(如JSON),或实现更精密的带错误校正的解析逻辑。工具输入/输出: 确保您的函数预期的工具输入与LLM可能生成的内容相符。同样,将工具输出清晰地格式化,以便LLM在Observation步骤中理解。在工具内优雅地处理错误并在观察中报告它们,这一点很重要。上下文管理: 随着暂存区的增长,它会占用上下文窗口空间并增加API成本。对于长期运行的任务,可采用策略,例如对暂存区早期部分进行概括,或使用更高级的内存技术(第3章会介绍)。终止条件: 在检测到Final Answer:和达到最大步骤数后,考虑其他终止标准,例如重复行动、特定错误模式,或者如果您的解析器或LLM提供,则考虑置信度得分。构建这个自定义智能体,即使是使用简化组件,也能阐明构建自主系统中的基本挑战和固有的设计选择。它在使用或扩展更复杂的智能体框架之前,提供了扎实的基础。