LangChain从入门到精通

LangChain从入门到精通

LangChain 是一个用于构建大语言模型(LLM)应用的开源框架,其核心目标是简化将 LLM 与外部数据、工具、记忆系统结合的过程,让开发者能像"搭积木"一样构建智能应用。于2022年11月第一次发布,但是一直到2025年10月之前风评其实好坏参板,俱是因为api变更太过频繁,导致今天的代码可能明天就运行不了,但是呢当时可选的大模型框架又不多。所以很多人也是骂着用着。真正转机是2025年10月,LangChain发不了1.0版本,这才进入稳定器。此后的1.1,1.2等版本就改动不是特别大了,且官方明确表示2.0之前不会做大规模改动,也不会像之前那种删除API的操作。目前在agent领域LangChain依然是使用者较多。

小结:对比 LangChain v0.3 与 LangChain v1.2

维度 LangChain v0.3 LangChain v1.2
核心架构与设计理念 过渡性版本,设计上以链(Chain)为核心。 生产级稳定版本,标志着从"链式调用"到"智能体框架"的范式转变。
Agent 构建方式 依赖 initialize_agent 等旧版 API,基于 AgentExecutor 硬编码。 官方推荐使用 create_agent 作为 Agent 的标准构建入口,底层基于 LangGraph。
工具定义 通过 @tool 装饰器和 Tool 类定义,类型安全性和参数验证能力较弱。 支持通过 Pydantic Schema 定义工具,类型安全,参数定义更清晰。
结构化输出 主要依赖 JSON Parser 和正则表达式,稳定性较差。 Structured Output 成为一等公民,直接绑定 Pydantic 类,由模型底层保证输出格式稳定性。
输出解析 输出为纯文本,需通过正则匹配等方式解析内容,较为繁琐且易出错。 引入标准化的 content_blocks,将模型输出(推理、文本、工具调用)统一为标准对象,无需再手动解析。
扩展机制 缺乏系统性的扩展方式,修改逻辑通常需要修改源码或提示词。 引入功能强大的 Middleware(中间件)系统,允许在模型调用、工具执行等各生命周期进行拦截和逻辑扩展。
多模态支持 支持不完善,无法无缝集成图像、音频等多模态数据。 完善了多模态适配,可轻松实现多模态对话、多模态 RAG 等功能。
异步执行性能 异步执行性能一般。 异步执行性能得到优化,据称响应速度提升 30% 以上。
包结构与依赖 包结构相对混乱,各模块耦合度较高。langchain 包为直接依赖。内部从 Pydantic v1 升级到 v2,对依赖版本要求更严格。 包结构清晰,主 langchain 包保持轻量,旧功能(如 Chains)被迁移至 langchain-classic 包。@langchain/core 作为对等依赖,版本管理更灵活。
Python 版本要求 要求 Python >= 3.9(停止支持 Python 3.8)。 要求 Python >= 3.10。
推荐用途 适用于维护依赖 v0.3 的老旧项目,不推荐用于新项目开发。 是所有新项目和学习 LangChain 的首选版本。官方承诺 1.x 版本系列不会引入破坏性变更,保证了长期稳定性。

核心功能如下:

  • ChatModel:统一的模型接口,通过 init_chat_model 抹平不同提供商(OpenAI、Anthropic、Google 等)的差异
  • Prompt:提示词模板与管理,支持动态构建和少量示例学习。
  • Tool:工具系统,通过 @tool 装饰器将外部功能(如搜索、计算)封装为 Agent 可调用的工具。
  • RAG 组件:包括文档加载器(Loader)、文本分割器(Splitter)、嵌入模型(Embedding)和检索器(Retriever),是实现检索增强生成的基础。
  • LCEL:LangChain Expression Language,一种声明式的管道语法,使用 | 操作符连接组件,让工作流构建更简洁。
  • Middleware:中间件类似AOP的概念,用于对特定场景进行增强,也可以自己编写
  • Memory 记忆机制 在多次调用之间持久化状态,实现对话的上下文记忆
  • Retrievers 数据连接器与检索器 负责加载、转换、存储和检索外部数据,是实现 RAG(检索增强生成) 的关键
  • Chains 链式调用逻辑 将多个 LLM 调用或其他组件的调用串联起来,形成连贯的处理流程

一、前置准备

1.前置工作

初始化一个conda项目

bash 复制代码
# 创建
create --name langchain-study python=3.13.12
# 初始化虚拟环境(执行完此指令,重新启动命令行窗口)
conda init
# 切换项目
conda activate langchain-study

安装langchain包,他们区别是conda对版本要求适配更高,pip相对宽松,比如下面这个命令conda执行不了,他要求python版本要低于3.13才行,但是pip可以。

bash 复制代码
conda install langchain==1.2.12
# 或者使用pip一样
pip install langchain==1.2.12

使用pycharm打开刚刚创建的conda环境创建项目即可

安装全部依赖:

txt 复制代码
# =========================================================
# LangChain 核心框架
# 作用:LangChain 1.x 主体、核心抽象、社区组件、实验性组件、文本切分器
# =========================================================
langchain==1.2.12
langchain-core==1.2.18
langchain-community==0.4.1
langchain-classic==1.0.2
langchain-text-splitters==1.1.1
langchain-experimental==0.4.1


# =========================================================
# jupyter
# 作用:交互式编程记事本,默认安装最新版即可
# =========================================================
jupyter


# =========================================================
# LangGraph / Agent 编排
# 作用:构建 Agent、状态图、多步骤工作流、检查点、PostgreSQL 持久化
# =========================================================
langgraph==1.1.2
langgraph-prebuilt==1.0.8
langgraph-checkpoint==4.0.1
langgraph-checkpoint-postgres==3.0.5
langgraph-sdk==0.3.9

# LangGraph 本地 Agent Server、Studio、热重载
langgraph-cli[inmem]==0.4.30

# =========================================================
# MCP / FastMCP 集成
# 作用:构建 MCP Server、连接 MCP 工具、资源、Prompt
# =========================================================
mcp==1.27.0
fastmcp==3.2.4
langchain-mcp-adapters==0.2.1


# =========================================================
# 大模型供应商与 LangChain 适配器
# 作用:接入 OpenAI 协议、DeepSeek、Anthropic、OpenRouter、通义千问、腾讯等模型
# =========================================================
openai==2.26.0
anthropic==0.84.0
langchain-openai==1.1.11
langchain-deepseek==1.0.1
langchain-anthropic==1.3.4
langchain-openrouter==0.1.0
openrouter==0.7.11
dashscope==1.25.6
tencentcloud-sdk-python==3.1.86
PyJWT==2.10.1


# =========================================================
# 搜索工具 / 外部工具集成
# 作用:接入 Tavily 等联网搜索工具
# =========================================================
langchain-tavily==0.2.17


# =========================================================
# Web 服务 / API / SSE
# 作用:MCP HTTP 服务、FastAPI 服务、SSE 流式通信、本地 API 服务
# =========================================================
fastapi==0.135.1
uvicorn==0.46.0
sse-starlette==3.3.4
httpx==0.28.1
httpx-sse==0.4.3
aiohttp==3.12.14
requests==2.32.5
requests-toolbelt==1.0.0
websockets==16.0
watchfiles==1.1.1


# =========================================================
# 配置管理 / 数据校验 / 序列化
# 作用:读取 .env、Pydantic 配置、JSON/YAML、结构化输出
# =========================================================
python-dotenv==1.2.1
pydantic==2.12.5
pydantic-settings==2.12.0
PyYAML==6.0.3
orjson==3.11.7
jsonschema==4.26.0
jsonref==1.1.0
dataclasses-json==0.6.7


