在这里插入图片描述

一、前言

在人工智能飞速发展的今天,大型语言模型(LLM)已展现出惊人的内容生成和理解能力。然而,一个更根本的问题摆在面前:如何让这些模型不仅“能说”,更能“会做”?如何让它们能够主动感知环境、制定计划、执行行动,并持续优化决策?这正是智能体(Agent)技术要解决的核心问题。

Agent 决策模型 是智能体的“大脑”和“决策中枢”。如果说大型语言模型提供了丰富的知识储备和推理基础,那么决策模型则提供了运用这些能力的“方法论”。它决定了智能体如何理解任务、如何规划步骤、如何选择工具,以及如何从结果中学习。无论是构建能够自动编写和调试代码的编程助手,还是打造能够理解用户复杂指令并操作软件的数字员工,亦或是开发能够进行长期规划和科学发现的AI科学家,其核心都在于一个高效、鲁棒的决策模型。

本文将深入浅出地剖析 Agent 决策模型的三种核心范式:经典的 ReAct 框架、擅长复杂任务分解的 Plan-and-Execute 框架,以及追求效率与简洁的 Self-Ask 框架。我们将不仅探讨其背后的设计哲学与工作原理,更将通过一个完整的“实现智能机器人”项目实战,手把手带你用代码实现一个具备思考和行动能力的智能体。文章包含详尽的代码注释和可直接运行的示例,并附有练习题供你巩固知识。

我们的目标是,让你不仅能掌握 Agent 决策的理论,更能获得将其付诸实践的能力,从而开启构建下一代智能应用的大门。


二、Agent 决策模型

智能体(Agent)的核心是感知-思考-行动的循环。决策模型就是这个循环中“思考”部分的具体实现,它定义了智能体如何将输入(观察、目标、历史)转化为下一步的行动(调用工具、给出最终答案)。下面,我们详细解析三种主流的决策框架。

2.1 ReAct 框架详解

1. 核心理念:
ReAct 是 Reason(思考)Act(行动) 的结合。其核心思想是模仿人类解决问题的方式:我们通常不会盲目尝试,而是先思考一下当前情况、需要做什么、为什么这么做,然后再采取具体的行动,并根据行动结果调整后续的思考。这个“思考-行动-观察-再思考”的循环,使得智能体能够进行有依据的、可解释的决策,并处理需要多步工具调用的任务。

2. 工作原理:

  1. 思考(Reason): 智能体分析当前的任务、已有的历史信息(之前的思考、行动、观察)以及最终目标,然后推理出下一步应该做什么,以及为什么要这样做。思考内容会被记录,形成一个内在的、可追溯的推理链。
  2. 行动(Act): 基于上一步的思考,智能体决定执行一个具体的动作。这通常表现为调用一个可用的工具(Tool),并传入相应的参数。例如,Search[“2025年诺贝尔物理学奖得主”]
  3. 观察(Observe): 工具执行后返回结果。这个结果(可能是成功的数据,也可能是错误信息)被智能体“观察”到,并作为新的上下文信息。
  4. 循环: 将“观察”到的结果纳入历史,智能体开始新一轮的“思考”,判断任务是否完成,若未完成则规划下一步行动。如此循环,直至任务完成或达到步骤限制。

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. 制定规划: “规划者”分析任务,将其分解为一系列连续的、更简单的子任务,并指定每个子任务需要使用的工具或操作。规划可以是树状结构(如有分支判断),但常见的是线性列表。例如,对于任务“写一篇关于气候变化的报告并总结成三点”,规划可能是:[1. 搜索‘气候变化最新研究’, 2. 根据搜索结果撰写报告草稿, 3. 从草稿中提取三个要点]
  3. 按序执行: “执行者”遍历规划列表。对于每个子任务,执行者调用指定的工具,并将执行结果传递给下一个子任务。如果某一步失败,框架可能会触发重新规划或报错。
  4. 整合输出: 所有子任务执行完毕后,将最终结果整合返回给用户。

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. 工作原理:

  1. 提出问题: 用户提出一个复杂的、需要多步推理的问题。
  2. 自我提问: 智能体(LLM)分析问题,识别出为了回答最终问题,首先需要回答什么中间问题。然后,它不直接去查最终答案,而是生成这个中间问题。格式通常是:问题:[生成的中间问题]
  3. 寻找答案: 智能体调用工具(通常是搜索工具)来寻找这个中间问题的答案。
  4. 整合与迭代: 获得中间答案后,智能体将其与原始问题结合,判断是否已能回答最终问题。如果不能,则重复步骤2,基于当前已知信息生成下一个中间问题,直到能推导出最终答案。
  5. 给出最终答案: 智能体输出最终答案。

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}")

