课程0基础Agent开发课 / LangChain / LangChain-Agent完整实战-从工具定义到部署
— 21 min read

LangChain-Agent完整实战-从工具定义到部署

前几章分别讲了 Tools、Memory、Retriever、Callback 等组件,本章把它们组合起来,构建一个完整的、生产可用的 Agent。目标是搭建一个能查天气、搜索新闻、执行数学计算的助手,并覆盖工具定义、错误处理、调试和部署的全流程。

LangChain Agent 完整实战:从工具定义到部署

前几章分别讲了 Tools、Memory、Retriever、Callback 等组件,本章把它们组合起来,构建一个完整的、生产可用的 Agent。目标是搭建一个能查天气、搜索新闻、执行数学计算的助手,并覆盖工具定义、错误处理、调试和部署的全流程。

1. LangChain Agent 的完整构建流程

LangChain Agent完整架构图
LangChain Agent 完整架构 — 从工具定义到 API 服务部署

调用工具

weather

search

calculator

最终回答

需要更多工具

用户输入

AgentExecutor

ChatPromptTemplate
含历史+工具描述

LLM
推理决策

ToolCall
工具名+参数

路由到对应工具

天气查询工具

新闻搜索工具

计算器工具

ToolMessage
工具返回结果

AIMessage
返回给用户

2. create_tool_calling_agent vs create_react_agent

LangChain 提供两种主流 Agent 创建方式:

方式 适用模型 工具调用格式 推荐程度
create_react_agent 所有模型(包括不支持 function calling 的) 文本格式(Action/Observation) 兼容性好,但容易解析出错
create_tool_calling_agent 支持 function calling(函数调用:LLM 原生支持调用外部工具的能力,模型会输出结构化 JSON 指定要调用哪个函数和参数)的模型(GPT-4、Claude、Gemini、DeepSeek) 结构化 JSON(原生 API 支持) 推荐,稳定可靠

优先用 create_tool_calling_agent。它依赖模型原生的 function calling 能力,工具调用通过结构化 JSON 传递,不依赖模型能否正确格式化文本,解析错误率极低。

3. 工具定义

3.1. 使用 @tool 装饰器

最简单的工具定义方式,函数的 docstring 就是工具的描述(模型靠这个理解何时调用这个工具):