# =========================================================
# 日志 / 命令行 / 调试辅助
# 作用:日志输出、CLI、富文本终端输出、重试机制
# =========================================================
loguru==0.7.3
rich==14.3.3
typer==0.24.1
click==8.3.1
tenacity==9.1.4
tqdm==4.67.3
python-dateutil==2.9.0.post0
pytz==2026.2


# =========================================================
# 测试工具
# 作用:单元测试、教程代码验证
# =========================================================
pytest==9.0.3


# =========================================================
# RAG / 向量数据库 / 数据库连接
# 作用:Milvus 向量库、PostgreSQL 检查点、SQL 数据源
# =========================================================
langchain-milvus==0.3.3
pymilvus==2.6.12
psycopg[binary]==3.3.3
psycopg-pool==3.3.0
SQLAlchemy==2.0.48


# =========================================================
# Embedding / Tokenizer / 文本处理
# 作用:TokenTextSplitter、SemanticChunker、文本相似度、传统 NLP 处理
# 注意:这里不包含 sentence-transformers,也不包含 torch
# =========================================================
tiktoken==0.12.0
numpy==2.4.4
scipy==1.17.1
scikit-learn==1.8.0
nltk==3.9.4
regex==2026.2.28
langdetect==1.0.9


# =========================================================
# HuggingFace / Transformers 基础组件
# 作用:本地模型、Tokenizer、部分文档解析模型可能会用到
# 注意:不包含 torch;如果加载本地深度学习模型,请单独安装匹配 CUDA 的 PyTorch
# =========================================================
transformers==5.3.0
tokenizers==0.22.2
huggingface-hub==1.11.0
safetensors==0.7.0


# =========================================================
# Unstructured / LangChain Loader 文档解析
# 作用:PDF、Word、PPT、Excel、HTML、Markdown、图片文档等 Loader 支持
# 注意:unstructured-inference 可能依赖本地推理环境,torch/torchvision 请单独安装
# =========================================================
unstructured==0.20.6
unstructured-client==0.44.0
unstructured-inference==1.6.11
unstructured.pytesseract==0.3.15

pdfminer.six==20260107
pdf2image==1.17.0
pypdf==6.10.2
pypdfium2==5.8.0
pikepdf==10.5.1
pi-heif==1.3.0
pillow==12.2.0
opencv-python==4.13.0.92
onnx==1.21.0
onnxruntime==1.25.1

python-docx==1.2.0
python-pptx==1.0.2
openpyxl==3.1.5
xlrd==2.0.2
xlsxwriter==3.2.9
pandas==3.0.2

beautifulsoup4==4.14.3
html5lib==1.1
lxml==6.1.0
Markdown==3.10.2
jq==1.11.0
filetype==1.2.0
python-magic==0.4.27
python-iso639==2026.4.20
msoffcrypto-tool==6.0.0
python-oxmsg==0.0.2
olefile==0.47
pypandoc-binary==1.17

执行安装:

bash 复制代码
pip install -r requirements_full.txt

2.验证

Langchain支持两种方式创建大模型调用对象,一种是各个厂商自己的调用方式,一种是Langchain提供的ChatOpenAI 这种是只要遵循了OpenAI的规范都可以使用这个API创建,如果没有自然也可以使用对应厂商的API

先配置下自己的API-key

下面先演示下DeepSeek提供的api,这种方式LangChain、LangGraph都是可以使用的。

python 复制代码
import langchain
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv
load_dotenv()

model = ChatDeepSeek(
    model="deepseek-v4-flash",
    extra_body={"thinking": {"type": "disabled"}}
)

response = model.invoke("你好")
print(response)

下面是使用ChatOpenAI的调用示例:

python 复制代码
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI

load_dotenv()

openai_model = ChatOpenAI(
    model="deepseek-v4-flash",
    openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
    openai_api_base="https://api.deepseek.com/"
)
print(openai_model.invoke("你好"))

3.init_chat_model 创建模型对象

init_chat_model 本质是对ChatDeepSeek、ChatOpenAI等的封装,推荐使用这个进行大模型对象的创建,示例如下:

python 复制代码
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model

load_dotenv()

openai_model = init_chat_model(
    model="deepseek:deepseek-v4-flash",
    base_url="https://api.deepseek.com/"
)
print(openai_model.invoke("你好"))

上面不用配置api-key的关键在于,底层执行的还是ChatDeepSeek,而ChatDeepSeek会去默认找env里面的DEEPSEEK_API_KEY,所以这里可以不用配置。

4.模型初始化时其他参数

模型的使用在Langchain中,大部分是通过agent操作的,不过也有一些是可以直接通过调用大模型解决的,但是基本没有,因为需要借助agent的持久化能力,如果不通过agent还需要单独提供持久化的能力,除非就是那种无需借助agent任何能力的。

  • temperature 温度
    可以控制模型输出内容的随机性,大模型有个显著特征,就是每次相同输入的输出时不同的,就是靠这个参数,这个值取值范围是0-2,越大随机性越高,越小随机性越低。一般需要不同输出、创意性较高时设置1以上,需要回答较为固定时设置1以下
  • max_tokens 输出最大token
    这里管控的是输出token,和输入没有关系,输出到达最大token时会被截断(不影响思考,只影响输出)。一般一个Token是1-1.8个汉字,3-4个英文字符。一般可以让AI回答简洁明了进行节省token。

二、使用LangChain创建Agent

在大模型应用开发中,智能体通常指一种以大语言模型为推理与决策核心,结合记忆、工具调用与环境交互能力,能够进行规划决策并执行复杂任务以达成目标的软件系统。

Agent的关键能力

  • 理解用户问题
  • 如何拆解任务
  • 判断是否需要工具
  • 需要调用哪些工具
  • 如何利用好工具结果生成回答&推进任务
    LangChain 在 1.0 版本后,团队做出了彻底重构:将所有 Agent 的创建方式统一为一个入口:create_agent()。它取代了旧版本中的 create_react_agent、create_json_agent、create_tool_calling_agent 等多种分支函数,真正让开发者用一行代码即可创建任何类型的智能体。
    同时在底层通过"中间件机制(Middleware)"和"标准模型接口(invoke / stream)"实现全局统一。这让框架更轻、更稳,也更易于被集成到其他 Agent 平台中。

1.创建一个简单智能体agent

来一个简单示例:

python 复制代码
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool

load_dotenv()

openai_model = init_chat_model(
    model="deepseek:deepseek-v4-flash",
    temperature=0.5,
    base_url="https://api.deepseek.com/"
)

@tool
def get_weather(city:str):
    """
    获取天气
    Args:
        city: 城市
    """
    return "天气晴朗"

agent = create_agent(
    model=openai_model,
    tools=[get_weather]
)

response = agent.invoke(input={"messages":[HumanMessage(content="北京天气怎么样")]})
print(response["messages"][-1].content)

以上是一种场景写法

  • model 支持直接传入字符串,比如"deepseek:deepseek-v4-flash"

  • @tool装饰器,修饰工具的,注意注释是必须的,可以让大模型更好的理解工具用途,上面的不就正常理解了吗

    LangChain本身即提供了很多的工具可以使用,官方工具列表:

    https://docs.langchain.com/oss/python/integrations/tools

    LangChain底层是LangGraph其实就是生成了工具节点,然后大模型通过条件边和工具接地那进行连接等实现了工具和大模型的一个来回调用。

  • input参数必须是{"messages":\[\]},HumanMessage可以省略直接字符串,默认就是他,也可以像上面的例子加上,然后使用content

  • {"messages":HumanMessage(content="北京天气怎么样")},也可以这么写:{"messages":{"role":"user","content":"上海天气如何"}}

  • response"messages"-1.content 获取最新的返回消息