运行说明:

  1. 安装依赖:pip install langchain langchain-openai langchain-community duckduckgo-search
  2. 设置你的 OPENAI_API_KEY 环境变量,或修改代码使用其他模型(如 DeepSeek)。
  3. 运行脚本:python smart_bot.py
  4. 在对话中尝试输入:
    • “现在几点了?”
    • “计算一下 3 的 4 次方加上 5 的平方等于多少?”
    • “搜索一下 LangChain 是什么?”
    • “今天北京的天气怎么样?”
    • “姚明有多高?”(这将触发搜索工具)

项目解析:

  • 工具抽象: 我们定义了四种工具,每种工具都有清晰的名称、描述和输入模式。这有助于LLM理解何时以及如何使用它们。
  • ReAct 智能体: 我们使用了 LangChain 的 create_react_agent 函数,它封装了 ReAct 的提示词构建和输出解析逻辑。执行器 AgentExecutor 负责运行“思考-行动-观察”循环。
  • 记忆: ConversationBufferMemory 使智能体能记住之前的对话内容,从而实现多轮连贯对话。
  • 可观测性:verbose=True 可以打印出智能体内部的思考链(Thought)、行动(Action)和观察(Observation),这对于调试和理解其工作原理至关重要。

这个智能机器人已经具备了初步的“思考”和“行动”能力。你可以通过添加更多工具(如发送邮件、操作数据库、调用API等)来扩展它的能力。


四、本章练习题及其答案

4.1 选择题

  1. ReAct 框架的核心循环是?
    A) 感知-规划-行动
    B) 思考-行动-观察
    C) 规划-执行-评估
    D) 提问-搜索-回答

答案:B。ReAct 是 Reason(思考)-Act(行动)-Observe(观察) 的循环。

  1. 在 Plan-and-Execute 框架中,如果执行阶段某一步骤失败,最可能的处理方式是?
    A) 立即放弃整个任务
    B) 尝试重试当前步骤
    C) 触发重新规划,调整后续步骤
    D) 忽略错误,继续执行下一步

答案:C。Plan-and-Execute 的优势之一是在面对意外时,可以由规划者重新评估并生成新的计划。选项B也可能,但重新规划是更根本的解决策略。

  1. Self-Ask 框架主要优化了哪种类型的任务?
    A) 创意写作
    B) 多跳问答
    C) 图像生成
    D) 代码编译

答案:B。Self-Ask 通过让模型自我提问中间问题,专门解决需要多个事实推理步骤的问答。

  1. 在 LangChain 中创建 Agent 时,Tool 的 description 字段的主要作用是?
    A) 给工具函数写注释
    B) 供 LLM 理解工具的功能和何时调用它
    C) 作为工具的错误信息
    D) 控制工具的调用权限

答案:Bdescription 是提供给 LLM 的提示,帮助它判断在什么情况下应该选择这个工具。

4.2 填空题

  1. Agent 决策模型是智能体的 决策中枢大脑,它负责将感知转化为行动。
  2. 在 ReAct 的思考步骤中,LLM 输出的内容会被记录,形成可追溯的 推理链
  3. Plan-and-Execute 框架中,通常由 规划者(Planner) LLM 负责生成任务分解步骤。
  4. 为了让智能体拥有多轮对话能力,我们需要为其添加 记忆(Memory) 组件。

4.3 简答题

  1. 简述 ReAct 和 Plan-and-Execute 框架的主要区别及各自的优缺点。

答:

  • 主要区别: ReAct 是“边想边做”,在循环中动态生成下一步的思考和行动;Plan-and-Execute 是“先谋后动”,在开始执行前就制定好完整的计划。
  • ReAct 优点: 灵活,能根据执行结果动态调整,决策过程可解释。缺点: LLM调用频繁,可能增加延迟和成本,思考可能冗余。
  • Plan-and-Execute 优点: 对复杂任务分解清晰,执行效率高(规划一次),计划可复用。缺点: 依赖初始规划质量,灵活性差,难以应对计划外的执行错误。
  1. **在实现一个工具(Tool)时,为什么定义清晰的 namedescription 非常重要?

答: name 是 LLM 在决定行动时选择的标识符。description 则向 LLM 清晰地解释了该工具的功能、适用场景以及期望的输入格式。LLM 根据当前任务和上下文,结合所有可用工具的 description
来判断哪个工具最合适。清晰准确的描述是智能体正确使用工具的关键。

4.4 实操题

题目: 扩展本章项目实战中的智能机器人,为其增加一个“待办事项(Todo List)管理”功能。需要实现两个新工具:

  1. add_todo(item: str) -> str: 添加一个待办事项。
  2. 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系统的大门。


🌟 感谢您耐心阅读到这里!
🚀 技术成长没有捷径,但每一次的阅读、思考和实践,都在默默缩短您与成功的距离。
💡 如果本文对您有所启发,欢迎点赞👍、收藏📌、分享📤给更多需要的伙伴!
🗣️ 期待在评论区看到您的想法、疑问或建议,我会认真回复,让我们共同探讨、一起进步~
🔔 关注我,持续获取更多干货内容!
🤗 我们下篇文章见!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