使用 LangChain 构建 Agent

前面几章我们都是"手工"构建 Agent——自己写工具 Schema、自己管理消息循环、自己处理工具调用。这虽然有助于理解底层原理,但在实际项目中太繁琐了。LangChain 提供了标准化的工具接口和 AgentExecutor,大大简化了 Agent 开发。

LangChain 做的事情,本质上是把我们之前手写的那些"样板代码"封装成了可复用的组件:

  • 工具定义:不用再手写 JSON Schema,用 @tool 装饰器或继承 BaseTool 即可
  • Agent 创建:不用再自己写执行循环,create_openai_tools_agent 一行搞定
  • 执行管理AgentExecutor 自动处理工具调用循环、超时控制、错误恢复

LangChain Agent三层架构

LangChain 工具定义

LangChain 提供了两种定义工具的方式:

方式一:@tool 装饰器——最简单直接。你只需要写一个普通的 Python 函数,加上 @tool 装饰器,LangChain 会自动从函数签名和 docstring 中提取参数信息、生成 Schema。这个 docstring 会成为工具的描述——所以一定要写清楚。

方式二:继承 BaseTool——当你需要更复杂的逻辑时使用,比如工具需要维护内部状态、需要异步执行、或者需要自定义参数验证。

from langchain_core.tools import tool, BaseTool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
# ⚠️ 注意:AgentExecutor 是 LangChain 的 legacy Agent 方案。
# LangChain 官方推荐新项目使用 LangGraph 构建 Agent(见第12章)。
# 此处使用 AgentExecutor 是为了快速理解 Agent 概念。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import math

# ============================
# 方式1:@tool 装饰器(最简单)
# ============================

@tool
def calculate(expression: str) -> str:
    """
    计算数学表达式。
    支持基本运算和 math 模块函数(sqrt, sin, cos, log 等)。
    示例:calculate("sqrt(144) + 2 * 3")
    """
    try:
        safe_env = {k: getattr(math, k) for k in dir(math) if not k.startswith('_')}
        result = eval(expression, {"__builtins__": {}}, safe_env)
        return f"{expression} = {result}"
    except Exception as e:
        return f"计算错误:{e}"

@tool
def search_knowledge(query: str) -> str:
    """
    在知识库中搜索相关信息。
    适合查询产品信息、政策文档、FAQ等内部资料。
    """
    # 模拟知识库查询
    knowledge_base = {
        "退款": "退款政策:购买7天内可以申请退款,需要保留原始包装",
        "发货": "发货时间:工作日1-3天内发货,节假日顺延",
        "保修": "保修政策:硬件产品享有1年保修期",
    }
    
    for keyword, info in knowledge_base.items():
        if keyword in query:
            return info
    
    return "未在知识库中找到相关信息"

@tool  
def get_order_status(order_id: str) -> str:
    """
    查询订单状态。
    输入:订单编号(格式:ORD-XXXXXXXX)
    返回:订单的当前状态和物流信息
    """
    # 模拟订单查询
    mock_orders = {
        "ORD-12345678": "已发货,预计明天到达,快递单号:SF1234567890",
        "ORD-87654321": "处理中,预计明天发货",
    }
    
    return mock_orders.get(order_id, f"订单 {order_id} 不存在,请检查订单号是否正确")

# ============================
# 方式2:继承 BaseTool(更灵活)
# ============================

from typing import Type
from pydantic import BaseModel, Field

class WeatherInput(BaseModel):
    city: str = Field(description="城市名称")
    unit: str = Field(default="celsius", description="温度单位:celsius 或 fahrenheit")

class WeatherTool(BaseTool):
    name: str = "get_weather"
    description: str = "获取指定城市的当前天气信息"
    args_schema: Type[BaseModel] = WeatherInput
    
    def _run(self, city: str, unit: str = "celsius") -> str:
        """同步执行"""
        # 模拟天气数据
        weather = {"北京": 15, "上海": 20, "广州": 28}
        temp = weather.get(city, 18)
        
        if unit == "fahrenheit":
            temp = temp * 9/5 + 32
            unit_str = "°F"
        else:
            unit_str = "°C"
        
        return f"{city}:{temp}{unit_str},晴"
    
    async def _arun(self, city: str, unit: str = "celsius") -> str:
        """异步执行(可以调用异步API)"""
        return self._run(city, unit)  # 这里简单地调用同步版本

# ============================
# 创建 Agent
# ============================

tools = [calculate, search_knowledge, get_order_status, WeatherTool()]

# 系统提示
prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个智能客服助手。
    
你可以使用以下工具帮助用户:
- calculate:数学计算
- search_knowledge:查询产品政策和FAQ
- get_order_status:查询订单状态
- get_weather:获取天气

遇到需要工具的问题先使用工具,再给出回答。"""),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),  # Agent 推理空间
])

llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 创建 OpenAI Tools Agent
agent = create_openai_tools_agent(llm, tools, prompt)

# AgentExecutor:负责运行循环
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,  # 打印推理过程
    max_iterations=5,
    return_intermediate_steps=True  # 返回中间步骤
)

# ============================
# 使用 Agent
# ============================

# 单次对话
result = agent_executor.invoke({
    "input": "帮我查询订单 ORD-12345678 的状态,另外北京今天天气怎么样?",
    "chat_history": []
})
print(result["output"])

# 带历史的多轮对话
from langchain_core.messages import HumanMessage, AIMessage

chat_history = []

def chat_with_agent(user_message: str) -> str:
    result = agent_executor.invoke({
        "input": user_message,
        "chat_history": chat_history
    })
    
    # 更新历史
    chat_history.extend([
        HumanMessage(content=user_message),
        AIMessage(content=result["output"])
    ])
    
    return result["output"]

# 多轮对话测试
print(chat_with_agent("我想了解退款政策"))
print(chat_with_agent("如果我昨天买的,还能退吗?"))  # 应该记住上下文

上面代码中有几个关键概念值得理解:

  • MessagesPlaceholder("agent_scratchpad"):这是 Agent 的"推理空间"——LangChain 会把工具调用的中间步骤(调用了什么工具、得到什么结果)填充到这里,让模型能看到之前的推理过程
  • AgentExecutor:它是整个 Agent 循环的"驱动器"。设置 verbose=True 可以在终端看到完整的推理过程,方便调试
  • return_intermediate_steps=True:让我们能在最终结果中看到 Agent 经历了哪些中间步骤——调用了哪些工具、传了什么参数、得到什么结果

自定义 Agent 执行策略

在生产环境中,你需要对 Agent 的执行行为做更精细的控制。以下参数是最常用的:

  • max_iterations:防止 Agent 陷入无限循环(比如模型反复调用同一个工具)
  • max_execution_time:设置总超时时间,保护用户体验
  • handle_parsing_errors:当模型输出格式异常时自动恢复,而不是直接报错
  • early_stopping_method:超限时的停止策略——"generate" 会让模型根据当前信息生成一个尽可能好的回答
# 控制 Agent 的执行行为
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=10,          # 最大循环次数
    max_execution_time=30,      # 最大执行时间(秒)
    handle_parsing_errors=True, # 自动处理解析错误
    early_stopping_method="generate",  # 超限时如何停止
    verbose=True
)

小结

LangChain Agent 的关键组件:

  • @tool 装饰器:最快的工具定义方式
  • BaseTool:需要复杂逻辑时使用
  • create_openai_tools_agent:创建使用 OpenAI Function Calling 的 Agent
  • AgentExecutor:负责运行 Agent 循环,处理工具调用

下一节:11.4 LCEL:LangChain 表达式语言