2.使用Langchain自带工具

如下示例代码,注意运行不了,因为运行的话一些工具都是收费的,需要去相关网站注册才能使用

python 复制代码
from langchain_tavily import TavilySearch

# 2.工具实例化
web_search = TavilySearch(max_results=2)

# 3.创建Agent
agent = create_agent(
    model=model,
    tools=[web_search],
    #system_prompt="你是一名多才多艺的智能助手,可以调用工具帮助用户解决问题。"
)

如有需要可以去官方查看:https://docs.langchain.com/oss/python/integrations/tools

注意工具声明的注释必须写的清楚,不然AI可能识别有问题。下面是工具调用的流程:

3.设置agent的name

这个有一些应用场景

  1. 流式输出归因

    在启用流式输出时,name 可用于标识当前输出内容来自哪个 Agent。

    这在多 Agent 协作、Agent 嵌套调用,或前端需要实时展示不同执行主体输出时尤其有用,便于准确区分 token 或事件的来源。

  2. 消息身份标记

    设置 name 后,Agent 产生的 AIMessage 会携带对应的name信息。

    这使得系统在保存会话记录、回放执行过程、构建审计日志或前端展示消息角色时,能够明确识别消息的生成者。

  3. 调试与trace可读性

    在调试、日志分析和链路追踪过程中,name 可以作为 Agent 的稳定标识,帮助开发者快速判断当前执行的是哪个 Agent。

    当系统中存在多个能力相近的 Agent,或一个 Agent 被嵌套在更复杂的工作流中时,名称能够显著提升trace 的可读性和问题定位效率。

  4. 组件化封装

    在工程实践中,Agent 常被封装为可复用的能力模块,例如检索助手、SQL 助手、报告生成助手等。

    为 Agent 设置 name,有助于在模块注册、运行监控、日志归档和能力复用时保持一致的身份标识。

    如果后续需要将该 Agent 进一步作为子图节点、工具能力或子模块接入更复杂系统,也能降低维护和迁移成本。

  5. 前端展示与运行态可观测性

    在带有可视化界面的应用中,name 还可以直接作为运行时展示标识使用。

    例如,在执行面板中显示"当前活跃 Agent""本轮输出来源"或"调用链路中的执行节点"时,name 能帮助开发者和用户更直观地理解系统当前的执行状态。

  6. 作为稳定的运行时身份标识

    从更通用的角度看,name 可以理解为 Agent 在系统中的" 运行时身份 ID "。

    相比临时性的展示名称,一个稳定、规范的 name 更适合用于日志检索、监控统计、链路分析和跨模块协作,因此在生产环境中通常建议显式设置,而不是依赖默认行为。

示例如下:

python 复制代码
agent = create_agent(
    model=openai_model,
    tools=[get_weather],
    name="贾维斯-1号"
)

response = agent.invoke(input={"messages":[{"role":"user","content":"上海天气如何"}]})
print(response["messages"][-1])

4.系统提示词设置 TODO 待添加动态提示词

LangChain的系统提示词设置和LangGraph略有区别,需要再创建智能体时显示的指定,LangGraph则可以通过全局状态进行隐藏式声明,当然LangChain也是可以做到这种,稍微麻烦一些再input中组装也是一样的效果其实,下面展示

python 复制代码
agent = create_agent(
    model=openai_model,
    tools=[get_weather],
    name="贾维斯-1号",
    system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
)

response = agent.invoke(input={"messages":[{"role":"user","content":"上海天气如何"}]})
print(response["messages"][-1])

一般智能体都需要设置提示词,且提示词相当重要。

5.结构化输出的三种方式

这里说的结构化输出指的是agent的结构化,构建agent时支持传入参数response_format

  • ProviderStrategy

    使用模型提供商的原生结构化输出功能实现结构化输出。这里所说的"原生结构化输出"指的是大语言模型(LLM)提供商通过其API直接提供的、在模型响应阶段就强制保证输出格式符合预定规范的能力,这种能力能够在模型生成内容的源头确保结构化准确性。适用于支持原生结构化输出的模型,比如OpenAI、Anthropic Claude或xAI Grok等

    python 复制代码
    # 2.Pydantic结构化方式定义
    class ContactInfo(BaseModel):
        """用户的联系方式"""
        name: str = Field(description="用户姓名")
        email: str = Field(description="用户邮箱地址")
        phone: str = Field(description="用户的手机号")
    
    # 3.agent初始化
    agent = create_agent(
        model=model,
        response_format=ProviderStrategy(ContactInfo)
    )
  • ToolStrategy

    对于不支持原生结构化输出的模型,LangChain采用"ToolStrategy"工具调用的方式实现结构化输出。

    此策略兼容绝大多数支持工具调用的现代模型,其核心原理是动态创建一个" 虚拟工具",该工具的输入参数对应着期望的数据结构。

    当模型需要生成最终答案时,系统会引导模型"调用"这个虚拟工具,从而间接产生符合要求的结构化数据。

  • 直接指定类型

    python 复制代码
    from pydantic import BaseModel
    
    class WeatherReport(BaseModel):
        city: str
        weather: str
        temperature: int
    
    agent = create_agent(
        model=model,
        tools=[get_weather],
        response_format=WeatherReport  # 直接指定输出结构
    )
    
    result = agent.invoke({"messages": [HumanMessage(content="北京天气")]})
    # result["structured_response"] 就是 WeatherReport 对象

结构化输出的底层原理其实和LangGraph的output(输出状态)的显示指定,原理其实是类似的。

后面主要介绍的是 ToolStrategy。ToolStrategy适用于任何支持工具调用的现代模型。

下面是使用举例:

python 复制代码
from typing import TypedDict

from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool

load_dotenv()

openai_model = init_chat_model(
    model="deepseek:deepseek-v4-flash",
    temperature=1,
    base_url="https://api.deepseek.com/",
    extra_body={"thinking": {"type": "disabled"}}
)

class ResponseFormat(TypedDict):
    name: str
    age: str

@tool
def get_weather(city:str):
    """
    获取天气
    Args:
        city: 城市
    """
    return "天气晴朗"

agent = create_agent(
    model=openai_model,
    tools=[get_weather],
    name="贾维斯-1号",
    system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
    response_format=ToolStrategy(ResponseFormat)
)

response = agent.invoke(input={"messages":[{"role":"user","content":"我是张三,我今年18岁,帮我返回我的姓名和年龄"}]})
print(response["structured_response"])
print("-"*50)

注意:

  • 使用ToolStrategy时,如果使用的是Deepseek,会报错,必须关闭思考模式,因为思考模式Deepseek默认不支持强制创建工具,这里的ToolStrategy就是走的创建工具策略
  • 获取响应时使用的是response"structured_response",这个需要注意