python
import os
import json
import math
import requests
from typing import Optional
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """
    查询指定城市的当前天气信息。
    当用户询问某个城市的天气、温度、是否下雨时使用此工具。

    Args:
        city: 城市名称,支持中文(如"北京")或英文(如"Beijing")
    """
    # 这里使用 OpenWeatherMap 免费 API,需要注册获取 API_KEY
    api_key = os.getenv("OPENWEATHER_API_KEY")
    if not api_key:
        # 开发测试时的模拟数据,避免依赖真实 API
        return json.dumps({
            "city": city,
            "temperature": 22,
            "condition": "晴天",
            "humidity": 65,
            "wind_speed": "3m/s",
        }, ensure_ascii=False)

    url = f"http://api.openweathermap.org/data/2.5/weather"
    params = {"q": city, "appid": api_key, "units": "metric", "lang": "zh_cn"}

    try:
        response = requests.get(url, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()
        return json.dumps({
            "city": city,
            "temperature": round(data["main"]["temp"]),
            "condition": data["weather"][0]["description"],
            "humidity": data["main"]["humidity"],
            "wind_speed": f"{data['wind']['speed']}m/s",
        }, ensure_ascii=False)
    except requests.RequestException as e:
        # 工具函数返回错误描述,而不是抛出异常
        # 让 Agent 知道工具失败了,可以选择重试或给用户解释
        return f"天气查询失败:{str(e)}。请检查城市名称是否正确。"


@tool
def search_news(query: str, max_results: int = 3) -> str:
    """
    搜索最新新闻。当用户询问最近发生的事件、新闻或实时信息时使用此工具。
    注意:此工具返回的是近期新闻,不适合查询历史事实。

    Args:
        query: 搜索关键词,尽量用中文关键词
        max_results: 返回的最大新闻条数,默认3条
    """
    # 实际项目中接入 Serper API、Bing News API 等
    # 这里使用模拟数据演示结构
    mock_news = [
        {"title": f"关于'{query}'的最新进展", "source": "科技日报", "summary": f"{query}领域近期有重要突破..."},
        {"title": f"{query}行业分析报告", "source": "财经网", "summary": f"专家分析{query}未来发展趋势..."},
        {"title": f"深度解读:{query}", "source": "36kr", "summary": f"本文将深入探讨{query}的技术细节..."},
    ]

    results = mock_news[:max_results]
    return json.dumps(results, ensure_ascii=False, indent=2)


@tool
def calculate(expression: str) -> str:
    """
    执行数学计算。支持加减乘除、幂运算、三角函数、对数等。
    当用户需要计算数字结果时使用此工具。

    支持的运算示例:
    - 基本运算:"2 + 3 * 4"
    - 幂运算:"2 ** 10"
    - 数学函数:"math.sqrt(144)"、"math.log(100, 10)"

    Args:
        expression: 数学表达式字符串
    """
    # 限制可用的命名空间,防止代码注入
    # 只允许 math 模块和基本数字类型,禁止 __import__、open 等危险操作
    safe_globals = {
        "__builtins__": {},  # 禁用所有内置函数
        "math": math,
        "abs": abs,
        "round": round,
        "int": int,
        "float": float,
    }

    try:
        result = eval(expression, safe_globals)
        return f"计算结果:{expression} = {result}"
    except ZeroDivisionError:
        return "计算失败:除数不能为零"
    except (SyntaxError, NameError) as e:
        return f"计算失败:表达式格式错误 - {str(e)}"
    except Exception as e:
        return f"计算失败:{str(e)}"

3.2. 使用 StructuredTool 定义复杂工具

当工具参数较多、需要参数验证时,用 Pydantic 模型定义参数结构更合适:

python
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field

class TranslateInput(BaseModel):
    text: str = Field(description="需要翻译的原文")
    target_language: str = Field(
        description="目标语言,使用语言代码:zh(中文)、en(英文)、ja(日文)、ko(韩文)",
        default="zh"
    )
    formal: bool = Field(description="是否使用正式语气", default=False)

def translate_text(text: str, target_language: str = "zh", formal: bool = False) -> str:
    """调用翻译服务(实际项目中接入 DeepL 或 Google Translate API)"""
    # 模拟翻译
    style = "正式" if formal else "口语"
    return f"[模拟翻译 -> {target_language} ({style}风格)] {text}"

translate_tool = StructuredTool.from_function(
    func=translate_text,
    name="translate",
    description="翻译文本到指定语言。当用户需要翻译文字时使用此工具。",
    args_schema=TranslateInput,
)

4. 构建完整 Agent

python
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.messages import HumanMessage, AIMessage

# 初始化 LLM
llm = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
    temperature=0,  # Agent 场景用 temperature=0,推理更稳定
)

# 工具列表
tools = [get_weather, search_news, calculate]

# Agent Prompt:必须包含 agent_scratchpad 占位符,Agent 用它存放工具调用的中间结果
prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个功能全面的智能助手,可以查询天气、搜索新闻和进行数学计算。

