网站开发 图片,wordpress 腾讯课堂,网站必须要求备案吗,太原北京网站建设一个现代的智能体#xff0c;其核心能力在于能将大语言模型的推理能力与外部世界联通。它能够自主地理解用户意图、拆解复杂任务#xff0c;并通过调用代码解释器、搜索引擎、API等一系列“工具”#xff0c;来获取信息、执行操作#xff0c;最终达成目标。 然而#xff0…一个现代的智能体其核心能力在于能将大语言模型的推理能力与外部世界联通。它能够自主地理解用户意图、拆解复杂任务并通过调用代码解释器、搜索引擎、API等一系列“工具”来获取信息、执行操作最终达成目标。 然而智能体并非万能它同样面临着来自大模型本身的“幻觉”问题、在复杂任务中可能陷入推理循环、以及对工具的错误使用等挑战这些也构成了智能体的能力边界。为了更好地组织智能体的“思考”与“行动”过程业界涌现出了多种经典的架构范式。在本章中我们将聚焦于其中最具代表性的三种并一步步从零实现它们ReAct (Reasoning and Acting)一种将“思考”和“行动”紧密结合的范式让智能体边想边做动态调整。Plan-and-Solve一种“三思而后行”的范式智能体首先生成一个完整的行动计划然后严格执行。Reflection一种赋予智能体“反思”能力的范式通过自我批判和修正来优化结果。了解了这些之后你可能会问市面上已有LangChain、LlamaIndex等众多优秀框架为何还要“重复造轮子”答案在于尽管成熟的框架在工程效率上优势显著但直接使用高度抽象的工具并不利于我们了解背后的设计机制是怎么运行的或者是有何好处。其次这个过程会暴露出项目的工程挑战。框架为我们处理了许多问题例如模型输出格式的解析、工具调用失败的重试、防止智能体陷入死循环等。亲手处理这些问题是培养系统设计能力的最直接方式。最后也是最重要的一点掌握了设计原理你才能真正地从一个框架的“使用者”转变为一个智能体应用的“创造者”。当标准组件无法满足你的复杂需求时你将拥有深度定制乃至从零构建一个全新智能体的能力。4.1 环境准备与基础工具定义在开始构建之前我们需要先搭建好开发环境并定义一些基础组件。这能帮助我们在后续实现不同范式时避免重复劳动更专注于核心逻辑。4.1.1 安装依赖库本书的实战部分将主要使用 Python 语言建议使用 Python 3.10 或更高版本。首先请确保你已经安装了openai库用于与大语言模型交互以及python-dotenv库用于安全地管理我们的 API 密钥。在你的终端中运行以下命令pipinstallopenai python-dotenv4.1.2 配置 API 密钥为了让我们的代码更通用我们将模型服务的相关信息模型ID、API密钥、服务地址统一配置在环境变量中。在你的项目根目录下创建一个名为.env的文件。在该文件中添加以下内容。你可以根据自己的需要将其指向 OpenAI 官方服务或任何兼容 OpenAI 接口的本地/第三方服务。# .env fileLLM_API_KEYYOUR-API-KEYLLM_MODEL_IDYOUR-MODELLLM_BASE_URLYOUR-URL我们的代码将从此文件自动加载这些配置。4.1.3 封装基础 LLM 调用函数为了让代码结构更清晰、更易于复用我们来定义一个专属的LLM客户端类。这个类将封装所有与模型服务交互的细节让我们的主逻辑可以更专注于智能体的构建。importosfromopenaiimportOpenAIfromdotenvimportload_dotenvfromtypingimportList,Dict# 加载 .env 文件中的环境变量load_dotenv()classHelloAgentsLLM: 为本书 Hello Agents 定制的LLM客户端。 它用于调用任何兼容OpenAI接口的服务并默认使用流式响应。 def__init__(self,model:strNone,apiKey:strNone,baseUrl:strNone,timeout:intNone): 初始化客户端。优先使用传入参数如果未提供则从环境变量加载。 self.modelmodeloros.getenv(LLM_MODEL_ID)apiKeyapiKeyoros.getenv(LLM_API_KEY)baseUrlbaseUrloros.getenv(LLM_BASE_URL)timeouttimeoutorint(os.getenv(LLM_TIMEOUT,60))ifnotall([self.model,apiKey,baseUrl]):raiseValueError(模型ID、API密钥和服务地址必须被提供或在.env文件中定义。)self.clientOpenAI(api_keyapiKey,base_urlbaseUrl,timeouttimeout)defthink(self,messages:List[Dict[str,str]],temperature:float0)-str: 调用大语言模型进行思考并返回其响应。 print(f 正在调用{self.model}模型...)try:responseself.client.chat.completions.create(modelself.model,messagesmessages,temperaturetemperature,streamTrue,)# 处理流式响应print(✅ 大语言模型响应成功:)collected_content[]forchunkinresponse:contentchunk.choices[0].delta.contentorprint(content,end,flushTrue)collected_content.append(content)print()# 在流式输出结束后换行return.join(collected_content)exceptExceptionase:print(f❌ 调用LLM API时发生错误:{e})returnNone# --- 客户端使用示例 ---if__name____main__:try:llmClientHelloAgentsLLM()exampleMessages[{role:system,content:You are a helpful assistant that writes Python code.},{role:user,content:写一个快速排序算法}]print(--- 调用LLM ---)responseTextllmClient.think(exampleMessages)ifresponseText:print(\n\n--- 完整模型响应 ---)print(responseText)exceptValueErrorase:print(e)---调用LLM--- 正在调用 xxxxxx 模型...✅ 大语言模型响应成功:快速排序是一种非常高效的排序算法...4.2 ReAct在准备好LLM客户端后我们将构建第一个也是最经典的一个智能体范式ReAct (Reason Act)。ReAct由Shunyu Yao于2022年提出[1]其核心思想是模仿人类解决问题的方式将推理 (Reasoning)与行动 (Acting)显式地结合起来形成一个“思考-行动-观察”的循环。4.2.1 ReAct 的工作流程在ReAct诞生之前主流的方法可以分为两类一类是“纯思考”型如思维链 (Chain-of-Thought)它能引导模型进行复杂的逻辑推理但无法与外部世界交互容易产生事实幻觉另一类是“纯行动”型模型直接输出要执行的动作但缺乏规划和纠错能力。ReAct的巧妙之处在于它认识到思考与行动是相辅相成的。思考指导行动而行动的结果又反过来修正思考。为此ReAct范式通过一种特殊的提示工程来引导模型使其每一步的输出都遵循一个固定的轨迹Thought (思考)这是智能体的“内心独白”。它会分析当前情况、分解任务、制定下一步计划或者反思上一步的结果。Action (行动)这是智能体决定采取的具体动作通常是调用一个外部工具例如Search[华为最新款手机]。Observation (观察)这是执行Action后从外部工具返回的结果例如搜索结果的摘要或API的返回值。智能体将不断重复这个Thought - Action - Observation的循环将新的观察结果追加到历史记录中形成一个不断增长的上下文直到它在Thought中认为已经找到了最终答案然后输出结果。这个过程形成了一个强大的协同效应推理使得行动更具目的性而行动则为推理提供了事实依据。我们可以将这个过程形式化地表达出来如图所示。具体来说在每个时间步t tt智能体的策略即大语言模型π \piπ会根据初始问题q qq和之前所有步骤的“行动-观察”历史轨迹( ( a 1 , o 1 ) , … , ( a t − 1 , o t − 1 ) ) ((a_1,o_1),\dots,(a_{t-1},o_{t-1}))((a1,o1),…,(at−1,ot−1))来生成当前的思考t h t th_ttht和行动a t a_tat( t h t , a t ) π ( q , ( a 1 , o 1 ) , … , ( a t − 1 , o t − 1 ) ) \left(th_t,a_t\right)\pi\left(q,(a_1,o_1),\ldots,(a_{t-1},o_{t-1})\right)(tht,at)π(q,(a1,o1),…,(at−1,ot−1))随后环境中的工具T TT会执行行动a t a_tat并返回一个新的观察结果o t o_toto t T ( a t ) o_t T(a_t)otT(at)这个循环不断进行将新的( a t , o t ) (a_t,o_t)(at,ot)对追加到历史中直到模型在思考t h t th_ttht中判断任务已完成。这种机制特别适用于以下场景需要外部知识的任务如查询实时信息天气、新闻、股价、搜索专业领域的知识等。需要精确计算的任务将数学问题交给计算器工具避免LLM的计算错误。需要与API交互的任务如操作数据库、调用某个服务的API来完成特定功能。因此我们将构建一个具备使用外部工具能力的ReAct智能体来回答一个大语言模型仅凭自身知识库无法直接回答的问题。例如“华为最新的手机是哪一款它的主要卖点是什么” 这个问题需要智能体理解自己需要上网搜索调用工具搜索结果并总结答案。4.2.2 工具的定义与实现如果说大语言模型是智能体的大脑那么工具 (Tools)就是其与外部世界交互的“手和脚”。为了让ReAct范式能够真正解决我们设定的问题智能体需要具备调用外部工具的能力。针对本节设定的目标——回答关于“华为最新手机”的问题我们需要为智能体提供一个网页搜索工具。在这里我们选用SerpApi它通过API提供结构化的Google搜索结果能直接返回“答案摘要框”或精确的知识图谱信息首先需要安装该库pipinstallgoogle-search-results同时你需要前往 SerpApi官网 注册一个免费账户获取你的API密钥并将其添加到我们项目根目录下的.env文件中# .env file# ... (保留之前的LLM配置)SERPAPI_API_KEYYOUR_SERPAPI_API_KEY接下来我们通过代码来定义和管理这个工具。我们将分步进行首先实现工具的核心功能然后构建一个通用的工具管理器。1实现搜索工具的核心逻辑一个良好定义的工具应包含以下三个核心要素名称 (Name) 一个简洁、唯一的标识符供智能体在Action中调用例如Search。描述 (Description) 一段清晰的自然语言描述说明这个工具的用途。这是整个机制中最关键的部分因为大语言模型会依赖这段描述来判断何时使用哪个工具。执行逻辑 (Execution Logic) 真正执行任务的函数或方法。我们的第一个工具是search函数它的作用是接收一个查询字符串然后返回搜索结果。fromserpapiimportSerpApiClientdefsearch(query:str)-str: 一个基于SerpApi的实战网页搜索引擎工具。 它会智能地解析搜索结果优先返回直接答案或知识图谱信息。 print(f 正在执行 [SerpApi] 网页搜索:{query})try:api_keyos.getenv(SERPAPI_API_KEY)ifnotapi_key:return错误:SERPAPI_API_KEY 未在 .env 文件中配置。params{engine:google,q:query,api_key:api_key,gl:cn,# 国家代码hl:zh-cn,# 语言代码}clientSerpApiClient(params)resultsclient.get_dict()# 智能解析:优先寻找最直接的答案ifanswer_box_listinresults:return\n.join(results[answer_box_list])ifanswer_boxinresultsandanswerinresults[answer_box]:returnresults[answer_box][answer]ifknowledge_graphinresultsanddescriptioninresults[knowledge_graph]:returnresults[knowledge_graph][description]iforganic_resultsinresultsandresults[organic_results]:# 如果没有直接答案则返回前三个有机结果的摘要snippets[f[{i1}]{res.get(title,)}\n{res.get(snippet,)}fori,resinenumerate(results[organic_results][:3])]return\n\n.join(snippets)returnf对不起没有找到关于 {query} 的信息。exceptExceptionase:returnf搜索时发生错误:{e}在上述代码中首先会检查是否存在answer_boxGoogle的答案摘要框或knowledge_graph知识图谱等信息如果存在就直接返回这些最精确的答案。如果不存在它才会退而求其次返回前三个常规搜索结果的摘要。这种“智能解析”能为LLM提供质量更高的信息输入。2构建通用的工具执行器当智能体需要使用多种工具时例如除了搜索还可能需要计算、查询数据库等我们需要一个统一的管理器来注册和调度这些工具。为此我们创建一个ToolExecutor类。fromtypingimportDict,AnyclassToolExecutor: 一个工具执行器负责管理和执行工具。 def__init__(self):self.tools:Dict[str,Dict[str,Any]]{}defregisterTool(self,name:str,description:str,func:callable): 向工具箱中注册一个新工具。 ifnameinself.tools:print(f警告:工具 {name} 已存在将被覆盖。)self.tools[name]{description:description,func:func}print(f工具 {name} 已注册。)defgetTool(self,name:str)-callable: 根据名称获取一个工具的执行函数。 returnself.tools.get(name,{}).get(func)defgetAvailableTools(self)-str: 获取所有可用工具的格式化描述字符串。 return\n.join([f-{name}:{info[description]}forname,infoinself.tools.items()])(3)测试现在我们将search工具注册到ToolExecutor中并模拟一次调用以验证整个流程是否正常工作。# --- 工具初始化与使用示例 ---if__name____main__:# 1. 初始化工具执行器toolExecutorToolExecutor()# 2. 注册我们的实战搜索工具search_description一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时应使用此工具。toolExecutor.registerTool(Search,search_description,search)# 3. 打印可用的工具print(\n--- 可用的工具 ---)print(toolExecutor.getAvailableTools())# 4. 智能体的Action调用这次我们问一个实时性的问题print(\n--- 执行 Action: Search[英伟达最新的GPU型号是什么] ---)tool_nameSearchtool_input英伟达最新的GPU型号是什么tool_functiontoolExecutor.getTool(tool_name)iftool_function:observationtool_function(tool_input)print(--- 观察 (Observation) ---)print(observation)else:print(f错误:未找到名为 {tool_name} 的工具。)工具Search已注册。---可用的工具----Search:一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时应使用此工具。---执行 Action:Search[英伟达最新的GPU型号是什么]--- 正在执行[SerpApi]网页搜索:英伟达最新的GPU型号是什么---观察(Observation)---[1]GeForce RTX50系列显卡 GeForce RTX™50系列GPU 搭载NVIDIA Blackwell 架构为游戏玩家和创作者带来全新玩法。RTX50系列具备强大的AI 算力带来升级体验和更逼真的画面。[2]比较GeForce 系列最新一代显卡和前代显卡 比较最新一代RTX30系列显卡和前代的RTX20系列、GTX10和900系列显卡。查看规格、功能、技术支持等内容。[3]GeForce 显卡|NVIDIA DRIVE AGX.强大的车载计算能力适用于AI 驱动的智能汽车系统 · Clara AGX.适用于创新型医疗设备和成像的AI 计算.游戏和创作.GeForce.探索显卡、游戏解决方案、AI...至此我们已经为智能体配备了连接真实世界互联网的Search工具为后续的ReAct循环提供了坚实的基础。4.2.3 ReAct 智能体的编码实现现在我们将所有独立的组件LLM客户端和工具执行器组装起来构建一个完整的 ReAct 智能体。我们将通过一个ReActAgent类来封装其核心逻辑。为了便于理解我们将这个类的实现过程拆分为以下几个关键部分进行讲解。1系统提示词设计提示词是整个 ReAct 机制的基石它为大语言模型提供了行动的操作指令。我们需要精心设计一个模板它将动态地插入可用工具、用户问题以及中间步骤的交互历史。# ReAct 提示词模板REACT_PROMPT_TEMPLATE 请注意你是一个有能力调用外部工具的智能助手。 可用工具如下:{tools}请严格按照以下格式进行回应: Thought: 你的思考过程用于分析问题、拆解任务和规划下一步行动。 Action: 你决定采取的行动必须是以下格式之一: -{{tool_name}}[{{tool_input}}]:调用一个可用工具。 -Finish[最终答案]:当你认为已经获得最终答案时。 - 当你收集到足够的信息能够回答用户的最终问题时你必须在Action:字段后使用 finish(answer...)来输出最终答案。 现在请开始解决以下问题: Question:{question}History:{history}这个模板定义了智能体与LLM之间交互的规范角色定义 “你是一个有能力调用外部工具的智能助手”设定了LLM的角色。工具清单 ({tools}) 告知LLM它有哪些可用的“手脚”。格式规约 (Thought/Action) 这是最重要的部分它强制LLM的输出具有结构性使我们能通过代码精确解析其意图。动态上下文 ({question}/{history}) 将用户的原始问题和不断累积的交互历史注入让LLM基于完整的上下文进行决策。2核心循环的实现ReActAgent的核心是一个循环它不断地“格式化提示词 - 调用LLM - 执行动作 - 整合结果”直到任务完成或达到最大步数限制。classReActAgent:def__init__(self,llm_client:HelloAgentsLLM,tool_executor:ToolExecutor,max_steps:int5):self.llm_clientllm_client self.tool_executortool_executor self.max_stepsmax_steps self.history[]defrun(self,question:str): 运行ReAct智能体来回答一个问题。 self.history[]# 每次运行时重置历史记录current_step0whilecurrent_stepself.max_steps:current_step1print(f--- 第{current_step}步 ---)# 1. 格式化提示词tools_descself.tool_executor.getAvailableTools()history_str\n.join(self.history)promptREACT_PROMPT_TEMPLATE.format(toolstools_desc,questionquestion,historyhistory_str)# 2. 调用LLM进行思考messages[{role:user,content:prompt}]response_textself.llm_client.think(messagesmessages)ifnotresponse_text:print(错误:LLM未能返回有效响应。)break# ... (后续的解析、执行、整合步骤)run方法是智能体的入口。它的while循环构成了 ReAct 范式的主体max_steps参数则是一个重要的安全阀防止智能体陷入无限循环而耗尽资源。3输出解析器的实现LLM 返回的是纯文本我们需要从中精确地提取出Thought和Action。这是通过几个辅助解析函数完成的它们通常使用正则表达式来实现。# (这些方法是 ReActAgent 类的一部分)def_parse_output(self,text:str):解析LLM的输出提取Thought和Action。thought_matchre.search(rThought: (.*),text)action_matchre.search(rAction: (.*),text)thoughtthought_match.group(1).strip()ifthought_matchelseNoneactionaction_match.group(1).strip()ifaction_matchelseNonereturnthought,actiondef_parse_action(self,action_text:str):解析Action字符串提取工具名称和输入。matchre.match(r(\w)\[(.*)\],action_text)ifmatch:returnmatch.group(1),match.group(2)returnNone,None_parse_output 负责从LLM的完整响应中分离出Thought和Action两个主要部分。_parse_action 负责进一步解析Action字符串例如从Search[华为最新手机]中提取出工具名Search和工具输入华为最新手机。(4) 工具调用与执行# (这段逻辑在 run 方法的 while 循环内)# 3. 解析LLM的输出thought,actionself._parse_output(response_text)ifthought:print(f思考:{thought})ifnotaction:print(警告:未能解析出有效的Action流程终止。)break# 4. 执行Actionifaction.startswith(Finish):# 如果是Finish指令提取最终答案并结束final_answerre.match(rFinish\[(.*)\],action).group(1)print(f 最终答案:{final_answer})returnfinal_answer tool_name,tool_inputself._parse_action(action)ifnottool_nameornottool_input:# ... 处理无效Action格式 ...continueprint(f 行动:{tool_name}[{tool_input}])tool_functionself.tool_executor.getTool(tool_name)ifnottool_function:observationf错误:未找到名为 {tool_name} 的工具。else:observationtool_function(tool_input)# 调用真实工具这段代码是Action的执行中心。它首先检查是否为Finish指令如果是则流程结束。否则它会通过tool_executor获取对应的工具函数并执行得到observation。(5观测结果的整合最后一步也是形成闭环的关键是将Action本身和工具执行后的Observation添加回历史记录中为下一轮循环提供新的上下文。# (这段逻辑紧随工具调用之后在 while 循环的末尾)print(f 观察:{observation})# 将本轮的Action和Observation添加到历史记录中self.history.append(fAction:{action})self.history.append(fObservation:{observation})# 循环结束print(已达到最大步数流程终止。)returnNone通过将Observation追加到self.history智能体在下一轮生成提示词时就能“看到”上一步行动的结果并据此进行新一轮的思考和规划。6运行实例与分析将以上所有部分组合起来我们就得到了完整的ReActAgent类。完整的代码运行实例可以在本书配套的代码仓库code文件夹中找到。下面是一次真实的运行记录工具 Search 已注册。 --- 第 1 步 --- 正在调用 xxxxxx 模型... ✅ 大语言模型响应成功: Thought: 要回答这个问题我需要查找华为最新发布的手机型号及其主要特点。这些信息可能在我的现有知识库之外因此需要使用搜索引擎来获取最新数据。 Action: Search[华为最新手机型号及主要卖点] 思考: 要回答这个问题我需要查找华为最新发布的手机型号及其主要特点。这些信息可能在我的现有知识库之外因此需要使用搜索引擎来获取最新数据。 行动: Search[华为最新手机型号及主要卖点] 正在执行 [SerpApi] 网页搜索: 华为最新手机型号及主要卖点 观察: [1] 华为手机- 华为官网 智能手机 ; Mate 系列. 非凡旗舰 · HUAWEI Mate XTs. 非凡大师 ; Pura 系列. 先锋影像 · HUAWEI Pura 80 Pro ; Pocket 系列. 美学新篇. HUAWEI Pocket 2 ; nova 系列. 专业人像. [2] 2025年华为手机哪一款性价比高华为手机推荐与市场分析 ... 现在华为手机最大的卖点只剩下鸿蒙HarmonyOS系统以及饱受争议的品牌信仰。 这里推荐目前值得入手的几款华为系列手机根据不同预算自行选择:. 华为目前最受欢迎也是搭载 ... [3] 2025年华为新款手机哪个性价比高10款华为新款手机推荐 选华为主要还是要推荐高端手机Mate 70和Pura 70系列是最新发布的旗舰机型。 HUAWEI Mate 70. 优点是拍照配置依旧顶级全焦段覆盖适合专业摄影做工出色户外抗摔 ... --- 第 2 步 --- 正在调用 xxxxxx 模型... ✅ 大语言模型响应成功: Thought: 根据搜索结果华为最新发布的旗舰机型包括Mate 70和Pura 80 Pro。为了确定最新型号及其主要卖点我将重点放在这些信息上。从提供的链接来看Mate 70系列和Pura 80 Pro都是近期发布的产品但具体哪一个是“最新”还需要进一步确认。同时我可以从这些信息中提取出它们的主要 卖点。 Action: Finish[根据最新信息华为的最新手机可能是HUAWEI Pura 80 Pro或HUAWEI Mate 70。其中HUAWEI Mate 70的主要卖点包括顶级的拍照配置全焦段覆盖适合专业摄影做工出色并且具有良好的户外抗摔性能。而HUAWEI Pura 80 Pro则强调了先锋影像技术。] 思考: 根据搜索结果华为最新发布的旗舰机型包括Mate 70和Pura 80 Pro。为了确定最新型号及其主要卖点我将重点放在这些信息上。从提供的链接来看Mate 70系列和Pura 80 Pro都是近期发布的产品但具体哪一个是“最新”还需要进一步确认。同时我可以从这些信息中提取出它们的主要 卖点。 最终答案: 根据最新信息华为的最新手机可能是HUAWEI Pura 80 Pro或HUAWEI Mate 70。其中HUAWEI Mate 70的主要卖点包括顶级的拍照配置全焦段覆盖适合专业摄影做工出色并且具有良好的户外抗摔性能。而HUAWEI Pura 80 Pro则强调了先锋影像技术。从上面的输出可以看到智能体清晰地展示了它的思考链条它首先意识到自己的知识不足需要使用搜索工具然后它根据搜索结果进行推理和总结并在两步之内得出了最终答案。值得注意的是由于模型的知识和互联网的信息是不断更新的你运行的结果可能与此不完全相同。截止本节内容编写的2025年9月8日搜索结果中提到的HUAWEI Mate 70与HUAWEI Pura 80 Pro确实是华为当时最新的旗舰系列手机。这充分展示了ReAct范式在处理时效性问题上的强大能力。4.2.4 ReAct 的特点、局限性与调试技巧通过亲手实现一个 ReAct 智能体我们不仅掌握了其工作流程也应该对其内在机制有了更深刻的认识。任何技术范式都有其闪光点和待改进之处本节将对 ReAct 进行总结。1ReAct 的主要特点高可解释性ReAct 最大的优点之一就是透明。通过Thought链我们可以清晰地看到智能体每一步的“心路历程”——它为什么会选择这个工具下一步又打算做什么。这对于理解、信任和调试智能体的行为至关重要。动态规划与纠错能力与一次性生成完整计划的范式不同ReAct 是“走一步看一步”。它根据每一步从外部世界获得的Observation来动态调整后续的Thought和Action。如果上一步的搜索结果不理想它可以在下一步中修正搜索词重新尝试。工具协同能力ReAct 范式天然地将大语言模型的推理能力与外部工具的执行能力结合起来。LLM 负责运筹帷幄规划和推理工具负责解决具体问题搜索、计算二者协同工作突破了单一 LLM 在知识时效性、计算准确性等方面的固有局限。2ReAct 的固有局限性对LLM自身能力的强依赖ReAct 流程的成功与否高度依赖于底层 LLM 的综合能力。如果 LLM 的逻辑推理能力、指令遵循能力或格式化输出能力不足就很容易在Thought环节产生错误的规划或者在Action环节生成不符合格式的指令导致整个流程中断。执行效率问题由于其循序渐进的特性完成一个任务通常需要多次调用 LLM。每一次调用都伴随着网络延迟和计算成本。对于需要很多步骤的复杂任务这种串行的“思考-行动”循环可能会导致较高的总耗时和费用。提示词的脆弱性整个机制的稳定运行建立在一个精心设计的提示词模板之上。模板中的任何微小变动甚至是用词的差异都可能影响 LLM 的行为。此外并非所有模型都能持续稳定地遵循预设的格式这增加了在实际应用中的不确定性。可能陷入局部最优步进式的决策模式意味着智能体缺乏一个全局的、长远的规划。它可能会因为眼前的Observation而选择一个看似正确但长远来看并非最优的路径甚至在某些情况下陷入“原地打转”的循环中。3调试技巧当你构建的 ReAct 智能体行为不符合预期时可以从以下几个方面入手进行调试检查完整的提示词在每次调用 LLM 之前将最终格式化好的、包含所有历史记录的完整提示词打印出来。这是追溯 LLM 决策源头的最直接方式。分析原始输出当输出解析失败时例如正则表达式没有匹配到Action务必将 LLM 返回的原始、未经处理的文本打印出来。这能帮助你判断是 LLM 没有遵循格式还是你的解析逻辑有误。验证工具的输入与输出检查智能体生成的tool_input是否是工具函数所期望的格式同时也要确保工具返回的observation格式是智能体可以理解和处理的。调整提示词中的示例 (Few-shot Prompting)如果模型频繁出错可以在提示词中加入一两个完整的“Thought-Action-Observation”成功案例通过示例来引导模型更好地遵循你的指令。尝试不同的模型或参数更换一个能力更强的模型或者调整temperature参数通常设为0以保证输出的确定性有时能直接解决问题。4.3 Plan-and-Solve在我们掌握了 ReAct 这种反应式的、步进决策的智能体范式后接下来将探讨一种风格迥异但同样强大的方法Plan-and-Solve。顾名思义这种范式将任务处理明确地分为两个阶段先规划 (Plan)后执行 (Solve)。如果说 ReAct 像一个经验丰富的侦探根据现场的蛛丝马迹Observation一步步推理随时调整自己的调查方向那么 Plan-and-Solve 则更像一位建筑师在动工之前必须先绘制出完整的蓝图Plan然后严格按照蓝图来施工Solve。事实上我们现在用的很多大模型工具的Agent模式都融入了这种设计模式。4.3.1 Plan-and-Solve 的工作原理Plan-and-Solve Prompting 由 Lei Wang 在2023年提出[2]。其核心动机是为了解决思维链在处理多步骤、复杂问题时容易“偏离轨道”的问题。与 ReAct 将思考和行动融合在每一步不同Plan-and-Solve 将整个流程解耦为两个核心阶段如图4.2所示规划阶段 (Planning Phase) 首先智能体会接收用户的完整问题。它的第一个任务不是直接去解决问题或调用工具而是将问题分解并制定出一个清晰、分步骤的行动计划。这个计划本身就是一次大语言模型的调用产物。执行阶段 (Solving Phase) 在获得完整的计划后智能体进入执行阶段。它会严格按照计划中的步骤逐一执行。每一步的执行都可能是一次独立的 LLM 调用或者是对上一步结果的加工处理直到计划中的所有步骤都完成最终得出答案。这种“先谋后动”的策略使得智能体在处理需要长远规划的复杂任务时能够保持更高的目标一致性避免在中间步骤中迷失方向。我们可以将这个两阶段过程进行形式化表达。首先规划模型π plan \pi_{\text{plan}}πplan根据原始问题q qq生成一个包含n nn个步骤的计划P ( p 1 , p 2 , … , p n ) P (p_1, p_2, \dots, p_n)P(p1,p2,…,pn)P π plan ( q ) P \pi_{\text{plan}}(q)Pπplan(q)随后在执行阶段执行模型π solve \pi_{\text{solve}}πsolve会逐一完成计划中的步骤。对于第i ii个步骤其解决方案s i s_isi的生成会同时依赖于原始问题q qq、完整计划P PP以及之前所有步骤的执行结果( s 1 , … , s i − 1 ) (s_1, \dots, s_{i-1})(s1,…,si−1)s i π solve ( q , P , ( s 1 , … , s i − 1 ) ) s_i \pi_{\text{solve}}(q, P, (s_1, \dots, s_{i-1}))siπsolve(q,P,(s1,…,si−1))最终的答案就是最后一个步骤的执行结果s n s_nsn。Plan-and-Solve 尤其适用于那些结构性强、可以被清晰分解的复杂任务例如多步数学应用题需要先列出计算步骤再逐一求解。需要整合多个信息源的报告撰写需要先规划好报告结构引言、数据来源A、数据来源B、总结再逐一填充内容。代码生成任务需要先构思好函数、类和模块的结构再逐一实现。4.3.2 规划阶段为了凸显 Plan-and-Solve 范式在结构化推理任务上的优势我们将不使用工具的方式而是通过提示词的设计完成一个推理任务。这类任务的特点是答案无法通过单次查询或计算得出必须先将问题分解为一系列逻辑连贯的子步骤然后按顺序求解。这恰好能发挥 Plan-and-Solve “先规划后执行”的核心能力。我们的目标问题是“一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果”这个问题对于大语言模型来说并不算特别困难但它包含了一个清晰的逻辑链条可供参考。在某些实际的逻辑难题上如果大模型不能高质量的推理出准确的答案可以参考这个设计模式来设计自己的Agent完成任务。智能体需要规划阶段首先将问题分解为三个独立的计算步骤计算周二销量、计算周三销量、计算总销量。执行阶段然后严格按照计划一步步执行计算并将每一步的结果作为下一步的输入最终得出总和。规划阶段的目标是让大语言模型接收原始问题并输出一个清晰、分步骤的行动计划。这个计划必须是结构化的以便我们的代码可以轻松解析并逐一执行。因此我们设计的提示词需要明确地告诉模型它的角色和任务并给出一个输出格式的范例。PLANNER_PROMPT_TEMPLATE 你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。 请确保计划中的每个步骤都是一个独立的、可执行的子任务并且严格按照逻辑顺序排列。 你的输出必须是一个Python列表其中每个元素都是一个描述子任务的字符串。 问题: {question} 请严格按照以下格式输出你的计划,python与作为前后缀是必要的: python [步骤1, 步骤2, 步骤3, ...] 这个提示词通过以下几点确保了输出的质量和稳定性角色设定 “顶级的AI规划专家”激发模型的专业能力。任务描述 清晰地定义了“分解问题”的目标。格式约束 强制要求输出为一个 Python 列表格式的字符串这极大地简化了后续代码的解析工作使其比解析自然语言更稳定、更可靠。接下来我们将这个提示词逻辑封装成一个Planner类这个类也是我们的规划器。# 假定 llm_client.py 中的 HelloAgentsLLM 类已经定义好# from llm_client import HelloAgentsLLMclassPlanner:def__init__(self,llm_client):self.llm_clientllm_clientdefplan(self,question:str)-list[str]: 根据用户问题生成一个行动计划。 promptPLANNER_PROMPT_TEMPLATE.format(questionquestion)# 为了生成计划我们构建一个简单的消息列表messages[{role:user,content:prompt}]print(--- 正在生成计划 ---)# 使用流式输出来获取完整的计划response_textself.llm_client.think(messagesmessages)orprint(f✅ 计划已生成:\n{response_text})# 解析LLM输出的列表字符串try:# 找到python和之间的内容plan_strresponse_text.split(python)[1].split()[0].strip()# 使用ast.literal_eval来安全地执行字符串将其转换为Python列表planast.literal_eval(plan_str)returnplanifisinstance(plan,list)else[]except(ValueError,SyntaxError,IndexError)ase:print(f❌ 解析计划时出错:{e})print(f原始响应:{response_text})return[]exceptExceptionase:print(f❌ 解析计划时发生未知错误:{e})return[]4.3.3 执行器与状态管理在规划器 (Planner) 生成了清晰的行动蓝图后我们就需要一个执行器 (Executor) 来逐一完成计划中的任务。执行器不仅负责调用大语言模型来解决每个子问题还承担着一个至关重要的角色状态管理。它必须记录每一步的执行结果并将其作为上下文提供给后续步骤确保信息在整个任务链条中顺畅流动执行器的提示词与规划器不同。它的目标不是分解问题而是在已有上下文的基础上专注解决当前这一个步骤。因此提示词需要包含以下关键信息原始问题 确保模型始终了解最终目标。完整计划 让模型了解当前步骤在整个任务中的位置。历史步骤与结果 提供至今为止已经完成的工作作为当前步骤的直接输入。当前步骤 明确指示模型现在需要解决哪一个具体任务。EXECUTOR_PROMPT_TEMPLATE 你是一位顶级的AI执行专家。你的任务是严格按照给定的计划一步步地解决问题。 你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。 请你专注于解决“当前步骤”并仅输出该步骤的最终答案不要输出任何额外的解释或对话。 # 原始问题: {question} # 完整计划: {plan} # 历史步骤与结果: {history} # 当前步骤: {current_step} 请仅输出针对“当前步骤”的回答: 我们将执行逻辑封装到Executor类中。这个类将循环遍历计划调用 LLM并维护一个历史记录状态。classExecutor:def__init__(self,llm_client):self.llm_clientllm_clientdefexecute(self,question:str,plan:list[str])-str: 根据计划逐步执行并解决问题。 history# 用于存储历史步骤和结果的字符串print(\n--- 正在执行计划 ---)fori,stepinenumerate(plan):print(f\n- 正在执行步骤{i1}/{len(plan)}:{step})promptEXECUTOR_PROMPT_TEMPLATE.format(questionquestion,planplan,historyhistoryifhistoryelse无,# 如果是第一步则历史为空current_stepstep)messages[{role:user,content:prompt}]response_textself.llm_client.think(messagesmessages)or# 更新历史记录为下一步做准备historyf步骤{i1}:{step}\n结果:{response_text}\n\nprint(f✅ 步骤{i1}已完成结果:{response_text})# 循环结束后最后一步的响应就是最终答案final_answerresponse_textreturnfinal_answer现在已经分别构建了负责“规划”的Planner和负责“执行”的Executor。最后一步是将这两个组件整合到一个统一的智能体PlanAndSolveAgent中并赋予它解决问题的完整能力。我们将创建一个主类PlanAndSolveAgent它的职责非常清晰接收一个 LLM 客户端初始化内部的规划器和执行器并提供一个简单的run方法来启动整个流程。classPlanAndSolveAgent:def__init__(self,llm_client): 初始化智能体同时创建规划器和执行器实例。 self.llm_clientllm_client self.plannerPlanner(self.llm_client)self.executorExecutor(self.llm_client)defrun(self,question:str): 运行智能体的完整流程:先规划后执行。 print(f\n--- 开始处理问题 ---\n问题:{question})# 1. 调用规划器生成计划planself.planner.plan(question)# 检查计划是否成功生成ifnotplan:print(\n--- 任务终止 --- \n无法生成有效的行动计划。)return# 2. 调用执行器执行计划final_answerself.executor.execute(question,plan)print(f\n--- 任务完成 ---\n最终答案:{final_answer})这个PlanAndSolveAgent类的设计体现了“组合优于继承”的原则。它本身不包含复杂的逻辑而是作为一个协调者 (Orchestrator)清晰地调用其内部组件来完成任务。4.3.4 运行实例与分析完整的代码同样参考本书配套的代码仓库code文件夹这里只演示最终结果。--- 开始处理问题 --- 问题: 一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果 --- 正在生成计划 --- 正在调用 xxxx 模型... ✅ 大语言模型响应成功: python[计算周一卖出的苹果数量 15个,计算周二卖出的苹果数量 周一数量 × 2 15 × 2 30个,计算周三卖出的苹果数量 周二数量 - 5 30 - 5 25个,计算三天总销量 周一 周二 周三 15 30 25 70个]✅ 计划已生成:python[计算周一卖出的苹果数量 15个,计算周二卖出的苹果数量 周一数量 × 2 15 × 2 30个,计算周三卖出的苹果数量 周二数量 - 5 30 - 5 25个,计算三天总销量 周一 周二 周三 15 30 25 70个] --- 正在执行计划 --- -正在执行步骤1/4: 计算周一卖出的苹果数量:15个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:15✅ 步骤1已完成结果:15-正在执行步骤2/4: 计算周二卖出的苹果数量: 周一数量 ×215×230个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:30✅ 步骤2已完成结果:30-正在执行步骤3/4: 计算周三卖出的苹果数量: 周二数量 -530-525个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:25✅ 步骤3已完成结果:25-正在执行步骤4/4: 计算三天总销量: 周一 周二 周三15302570个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:70✅ 步骤4已完成结果:70--- 任务完成 --- 最终答案:70从上面的输出日志中我们可以清晰地看到 Plan-and-Solve 范式的工作流程规划阶段 智能体首先调用Planner成功地将复杂的应用题分解成了一个包含四个逻辑步骤的 Python 列表。这个结构化的计划为后续的执行奠定了基础。执行阶段Executor严格按照生成的计划一步一步地向下执行。在每一步中它都将历史结果作为上下文确保了信息的正确传递例如步骤2正确地使用了步骤1的结果“15个”步骤3也正确使用了步骤2的结果“30个”。结果整个过程逻辑清晰步骤明确最终智能体准确地得出了正确答案“70个”。4.4 Reflection在我们已经实现的 ReAct 和 Plan-and-Solve 范式中智能体一旦完成了任务其工作流程便告结束。然而它们生成的初始答案无论是行动轨迹还是最终结果都可能存在谬误或有待改进之处。Reflection 机制的核心思想正是为智能体引入一种事后post-hoc的自我校正循环使其能够像人类一样审视自己的工作发现不足并进行迭代优化。4.4.1 Reflection 机制的核心思想Reflection 机制的灵感来源于人类的学习过程我们完成初稿后会进行校对解出数学题后会进行验算。这一思想在多个研究中得到了体现例如 Shinn, Noah 在2023年提出的 Reflexion 框架[3]。其核心工作流程可以概括为一个简洁的三步循环执行 - 反思 - 优化。执行 (Execution)首先智能体使用我们熟悉的方法如 ReAct 或 Plan-and-Solve尝试完成任务生成一个初步的解决方案或行动轨迹。这可以看作是“初稿”。反思 (Reflection)接着智能体进入反思阶段。它会调用一个独立的、或者带有特殊提示词的大语言模型实例来扮演一个“评审员”的角色。这个“评审员”会审视第一步生成的“初稿”并从多个维度进行评估例如事实性错误是否存在与常识或已知事实相悖的内容逻辑漏洞推理过程是否存在不连贯或矛盾之处效率问题是否有更直接、更简洁的路径来完成任务遗漏信息是否忽略了问题的某些关键约束或方面 根据评估它会生成一段结构化的反馈 (Feedback)指出具体的问题所在和改进建议。优化 (Refinement)最后智能体将“初稿”和“反馈”作为新的上下文再次调用大语言模型要求它根据反馈内容对初稿进行修正生成一个更完善的“修订稿”。如图4.3所示这个循环可以重复进行多次直到反思阶段不再发现新的问题或者达到预设的迭代次数上限。我们可以将这个迭代优化的过程形式化地表达出来。假设O i O_iOi是第i ii次迭代产生的输出O 0 O_0O0为初始输出反思模型π reflect \pi_{\text{reflect}}πreflect会生成针对O i O_iOi的反馈F i F_iFiF i π reflect ( Task , O i ) F_i \pi_{\text{reflect}}(\text{Task}, O_i)Fiπreflect(Task,Oi)随后优化模型π refine \pi_{\text{refine}}πrefine会结合原始任务、上一版输出以及反馈生成新一版的输出O i 1 O_{i1}Oi1O i 1 π refine ( Task , O i , F i ) O_{i1} \pi_{\text{refine}}(\text{Task}, O_i, F_i)Oi1πrefine(Task,Oi,Fi)Plan-and-Solve 尤其适用于那些结构性强、可以被清晰分解的复杂任务例如多步数学应用题需要先列出计算步骤再逐一求解。需要整合多个信息源的报告撰写需要先规划好报告结构引言、数据来源A、数据来源B、总结再逐一填充内容。代码生成任务需要先构思好函数、类和模块的结构再逐一实现。4.3.2 规划阶段为了凸显 Plan-and-Solve 范式在结构化推理任务上的优势我们将不使用工具的方式而是通过提示词的设计完成一个推理任务。这类任务的特点是答案无法通过单次查询或计算得出必须先将问题分解为一系列逻辑连贯的子步骤然后按顺序求解。这恰好能发挥 Plan-and-Solve “先规划后执行”的核心能力。我们的目标问题是“一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果”这个问题对于大语言模型来说并不算特别困难但它包含了一个清晰的逻辑链条可供参考。在某些实际的逻辑难题上如果大模型不能高质量的推理出准确的答案可以参考这个设计模式来设计自己的Agent完成任务。智能体需要规划阶段首先将问题分解为三个独立的计算步骤计算周二销量、计算周三销量、计算总销量。执行阶段然后严格按照计划一步步执行计算并将每一步的结果作为下一步的输入最终得出总和。规划阶段的目标是让大语言模型接收原始问题并输出一个清晰、分步骤的行动计划。这个计划必须是结构化的以便我们的代码可以轻松解析并逐一执行。因此我们设计的提示词需要明确地告诉模型它的角色和任务并给出一个输出格式的范例。PLANNER_PROMPT_TEMPLATE 你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。 请确保计划中的每个步骤都是一个独立的、可执行的子任务并且严格按照逻辑顺序排列。 你的输出必须是一个Python列表其中每个元素都是一个描述子任务的字符串。 问题: {question} 请严格按照以下格式输出你的计划,python与作为前后缀是必要的: python [步骤1, 步骤2, 步骤3, ...] 这个提示词通过以下几点确保了输出的质量和稳定性角色设定 “顶级的AI规划专家”激发模型的专业能力。任务描述 清晰地定义了“分解问题”的目标。格式约束 强制要求输出为一个 Python 列表格式的字符串这极大地简化了后续代码的解析工作使其比解析自然语言更稳定、更可靠。接下来我们将这个提示词逻辑封装成一个Planner类这个类也是我们的规划器。# 假定 llm_client.py 中的 HelloAgentsLLM 类已经定义好# from llm_client import HelloAgentsLLMclassPlanner:def__init__(self,llm_client):self.llm_clientllm_clientdefplan(self,question:str)-list[str]: 根据用户问题生成一个行动计划。 promptPLANNER_PROMPT_TEMPLATE.format(questionquestion)# 为了生成计划我们构建一个简单的消息列表messages[{role:user,content:prompt}]print(--- 正在生成计划 ---)# 使用流式输出来获取完整的计划response_textself.llm_client.think(messagesmessages)orprint(f✅ 计划已生成:\n{response_text})# 解析LLM输出的列表字符串try:# 找到python和之间的内容plan_strresponse_text.split(python)[1].split()[0].strip()# 使用ast.literal_eval来安全地执行字符串将其转换为Python列表planast.literal_eval(plan_str)returnplanifisinstance(plan,list)else[]except(ValueError,SyntaxError,IndexError)ase:print(f❌ 解析计划时出错:{e})print(f原始响应:{response_text})return[]exceptExceptionase:print(f❌ 解析计划时发生未知错误:{e})return[]4.3.3 执行器与状态管理在规划器 (Planner) 生成了清晰的行动蓝图后我们就需要一个执行器 (Executor) 来逐一完成计划中的任务。执行器不仅负责调用大语言模型来解决每个子问题还承担着一个至关重要的角色状态管理。它必须记录每一步的执行结果并将其作为上下文提供给后续步骤确保信息在整个任务链条中顺畅流动执行器的提示词与规划器不同。它的目标不是分解问题而是在已有上下文的基础上专注解决当前这一个步骤。因此提示词需要包含以下关键信息原始问题 确保模型始终了解最终目标。完整计划 让模型了解当前步骤在整个任务中的位置。历史步骤与结果 提供至今为止已经完成的工作作为当前步骤的直接输入。当前步骤 明确指示模型现在需要解决哪一个具体任务。EXECUTOR_PROMPT_TEMPLATE 你是一位顶级的AI执行专家。你的任务是严格按照给定的计划一步步地解决问题。 你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。 请你专注于解决“当前步骤”并仅输出该步骤的最终答案不要输出任何额外的解释或对话。 # 原始问题: {question} # 完整计划: {plan} # 历史步骤与结果: {history} # 当前步骤: {current_step} 请仅输出针对“当前步骤”的回答: 我们将执行逻辑封装到Executor类中。这个类将循环遍历计划调用 LLM并维护一个历史记录状态。classExecutor:def__init__(self,llm_client):self.llm_clientllm_clientdefexecute(self,question:str,plan:list[str])-str: 根据计划逐步执行并解决问题。 history# 用于存储历史步骤和结果的字符串print(\n--- 正在执行计划 ---)fori,stepinenumerate(plan):print(f\n- 正在执行步骤{i1}/{len(plan)}:{step})promptEXECUTOR_PROMPT_TEMPLATE.format(questionquestion,planplan,historyhistoryifhistoryelse无,# 如果是第一步则历史为空current_stepstep)messages[{role:user,content:prompt}]response_textself.llm_client.think(messagesmessages)or# 更新历史记录为下一步做准备historyf步骤{i1}:{step}\n结果:{response_text}\n\nprint(f✅ 步骤{i1}已完成结果:{response_text})# 循环结束后最后一步的响应就是最终答案final_answerresponse_textreturnfinal_answer现在已经分别构建了负责“规划”的Planner和负责“执行”的Executor。最后一步是将这两个组件整合到一个统一的智能体PlanAndSolveAgent中并赋予它解决问题的完整能力。我们将创建一个主类PlanAndSolveAgent它的职责非常清晰接收一个 LLM 客户端初始化内部的规划器和执行器并提供一个简单的run方法来启动整个流程。classPlanAndSolveAgent:def__init__(self,llm_client): 初始化智能体同时创建规划器和执行器实例。 self.llm_clientllm_client self.plannerPlanner(self.llm_client)self.executorExecutor(self.llm_client)defrun(self,question:str): 运行智能体的完整流程:先规划后执行。 print(f\n--- 开始处理问题 ---\n问题:{question})# 1. 调用规划器生成计划planself.planner.plan(question)# 检查计划是否成功生成ifnotplan:print(\n--- 任务终止 --- \n无法生成有效的行动计划。)return# 2. 调用执行器执行计划final_answerself.executor.execute(question,plan)print(f\n--- 任务完成 ---\n最终答案:{final_answer})这个PlanAndSolveAgent类的设计体现了“组合优于继承”的原则。它本身不包含复杂的逻辑而是作为一个协调者 (Orchestrator)清晰地调用其内部组件来完成任务。4.3.4 运行实例与分析完整的代码同样参考本书配套的代码仓库code文件夹这里只演示最终结果。--- 开始处理问题 --- 问题: 一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果 --- 正在生成计划 --- 正在调用 xxxx 模型... ✅ 大语言模型响应成功: python[计算周一卖出的苹果数量 15个,计算周二卖出的苹果数量 周一数量 × 2 15 × 2 30个,计算周三卖出的苹果数量 周二数量 - 5 30 - 5 25个,计算三天总销量 周一 周二 周三 15 30 25 70个]✅ 计划已生成:python[计算周一卖出的苹果数量 15个,计算周二卖出的苹果数量 周一数量 × 2 15 × 2 30个,计算周三卖出的苹果数量 周二数量 - 5 30 - 5 25个,计算三天总销量 周一 周二 周三 15 30 25 70个] --- 正在执行计划 --- -正在执行步骤1/4: 计算周一卖出的苹果数量:15个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:15✅ 步骤1已完成结果:15-正在执行步骤2/4: 计算周二卖出的苹果数量: 周一数量 ×215×230个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:30✅ 步骤2已完成结果:30-正在执行步骤3/4: 计算周三卖出的苹果数量: 周二数量 -530-525个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:25✅ 步骤3已完成结果:25-正在执行步骤4/4: 计算三天总销量: 周一 周二 周三15302570个 正在调用 xxxx 模型... ✅ 大语言模型响应成功:70✅ 步骤4已完成结果:70--- 任务完成 --- 最终答案:70从上面的输出日志中我们可以清晰地看到 Plan-and-Solve 范式的工作流程规划阶段 智能体首先调用Planner成功地将复杂的应用题分解成了一个包含四个逻辑步骤的 Python 列表。这个结构化的计划为后续的执行奠定了基础。执行阶段Executor严格按照生成的计划一步一步地向下执行。在每一步中它都将历史结果作为上下文确保了信息的正确传递例如步骤2正确地使用了步骤1的结果“15个”步骤3也正确使用了步骤2的结果“30个”。结果整个过程逻辑清晰步骤明确最终智能体准确地得出了正确答案“70个”。4.4 Reflection在我们已经实现的 ReAct 和 Plan-and-Solve 范式中智能体一旦完成了任务其工作流程便告结束。然而它们生成的初始答案无论是行动轨迹还是最终结果都可能存在谬误或有待改进之处。Reflection 机制的核心思想正是为智能体引入一种事后post-hoc的自我校正循环使其能够像人类一样审视自己的工作发现不足并进行迭代优化。4.4.1 Reflection 机制的核心思想Reflection 机制的灵感来源于人类的学习过程我们完成初稿后会进行校对解出数学题后会进行验算。这一思想在多个研究中得到了体现例如 Shinn, Noah 在2023年提出的 Reflexion 框架[3]。其核心工作流程可以概括为一个简洁的三步循环执行 - 反思 - 优化。执行 (Execution)首先智能体使用我们熟悉的方法如 ReAct 或 Plan-and-Solve尝试完成任务生成一个初步的解决方案或行动轨迹。这可以看作是“初稿”。反思 (Reflection)接着智能体进入反思阶段。它会调用一个独立的、或者带有特殊提示词的大语言模型实例来扮演一个“评审员”的角色。这个“评审员”会审视第一步生成的“初稿”并从多个维度进行评估例如事实性错误是否存在与常识或已知事实相悖的内容逻辑漏洞推理过程是否存在不连贯或矛盾之处效率问题是否有更直接、更简洁的路径来完成任务遗漏信息是否忽略了问题的某些关键约束或方面 根据评估它会生成一段结构化的反馈 (Feedback)指出具体的问题所在和改进建议。优化 (Refinement)最后智能体将“初稿”和“反馈”作为新的上下文再次调用大语言模型要求它根据反馈内容对初稿进行修正生成一个更完善的“修订稿”。如图4.3所示这个循环可以重复进行多次直到反思阶段不再发现新的问题或者达到预设的迭代次数上限。我们可以将这个迭代优化的过程形式化地表达出来。假设O i O_iOi是第i ii次迭代产生的输出O 0 O_0O0为初始输出反思模型π reflect \pi_{\text{reflect}}πreflect会生成针对O i O_iOi的反馈F i F_iFiF i π reflect ( Task , O i ) F_i \pi_{\text{reflect}}(\text{Task}, O_i)Fiπreflect(Task,Oi)随后优化模型π refine \pi_{\text{refine}}πrefine会结合原始任务、上一版输出以及反馈生成新一版的输出O i 1 O_{i1}Oi1O i 1 π refine ( Task , O i , F i ) O_{i1} \pi_{\text{refine}}(\text{Task}, O_i, F_i)Oi1πrefine(Task,Oi,Fi)图 4.3 Reflection 机制中的“执行-反思-优化”迭代循环与前两种范式相比Reflection 的价值在于它为智能体提供了一个内部纠错回路使其不再完全依赖于外部工具的反馈ReAct 的 Observation从而能够修正更高层次的逻辑和策略错误。它将一次性的任务执行转变为一个持续优化的过程显著提升了复杂任务的最终成功率和答案质量。它为智能体构建了一个临时的“短期记忆”。整个“执行-反思-优化”的轨迹形成了一个宝贵的经验记录智能体不仅知道最终答案还记得自己是如何从有缺陷的初稿迭代到最终版本的。更进一步这个记忆系统还可以是多模态的允许智能体反思和修正文本以外的输出如代码、图像等为构建更强大的多模态智能体奠定了基础。4.4.2 案例设定与记忆模块设计为了在实战中体现 Reflection 机制我们将引入记忆管理机制因为reflection通常对应着信息的存储和提取如果上下文足够长的情况想让“评审员”直接获取所有的信息然后进行反思往往会传入很多冗余信息。这一步实践我们主要完成代码生成与迭代优化。这一步的目标任务是“编写一个Python函数找出1到n之间所有的素数 (prime numbers)。”这个任务是检验 Reflection 机制的绝佳场景存在明确的优化路径大语言模型初次生成的代码很可能是一个简单但效率低下的递归实现。反思点清晰可以通过反思发现其“时间复杂度过高”或“存在重复计算”的问题。优化方向明确可以根据反馈将其优化为更高效的迭代版本或使用备忘录模式的版本。Reflection 的核心在于迭代而迭代的前提是能够记住之前的尝试和获得的反馈。因此一个“短期记忆”模块是实现该范式的必需品。这个记忆模块将负责存储每一次“执行-反思”循环的完整轨迹。fromtypingimportList,Dict,Any,OptionalclassMemory: 一个简单的短期记忆模块用于存储智能体的行动与反思轨迹。 def__init__(self): 初始化一个空列表来存储所有记录。 self.records:List[Dict[str,Any]][]defadd_record(self,record_type:str,content:str): 向记忆中添加一条新记录。 参数: - record_type (str): 记录的类型 (execution 或 reflection)。 - content (str): 记录的具体内容 (例如生成的代码或反思的反馈)。 record{type:record_type,content:content}self.records.append(record)print(f 记忆已更新新增一条 {record_type} 记录。)defget_trajectory(self)-str: 将所有记忆记录格式化为一个连贯的字符串文本用于构建提示词。 trajectory_parts[]forrecordinself.records:ifrecord[type]execution:trajectory_parts.append(f--- 上一轮尝试 (代码) ---\n{record[content]})elifrecord[type]reflection:trajectory_parts.append(f--- 评审员反馈 ---\n{record[content]})return\n\n.join(trajectory_parts)defget_last_execution(self)-Optional[str]: 获取最近一次的执行结果 (例如最新生成的代码)。 如果不存在则返回 None。 forrecordinreversed(self.records):ifrecord[type]execution:returnrecord[content]returnNone这个Memory类的设计比较简洁主体是这样的使用一个列表records来按顺序存储每一次的行动和反思。add_record方法负责向记忆中添加新的条目。get_trajectory方法是核心它将记忆轨迹“序列化”成一段文本可以直接插入到后续的提示词中为模型的反思和优化提供完整的上下文。get_last_execution方便我们获取最新的“初稿”以供反思。4.4.3 Reflection 智能体的编码实现有了Memory模块作为基础我们现在可以着手构建ReflectionAgent的核心逻辑。整个智能体的工作流程将围绕我们之前讨论的“执行-反思-优化”循环展开并通过精心设计的提示词来引导大语言模型扮演不同的角色。1提示词设计与之前的范式不同Reflection 机制需要多个不同角色的提示词来协同工作。初始执行提示词 (Execution Prompt)这是智能体首次尝试解决问题的提示词内容相对直接只要求模型完成指定任务。INITIAL_PROMPT_TEMPLATE 你是一位资深的Python程序员。请根据以下要求编写一个Python函数。 你的代码必须包含完整的函数签名、文档字符串并遵循PEP8编码规范。 要求:{task}请直接输出代码不要包含任何额外的解释。反思提示词 (Reflection Prompt)这个提示词是 Reflection 机制的灵魂。它指示模型扮演“代码评审员”的角色对上一轮生成的代码进行批判性分析并提供具体的、可操作的反馈。REFLECT_PROMPT_TEMPLATE 你是一位极其严格的代码评审专家和资深算法工程师对代码的性能有极致的要求。 你的任务是审查以下Python代码并专注于找出其在strong算法效率/strong上的主要瓶颈。# 原始任务:{task}# 待审查的代码:python{code} 请分析该代码的时间复杂度并思考是否存在一种strong算法上更优/strong的解决方案来显著提升性能。 如果存在请清晰地指出当前算法的不足并提出具体的、可行的改进算法建议例如使用筛法替代试除法。 如果代码在算法层面已经达到最优才能回答“无需改进”。 请直接输出你的反馈不要包含任何额外的解释。优化提示词 (Refinement Prompt)当收到反馈后这个提示词将引导模型根据反馈内容对原有代码进行修正和优化。REFINE_PROMPT_TEMPLATE 你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。# 原始任务:{task}# 你上一轮尝试的代码:{last_code_attempt}评审员的反馈{feedback}请根据评审员的反馈生成一个优化后的新版本代码。 你的代码必须包含完整的函数签名、文档字符串并遵循PEP8编码规范。 请直接输出优化后的代码不要包含任何额外的解释。2智能体封装与实现现在我们将这套提示词逻辑和Memory模块整合到ReflectionAgent类中。# 假设 llm_client.py 和 memory.py 已定义# from llm_client import HelloAgentsLLM# from memory import MemoryclassReflectionAgent:def__init__(self,llm_client,max_iterations3):self.llm_clientllm_client self.memoryMemory()self.max_iterationsmax_iterationsdefrun(self,task:str):print(f\n--- 开始处理任务 ---\n任务:{task})# --- 1. 初始执行 ---print(\n--- 正在进行初始尝试 ---)initial_promptINITIAL_PROMPT_TEMPLATE.format(tasktask)initial_codeself._get_llm_response(initial_prompt)self.memory.add_record(execution,initial_code)# --- 2. 迭代循环:反思与优化 ---foriinrange(self.max_iterations):print(f\n--- 第{i1}/{self.max_iterations}轮迭代 ---)# a. 反思print(\n- 正在进行反思...)last_codeself.memory.get_last_execution()reflect_promptREFLECT_PROMPT_TEMPLATE.format(tasktask,codelast_code)feedbackself._get_llm_response(reflect_prompt)self.memory.add_record(reflection,feedback)# b. 检查是否需要停止if无需改进infeedback:print(\n✅ 反思认为代码已无需改进任务完成。)break# c. 优化print(\n- 正在进行优化...)refine_promptREFINE_PROMPT_TEMPLATE.format(tasktask,last_code_attemptlast_code,feedbackfeedback)refined_codeself._get_llm_response(refine_prompt)self.memory.add_record(execution,refined_code)final_codeself.memory.get_last_execution()print(f\n--- 任务完成 ---\n最终生成的代码:\npython\n{final_code}\n)returnfinal_codedef_get_llm_response(self,prompt:str)-str:一个辅助方法用于调用LLM并获取完整的流式响应。messages[{role:user,content:prompt}]response_textself.llm_client.think(messagesmessages)orreturnresponse_text4.4.4 运行实例与分析完整的代码同样参考本书配套的代码仓库code文件夹这里提供一个输出实例。---开始处理任务---任务 编写一个Python函数找出1到n之间所有的素数(prime numbers)。---正在进行初始尝试--- 正在调用 xxxxxx 模型...✅ 大语言模型响应成功 pythondeffind_primes(n):...returnprimes 记忆已更新新增一条execution记录。---第1/2轮迭代----正在进行反思... 正在调用 xxxxxx 模型...✅ 大语言模型响应成功 当前代码的时间复杂度为O(n*sqrt(n))。虽然对于较小的n值这种实现是可以接受的但当n非常大时性能会显著下降。主要瓶颈在于每个数都需要进行试除法检查这导致了较高的时间开销。 建议使用埃拉托斯特尼筛法Sieve of Eratosthenes该算法的时间复杂度为O(n log(log n))能够显著提高查找素数的效率。 改进后的代码如下 pythondeffind_primes(n):...returnprimes 记忆已更新新增一条reflection记录。-正在进行优化... 正在调用 xxxxxx 模型...✅ 大语言模型响应成功 pythondeffind_primes(n):...returnprimes 记忆已更新新增一条execution记录。---第2/2轮迭代----正在进行反思... 正在调用 xxxxxx 模型...✅ 大语言模型响应成功 当前代码使用了Eratosthenes筛法时间复杂度为O(n log log n)空间复杂度为O(n)。此算法在寻找1到n之间的所有素数时已经非常高效通常情况下无需进一步优化。但在某些特定场景下可以考虑以下改进1.strong分段筛法Segmented Sieve/strong适用于n非常大但内存有限的情况。将区间分成多个小段每段分别用筛法处理减少内存使用。2.strong奇数筛法Odd Number Sieve/strong除了2以外所有素数都是奇数。可以在初始化is_prime数组时只标记奇数这样可以将空间复杂度降低一半同时减少一些不必要的计算。 然而这些改进对于大多数应用场景来说并不是必需的因为标准的Eratosthenes筛法已经足够高效。因此在一般情况下strong无需改进/strong。 记忆已更新新增一条reflection记录。 ✅ 反思认为代码已无需改进任务完成。---任务完成---最终生成的代码 pythondeffind_primes(n): Finds all prime numbers between 1 and n using the Sieve of Eratosthenes algorithm. :param n: The upper limit of the range to find prime numbers. :return: A list of all prime numbers between 1 and n. ifn2:return[]is_prime[True]*(n1)is_prime[0]is_prime[1]Falsep2whilep*pn:ifis_prime[p]:foriinrange(p*p,n1,p):is_prime[i]Falsep1primes[numfornuminrange(2,n1)ifis_prime[num]]returnprimes 这个运行实例展示了 Reflection 机制是如何驱动智能体进行深度优化的:有效的“批判”是优化的前提:在第一轮反思中由于我们使用了“极其严格”且“专注于算法效率”的提示词智能体没有满足于功能正确的初版代码而是精准地指出了其O(n * sqrt(n))的时间复杂度瓶颈并提出了算法层面的改进建议——埃拉托斯特尼筛法。迭代式改进: 智能体在接收到明确的反馈后于优化阶段成功地实现了更高效的筛法将算法复杂度降至O(n log log n)完成了第一次有意义的自我迭代。收敛与终止: 在第二轮反思中智能体面对已经高效的筛法展现出了更深层次的知识。它不仅肯定了当前算法的效率甚至还提及了分段筛法等更高级的优化方向但最终做出了“在一般情况下无需改进”的正确判断。这个判断触发了我们的终止条件使优化过程得以收敛。这个案例充分证明一个设计良好的 Reflection 机制其价值不仅在于修复错误更在于驱动解决方案在质量和效率上实现阶梯式的提升这使其成为构建复杂、高质量智能体的关键技术之一。4.4.5 Reflection 机制的成本收益分析尽管 Reflection 机制在提升任务解决质量上表现出色但这种能力的获得并非没有代价。在实际应用中我们需要权衡其带来的收益与相应的成本。1主要成本模型调用开销增加:这是最直接的成本。每进行一轮迭代至少需要额外调用两次大语言模型一次用于反思一次用于优化。如果迭代多轮API 调用成本和计算资源消耗将成倍增加。任务延迟显著提高:Reflection 是一个串行过程每一轮的优化都必须等待上一轮的反思完成。这使得任务的总耗时显著延长不适合对实时性要求高的场景。提示工程复杂度上升:如我们的案例所示Reflection 的成功在很大程度上依赖于高质量、有针对性的提示词。为“执行”、“反思”、“优化”等不同阶段设计和调试有效的提示词需要投入更多的开发精力。2核心收益解决方案质量的跃迁:最大的收益在于它能将一个“合格”的初始方案迭代优化成一个“优秀”的最终方案。这种从功能正确到性能高效、从逻辑粗糙到逻辑严谨的提升在很多关键任务中是至关重要的。鲁棒性与可靠性增强:通过内部的自我纠错循环智能体能够发现并修复初始方案中可能存在的逻辑漏洞、事实性错误或边界情况处理不当等问题从而大大提高了最终结果的可靠性。综上所述Reflection 机制是一种典型的“以成本换质量”的策略。它非常适合那些对最终结果的质量、准确性和可靠性有极高要求且对任务完成的实时性要求相对宽松的场景。例如:生成关键的业务代码或技术报告。在科学研究中进行复杂的逻辑推演。需要深度分析和规划的决策支持系统。反之如果应用场景需要快速响应或者一个“大致正确”的答案就已经足够那么使用更轻量的 ReAct 或 Plan-and-Solve 范式可能会是更具性价比的选择。4.5 本章小结在本章中以第三章掌握的大语言模型知识为基础我们通过“亲手造轮子”的方式从零开始编码实现了三种业界经典的智能体构建范式:ReAct、Plan-and-Solve 与 Reflection。我们不仅探索了它们的核心工作原理还通过具体的实战案例深入了解了各自的优势、局限与适用场景。核心知识点回顾:ReAct:我们构建了一个能与外部世界交互的 ReAct 智能体。通过“思考-行动-观察”的动态循环它成功地利用搜索引擎回答了自身知识库无法覆盖的实时性问题。其核心优势在于环境适应性和动态纠错能力使其成为处理探索性、需要外部工具输入的任务的首选。Plan-and-Solve:我们实现了一个先规划后执行的 Plan-and-Solve 智能体并利用它解决了需要多步推理的数学应用题。它将复杂的任务分解为清晰的步骤然后逐一执行。其核心优势在于结构性和稳定性特别适合处理逻辑路径确定、内部推理密集的任务。Reflection (自我反思与迭代):我们构建了一个具备自我优化能力的 Reflection 智能体。通过引入“执行-反思-优化”的迭代循环它成功地将一个效率较低的初始代码方案优化为了一个算法上更优的高性能版本。其核心价值在于能显著提升解决方案的质量适用于对结果的准确性和可靠性有极高要求的场景。本章探讨的三种范式代表了智能体解决问题的三种不同策略如表4.1所示。在实际应用中选择哪一种取决于任务的核心需求:至此我们已经掌握了构建单个智能体的核心技术。为了过渡知识以及对实际应用更加深入。下一节我们将会探索不同低代码平台的使用方式以及轻代码构建agent的方案。