下面是源码的参数声明:

  • schema 就是我们声明的返回格式化输出,上面用的是TypedDict,还支持dataclass、Pydantic、json 等共四类定义方式,还有一种联合形式: Union类型1, 类型2

    这种写法,LLM能够根据输入文本的内容,智能地选择最合适的一个数据模型(Schema)来生成结构化输出,但是最终会只有一种类型输出。

  • tool_message_conntent 则是ToolMessage的返回Content,因为默认创建个工具,所以会有content返回,如果不指定就是返回的如下这种,其实就是一个ToolMessage

    可以手动指定,这样就会返回我们指定的信息了。

  • handle_errors 异常处理策略,True 默认值,

    当ToolStrategy通过"UnionContactInfo, EventDetails"指定多个类型时,在内部调用生成结构化

    类型工具会报错。此时:handle_errors=True(默认值)开始发挥作用,系统会生成一个ToolMessage,明确告诉LLM"Error: Model incorrectly returned multiple structured responses (ContactInfo,EventDetails) when only one is expected.",大模型收到这个精准的反馈后,会重新进行推理,最终选择并输出一个最符合要求的Schema。如果handle_errors 设置为False,执行代码过程直接报错。当格式化输出有错误时,Agent内部会进行工具调用重试,直到符合要求格式化输出前,可能会进

    • handle_errors=True:LangChain默认方式,捕获所有异常,并使用LangChain 内置的、信息明确的错误消息模板提示模型重试,确保最终能得到符合预定格式的有效数据。适用于大多数希望自动处理错误的通用场景。

    • handle_errors=False:关闭自动重试机制,任何异常都会直接抛出,会中断程序运行。

    • handle_errors="自定义字符串":捕获所有异常,但使用开发者预设的固定字符串作为错误消息。适用于需要统一、友好的用户提示,或进行特定业务引导的场景。
      handle_errors=ExceptionType:仅捕获指定类型(如ValueError) 或元组中的异常类型并进行重试,其他异常直接抛出。适用于需要精准控制,只对特定错误进行重试的场景。

    • handle_errors=callable:灵活性最高的方式,使用开发者自定义的函数来处理异常,可根据不同的异常类型返回差异化的提示信息。适用于需要复杂、精细化错误处理的场景。

      python 复制代码
      def custom_error_handler(error: Exception) -> str:
      """自定义错误处理器"""
      error_str = str(error)
      
      print(f"捕获到错误类型:{type(error).__name__}")
      print(f"错误详情:{error_str}")
      
      if isinstance(error, StructuredOutputValidationError):
          return "数据格式有误,请检查字段是否符合要求。"
      elif isinstance(error, MultipleStructuredOutputsError):
          return "检测到多个响应,请选择最相关的一个进行返回。"
      else:
          return f"Error: {error_str}"
      
      
      agent = create_agent(
      model=model,
      response_format=ToolStrategy(
          Union[ContactInfo, EventDetails],
          tool_message_content="提取完成!",
          handle_errors=custom_error_handler
      )

    )

    ~~~

6.流式输出 agent.stream

通过invoke 调用Agent时,内部可能经历多次调用,长时间看不到调用情况,用户体验不好,可以通过流式调用(渐进式显示输出)优化用户体验,实时显示 Agent 运行过程中的更新。特别是在处理LLM 延迟时尤其有效。

这些其实都是LangGraph的东西,和LangGraph一致,包括可以指定的输出内容,下面是简单示例:

python 复制代码
from typing import TypedDict

from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool

load_dotenv()

openai_model = init_chat_model(
    model="deepseek:deepseek-v4-flash",
    temperature=1,
    base_url="https://api.deepseek.com/",
    extra_body={"thinking": {"type": "disabled"}}
)

class ResponseFormat(TypedDict):
    name: str
    age: str

@tool
def get_weather(city:str):
    """
    获取天气
    Args:
        city: 城市
    """
    return "天气晴朗"

agent = create_agent(
    model=openai_model,
    tools=[get_weather],
    name="贾维斯-1号",
    system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
    response_format=ToolStrategy(ResponseFormat)
)

for chunk in agent.stream(input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]}):
    print(chunk)

然后会发现,这输出的内容和之前不一样了,这是为什么呢,其实是因为流式输出一般是需要指定输出模式的,根据输出模式的不同会有不同维度和不同密度的信息展示出来。

下面是输出模式:

  • values: 当stream_mode 设置为values模式时,每个步骤执行后,都会输出完整的状态信息,适用于每一步都要获取完整状态、状态持久化场景。

    python 复制代码
    from rich import print as rprint
    
    for chunk in agent.stream(input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},stream_mode="values"):
    	rprint(chunk)
  • updates 这种模式就是默认模式。该模式中,每个步骤执行后,只增量更新状态中发生变化的内容,用于监控Agent 执行进度,例如观察Agent决定调用工具、工具执行结果等步骤。

    python 复制代码
    for chunk in agent.stream(input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},stream_mode="updates"):
        rprint(chunk)
  • messages 该模式中会输出流式返回的Token以及相关的元数据(如:来自哪个节点),可以用在实现类似ChatGPT 的打字机效果场景,为聊天机器人等交互式应用提供最佳的实时体验。非常适合配合流式输出时对客户端进行信息返回

    python 复制代码
    for chunk in agent.stream(input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},stream_mode="messages"):
    	rprint(chunk)
  • tasks 该模式会输出当前task任务开始和结束的时间,包含任务的结果和错误信息,该模式用于监控任务的生命周期。

    python 复制代码
    for chunk in agent.stream(input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},stream_mode="tasks"):
    	print(chunk)
  • debug 该模式与tasks模式类似,比task模式多输出任务步骤、时间戳、task类型(task/task_result),该模式用于调试、监控task任务的生命周期。

    python 复制代码
    for chunk in agent.stream(input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},stream_mode="debug"):
    	print(chunk)
  • checkpoints 该模式中,每当检查点(checkpoint)被创建时会触发输出,输出包含检查点中的状态,用于需要状态持久化、工作流恢复或分布式执行跟踪的高级场景。

    注意使用这个模式必须启用checkpoint,这个checkpoint使用和langGraph几乎完全一致,示例如下:

    python 复制代码
    from typing import TypedDict
    from langgraph.checkpoint.memory import InMemorySaver
    from dotenv import load_dotenv
    from langchain.agents import create_agent
    from langchain.chat_models import init_chat_model
    from langchain_core.tools import tool
    
    load_dotenv()
    
    openai_model = init_chat_model(
        model="deepseek:deepseek-v4-flash",
        temperature=1,
        base_url="https://api.deepseek.com/",
        extra_body={"thinking": {"type": "disabled"}}
    )
    
    class ResponseFormat(TypedDict):
        name: str
        age: str
    
    @tool
    def get_weather(city:str):
        """
        获取天气
        Args:
            city: 城市
        """
        return "天气晴朗"
    
    store = InMemorySaver()
    
    agent = create_agent(
        model=openai_model,
        tools=[get_weather],
        name="贾维斯-1号",
        system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
        checkpointer=store
        # response_format=ToolStrategy(ResponseFormat)
    )
    
    for chunk in agent.stream(
            input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},
            stream_mode="checkpoints",
            config={"configurable":{
                "thread_id":"chat--001001"
            }}
    ):
        print(chunk)
  • custom 开发者通过get_stream_writer 在工具或节点内部自定义发送的数据,用于输出业务逻辑相关的进度信息(如"已处理10/100条记录")、自定义日志或指标。

    这个模式主要是为了接收 agent运行时从内部实时输出到客户端的信息。write也可以写入结构化数据也是可以的,这里展示的是使用字符串

    示例如下:

    python 复制代码
    from typing import TypedDict
    from langgraph.checkpoint.memory import InMemorySaver
    from dotenv import load_dotenv
    from langchain.agents import create_agent
    from langchain.chat_models import init_chat_model
    from langchain_core.tools import tool
    from langgraph.config import get_stream_writer
    
    load_dotenv()
    
    openai_model = init_chat_model(
        model="deepseek:deepseek-v4-flash",
        temperature=1,
        base_url="https://api.deepseek.com/",
        extra_body={"thinking": {"type": "disabled"}}
    )
    
    class ResponseFormat(TypedDict):
        name: str
        age: str
    
    @tool
    def get_weather(city:str):
        """
        获取天气
        Args:
            city: 城市
        """
        write = get_stream_writer()
        write("获取天气成功了")
        return "天气晴朗"
    
    store = InMemorySaver()
    
    agent = create_agent(
        model=openai_model,
        tools=[get_weather],
        name="贾维斯-1号",
        system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
        checkpointer=store
        # response_format=ToolStrategy(ResponseFormat)
    )
    
    for chunk in agent.stream(
            input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},
            stream_mode="custom",
            config={"configurable":{
                "thread_id":"chat--001001"
            }}
    ):
        print(chunk)