工作原则:
1. 需要实时信息(天气、新闻)时,必须调用相应工具,不要凭记忆回答
2. 数学计算统一使用计算器工具,避免心算出错
3. 工具调用失败时,向用户说明情况并提供替代帮助
4. 回答要简洁,重点突出"""),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),  # 必须包含,存放 ReAct 中间步骤
])

# 创建 Agent(注意:create_tool_calling_agent 返回的是 Runnable,不是 AgentExecutor)
agent = create_tool_calling_agent(llm, tools, prompt)

# AgentExecutor 是 Agent 的运行容器,负责循环执行工具调用直到得出最终答案
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=5,          # 最多调用工具 5 次,防止无限循环
    handle_parsing_errors=True, # 工具调用解析失败时自动恢复,而不是抛异常
    verbose=True,               # 开发时开启,查看推理过程
    return_intermediate_steps=False,  # 生产环境不需要返回中间步骤
)

5. 工具错误处理:Agent 如何恢复

工具失败是常见情况,handle_parsing_errors=True 只处理 Agent 输出格式解析错误,实际工具执行失败需要在工具函数内部处理。

更健壮的做法是给工具加上重试机制和降级逻辑:

python
from functools import wraps
import time
from typing import Callable

def with_retry(max_retries: int = 2, delay_seconds: float = 1.0):
    """工具重试装饰器:网络类工具失败时自动重试"""
    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries:
                        # 最后一次失败,返回错误描述(不抛出异常)
                        return f"工具执行失败(已重试 {max_retries} 次):{str(e)}"
                    time.sleep(delay_seconds * (attempt + 1))  # 指数退避
        return wrapper
    return decorator

# 对网络工具加重试
@tool
@with_retry(max_retries=2, delay_seconds=1.0)
def get_weather_with_retry(city: str) -> str:
    """查询天气(带重试机制)"""
    # 函数体同前面的 get_weather
    ...

6. AgentExecutor 关键配置

python
# 完整配置示例
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,

    # 防止工具调用死循环(重要!)
    max_iterations=10,

    # 按执行时间限制(秒),适合有 SLA(Service Level Agreement,服务级别协议:约定系统响应时间上限)要求的生产环境
    max_execution_time=30.0,

    # 工具调用解析失败时的行为
    # True:把错误信息传给模型让它重试
    # 字符串:直接返回该字符串作为最终答案
    handle_parsing_errors="我遇到了一些问题,请重新描述您的需求。",

    # 开发调试用,生产环境设为 False
    verbose=False,

    # 是否在结果中包含中间步骤(工具调用记录)
    # 设为 True 方便调试,生产环境通常 False
    return_intermediate_steps=True,
)

7. 完整对话实战

python
def run_agent_conversation():
    """演示多轮对话,Agent 保持历史上下文"""
    chat_history = []

    test_queries = [
        "北京今天天气怎么样?",
        "如果我要从北京飞上海,飞行距离大约1200公里,假设飞机时速900km/h,飞行时间是多少分钟?",
        "最近有什么关于人工智能的新闻?",
        "综合前面查到的信息,给我一个去上海出行的建议",  # 需要记忆前面的对话
    ]

    for query in test_queries:
        print(f"\n{'='*50}")
        print(f"用户:{query}")

        result = agent_executor.invoke({
            "input": query,
            "chat_history": chat_history,
        })

        response = result["output"]
        print(f"Agent:{response}")

        # 更新历史,保持多轮对话上下文
        chat_history.append(HumanMessage(content=query))
        chat_history.append(AIMessage(content=response))

        # 只保留最近 10 轮历史,避免 context 过长
        if len(chat_history) > 20:
            chat_history = chat_history[-20:]


if __name__ == "__main__":
    run_agent_conversation()

8. 调试技巧:verbose=True 查看推理过程

开启 verbose=True 后,AgentExecutor 会打印完整的推理过程:

code
> Entering new AgentExecutor chain...

调用工具:get_weather
参数:{"city": "北京"}
返回:{"city": "北京", "temperature": 22, "condition": "晴天", "humidity": 65}

调用工具:calculate
参数:{"expression": "1200 / 900 * 60"}
返回:计算结果:1200 / 900 * 60 = 80.0

最终答案:北京今天晴天,气温22℃,湿度65%,适合出行。
北京到上海飞行时间约80分钟。

> Finished chain.

除了 verbose,还可以用 Callback 获取更结构化的调试信息:

python
from langchain_core.callbacks import BaseCallbackHandler
from typing import Any, Dict, List, Union

class AgentDebugCallback(BaseCallbackHandler):
    """记录 Agent 的每一步操作,方便调试和监控"""

    def on_tool_start(self, serialized: Dict, input_str: str, **kwargs):
        tool_name = serialized.get("name", "unknown")
        print(f"[DEBUG] 调用工具:{tool_name},参数:{input_str[:100]}")

    def on_tool_end(self, output: str, **kwargs):
        print(f"[DEBUG] 工具返回:{output[:100]}")

    def on_tool_error(self, error: Exception, **kwargs):
        print(f"[ERROR] 工具出错:{error}")

    def on_agent_action(self, action, **kwargs):
        print(f"[DEBUG] Agent 决策:调用 {action.tool}")

    def on_agent_finish(self, finish, **kwargs):
        print(f"[DEBUG] Agent 完成,最终答案长度:{len(finish.return_values.get('output', ''))}")


# 注册 Callback
debug_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=5,
    handle_parsing_errors=True,
    callbacks=[AgentDebugCallback()],
)

9. 封装为 API 服务

实际部署时,Agent 通常以 HTTP API 的形式对外提供服务。以下示例使用 FastAPI(Python 的高性能 Web 框架,用于快速构建 HTTP 接口)和 uvicorn(异步 Web 服务器,负责运行 FastAPI 应用):

python
# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import uvicorn

app = FastAPI(title="AI Agent API", version="1.0.0")

class ChatRequest(BaseModel):
    message: str
    session_id: str = "default"
    history: Optional[List[dict]] = []

class ChatResponse(BaseModel):
    answer: str
    session_id: str

# 会话历史存储(生产环境用 Redis 存储)
session_store: dict = {}

@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    try:
        # 从存储恢复会话历史
        history = session_store.get(request.session_id, [])

        # 执行 Agent
        result = agent_executor.invoke({
            "input": request.message,
            "chat_history": history,
        })

        answer = result["output"]

        # 更新会话历史
        history.append(HumanMessage(content=request.message))
        history.append(AIMessage(content=answer))
        # 保留最近 20 条消息,控制内存使用
        session_store[request.session_id] = history[-20:]

        return ChatResponse(answer=answer, session_id=request.session_id)

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Agent 执行失败:{str(e)}")

@app.delete("/session/{session_id}")
async def clear_session(session_id: str):
    """清除会话历史"""
    session_store.pop(session_id, None)
    return {"message": f"会话 {session_id} 已清除"}

@app.get("/health")
async def health_check():
    return {"status": "healthy"}


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

启动服务:

bash
pip install fastapi uvicorn

# 开发模式,支持热重载
uvicorn app:app --reload --port 8000

# 测试
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "北京今天天气怎么样?", "session_id": "user_001"}'

10. Agent 执行完整流程

工具LLM (DeepSeek)AgentExecutor用户工具LLM (DeepSeek)AgentExecutor用户循环直到 LLM 不再调用工具发送消息Prompt(含系统提示+历史+工具描述)决定调用工具(ToolCall JSON)执行工具返回工具结果把工具结果作为 ToolMessage 返回继续推理(可能再次调用工具)最终回答(AIMessage)返回最终答案

11. 小结

一个生产可用的 LangChain Agent 需要关注的要点:

  1. 优先使用 create_tool_calling_agent,稳定性远高于 ReAct 文本格式
  2. 工具函数返回错误描述而不是抛异常,让 Agent 能够感知失败并恢复
  3. 设置 max_iterationsmax_execution_time,防止工具循环调用导致的超时和费用失控
  4. 开发时开 verbose=True,生产时关掉,改用结构化 Callback 记录日志
  5. 会话历史限制长度,避免 context 越来越长导致性能下降和费用增加

LangChain 的核心组件——LCEL、Memory、Tools、OutputParser、Retriever、Callback、DocumentLoader、TextSplitter、Agent——到这里都讲完了。接下来可以找一个真实项目来用,理解会比单独读文档快得多。

本页目录