第五章 Planning — 让 Agent 学会“思考“
第5章Planning — 让 Agent 学会思考 本章目标理解 ReAct 模式的完整实现掌握 Chain-of-Thought思维链提示技巧学会任务分解把大任务拆成小步骤实现带自我反思的 Agent5.1 为什么 Tool Calling 还不够第3章的 Tool Calling Agent 能工作但它有一个问题它不怎么想直接做。看一个例子用户: 帮我规划一个周末北京两日游 Tool Calling Agent 的行为: 调 get_weather(北京) → 直接回复答案 问题: 它没有思考过程—— - 没有先想我需要哪些信息天气、景点、交通... - 没有规划先查天气 → 再找景点 → 最后排路线 - 没有验证我拿到的信息够不够完整ReAct 模式的核心就是让 Agent先把思考说出来再行动。5.2 ReAct 模式完整实现ReAct Reasoning Acting要求 Agent 按固定格式输出Thought: 我现在需要做什么为什么 Action: 工具名称 Action Input: 工具参数JSON Observation: 系统填入工具执行结果 ...重复上述步骤 Thought: 我现在有足够信息了 Final Answer: 最终回答完整代码实现 ReAct Agent —— 会思考的 Agent 使用前请先: pip install openai python-dotenv 并在 .env 文件中设置: DEEPSEEK_API_KEYsk-你的密钥 importjsonimportreimportosfromopenaiimportOpenAIfromdotenvimportload_dotenv load_dotenv()# DeepSeek 客户端clientOpenAI(api_keyos.getenv(DEEPSEEK_API_KEY),base_urlhttps://api.deepseek.com/v1)defchat(messages:list,model:strdeepseek-chat,temperature:float0.7)-str:封装 LLM 调用responseclient.chat.completions.create(modelmodel,messagesmessages,temperaturetemperature,)returnresponse.choices[0].message.content# ── 工具定义复用第3章的 ──defget_weather(city:str)-str:weather_db{北京:晴25°C轻度雾霾,上海:多云28°C空气质量优,深圳:阵雨30°C湿度大,杭州:小雨22°C适合室内活动,}returnweather_db.get(city,f未找到{city}天气)defweb_search(query:str)-str:模拟搜索实际接搜索APIsearch_db{北京景点:故宫(5A)、长城(5A)、颐和园(5A)、798艺术区、南锣鼓巷,北京美食:烤鸭(全聚德/大董)、炸酱面、豆汁、卤煮、涮羊肉,北京交通:地铁覆盖主城区建议办交通卡高峰期(7-9点,17-19点)打车困难,}forkey,valueinsearch_db.items():ifkeyinquery:returnvaluereturnf搜索「{query}」未找到相关信息TOOLS{get_weather:get_weather,web_search:web_search,}# ── 构建 System Prompt ──REACT_SYSTEM_PROMPT你是一个能使用工具的 AI Agent。你必须严格按照以下格式回复 当你需要调用工具时 Thought: 分析当前情况解释为什么需要调用这个工具 Action: 工具名称 Action Input: {参数名: 参数值} 当工具执行完后你会看到 Observation: 工具返回的结果 然后你应该再次思考直到得到最终答案 Thought: 我现在有了足够的信息 Final Answer: 给用户的完整回答 重要规则 1. 每次只调用一个工具 2. Thought 必须解释你的推理过程 3. Action Input 必须是合法的 JSON 4. 信息足够时直接给 Final Answer 5. 工具失败时思考替代方案 可用工具 - get_weather: 查询城市天气参数 city (字符串) - web_search: 搜索网络信息参数 query (字符串) defparse_action(text:str)-tuple[str,dict]|None:从 LLM 输出中解析 Action 和 Action Inputaction_matchre.search(rAction:\s*(\w),text)input_matchre.search(rAction Input:\s*(\{.*?\}),text,re.DOTALL)ifaction_matchandinput_match:try:actionaction_match.group(1)action_inputjson.loads(input_match.group(1))returnaction,action_inputexceptjson.JSONDecodeError:passreturnNonedefparse_final_answer(text:str)-str|None:从 LLM 输出中解析 Final Answermatchre.search(rFinal Answer:\s*(.*),text,re.DOTALL)ifmatch:returnmatch.group(1).strip()returnNone# ── ReAct Agent 主循环 ──defreact_agent(task:str,max_steps:int8,verbose:boolTrue)-str: ReAct Agent先思考再行动观察结果继续思考... messages[{role:system,content:REACT_SYSTEM_PROMPT},{role:user,content:task}]forstepinrange(max_steps):ifverbose:print(f\n{*60})print(f 第{step1}步)print(f{*60})# 调用 LLMresponsechat(messages,temperature0.3)# 低温度保证格式稳定messages.append({role:assistant,content:response})ifverbose:print(f LLM 输出:\n{response})# 检查是否是最终回答final_answerparse_final_answer(response)iffinal_answer:ifverbose:print(f\n✅ 任务完成共{step1}步)returnfinal_answer# 检查是否需要执行工具action_dataparse_action(response)ifaction_data:action_name,action_inputaction_dataifaction_nameinTOOLS:# 执行工具funcTOOLS[action_name]resultfunc(**action_input)ifverbose:print(f 执行:{action_name}({action_input}))print(f 结果:{result})# 把结果作为 Observation 加入messages.append({role:user,content:fObservation:{result}})else:messages.append({role:user,content:fObservation: 错误 - 未知工具 {action_name}})else:# 既不是 Action 也不是 Final Answer提示模型遵循格式messages.append({role:user,content:请按照格式回复先 Thought然后选择 Action 或 Final Answer。})return任务在最大步数内未完成请简化需求。# ── 测试 ──if__name____main__:# 简单对比同一个任务看 ReAct Agent 的思考过程task我想周末去北京玩两天帮我查一下天气推荐几个景点和美食再给个出行建议resultreact_agent(task)print(f\n{*60})print(f 最终回答:\n{result})运行效果 第 1 步 LLM 输出: Thought: 用户要规划北京两日游我需要先知道天气来确定户外还是室内活动 Action: get_weather Action Input: {city: 北京} 执行: get_weather({city: 北京}) 结果: 晴25°C轻度雾霾 第 2 步 LLM 输出: Thought: 天气适合户外(晴天25°C)但有雾霾需要注意。现在查景点和美食 Action: web_search Action Input: {query: 北京景点} 执行: web_search({query: 北京景点}) 结果: 故宫(5A)、长城(5A)、颐和园(5A)、798艺术区、南锣鼓巷 第 3 步 LLM 输出: Thought: 有了景点列表现在查美食 Action: web_search Action Input: {query: 北京美食} 执行: web_search({query: 北京美食}) 结果: 烤鸭(全聚德/大董)、炸酱面、豆汁、卤煮、涮羊肉 第 4 步 LLM 输出: Thought: 天气、景点、美食都有了。现在可以给出完整的规划 Final Answer: 北京周末天气晴好25°C但有轻度雾霾建议戴口罩... ✅ 任务完成共 4 步5.3 Chain-of-Thought让 Agent 先想再做的简单方法如果你不想用 ReAct 这种严格的格式一个更轻量的方法是思维链提示Chain-of-Thought 思维链 Agent —— 将本节代码追加到5.2节完整代码后面即可运行 依赖5.2节定义的 client 和 chat 函数 defagent_with_cot(task:str)-str:使用思维链提示的 Agent — 更简单但效果也不错system_prompt你是一个会仔细思考的助手。回答每个问题之前先 1. 分析问题用户真正想要什么 2. 列出步骤需要做哪些事 3. 逐步执行一步一步来 4. 总结回答给用户清晰的结论 在 {{思考}} 中写下你的分析过程这部分不会给用户看到 然后用自然语言回复用户。responsechat([{role:system,content:system_prompt},{role:user,content:task}])returnresponse这个方法不需要解析 Action/Action Input但对于简单任务效果很好。5.4 任务分解把大任务拆小复杂任务一次性交给 Agent它容易迷路。更好的做法是先分解再逐个击破。 任务分解 —— 将本节代码追加到5.2节完整代码后面即可运行 依赖5.2节定义的 client、chat、react_agent 函数 defdecompose_task(complex_task:str)-list[str]:用 LLM 把复杂任务分解为子任务列表promptf请把以下复杂任务分解成3-5个简单的子任务每个子任务一句话。 用序号列出不要说其他内容。 复杂任务:{complex_task}子任务列表:resultchat([{role:system,content:你是任务分解专家只输出子任务列表。},{role:user,content:prompt}])# 解析子任务subtasks[]forlineinresult.strip().split(\n):lineline.strip()iflineand(line[0].isdigit()orline.startswith(-)):# 去掉序号和符号taskline.lstrip(0123456789.-) ).strip()iftask:subtasks.append(task)returnsubtasksdefagent_with_decomposition(complex_task:str)-str: 先分解任务再逐个执行最后汇总 # 第1步分解任务print( 正在分解任务...)subtasksdecompose_task(complex_task)print(f分解为{len(subtasks)}个子任务:)fori,tinenumerate(subtasks,1):print(f{i}.{t})# 第2步逐个执行results[]fori,subtaskinenumerate(subtasks,1):print(f\n 执行子任务{i}/{len(subtasks)}:{subtask})resultreact_agent(subtask,max_steps3,verboseFalse)results.append({task:subtask,result:result})print(f 结果:{result[:100]}...)# 第3步汇总print(\n 正在汇总结果...)summary_promptf请基于以下子任务的结果给出一份完整的回答 原始任务:{complex_task}子任务结果:{json.dumps(results,ensure_asciiFalse,indent2)}请给出完整、连贯、有用的回答final_answerchat([{role:system,content:你是信息整合专家},{role:user,content:summary_prompt}])returnfinal_answer5.5 自我反思Agent 检查自己的输出好的 Agent 应该能自己检查自己。这是一个简单的反思模式 自我反思 Agent —— 将本节代码追加到5.2节完整代码后面即可运行 依赖5.2节定义的 client、chat、react_agent 函数 defagent_with_reflection(task:str)-str:Agent 先回答问题 → 再反思自己的回答 → 改进后输出# 第1轮生成初始回答print(✍️ 生成初始回答...)initial_answerreact_agent(task,verboseFalse)# 第2轮自我反思print( 自我反思中...)reflection_promptf请检查以下回答的质量指出问题 原始问题:{task}待检查的回答:{initial_answer}请检查 1. 信息是否准确完整 2. 逻辑是否清晰 3. 有没有遗漏用户的需求 4. 有没有更好的建议 如果回答已经很好说无需改进。 如果有问题具体指出。reflectionchat([{role:system,content:你是严格的质量审核员},{role:user,content:reflection_prompt}])print(f反思结果:{reflection[:200]}...)# 第3轮改进if无需改进notinreflection:print( 改进回答中...)improve_promptf请基于以下反馈改进你的回答 原始回答:{initial_answer}改进建议:{reflection}请给出改进后的完整回答improved_answerchat([{role:system,content:你追求卓越善于接受反馈},{role:user,content:improve_prompt}])returnimproved_answerreturninitial_answer5.6 三种 Planning 模式对比模式复杂度适用场景优点缺点Tool Calling⭐简单查询、单步骤任务快速、可靠没有显式推理ReAct⭐⭐多步骤推理任务思考过程可见、易调试格式可能不稳定分解汇总⭐⭐⭐复杂、开放式任务复杂任务结构化需要多次调用、成本高反思改进⭐⭐⭐需要高质量输出的场景输出质量更高多一次调用 本章小结ReAct 让 Agent 在每一步先说 Thought思考再做 Action行动思维链 要求 LLM “让我们一步一步思考”任务分解 大任务拆小逐个击破最后汇总自我反思 Agent 检查自己的输出发现不足并改进✏️ 练习题基础题给 ReAct Agent 增加一个save_note(content)工具让它能把用户的行程规划保存下来。进阶题改造 React Agent让它支持一次输出多个 Action并行工具调用提高效率。挑战题实现一个Plan-and-Execute Agent——先用 LLM 制定完整计划不执行用户确认后再逐步执行。下一章预告第6章Multi-Agent — 多个 Agent 协作 —— 一个 Agent 不够我们来组建一个 Agent 团队