以上就是流式输出支持的参数了,上面这些参数也可以混合使用,比如:

python 复制代码
for chunk in agent.stream(
        input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},
        stream_mode=["updates","custom"],
        config={"configurable":{
            "thread_id":"chat--001001"
        }}
):
    print(chunk)

拆分输出使用:

python 复制代码
for stream_mode,chunk in agent.stream(
        input={"messages":[{"role":"user","content":"帮我出一个北京的出游策略"}]},
        stream_mode=["updates","custom"],
        config={"configurable":{
            "thread_id":"chat--001001"
        }}
):
    print(f"模式{stream_mode}:{chunk}")

三、中间件Middleware

在 create_agent() 的底层运行机制中,有几个重要的组件,分别是:

  • 模型(Model) :Agent 的"大脑",负责理解任务与决策推理。
  • 工具(Tools) :Agent 的"手脚",执行模型自己做不到的外部操作。
  • 系统提示词(System Prompt) :Agent的"角色",告诉模型该怎么想、参考什么上下文。
  • 中间件(Middleware) :Agent的"中枢",在执行流程的关键节点进行拦截、控制和增强。
    官方中间件一览:https://docs.langchain.com/oss/python/langchain/middleware/overview
    Middleware(中间件),简单说就是Agent 执行过程中的钩子函数,是 LangChain 1.x 的"王牌"工程化能力。

    解决痛点
    想根据问题复杂度动态切换模型;
    想限制某些用户只能调用部分工具;
    想在工具报错时自动重试或返回兜底结果;
    想在模型调用前插入额外的系统提示;
    想记录每一步的执行日志,方便排查问题;
    想在敏感信息出现时阻断执行;
    想在正式执行工具前增加人工审批。

1.成本与资源控制类

核心目标:控成本、控配额、避免无限调用这类中间件主要解决" Agent太贵、太能跑、停不下来"的问题。

业务场景理解:适合生产环境的成本治理、配额治理、长会话优化、SaaS 产品控费。

包含:

  • Model call limit:限制模型调用次数,防止一次任务反复请求 LLM,导致费用失控

  • Tool call limit:限制工具调用次数,避免 Agent 无限试错、死循环调工具

  • Summarization:在上下文快满时自动总结历史,减少 token 消耗

    示例:

    python 复制代码
    from typing import TypedDict
    
    from langchain.agents.middleware import SummarizationMiddleware
    from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
    from langgraph.checkpoint.memory import InMemorySaver
    from dotenv import load_dotenv
    from langchain.agents import create_agent
    from langchain.chat_models import init_chat_model
    from langchain_core.tools import tool
    from langgraph.config import get_stream_writer
    
    load_dotenv()
    
    openai_model = init_chat_model(
        model="deepseek:deepseek-v4-flash",
        temperature=1,
        base_url="https://api.deepseek.com/",
        extra_body={"thinking": {"type": "disabled"}},
        profile={"max_input_tokens": 1000}
    )
    
    class ResponseFormat(TypedDict):
        name: str
        age: str
    
    @tool
    def get_weather(city:str):
        """
        获取天气
        Args:
            city: 城市
        """
        write = get_stream_writer()
        write("获取天气成功了")
        return "天气晴朗"
    
    store = InMemorySaver()
    
    
    
    
    
    agent = create_agent(
        model=openai_model,
        tools=[get_weather],
        name="贾维斯-1号",
        system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
        checkpointer=store,
        middleware=[
            SummarizationMiddleware(
                model=openai_model,
                trigger=[
                    ("tokens",30), # 30个token时触发总结
                    ("messages",4), # 4条消息时触发总结
                    ("fraction",0.001) # profile={"max_input_tokens": 1000} 必须设置这个才可以使用,否则会报错 
                ],
                keep=("messages", 2) # 保留最近2条消息
            )
        ]
        # response_format=ToolStrategy(ResponseFormat)
    )
    
    
    message = {"messages":[
        HumanMessage(content="帮我出一个北京的出游策略"),
        AIMessage(
            content="",
            tool_calls=[{
                "name": "get_weather",
                "args": {"city": "北京"},
                "id": "call_00_A6Trc5wv3LnttmVuL69s7194",
                "type": "tool_call"
            }]
        ),
        ToolMessage(content='天气晴朗', name='get_weather', id='b8455639-3d2e-46f4-b4c1-2b3689746ea9', tool_call_id='call_00_A6Trc5wv3LnttmVuL69s7194'),
        AIMessage(content="北京天气晴朗,建议你去北京旅游"),
        HumanMessage(content="哪些地方适合今天去旅游"),
        AIMessage(content="故宫,天安门,长城,八达岭都可以"),
        HumanMessage(content="我选择故宫")
    ]}
    
    
    for msg in agent.invoke(
            input=message,
            config={"configurable":{
                "thread_id":"chat--001001"
            }}
    )['messages']:
        print(msg)
    • 参数1:model ---用于摘要的模型

      可以是模型名称也可以是模型对象,如果传递的是模型名称,底层会调用init_chat_model 初始化模型。

    • 参数2:trigger ---摘要触发条件

      是一个列表,每个元素对应一个条件,当任一条件满足时,触发摘要。

      tokens :token的数量,历史token的累计数量达到该值触发摘要。

      messages :历史消息数量,历史消息条数达到该值触发摘要。

      fraction :上下文长度比例。历史token的累计数量达到模型的max_input_tokens*fraction 触发摘要

      如果条件包含fraction ,要求模型的profile包含max_input_tokens ,Deepseek模型的profile为空,此时需要手动添加该配置项。Deepseek-V3.2的上下文长度为128K。

    • 参数3:keep ---摘要时保留的原始消息

      支持三种条件,但和trigger不同,keep同一时间只接收一种条件。

      tokens :摘要时保留的token数量。

      messages :摘要时保留的历史消息条数。

      fraction :摘要时保留max_input_tokens*fraction 个token。

    • 参数4:token_counter ---统计token数量的函数

      默认使用LangChain提供的count_tokens_approximately ,一般不用更改。

      对于纯文本消息,该函数的大致思路是先统计消息的字符数,也就是len(字符串) ,然后再除以每

      个token大致的字符数,转换为粗略的token数,再加一些额外开销。作为估算的token数。

    • 参数5:summary_prompt ---摘要时的自定义提示词

      该提示词需要包含{messages} 占位符,使得历史消息列表可以被插入。不指定则使用内置提示词。

    • 参数6:trim_token_to_summarize ---摘要时历史消息的最大token数

      如果历史消息token数大于该值,则会被裁剪。默认为" 4000 "。

      如果trigger用token作为度量,调大触发阈值时,当前配置项应相应调整,否则会丢失信息。

      注意:传入fraction,必须声明profile={"max_input_tokens": 1000},三个限制条件是或的关系,到达一个就会触发总结,摘要结果作为HumanMessage,传入消息列表头部,keep必传,否则也不会触发总结,langchain1.2自测结果如此。

  • Context editing:裁剪上下文、清理工具调用痕迹,本质上也是为了节省上下文成本

