第7节:Agent 决策模型:从理论到实践,构建会思考的智能体

文章目录
一、前言
在人工智能飞速发展的今天,大型语言模型(LLM)已展现出惊人的内容生成和理解能力。然而,一个更根本的问题摆在面前:如何让这些模型不仅“能说”,更能“会做”?如何让它们能够主动感知环境、制定计划、执行行动,并持续优化决策?这正是智能体(Agent)技术要解决的核心问题。
Agent 决策模型 是智能体的“大脑”和“决策中枢”。如果说大型语言模型提供了丰富的知识储备和推理基础,那么决策模型则提供了运用这些能力的“方法论”。它决定了智能体如何理解任务、如何规划步骤、如何选择工具,以及如何从结果中学习。无论是构建能够自动编写和调试代码的编程助手,还是打造能够理解用户复杂指令并操作软件的数字员工,亦或是开发能够进行长期规划和科学发现的AI科学家,其核心都在于一个高效、鲁棒的决策模型。
本文将深入浅出地剖析 Agent 决策模型的三种核心范式:经典的 ReAct 框架、擅长复杂任务分解的 Plan-and-Execute 框架,以及追求效率与简洁的 Self-Ask 框架。我们将不仅探讨其背后的设计哲学与工作原理,更将通过一个完整的“实现智能机器人”项目实战,手把手带你用代码实现一个具备思考和行动能力的智能体。文章包含详尽的代码注释和可直接运行的示例,并附有练习题供你巩固知识。
我们的目标是,让你不仅能掌握 Agent 决策的理论,更能获得将其付诸实践的能力,从而开启构建下一代智能应用的大门。
二、Agent 决策模型
智能体(Agent)的核心是感知-思考-行动的循环。决策模型就是这个循环中“思考”部分的具体实现,它定义了智能体如何将输入(观察、目标、历史)转化为下一步的行动(调用工具、给出最终答案)。下面,我们详细解析三种主流的决策框架。
2.1 ReAct 框架详解
1. 核心理念:
ReAct 是 Reason(思考) 与 Act(行动) 的结合。其核心思想是模仿人类解决问题的方式:我们通常不会盲目尝试,而是先思考一下当前情况、需要做什么、为什么这么做,然后再采取具体的行动,并根据行动结果调整后续的思考。这个“思考-行动-观察-再思考”的循环,使得智能体能够进行有依据的、可解释的决策,并处理需要多步工具调用的任务。
2. 工作原理:
- 思考(Reason): 智能体分析当前的任务、已有的历史信息(之前的思考、行动、观察)以及最终目标,然后推理出下一步应该做什么,以及为什么要这样做。思考内容会被记录,形成一个内在的、可追溯的推理链。
- 行动(Act): 基于上一步的思考,智能体决定执行一个具体的动作。这通常表现为调用一个可用的工具(Tool),并传入相应的参数。例如,
Search[“2025年诺贝尔物理学奖得主”]。 - 观察(Observe): 工具执行后返回结果。这个结果(可能是成功的数据,也可能是错误信息)被智能体“观察”到,并作为新的上下文信息。
- 循环: 将“观察”到的结果纳入历史,智能体开始新一轮的“思考”,判断任务是否完成,若未完成则规划下一步行动。如此循环,直至任务完成或达到步骤限制。
3. 代码示例:
下面我们实现一个简化版的 ReAct 智能体,它可以使用“搜索”和“计算器”两个工具。
# 示例:简化版 ReAct 智能体框架
import re
import json
# 注意:以下为模拟实现。在生产环境中,你会使用 LangChain、LlamaIndex 等框架的 ReAct 模块。
# 这里我们手动实现流程以阐明原理。
class SimpleReActAgent:
"""
一个简单的手动实现 ReAct 代理。
它通过维护一个包含 思考(Thought)、行动(Action)、观察(Observation) 的循环来工作。
"""
def __init__(self, llm_client, tools, max_steps=10):
"""
初始化智能体。
:param llm_client: 一个模拟的LLM调用函数,接收提示词返回响应。
:param tools: 字典,工具名称到工具函数的映射。
:param max_steps: 最大循环步数,防止无限循环。
"""
self.llm = llm_client
self.tools = tools
self.max_steps = max_steps
# 用于存储执行过程中的轨迹,便于调试和展示
self.trajectory = []
def run(self, query):
"""
执行主循环处理用户查询。
:param query: 用户的初始问题。
:return: 最终的答案字符串。
"""
# 初始化上下文,包含任务描述
context = f"任务: {query}\n\n你需要通过思考和行动来逐步解决这个问题。你可以使用的工具有: {list(self.tools.keys())}。"
print(f"【任务开始】: {query}")
for step in range(self.max_steps):
print(f"\n--- 步骤 {step+1} ---")
# 1. 思考阶段 (Reason)
# 构建提示词,要求模型根据当前上下文思考下一步
thought_prompt = f"""{context}
你当前的解决过程:
{self._format_trajectory()}
请严格按以下格式回应:
Thought: 这里写下你的思考。分析当前情况,决定下一步做什么。
Action: 要调用的工具名称,例如 `search` 或 `calculator`。如果认为任务已完成,就写 `Final Answer`。
Action Input: 调用工具时需要的输入参数,如果是最终答案,这里写下答案内容。
"""
response = self.llm(thought_prompt)
print(f"智能体原始响应:\n{response}")
# 解析响应,提取 Thought, Action, Action Input
thought, action, action_input = self._parse_response(response)
self.trajectory.append({'thought': thought, 'action': action, 'action_input': action_input})
# 2. 检查是否是最终答案
if action.lower() == "final answer":
print(f"\n【任务完成】最终答案: {action_input}")
return action_input
# 3. 行动阶段 (Act)
if action in self.tools:
print(f"执行行动: {action}({action_input})")
try:
# 调用对应的工具函数
observation = self.toolsaction_input
except Exception as e:
observation = f"工具执行出错: {e}"
else:
observation = f"错误: 未知的工具名称 '{action}'。可用工具: {list(self.tools.keys())}"
print(f"观察结果: {observation}")
# 4. 观察阶段 (将结果加入轨迹和上下文)
# 注意:实际上下文中,观察结果会以特定格式(如 `Observation: ...`)被追加,
# 从而在下一轮思考中被模型看到。我们这里将其记录在轨迹中,_format_trajectory 方法会处理。
self.trajectory[-1]['observation'] = observation
# 如果达到最大步数仍未得到最终答案
return f"达到最大步数({self.max_steps})仍未解决问题。最后轨迹:{self.trajectory}"
def _parse_response(self, response):
"""解析LLM的响应,提取 Thought, Action, Action Input 三个部分。"""
# 使用简单的正则表达式进行解析
thought_match = re.search(r'Thought:\s*(.*?)(?=\nAction:|\n*$)', response, re.DOTALL)
action_match = re.search(r'Action:\s*(.*?)(?=\nAction Input:|\n*$)', response, re.DOTALL)
action_input_match = re.search(r'Action Input:\s*(.*?)(?=\n*$)', response, re.DOTALL)
thought = thought_match.group(1).strip() if thought_match else "未提供思考"
action = action_match.group(1).strip() if action_match else ""
action_input = action_input_match.group(1).strip() if action_input_match else ""
return thought, action, action_input
def _format_trajectory(self):
"""将历史轨迹格式化为字符串,用于构建上下文。"""
formatted = ""
for i, step in enumerate(self.trajectory):
formatted += f"Thought {i+1}: {step['thought']}\n"
formatted += f"Action {i+1}: {step['action']}\n"
formatted += f"Action Input {i+1}: {step.get('action_input', '')}\n"
if 'observation' in step:
formatted += f"Observation {i+1}: {step['observation']}\n"
return formatted
# ========== 模拟工具和LLM ==========
def mock_llm(prompt):
"""
模拟一个大型语言模型。
在实际应用中,这里会调用 OpenAI API、DeepSeek API 或本地模型。
为了示例清晰,我们用一个基于规则的条件判断来模拟其行为。
"""
# 这是一个极其简化的模拟!真实LLM要复杂得多。
if "姚明" in prompt and "搜索" in prompt:
return """Thought: 用户想了解姚明的相关信息。我应该先搜索他的基本信息。
Action: search
Action Input: 姚明 简介"""
elif "Observation: 姚明" in prompt and "身高" in prompt:
return """Thought: 我拿到了姚明的简介。用户问他的身高是多少米,我需要从简介中找到身高信息。简介里说“身高2.26米”,这已经是米为单位了。所以我可以直接给出最终答案。
Action: Final Answer
Action Input: 姚明的身高是2.26米。"""
elif "计算" in prompt and "5的平方" in prompt:
return """Thought: 用户需要计算5的平方。我可以使用计算器工具。
Action: calculator
Action Input: 5 ** 2"""
elif "Observation: 25" in prompt:
return """Thought: 计算器返回结果是25。任务完成,可以给出最终答案。
Action: Final Answer
Action Input: 5的平方等于25。"""
else:
# 一个更通用的、鼓励使用工具的回应
return """Thought: 我需要分析这个问题。看起来用户问了一个我不知道确切答案的问题,也许可以尝试搜索一下。
Action: search
Action Input: 用户的问题"""
def search_tool(query):
"""模拟搜索工具。"""
# 模拟一个简单的知识库
knowledge_base = {
"姚明 简介": "姚明,前中国职业篮球运动员,司职中锋。1980年生于上海,身高2.26米,曾效力于NBA休斯顿火箭队,是中国篮球的旗帜性人物。",
"Python 是什么": "Python是一种高级、通用、解释型的编程语言,由吉多·范罗苏姆创造,设计哲学强调代码的可读性。",
}
return knowledge_base.get(query, f"未找到关于 '{query}' 的明确信息。")
def calculator_tool(expression):
"""模拟计算器工具。警告:实际使用中应对输入进行严格安全检查,此处为演示简化。"""
try:
# 使用 eval 极为危险!此处仅用于演示,绝对不要在生产环境中使用!
# 生产环境应使用 ast.literal_eval 或自己编写安全解析器。
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"计算表达式 '{expression}' 时出错: {e}"
# ========== 运行示例 ==========
if __name__ == "__main__":
# 1. 准备工具集
tools = {
"search": search_tool,
"calculator": calculator_tool,
}
# 2. 创建智能体,传入模拟的LLM和工具
agent = SimpleReActAgent(llm_client=mock_llm, tools=tools, max_steps=5)
# 3. 运行几个示例查询
print("="*50)
print("示例1: 查询事实信息")
answer1 = agent.run("姚明的身高是多少米?")
print(f"\n示例1最终结果: {answer1}")
print("\n" + "="*50)
agent.trajectory = [] # 重置轨迹
print("示例2: 计算问题")
answer2 = agent.run("请计算5的平方是多少?")
print(f"\n示例2最终结果: {answer2}")
运行上述代码,你会看到类似以下的输出:
==================================================
示例1: 查询事实信息
【任务开始】: 姚明的身高是多少米?
--- 步骤 1 ---
智能体原始响应:
Thought: 用户想了解姚明的相关信息。我应该先搜索他的基本信息。
Action: search
Action Input: 姚明 简介
执行行动: search(姚明 简介)
观察结果: 姚明,前中国职业篮球运动员,司职中锋。1980年生于上海,身高2.26米,曾效力于NBA休斯顿火箭队,是中国篮球的旗帜性人物。
--- 步骤 2 ---
智能体原始响应:
Thought: 我拿到了姚明的简介。用户问他的身高是多少米,我需要从简介中找到身高信息。简介里说“身高2.26米”,这已经是米为单位了。所以我可以直接给出最终答案。
Action: Final Answer
Action Input: 姚明的身高是2.26米。
【任务完成】最终答案: 姚明的身高是2.26米。
示例1最终结果: 姚明的身高是2.26米。
...
4. 特点与适用场景:
- 优点: 决策过程透明、可解释性强;通过思考步骤,能有效利用工具,处理多步任务;能根据观察结果动态调整计划。
- 缺点: 每一步都需要调用LLM生成思考,在工具调用本身不耗时的情况下,可能会增加延迟和成本;思考内容可能冗余。
- 适用场景: 需要明确推理过程的任务、探索性任务、以及工具调用序列不固定的场景。
2.2 Plan-and-Execute 框架详解
1. 核心理念:
“先谋后动”。这个框架将决策过程明确分为两个阶段:
- 规划阶段(Plan): 智能体(通常由一个专门的“规划者”LLM负责)根据任务目标,一次性生成一个完整的、分步骤的规划。这个规划类似于一个待办事项列表或流程图。
- 执行阶段(Execute): 另一个“执行者”模块(可以是另一个LLM,也可以是简单的程序)严格按照规划好的步骤,依次调用相应的工具来执行。执行者通常不需要再进行复杂的“思考”,只需按部就班地完成每个步骤,并将上一步的结果作为下一步的输入。
2. 工作原理:
- 接收任务: 智能体获得一个复杂任务。
- 制定规划: “规划者”分析任务,将其分解为一系列连续的、更简单的子任务,并指定每个子任务需要使用的工具或操作。规划可以是树状结构(如有分支判断),但常见的是线性列表。例如,对于任务“写一篇关于气候变化的报告并总结成三点”,规划可能是:
[1. 搜索‘气候变化最新研究’, 2. 根据搜索结果撰写报告草稿, 3. 从草稿中提取三个要点]。 - 按序执行: “执行者”遍历规划列表。对于每个子任务,执行者调用指定的工具,并将执行结果传递给下一个子任务。如果某一步失败,框架可能会触发重新规划或报错。
- 整合输出: 所有子任务执行完毕后,将最终结果整合返回给用户。
3. 与 ReAct 的对比:
- ReAct 是“边想边做”,规划是隐式的、在思考中动态生成的。适合路径不明确、需要探索的任务。
- Plan-and-Execute 是“先想好再做”,规划是显式的、预先制定的。适合目标明确、步骤相对清晰、可以预先分解的复杂任务。通常效率更高(规划只调用一次LLM),但灵活性稍差,难以应对规划时未预料到的执行失败。
4. 代码示例思路:
由于篇幅所限,此处不展开完整代码,但给出核心逻辑结构:
class PlanAndExecuteAgent:
def __init__(self, planner_llm, executor_llm, tools):
self.planner = planner_llm # 负责规划的LLM
self.executor = executor_llm # 负责执行的LLM或程序
self.tools = tools
def run(self, task):
# 阶段一:规划
plan_prompt = f"任务:{task}。请将任务分解为不超过5个清晰的步骤。每个步骤格式为:`步骤N: [动作描述] 使用工具:[工具名] 输入:[输入参数]`"
plan_text = self.planner(plan_prompt)
steps = self._parse_plan(plan_text) # 解析出步骤列表
# 阶段二:执行
context = {}
for i, step in enumerate(steps):
tool_name = step['tool']
tool_input = self._fill_template(step['input'], context) # 用之前的结果填充输入参数
result = self.toolstool_input
context[f"step_{i}_result"] = result # 存储结果供后续步骤使用
# 阶段三:汇总(可选)
final_answer = self._summarize(context, task)
return final_answer
5. 特点与适用场景:
- 优点: 对复杂任务分解能力强;执行过程高效,减少了LLM调用次数;规划可复用。
- 缺点: 依赖规划的质量,若初始规划有误或不符合实际情况,整个任务可能失败;灵活性较低,难以处理执行中的意外。
- 适用场景: 流程相对固定的复杂任务,如数据处理流水线、多步骤内容生成、遵循固定模式的自动化任务。
2.3 Self-Ask 框架详解
1. 核心理念:
Self-Ask 是 ReAct 的一种精简和特化形式,专门针对问答(QA) 场景,特别是需要多跳推理的问题。例如,“现任美国总统的夫人毕业于哪所大学?” 要回答这个问题,智能体需要先知道“现任美国总统是谁”(第一跳),然后再查询“这位总统的夫人的教育背景”(第二跳)。Self-Ask 的核心是让智能体学会“自我提问”来分解复杂问题。
2. 工作原理:
- 提出问题: 用户提出一个复杂的、需要多步推理的问题。
- 自我提问: 智能体(LLM)分析问题,识别出为了回答最终问题,首先需要回答什么中间问题。然后,它不直接去查最终答案,而是生成这个中间问题。格式通常是:
问题:[生成的中间问题]。 - 寻找答案: 智能体调用工具(通常是搜索工具)来寻找这个中间问题的答案。
- 整合与迭代: 获得中间答案后,智能体将其与原始问题结合,判断是否已能回答最终问题。如果不能,则重复步骤2,基于当前已知信息生成下一个中间问题,直到能推导出最终答案。
- 给出最终答案: 智能体输出最终答案。
3. 代码示例思路:
class SelfAskAgent:
def run(self, complex_question):
known_facts = []
for _ in range(self.max_hops):
# 根据原始问题和已知事实,判断是否需要/生成一个中间问题
prompt = f"原始问题:{complex_question}\n已知信息:{known_facts}\n是否需要问一个中间问题来帮助推理?如果需要,请以‘问题:’开头写出问题。如果可以直接回答,请以‘答案:’开头写出答案。"
llm_response = self.llm(prompt)
if llm_response.startswith("答案:"):
return llm_response[len("答案:"):]
elif llm_response.startswith("问题:"):
intermediate_q = llm_response[len("问题:"):]
# 调用搜索工具获取中间答案
intermediate_a = self.search_tool(intermediate_q)
known_facts.append(f"问:{intermediate_q} 答:{intermediate_a}")
else:
# 处理意外响应
break
return "无法通过多跳推理找到答案。"
4. 特点与适用场景:
- 优点: 专门为多跳问答优化,推理路径清晰;比通用ReAct更简洁,减少了冗余思考。
- 缺点: 适用范围较窄,主要针对问答;对LLM分解问题的能力要求高。
- 适用场景: 开放域多跳问答、需要事实链推理的查询。
三、项目实战
3.1 Agent 实现智能机器人
我们将综合运用上述概念,使用 LangChain 框架构建一个功能更完善的智能机器人 Agent。它将具备思考(ReAct模式)、使用多种工具、并维持对话记忆的能力。
项目目标: 创建一个可以通过自然语言与用户交互,并调用各种工具(如搜索、计算、查天气、读文件等)来完成任务的智能助手。
技术栈: Python, LangChain, OpenAI API (或开源LLM如DeepSeek, Qwen), DuckDuckGo 搜索。
步骤 1: 环境准备与工具定义
# 文件:smart_bot.py
import os
from datetime import datetime
import requests
from typing import Optional, Type
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import BaseTool, Tool
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
from langchain_community.utilities import DuckDuckGoSearchAPIWrapper
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_community.agent_toolkits import FileManagementToolkit
from langchain_community.tools.file_management import ReadFileTool
import math
# 设置你的API密钥(此处使用环境变量,更安全)
# os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
# 或者使用 DeepSeek
# from langchain_deepseek import ChatDeepSeek
# 1. 定义各种自定义工具
class CalculatorToolInput(BaseModel):
"""计算器工具的输入模式。"""
expression: str = Field(description="一个有效的数学表达式,例如:'3 + 4 * 2' 或 'sqrt(16)'")
class CalculatorTool(BaseTool):
"""一个安全的计算器工具。"""
name: str = "calculator"
description: str = "用于计算数学表达式。输入应为一个字符串格式的数学表达式。支持加减乘除(+,-,*,/)、乘方(**)、括号和常见函数如sqrt, sin, cos等。"
args_schema: Type[BaseModel] = CalculatorToolInput
def _run(self, expression: str) -> str:
"""执行计算。"""
# 安全评估:使用 math 函数和有限的操作符
allowed_names = {
k: v for k, v in math.__dict__.items() if not k.startswith("__")
}
allowed_names.update({
'abs': abs,
'round': round,
'pow': pow,
'max': max,
'min': min,
})
try:
# 警告:eval 在受限环境下使用。对于生产环境,考虑使用 ast.literal_eval 或专用库(如numexpr, sympy)
result = eval(expression, {"__builtins__": {}}, allowed_names)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {e}。请确保表达式 '{expression}' 是有效的。"
def _arun(self, expression: str):
raise NotImplementedError("此工具不支持异步")
class GetCurrentTimeTool(BaseTool):
"""获取当前时间的工具。"""
name: str = "get_current_time"
description: str = "获取当前的日期和时间。无需输入参数。"
def _run(self, tool_input: str = "") -> str: # tool_input 占位,实际不需要
now = datetime.now()
return f"当前日期和时间是: {now.strftime('%Y-%m-%d %H:%M:%S')}"
def _arun(self, tool_input: str):
raise NotImplementedError("此工具不支持异步")
def get_weather(city: str) -> str:
"""模拟一个天气查询函数。实际项目中应调用真实天气API。"""
# 模拟数据
weather_data = {
"北京": "晴,温度 5-15°C,北风2级。",
"上海": "多云,温度 10-18°C,东南风1级。",
"深圳": "阵雨,温度 20-25°C,南风3级。",
"纽约": "阴,温度 2-8°C,西风4级。",
"伦敦": "小雨,温度 3-9°C,西南风3级。",
}
return weather_data.get(city, f"抱歉,未找到{city}的天气信息。目前支持:{', '.join(weather_data.keys())}")
class WeatherToolInput(BaseModel):
city: str = Field(description="城市名称,例如:'北京','New York'")
class WeatherTool(BaseTool):
"""查询天气的工具。"""
name: str = "get_weather"
description: str = "查询指定城市的天气情况。输入为城市名称。"
args_schema: Type[BaseModel] = WeatherToolInput
def _run(self, city: str) -> str:
return get_weather(city)
def _arun(self, city: str):
raise NotImplementedError("此工具不支持异步")
# 步骤 2: 设置LLM、工具集和记忆
def create_smart_agent():
"""
创建并配置智能体。
"""
# 初始化LLM。这里使用 GPT-3.5-turbo 作为示例。你可以替换为其他模型。
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 如果想用 DeepSeek,可以这样(需先安装 langchain-deepseek):
# from langchain_deepseek import ChatDeepSeek
# llm = ChatDeepSeek(model="deepseek-chat", temperature=0)
# 初始化工具列表
search = DuckDuckGoSearchRun() # 使用 DuckDuckGo 进行网页搜索
calculator = CalculatorTool()
get_time = GetCurrentTimeTool()
weather = WeatherTool()
# 文件读取工具 (需要指定工作目录)
# file_toolkit = FileManagementToolkit(root_dir="./workspace")
# read_tool = file_toolkit.get_tools()[0] # 获取读取文件工具
tools = [search, calculator, get_time, weather] # 可以加入 read_tool
# 创建对话记忆,让智能体记得之前的对话
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# 步骤 3: 使用 LangChain 的 ReAct 智能体框架
# LangChain 提供了内置的 ReAct 提示词模板
from langchain import hub
# 从 LangChain Hub 拉取一个适合的 ReAct 提示词 (这是一个社区共享的模板)
prompt = hub.pull("hwchase17/react-chat")
# 你也可以自定义提示词:
# prompt = PromptTemplate.from_template("...")
# 创建智能体
agent = create_react_agent(llm, tools, prompt)
# 创建执行器,它将处理智能体与工具、记忆之间的交互循环
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=True, # 设置为 True 可以看到详细的思考过程!
handle_parsing_errors=True, # 优雅地处理解析错误
max_iterations=5, # 限制最大迭代次数,防止死循环
early_stopping_method="generate" # 当智能体输出最终答案时停止
)
return agent_executor
# 步骤 4: 运行交互式对话
if __name__ == "__main__":
print("="*60)
print("智能机器人启动中...")
print("我可以帮你:搜索网页、计算数学、查询天气、报时等。")
print("输入 '退出' 或 'quit' 来结束对话。")
print("="*60)
bot = create_smart_agent()
while True:
try:
user_input = input("\n你: ")
if user_input.lower() in ['退出', 'quit', 'exit']:
print("机器人: 再见!期待下次为你服务。")
break
if not user_input.strip():
continue
# 调用智能体执行任务
response = bot.invoke({"input": user_input, "chat_history": bot.memory.chat_memory.messages})
print(f"机器人: {response['output']}")
except KeyboardInterrupt:
print("\n\n会话被用户中断。")
break
except Exception as e:
print(f"抱歉,出错了: {e}")
运行说明:
- 安装依赖:
pip install langchain langchain-openai langchain-community duckduckgo-search - 设置你的
OPENAI_API_KEY环境变量,或修改代码使用其他模型(如 DeepSeek)。 - 运行脚本:
python smart_bot.py - 在对话中尝试输入:
- “现在几点了?”
- “计算一下 3 的 4 次方加上 5 的平方等于多少?”
- “搜索一下 LangChain 是什么?”
- “今天北京的天气怎么样?”
- “姚明有多高?”(这将触发搜索工具)
项目解析:
- 工具抽象: 我们定义了四种工具,每种工具都有清晰的名称、描述和输入模式。这有助于LLM理解何时以及如何使用它们。
- ReAct 智能体: 我们使用了 LangChain 的
create_react_agent函数,它封装了 ReAct 的提示词构建和输出解析逻辑。执行器AgentExecutor负责运行“思考-行动-观察”循环。 - 记忆:
ConversationBufferMemory使智能体能记住之前的对话内容,从而实现多轮连贯对话。 - 可观测性: 将
verbose=True可以打印出智能体内部的思考链(Thought)、行动(Action)和观察(Observation),这对于调试和理解其工作原理至关重要。
这个智能机器人已经具备了初步的“思考”和“行动”能力。你可以通过添加更多工具(如发送邮件、操作数据库、调用API等)来扩展它的能力。
四、本章练习题及其答案
4.1 选择题
- ReAct 框架的核心循环是?
A) 感知-规划-行动
B) 思考-行动-观察
C) 规划-执行-评估
D) 提问-搜索-回答
答案:B。ReAct 是 Reason(思考)-Act(行动)-Observe(观察) 的循环。
- 在 Plan-and-Execute 框架中,如果执行阶段某一步骤失败,最可能的处理方式是?
A) 立即放弃整个任务
B) 尝试重试当前步骤
C) 触发重新规划,调整后续步骤
D) 忽略错误,继续执行下一步
答案:C。Plan-and-Execute 的优势之一是在面对意外时,可以由规划者重新评估并生成新的计划。选项B也可能,但重新规划是更根本的解决策略。
- Self-Ask 框架主要优化了哪种类型的任务?
A) 创意写作
B) 多跳问答
C) 图像生成
D) 代码编译
答案:B。Self-Ask 通过让模型自我提问中间问题,专门解决需要多个事实推理步骤的问答。
- 在 LangChain 中创建 Agent 时,Tool 的
description字段的主要作用是?
A) 给工具函数写注释
B) 供 LLM 理解工具的功能和何时调用它
C) 作为工具的错误信息
D) 控制工具的调用权限
答案:B。
description是提供给 LLM 的提示,帮助它判断在什么情况下应该选择这个工具。
4.2 填空题
- Agent 决策模型是智能体的
决策中枢或大脑,它负责将感知转化为行动。 - 在 ReAct 的思考步骤中,LLM 输出的内容会被记录,形成可追溯的
推理链。 - Plan-and-Execute 框架中,通常由
规划者(Planner)LLM 负责生成任务分解步骤。 - 为了让智能体拥有多轮对话能力,我们需要为其添加
记忆(Memory)组件。
4.3 简答题
- 简述 ReAct 和 Plan-and-Execute 框架的主要区别及各自的优缺点。
答:
- 主要区别: ReAct 是“边想边做”,在循环中动态生成下一步的思考和行动;Plan-and-Execute 是“先谋后动”,在开始执行前就制定好完整的计划。
- ReAct 优点: 灵活,能根据执行结果动态调整,决策过程可解释。缺点: LLM调用频繁,可能增加延迟和成本,思考可能冗余。
- Plan-and-Execute 优点: 对复杂任务分解清晰,执行效率高(规划一次),计划可复用。缺点: 依赖初始规划质量,灵活性差,难以应对计划外的执行错误。
- **在实现一个工具(Tool)时,为什么定义清晰的
name和description非常重要?
答:
name是 LLM 在决定行动时选择的标识符。description则向 LLM 清晰地解释了该工具的功能、适用场景以及期望的输入格式。LLM 根据当前任务和上下文,结合所有可用工具的description
来判断哪个工具最合适。清晰准确的描述是智能体正确使用工具的关键。
4.4 实操题
题目: 扩展本章项目实战中的智能机器人,为其增加一个“待办事项(Todo List)管理”功能。需要实现两个新工具:
add_todo(item: str) -> str: 添加一个待办事项。list_todos() -> str: 列出所有当前的待办事项。
要求:
- 使用 Pydantic 定义
AddTodoToolInput模型。 - 将待办事项列表 (
todo_items) 作为智能体类的属性或全局变量进行管理。 - 修改
create_smart_agent函数,将这两个新工具加入到工具列表中。 - 测试你的机器人,输入“添加一个待办事项:购买 groceries”和“显示我的待办列表”。
参考答案(代码片段):
# ... 前面的导入和工具定义保持不变 ...
# 全局变量存储待办事项(简单起见,生产环境应用数据库)
TODO_ITEMS = []
class AddTodoToolInput(BaseModel):
item: str = Field(description="要添加的待办事项内容,例如:'写周报'")
class AddTodoTool(BaseTool):
name = "add_todo"
description = "添加一个新的待办事项到列表中。"
args_schema: Type[BaseModel] = AddTodoToolInput
def _run(self, item: str) -> str:
TODO_ITEMS.append(item)
return f"已添加待办事项: '{item}'。当前共有 {len(TODO_ITEMS)} 项。"
def _arun(self, item: str):
raise NotImplementedError("此工具不支持异步")
class ListTodosTool(BaseTool):
name = "list_todos"
description = "列出所有当前的待办事项。无需输入参数。"
def _run(self, tool_input: str = "") -> str:
if not TODO_ITEMS:
return "当前待办事项列表为空。"
list_str = "\n".join([f"{i+1}. {item}" for i, item in enumerate(TODO_ITEMS)])
return f"当前待办事项 ({len(TODO_ITEMS)} 项):\n{list_str}"
def _arun(self, tool_input: str):
raise NotImplementedError("此工具不支持异步")
def create_smart_agent():
# ... 之前的LLM、记忆初始化代码 ...
# 初始化工具列表
search = DuckDuckGoSearchRun()
calculator = CalculatorTool()
get_time = GetCurrentTimeTool()
weather = WeatherTool()
add_todo = AddTodoTool() # 新增
list_todos = ListTodosTool() # 新增
tools = [search, calculator, get_time, weather, add_todo, list_todos] # 加入新工具
# ... 后续创建agent和executor的代码不变 ...
测试对话示例:
你: 添加一个待办事项:购买 groceries
机器人: 已添加待办事项: '购买 groceries'。当前共有 1 项。
你: 再添加一个:准备技术分享PPT
机器人: 已添加待办事项: '准备技术分享PPT'。当前共有 2 项。
你: 显示我的待办列表
机器人: 当前待办事项 (2 项):
1. 购买 groceries
2. 准备技术分享PPT
五、总结
Agent 决策模型是赋予大语言模型“行动力”和“自主性”的关键。通过本文,我们系统性地探讨了三种核心范式:
- ReAct 框架 以其模仿人类“思考-行动”循环的直观性,成为构建可解释、灵活智能体的基石。它适用于需要探索和动态调整的开放式任务。
- Plan-and-Execute 框架 通过“先规划后执行”的解耦设计,为处理步骤明确、结构复杂的任务提供了高效、清晰的解决方案,尤其擅长工作流的自动化。
- Self-Ask 框架 则针对多跳推理问答这一特定场景进行了优化,通过引导模型“自我提问”来拆解复杂问题,展现了任务特化设计的价值。
在项目实战中,我们利用 LangChain 框架,快速构建了一个具备 ReAct 推理能力、并能调用搜索、计算、查天气等多种工具的智能机器人。这个过程清晰地展示了如何将理论框架落地:定义工具、构建提示、集成记忆、创建执行循环。通过添加“待办事项管理”工具的练习,你也实践了扩展智能体能力的基本方法。
展望未来,Agent
决策模型正朝着更高效(如减少LLM调用)、更鲁棒(如更好的错误处理和规划修正)、更复杂(如多智能体协作、长期记忆与反思)的方向演进。掌握这些核心决策模型,就如同掌握了驱动智能体思考和行动的“算法引擎”,是开发生成式AI时代强大应用不可或缺的技能。无论你是开发者、研究者还是爱好者,理解并能够实现这些模型,都将为你打开一扇通往构建更智能、更自主AI系统的大门。
🌟 感谢您耐心阅读到这里!
🚀 技术成长没有捷径,但每一次的阅读、思考和实践,都在默默缩短您与成功的距离。
💡 如果本文对您有所启发,欢迎点赞👍、收藏📌、分享📤给更多需要的伙伴!
🗣️ 期待在评论区看到您的想法、疑问或建议,我会认真回复,让我们共同探讨、一起进步~
🔔 关注我,持续获取更多干货内容!
🤗 我们下篇文章见!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)