2.稳定性与容错保障类

核心目标:保证服务不中断、失败后尽量自动恢复

业务场景理解:适合线上生产系统,尤其是多模型、多工具依赖的 Agent。本质上是在做 高可用、容灾、鲁棒性建设。

这类中间件主要解决" 调用失败怎么办、模型挂了怎么办、工具超时怎么办"。

包含:

  • Model fallback:主模型失败时切换备用模型

  • Model retry:模型调用失败后自动重试

    模型在调用过程中很可能是存在失败场景的,网络原因或者模型自身原因等都可能存在,影响异常的场景,所以给模型加个重试机制一般也是很有必要的。

    基于指数退避算法,设置工具调用失败时的重试策略。指数退避(Exponential Backoff) 的核心思想就是:当某个操作失败(通常是网络请求、API 调用或数据库连接)时,系统不会立刻重试,也不会每次都等待相同的固定时间,而是让每一次重试的延迟时间按指数级增长。

    代码示例:

    python 复制代码
    agent = create_agent(
        model=openai_model,
        tools=[get_weather, get_news],
        name="贾维斯-2号",
        system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
        checkpointer=store,
        middleware=[
            ModelRetryMiddleware(
                max_retries=5,
                on_failure='continue',
                backoff_factor=2,
                initial_delay=1,
                max_delay=20,
                jitter=False
            )
        ]
        # response_format=ToolStrategy(ResponseFormat)
    )

    通过上面可以看出来,重试了5次加上原先一次总共是6次,都失败了,这里是因为我爸模型名称故意改错了进行模拟调用错误。

    参数解释

    • max_retries=6, # 最大重试次数(不包含初始的那次调用,一共最多调 1 + 6 = 7 次)
    • backoff_factor=2.0, # 指数退避因子(每次重试等待时间乘以 2)
    • initial_delay=1.0, # 第一次重试前的初始等待时间(1 秒)
    • max_delay=10.0, # 最大等待延迟上限(防止指数增长无限大,限制在 10 秒)
    • jitter=True, # 开启抖动(在等待时间中加入随机性,防止并发请求时出现"惊群效 应")
    • retry_on=(TimeoutError,), # 仅针对捕获到特定的 TimeoutError 异常时才 触发重试
    • on_failure="continue" # 当达到最大重试次数依然失败时,Agent 的行 为:"continue" 表示将错误信息包装后塞回对话历史,让大模型知道失败了并继续决策
  • Tool retry:工具调用失败后自动重试

3.安全与合规风控类

核心目标:让 Agent 可控、可审、合规这类中间件主要解决" Agent乱执行、泄露敏感信息、做危险操作"的问题。

业务场景理解:适合企业内部系统、客服系统、审批流、数据查询类 Agent。尤其是涉及:发邮件、调数据库、调财务/人事系统、导出敏感信息、执行外部动作等

包含:

  • Human-in-the-loop:在关键工具调用前暂停,等人工审批

    这是一个非常核心的MIddleware,也是必须完全掌握的。在工具调用前中断Agent运行,等待用户对工具调用请求决策。可选的决策有:approve(同意执行)、edit(编辑调用配置后执行)、reject(拒绝执行)。

    • 参数1:interrupt_on ---工具名和中断策略的映射

      策略可以是True、False或InterruptOnConfig对象,精细控制决策选项。

      比如:

      python 复制代码
      	interrupt_on={
      	    "get_weather": True,
      	    "read_email_tool": False,
      	    "send_email_tool": {
      	        "allowed_decisions": ["approve", "reject"],
      	    },
      	}

      True表示所有决策(approve, edit, reject) 都可以选择,False表示不中断,即无需审批即可执行。

      InterruptOnConfig 是一个TypedDict的子类,可以用字典直接赋值。支持的Key有:

      ① allowed_decisions 精细控制中断后允许的决策。

      ② description :特定工具的中断描述信息,优先级高于description_prefix,后 者会更改所有工具中断的描述。

    • 参数2:description_prefix ---自定义中断描述

      默认为"Tool execution requires approval" ,下面的举例可以看到效果

    示例如下:

    python 复制代码
    from typing import TypedDict
    from dotenv import load_dotenv
    from langchain.agents import create_agent
    from langchain.agents.middleware import HumanInTheLoopMiddleware
    from langchain.chat_models import init_chat_model
    from langchain_core.messages import HumanMessage
    from langchain_core.tools import tool
    from langgraph.checkpoint.memory import InMemorySaver
    from langgraph.types import Command
    
    load_dotenv()
    
    openai_model = init_chat_model(
        model="deepseek:deepseek-v4-flash",
        temperature=1,
        base_url="https://api.deepseek.com/",
        extra_body={"thinking": {"type": "disabled"}},
        profile={"max_input_tokens": 1000}
    )
    
    class ResponseFormat(TypedDict):
        name: str
        age: str
    
    @tool
    def get_weather(city:str):
        """
        获取天气
        Args:
            city: 城市
        """
        return "天气晴朗"
    
    @tool
    def get_news(topic:str):
        """
        获取新闻
        Args:
            topic: 新闻主题
        """
    
        return f"{topic}的新闻是:AI发展迅速"
    
    store = InMemorySaver()
    
    agent = create_agent(
        model=openai_model,
        tools=[get_weather, get_news],
        name="贾维斯-2号",
        system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
        checkpointer=store,
        middleware=[
            HumanInTheLoopMiddleware(
                interrupt_on={
                    "get_weather": True,
                    "get_news": {
                        "allowed_decisions": ["approve", "reject"],
                        "description": "是否同意获取新闻? approve或reject" # 优先级高于description_prefix
                    }
                },
                description_prefix="可以执行工具? approve或reject或edit"
            )
        ]
        # response_format=ToolStrategy(ResponseFormat)
    )
    
    response = agent.invoke(
            input={"messages":[HumanMessage(content="帮我出一个北京的出游策略,再看看有什么新闻")]},
            config={"configurable":{
                "thread_id":"chat--001005"
            }}
    )
    print(response['messages'])

下面是恢复执行的示例,需要使用Command,在Langchain中,这个被用来做恢复执行使用,LangGrah中也是类似,graph中还可以支持goto进行跳转节点。

python 复制代码
from langgraph.types import Command
# 天气的审核
response2 = agent.invoke(
    Command(resume={
    "decisions": [{"type": "approve"},{"type": "reject"}],
    }),
    config={"configurable":{
        "thread_id":"chat--001005"
    }}
)
print(response2)
  • PII detection:检测和处理个人敏感信息
  • Model call limit / Tool call limit:某种意义上也可归到风控,因为它能防止异常滥用

4.决策增强与智能编排类

核心目标:提升 Agent 的决策质量和任务拆解能力这类中间件主要解决" Agent不够聪明、不会规划、不会先筛工具"的问题。

业务场景理解:适合复杂任务流,比如:研究型 Agent、多步骤分析、报告生成、多角色协作、长链路任务编排等。这类本质上是在增强 Agent的"脑子"与"组织能力"。

包含:

  • To-do list:给 Agent 增加任务规划、分步骤执行和状态跟踪能力

    TodoListMiddleware中间件赋予了Agent 任务规划和追踪进度的能力,可以应对复杂的多步任务。比如,当一个大任务需要被拆解为 3 个以上的子任务,且前面的步骤是后面步骤的前提时,如果不列Todo 列表,大模型在执行到第 3 步时,很容易忘记自己最初的目标,或者在工具返回大量报错信息后"应激",直接跳过验证去回答用户。

    下面只列出来了相关的核心代码

    python 复制代码
    store = InMemorySaver()
    
    agent = create_agent(
        model=openai_model,
        tools=[get_weather, get_news],
        name="贾维斯-2号",
        system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
        checkpointer=store,
        middleware=[
            TodoListMiddleware()
        ]
        # response_format=ToolStrategy(ResponseFormat)
    )
    
    response = agent.invoke(
            input={"messages":[HumanMessage(content="帮我分布计划写一个玄幻小说的大纲出来,小说核心是玄幻诡异,可以参考全员恶仙")]},
            config={"configurable":{
                "thread_id":"chat--001005"
            }}
    )
    print(response)

    注意:write_todos 工具不需要你手动声明,它是由 TodoListMiddleware 自动注入的。只需要在创建 Agent 时传入这个中间件,Agent 就会自动获得这个工具。

    TodoListMiddleware 的核心功能之一,就是在中间件初始化时,自动将 write_todos 工具注入到 Agent 的工具列表中。

    它同时会完成三件事:

    • 注入工具:添加 write_todos 工具。
    • 注入状态:添加一个 todos 状态通道,用于跟踪任务进度。
    • 注入提示词:添加系统提示词,指导 Agent 何时以及如何使用这个工具。

    再看看 ToDoListMiddleware支持的参数:

    • system_prompt:其实就是告诉他怎么使用工具拆分任务,有默认值,基本无需指定
    • tool_description:这个其实就是描述工具如何使用的,有默认值,基本不用指定
  • LLM tool selector:当工具太多时,用子模型筛选最相关的几个工具交给主模型

  • Subagent:允许生成子Agent,把复杂任务拆给不同角色处理

5.执行能力扩展类

核心目标:给 Agent 更多"手脚"这类中间件主要解决" Agent只能聊天,不能真正操作环境"的问题。

业务场景理解:适合工程 Agent、代码 Agent、本地自动化 Agent、运维 Agent。本质上是把 Agent 从"纯推理"扩展成"能操作环境的执行体"。

包含:

  • Shell tool:给 Agent 持久 shell,会执行命令
  • File search:给 Agent 文件搜索能力,能做 Glob/Grep
  • Filesystem:给 Agent 文件系统读写与长期存储能力

6.开发调试与测试辅助类

核心目标:方便开发、测试、验证 Agent 行为这类中间件主要不是直接服务业务,而是服务于研发和调试阶段。

业务场景理解:适合开发阶段快速验证流程、做 mock、减少真实工具依赖。

包含:

  • LLM tool emulator:用 LLM 模拟工具执行,便于测试(最典型)
    就是不真正调用,适合在工具未开发完成时想要验证整体流程时使用,此时llm会模拟结果返回,而不是真正调用工具,当然工具是需要存在的,只是不需要实现逻辑也不影响流程了。

    python 复制代码
    @tool
    def get_weather(city:str):
        """
        获取天气
        Args:
            city: 城市
        """
        return "天气晴朗"
        
    agent = create_agent(
        model=openai_model,
        tools=[get_weather, get_news],
        name="贾维斯-2号",
        system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
        checkpointer=store,
        middleware=[
            LLMToolEmulator(
                model=openai_model
            )
        ]
        # response_format=ToolStrategy(ResponseFormat)
    )


    可以看到确实是杜撰的消息,适合测试场景。

  • Summarization:有时也可辅助调试长会话表现

  • Context editing:可用于测试上下文裁剪效果

  • Human-in-the-loop:也常用于调试高风险步骤

7.自定义中间件

某些复杂场景下,官方内置的中间件不能完全满足需求,此时可以通过实现LangChain暴露的中间件hook函数构建自定义中间件(尽量使用官方的)。

LangChain的中间件作用在Agent架构中,后者是基于LangGraph构建的流程图。如下列出了六个hook函数(钩子函数):

LangChain 提供了两种风格的钩子,分别适用于不同的场景:

  • 节点式钩子 (Node-style Hooks):在流程的固定节点按顺序执行,适合做日志、验证、状态更新等
  • 包裹式钩子 (Wrap-style Hooks):包裹在模型或工具调用的前后,让你能完全控制调用的执行,比如实现重试、缓存、短路等。
    下面是所有可用钩子及其触发时机的总览:

7.1 节点式钩子(before_model / after_model / before_agent / after_agent)

除了以上还有before_tool,after_tool,不过有的版本不支持这哥俩,如果导入不进去就不用一直试了。这里以其中一个举例,其他使用都是类似的,代码如下:

python 复制代码
from typing import TypedDict
from dotenv import load_dotenv
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import HumanInTheLoopMiddleware, ModelRetryMiddleware, TodoListMiddleware, \
    LLMToolEmulator, before_model
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.runtime import Runtime
from langgraph.types import Command

load_dotenv()

openai_model = init_chat_model(
    model="deepseek:deepseek-v4-flash",
    temperature=1,
    base_url="https://api.deepseek.com/",
    extra_body={"thinking": {"type": "disabled"}},
    profile={"max_input_tokens": 1000}
)

class ResponseFormat(TypedDict):
    name: str
    age: str

@tool
def get_weather(city:str):
    """
    获取天气
    Args:
        city: 城市
    """
    return "天气晴朗"

@tool
def get_news(topic:str):
    """
    获取新闻
    Args:
        topic: 新闻主题
    """

    return f"{topic}的新闻是:AI发展迅速"

@before_model(can_jump_to=['end'])
def jump_to_end(state: AgentState, runtime: Runtime):
    print("执行模型前调整message,输出内容,并跳转到end")
    return {
        "messages":[AIMessage("手动调整end")],
        "jump_to":'end'
    }


store = InMemorySaver()

agent = create_agent(
    model=openai_model,
    tools=[get_weather, get_news],
    name="贾维斯-2号",
    system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
    checkpointer=store,
    middleware=[
        jump_to_end
    ]
    # response_format=ToolStrategy(ResponseFormat)
)

response = agent.invoke(
        input={"messages":[HumanMessage(content="帮我看下今天纽约天气")]},
        config={"configurable":{
            "thread_id":"chat--001006"
        }}
)
print(response)

注意:

  • before_model: 如果hook想要调整必须声明can_jump_to
  • 节点装饰的返回必须是字典,如果想要调整,返回字典必须声明jump_to,且他的值只能是这三个其中一个:model、tools、end,其他返回会被默认合并到state中
  • 入参state、Runtime,建议必须。AgentState 是默认的状态,是一个TypedDict类型,如果想要自定义建议继承他,AgentState包含了messages、剩余步数两个关键信息,Runtime则是agent的运行时
  • 创建agent时声明的自定义中间件就是方法名,langchain底层会根据方法创建一个中间件出来。
  • 中间件的核心能力就是支持动态调整state,支持随时跳转节点(model、tools、end)
  • 如果返回的是None,则表示不对状态、跳转等做出更改。

7.2 包裹式钩子(wrap_model_call,wrap_tool_call)

这里以工具的包裹进行举例:

python 复制代码
@wrap_tool_call
def jump_to_end(request: ToolCallRequest,handler: Callable[[ToolCallRequest], ToolMessage | Command])-> ToolMessage | Command:

    print("我是工具执行环绕中间件")

    if len(request.state['messages']) > 5:
        return Command(
                update={
                    'messages':[AIMessage("手动结束了")]
                },
                goto='end'

            )
    return handler(request)

下面是模型的包装示例:

python 复制代码
@wrap_model_call
def model_logger(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
    print(f"📤 模型调用前,消息数: {len(request.state['messages'])}")
    response = handler(request)
    print(f"📥 模型调用后,返回: {response.result[0].content[:80]}")
    return response

注意:

  • 无论是wrap_tool还是wrap_model他的底层原理都是,langgrah的节点包装函数
  • 入参都是一个request类型,一个handler类型
  • 返参可以是ToolMessage(正常工具调用返回类型),也可以是ModelResponse(正常模型调用返回),或者他们的返回类型都可以是Command,用这个可以实现AgentState的更新,同时也可以使用它的goto进行节点调整,只不过在LangChain中可以跳转的节点比较少,就那三个。
  • 注意包装式钩子和节点钩子原理是不一样的,他们的入参、返回处理也是不一样,需要注意

7.3 继承AgentMiddleware 实现中间件

AgentMiddleware是LangChain提供的一个帮助实现中间件的类,他里面有这些方法(异步版本只是方法名前增加a):

节点式钩子 (Node-style Hooks)

这些钩子按顺序在特定执行点运行,适合做日志、验证和状态更新。

  • before_agent(state, runtime): Agent 启动前执行,每次调用仅一次。
  • before_model(state, runtime): 每次调用模型前执行。
  • after_model(state, runtime): 每次模型返回响应后执行。
  • after_agent(state, runtime): Agent 完成后执行,每次调用仅一次。
    包裹式钩子 (Wrap-style Hooks)
    这些钩子会"包裹"模型或工具的调用,让你能完全控制其执行过程,例如实现重试、缓存或短路。
  • wrap_model_call(request, handler): 包裹每次模型调用。
  • wrap_tool_call(request, handler): 包裹每次工具调用

然后需要使用哪个场景,就去重写对应函数即可,如需使用多个就重写多个,传入中间件时还是传入自定义的类即可。

python 复制代码
from langgraph.prebuilt.tool_node import ToolCallRequest
from typing import TypedDict, Callable
from dotenv import load_dotenv
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import HumanInTheLoopMiddleware, ModelRetryMiddleware, TodoListMiddleware, \
    LLMToolEmulator, before_model, wrap_model_call, wrap_tool_call, AgentMiddleware, ModelRequest, ModelResponse
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.runtime import Runtime
from langgraph.types import Command

load_dotenv()

openai_model = init_chat_model(
    model="deepseek:deepseek-v4-flash",
    temperature=1,
    base_url="https://api.deepseek.com/",
    extra_body={"thinking": {"type": "disabled"}},
    profile={"max_input_tokens": 1000}
)

class ResponseFormat(TypedDict):
    name: str
    age: str



class MyMiddleware(AgentMiddleware):

    def before_agent(self, state: AgentState, runtime: Runtime):
        print("agent执行前检查")
        return None

    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        print("模型执行前调用")
        resp = handler(request)
        print("模型执行后调用")
        return resp

@tool
def get_weather(city:str):
    """
    获取天气
    Args:
        city: 城市
    """
    return "天气晴朗"

@tool
def get_news(topic:str):
    """
    获取新闻
    Args:
        topic: 新闻主题
    """

    return f"{topic}的新闻是:AI发展迅速"


store = InMemorySaver()

agent = create_agent(
    model=openai_model,
    tools=[get_weather, get_news],
    name="贾维斯-2号",
    system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
    checkpointer=store,
    middleware=[
        MyMiddleware()
    ]
    # response_format=ToolStrategy(ResponseFormat)
)

response = agent.invoke(
        input={"messages":[HumanMessage(content="帮我看下今天纽约天气")]},
        config={"configurable":{
            "thread_id":"chat--001008"
        }}
)
print(response)

简单的逻辑可以使用装饰器实现,如果逻辑复杂建议使用继承方式,此外如果官方的能用就用官方的,不能用再自己写。

8.多中间件组合使用

多中间件使用场景还是很普遍的,两个关注点

  • 1.多中间件顺序很重要,需要区分哪个需要在前,哪个在后更合适
  • 2.多中间件执行顺序类似套娃
    如下举例:
python 复制代码
from langgraph.prebuilt.tool_node import ToolCallRequest
from typing import TypedDict, Callable
from dotenv import load_dotenv
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import HumanInTheLoopMiddleware, ModelRetryMiddleware, TodoListMiddleware, \
    LLMToolEmulator, before_model, wrap_model_call, wrap_tool_call, AgentMiddleware, ModelRequest, ModelResponse
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.runtime import Runtime
from langgraph.types import Command

load_dotenv()

openai_model = init_chat_model(
    model="deepseek:deepseek-v4-flash",
    temperature=1,
    base_url="https://api.deepseek.com/",
    extra_body={"thinking": {"type": "disabled"}},
    profile={"max_input_tokens": 1000}
)

class ResponseFormat(TypedDict):
    name: str
    age: str



class Middleware1(AgentMiddleware):
    def before_model(self, state, runtime):
        print("[中间件1] before_model")
        return None

    def after_model(self, state, runtime):
        print("[中间件1] after_model")
        return None

class Middleware2(AgentMiddleware):
    def before_model(self, state, runtime):
        print("[中间件2] before_model")
        return None

    def after_model(self, state, runtime):
        print("[中间件2] after_model")
        return None

class Middleware3(AgentMiddleware):
    def before_model(self, state, runtime):
        print("[中间件3] before_model")
        return None

    def after_model(self, state, runtime):
        print("[中间件3] after_model")
        return None

@tool
def get_weather(city:str):
    """
    获取天气
    Args:
        city: 城市
    """
    return "天气晴朗"

@tool
def get_news(topic:str):
    """
    获取新闻
    Args:
        topic: 新闻主题
    """

    return f"{topic}的新闻是:AI发展迅速"


store = InMemorySaver()

agent = create_agent(
    model=openai_model,
    tools=[get_weather, get_news],
    name="贾维斯-2号",
    system_prompt="你是一个智能助手,你的任务是根据用户的问题,调用工具来回答用户的问题",
    checkpointer=store,
    middleware=[
        Middleware1(),Middleware2(),Middleware3()
    ]
    # response_format=ToolStrategy(ResponseFormat)
)

response = agent.invoke(
        input={"messages":[HumanMessage(content="你是谁")]},
        config={"configurable":{
            "thread_id":"chat--001008"
        }}
)
print(response)

四、上下文和记忆

上下文context,和短期记忆以及长期记忆完全使用的是LangGraph的,这里不重复写了,感兴趣可以看看这里。

LangGraph入门到精通

相关推荐
淘气的小猴子1 小时前
Python 魔术方法入门
python
我要见SA姐12 小时前
DeepSeek Harness 开源贡献手记:从 Issue 到 Merge 的完整旅程
ide·vscode·python·自动化·编辑器
余槐i2 小时前
从useState到Agent状态:React状态管理为何在AI工作流中失灵?
react·状态管理·ai agent·langgraph·tool calling
沉下心来学鲁班2 小时前
初识DeepAgents搭建第一个智能体
人工智能·python·langchain
带鱼吃猫2 小时前
LangGraph入门:支持搜索的智能代理系统
人工智能·langchain
ctlover3 小时前
数据结构:树
数据结构·python
BreezeJiang3 小时前
给 AI 补记性,别只会把历史一股脑塞 prompt:截断、总结、检索三条路怎么选
langchain
染指11104 小时前
118.Agent-LangChain核心组件-StructuredOutPut结构化输出-工具策略(ToolStrategy )
人工智能·langchain·agent·agents
触底反弹4 小时前
🐍 Python 语法全景图:一个 print() 引发的深度探索
python