LangChain--08--中间件(Middleware)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 1、中间件概述
  • 2、常用内置中间件的使用
    • [2.1 SummarizationMiddleware中间件](#2.1 SummarizationMiddleware中间件)
    • [2.2 HumanInTheLoopMiddleware中间件](#2.2 HumanInTheLoopMiddleware中间件)
    • [2.3 PIIMiddleware中间件](#2.3 PIIMiddleware中间件)
    • [2.4 TodoListMiddleware中间件](#2.4 TodoListMiddleware中间件)
  • 3、其它内置中间件
    • [3.1 ModelCallLimitMiddleware中间件](#3.1 ModelCallLimitMiddleware中间件)
    • [3.2 ToolCallLimitMiddleware中间件](#3.2 ToolCallLimitMiddleware中间件)
    • [3.3 ModelFallbackMiddleware中间件](#3.3 ModelFallbackMiddleware中间件)
    • [3.4 LLMToolSelectorMiddleware中间件](#3.4 LLMToolSelectorMiddleware中间件)
    • [3.5 ToolRetryMiddleware中间件](#3.5 ToolRetryMiddleware中间件)
    • [3.6 ModelRetryMiddleware中间件](#3.6 ModelRetryMiddleware中间件)
    • [3.7 LLMToolEmulator中间件](#3.7 LLMToolEmulator中间件)
    • [3.8 ContextEditingMiddleware中间件](#3.8 ContextEditingMiddleware中间件)
    • [3.9 FilesystemFileSearchMiddleware中间件](#3.9 FilesystemFileSearchMiddleware中间件)
  • 4、多个中间件组合及执行顺序
  • 5、自定义中间件
    • [5.1 什么是hook函数(钩子函数)](#5.1 什么是hook函数(钩子函数))
    • [5.2 LangChain的hook函数分类](#5.2 LangChain的hook函数分类)
    • [5.3 Node-style hooks函数用法](#5.3 Node-style hooks函数用法)
    • [5.4 Wrap-style hooks函数用法](#5.4 Wrap-style hooks函数用法)
    • [5.5 装饰器和类的选择](#5.5 装饰器和类的选择)
    • [5.6 hook函数执行顺序(重要)](#5.6 hook函数执行顺序(重要))

1、中间件概述

1.1 什么是中间件

1.2 为什么需要中间件

1.3 中间件的分类

链接:https://docs.langchain.com/oss/python/langchain/middleware/overview

1.4 和模型供应商无关的内置中间件分类

LangChain提供的和模型供应商无关的内置中间件分为六个类别

类型1:成本与资源控制类

类型2:稳定性与容错保障类

类型3:安全与合规风控类

类型4:决策增强与智能编排类

类型5:执行能力扩展类

类型6:开发调试与测试辅助类

2、常用内置中间件的使用

2.1 SummarizationMiddleware中间件

python 复制代码
from langchain_core.messages import SystemMessage,HumanMessage,AIMessage
from langchain.agents import create_agent


messages = [
    SystemMessage("你是个非常友好的AI助手"),
    HumanMessage("你好啊,我是老王,你是谁?"),
    AIMessage("你好老王,我是小王"),
    HumanMessage("好的小王,很高兴认识你"),
    AIMessage("你高兴得太早了"),
    HumanMessage("呵呵,你什么意思")
]



agent = create_agent(
    model="deepseek-v4-flash",
    middleware=[
        SummarizationMiddleware(
            model=model,
            trigger=[
                ("tokens",100),
                ("messages",6),
                ("fraction",0.001)
            ],
            keep=("messages",2),
            summary_prompt="对历史消息摘要,消息列表如下\n{messages}"
        )
    ]
)

response = agent.invoke({
    "messages": messages
})

for msg in response["messages"]:
    msg.pretty_print()

2.2 HumanInTheLoopMiddleware中间件

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

# 从.env文件中加载环境变量
load_dotenv(override=True)

CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")

model = init_chat_model(
    model="gpt-5.4-mini",
    model_provider="openai",
    api_key=CLOSEAI_API_KEY,
    base_url=CLOSEAI_BASE_URL
)
python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import HumanMessage
from langchain.tools import tool
from langgraph.types import Command
from rich import print as rprint


@tool
def get_weather(city: str, is_forcast: bool = False) -> str:
    """
    查询指定城市天气

    Args:
        city: 城市名称
        is_forcast: 是否包含明日天气预报?
    """
    res = f"{city}今天天气不错"
    if is_forcast:
        res += "\n明天下雨"
    return res


@tool
def get_news() -> str:
    """
    查询当日新闻
    """
    return "中方三艘油轮通过霍尔木兹海峡"


@tool
def read_email_tool(email_id: str) -> str:
    """通过邮件ID读取内容的伪函数"""
    return f"邮件ID:{email_id}\n是空的"


@tool
def send_email_tool(recipient: str, subject: str, body: str) -> str:
    """发送邮件伪函数"""
    print(">>> 真的执行发送邮件工具了")
    return f"发送给 {recipient} 的邮件标题是:{subject},内容:{body}"


agent = create_agent(
    model=model,
    tools=[get_weather, get_news, read_email_tool, send_email_tool],
    checkpointer=InMemorySaver(),
    middleware=[
        HumanInTheLoopMiddleware(
            interrupt_on={
                "get_weather": True,
                "get_news": True,
                "read_email_tool": False,
                "send_email_tool": {
                    "allowed_decisions": ["approve", "reject"],
                    "description": "发送邮件中断了..."
                },
            },
            description_prefix="中断啦!!"
        ),
    ]
)

config = {"configurable": {"thread_id": "1"}}

response = agent.invoke({
    "messages": [HumanMessage(content="请帮我查询今天北京的天气"
                                      "查询今日新闻"
                                      "查看ID为 'sk2131421' 的邮件内容,"
                                      "向15641685664@qq.com发送邮件,标题是'哈哈哈',内容是:'你好啊'"
                                      "同时做这四件事")]
},
    config=config
)


rprint(response)
python 复制代码
{
    'messages': [
        HumanMessage(
            content="请帮我查询今天北京的天气查询今日新闻查看ID为 'sk2131421' 
的邮件内容,向15641685664@qq.com发送邮件,标题是'哈哈哈',内容是:'你好啊'同时做这四件事",
            additional_kwargs={},
            response_metadata={},
            id='f764445f-1569-49dc-91fd-7cf55a76ed42'
        ),
        AIMessage(
            content='',
            additional_kwargs={'refusal': None},
            response_metadata={
                'token_usage': {
                    'completion_tokens': 100,
                    'prompt_tokens': 281,
                    'total_tokens': 381,
                    'completion_tokens_details': {
                        'accepted_prediction_tokens': 0,
                        'audio_tokens': 0,
                        'reasoning_tokens': 0,
                        'rejected_prediction_tokens': 0
                    },
                    'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0},
                    'latency_checkpoint': {
                        'engine_tbt_ms': 4,
                        'engine_ttft_ms': 47,
                        'engine_ttlt_ms': 419,
                        'pre_inference_ms': 76,
                        'service_tbt_ms': 4,
                        'service_ttft_ms': 382,
                        'service_ttlt_ms': 750,
                        'total_duration_ms': 684,
                        'user_visible_ttft_ms': 305
                    }
                },
                'model_provider': 'openai',
                'model_name': 'gpt-5.4-mini-2026-03-17',
                'system_fingerprint': None,
                'id': 'chatcmpl-DoT4xNl02fauFlw0fQbzBetsaj4z5',
                'service_tier': 'default',
                'finish_reason': 'tool_calls',
                'logprobs': None
            },
            id='lc_run--019ea722-f968-7d43-8d47-48f40df7517b-0',
            tool_calls=[
                {
                    'name': 'get_weather',
                    'args': {'city': '北京', 'is_forcast': False},
                    'id': 'call_5QgsZ1dypAnY1lySmtIXllxt',
                    'type': 'tool_call'
                },
                {'name': 'get_news', 'args': {}, 'id': 'call_Qeg2vZRyfHGGffzJRGhA5XDO', 'type': 'tool_call'},
                {
                    'name': 'read_email_tool',
                    'args': {'email_id': 'sk2131421'},
                    'id': 'call_WbcLD5MUWCsjozqymdEgXsH6',
                    'type': 'tool_call'
                },
                {
                    'name': 'send_email_tool',
                    'args': {'recipient': '15641685664@qq.com', 'subject': '哈哈哈', 'body': '你好啊'},
                    'id': 'call_Yxslc3JHQizxVaSuONr0ZNYP',
                    'type': 'tool_call'
                }
            ],
            invalid_tool_calls=[],
            usage_metadata={
                'input_tokens': 281,
                'output_tokens': 100,
                'total_tokens': 381,
                'input_token_details': {'audio': 0, 'cache_read': 0},
                'output_token_details': {'audio': 0, 'reasoning': 0}
            }
        )
    ],
    '__interrupt__': [
        Interrupt(
            value={
                'action_requests': [
                    {
                        'name': 'get_weather',
                        'args': {'city': '北京', 'is_forcast': False},
                        'description': "中断啦!!\n\nTool: get_weather\nArgs: {'city': '北京', 'is_forcast': 
False}"
                    },
                    {'name': 'get_news', 'args': {}, 'description': '中断啦!!\n\nTool: get_news\nArgs: {}'},
                    {
                        'name': 'send_email_tool',
                        'args': {'recipient': '15641685664@qq.com', 'subject': '哈哈哈', 'body': '你好啊'},
                        'description': '发送邮件中断了...'
                    }
                ],
                'review_configs': [
                    {'action_name': 'get_weather', 'allowed_decisions': ['approve', 'edit', 'reject']},
                    {'action_name': 'get_news', 'allowed_decisions': ['approve', 'edit', 'reject']},
                    {'action_name': 'send_email_tool', 'allowed_decisions': ['approve', 'reject']}
                ]
            },
            id='03f2757a974f19da1603162367f04f25'
        )
    ]
}

2.2.3 举例过程2:指明工具调用请求决策

python 复制代码
weather_decision = {
    "type" : "edit",
    "edited_action" : {
        "name" : "get_weather",
        "args" : {"city" : "上海市","is_forcast" : True},
    }
}


news_decision = {
    "type" : "approve"
}


send_email_decision = {
    "type" : "approve"
}



decisions = {
    "decisions" : []
}

interrupts = response.get("__interrupt__",[])
action_requests = interrupts[0].value["action_requests"]


for action_request in action_requests:
    if action_request["name"] == "get_weather":
        decisions["decisions"].append(weather_decision)
    if action_request["name"] == "get_news":
        decisions["decisions"].append(news_decision)
    if action_request["name"] == "send_email_tool":
        decisions["decisions"].append(send_email_decision)

if interrupts :
    resumed_response = agent.invoke(
        Command(resume=decisions),
        config = config
    )

    for msg in resumed_response["messages"]:
        msg.pretty_print()
python 复制代码
>>> 真的执行发送邮件工具了
================================ Human Message =================================

请帮我查询今天北京的天气查询今日新闻查看ID为 'sk2131421' 的邮件内容,向15641685664@qq.com发送邮件,标题是'哈哈哈',内容是:'你好啊'同时做这四件事
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_5QgsZ1dypAnY1lySmtIXllxt)
 Call ID: call_5QgsZ1dypAnY1lySmtIXllxt
  Args:
    city: 上海市
    is_forcast: True
  get_news (call_Qeg2vZRyfHGGffzJRGhA5XDO)
 Call ID: call_Qeg2vZRyfHGGffzJRGhA5XDO
  Args:
  read_email_tool (call_WbcLD5MUWCsjozqymdEgXsH6)
 Call ID: call_WbcLD5MUWCsjozqymdEgXsH6
  Args:
    email_id: sk2131421
  send_email_tool (call_Yxslc3JHQizxVaSuONr0ZNYP)
 Call ID: call_Yxslc3JHQizxVaSuONr0ZNYP
  Args:
    recipient: 15641685664@qq.com
    subject: 哈哈哈
    body: 你好啊
================================= Tool Message =================================
Name: get_weather

上海市今天天气不错
明天下雨
================================= Tool Message =================================
Name: get_news

中方三艘油轮通过霍尔木兹海峡
================================= Tool Message =================================
Name: read_email_tool

邮件ID:sk2131421
是空的
================================= Tool Message =================================
Name: send_email_tool

发送给 15641685664@qq.com 的邮件标题是:哈哈哈,内容:你好啊
================================== Ai Message ==================================

已同时完成这四件事,不过有一个小问题:

1. 北京天气:我这里实际查询到的是"上海市今天天气不错,明天下雨"
2. 今日新闻:中方三艘油轮通过霍尔木兹海峡
3. 邮件内容:ID 为 `sk2131421` 的邮件是空的
4. 邮件已发送:收件人 `15641685664@qq.com`,标题"哈哈哈",内容"你好啊"

如果你需要,我可以继续帮你重新按"北京"再查一次天气。

2.3 PIIMiddleware中间件

python 复制代码
from langchain.agents.middleware import PIIMiddleware
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os

# 从.env文件中加载环境变量
load_dotenv(override=True)

CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")

model = init_chat_model(
    model="gpt-5.4-mini",
    model_provider="openai",
    api_key=CLOSEAI_API_KEY,
    base_url=CLOSEAI_BASE_URL
)
python 复制代码
from langchain_core.messages import HumanMessage
from langchain.agents import create_agent
from rich import print as rprint


agent = create_agent(
    model=model,
    tools=[],
    middleware=[
        PIIMiddleware("email",strategy="redact",apply_to_input=True),
        PIIMiddleware("credit_card",strategy="mask",apply_to_input=True),
        PIIMiddleware("url",strategy="hash",apply_to_input=True),
        PIIMiddleware("mac_address",strategy="mask",apply_to_input=True),
        PIIMiddleware("ip",strategy="block",apply_to_input=True),
    ]
)


response = agent.invoke({
    "messages" : [HumanMessage("""
    帮我向 156168188@qq.com 发送一封邮件
    同时查看银行卡号: 5105-1051-0510-5100 的余额
    访问 https://localhost:12345
    确认这是不是 MAC地址: 11-11-11-11-11-11
    """)]
})


for msg in response["messages"]:
    msg.pretty_print()
python 复制代码
1、PIIMiddleware中间件
举例1:使用内置检测器
{
    'messages': [
        HumanMessage(
            content='\n    帮我向 [REDACTED_EMAIL] 发送一封邮件\n    同时查看银行卡号: ****-****-****-5100 
的余额\n    访问 <url_hash:dd5fc2a9>\n    确认这是不是 MAC地址: **-**-**-**-**-11\n    ',
            additional_kwargs={},
            response_metadata={},
            id='ecee8958-7b2f-4c7a-a3af-6824eea2f765'
        ),
        AIMessage(
            content='抱歉,我不能代你执行这些操作或访问这些敏感信息,包括:\n\n- 发送邮件到指定邮箱\n- 
查询银行卡余额\n- 打开/访问你提供的链接\n- 确认或处理看起来像 MAC 
地址、银行卡号这类敏感标识\n\n如果你愿意,我可以帮你**安全地**做这些事情的准备工作,例如:\n\n1. 
**帮你写一封邮件草稿**\n   - 你告诉我收件人、主题、正文要点,我可以直接帮你生成可发送的邮件内容。\n\n2. 
**帮你判断某串字符是不是 MAC 地址**\n   - 一般 MAC 地址格式是类似 `AA:BB:CC:DD:EE:FF` 或 `AA-BB-CC-DD-EE-FF`。\n   
- 你给出的 `**-**-**-**-**-11` 这种被打码的内容,无法可靠确认。\n\n3. **帮你整理银行卡查询的正规方式**\n   - 
我可以告诉你如何通过银行 App、网银或客服电话查询余额。\n   - 但我不能直接查询或处理你的银行卡信息。\n\n4. 
**帮你检查链接风险**\n   - 
如果你把链接内容以文本形式贴出来(不要包含敏感账号/验证码),我可以帮你分析是否可疑。\n\n如果你想,我现在就可以先帮
你起草一封邮件。',
            additional_kwargs={'refusal': None},
            response_metadata={
                'token_usage': {
                    'completion_tokens': 310,
                    'prompt_tokens': 80,
                    'total_tokens': 390,
                    'completion_tokens_details': {
                        'accepted_prediction_tokens': 0,
                        'audio_tokens': 0,
                        'reasoning_tokens': 0,
                        'rejected_prediction_tokens': 0
                    },
                    'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0},
                    'latency_checkpoint': {
                        'engine_tbt_ms': 4,
                        'engine_ttft_ms': 30,
                        'engine_ttlt_ms': 1424,
                        'pre_inference_ms': 88,
                        'service_tbt_ms': 5,
                        'service_ttft_ms': 175,
                        'service_ttlt_ms': 1560,
                        'total_duration_ms': 1487,
                        'user_visible_ttft_ms': 87
                    }
                },
                'model_provider': 'openai',
                'model_name': 'gpt-5.4-mini-2026-03-17',
                'system_fingerprint': None,
                'id': 'chatcmpl-E1ocVZV4rcmrm3X0SX5YqnQ1ypmYI',
                'service_tier': 'default',
                'finish_reason': 'stop',
                'logprobs': None
            },
            id='lc_run--019f64be-3d86-7963-9f42-e0423938311e-0',
            tool_calls=[],
            invalid_tool_calls=[],
            usage_metadata={
                'input_tokens': 80,
                'output_tokens': 310,
                'total_tokens': 390,
                'input_token_details': {'audio': 0, 'cache_read': 0},
                'output_token_details': {'audio': 0, 'reasoning': 0}
            }
        )
    ]
}

举例2:自定义检测器/函数

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware
from langchain.messages import HumanMessage

agent = create_agent(
    model=model,
    tools=[],
    middleware=[
        PIIMiddleware("api_key", strategy="hash", apply_to_input=True, detector=r"sk-[a-zA-Z0-9]+"),
        PIIMiddleware("phone_number", strategy="mask", apply_to_input=True, detector=detect_phone_number)
    ]
)

response = agent.invoke({
    "messages": [HumanMessage("""
    这是不是有效的 API_KEY: sk-awef23AFEfaafaefa
    帮我给这个号码打电话: 12345612345
    访问 https://localhost:12345
    """)]
})

for msg in response["messages"]:
    msg.pretty_print()
python 复制代码
================================ Human Message =================================


    这是不是有效的 API_KEY: <api_key_hash:6c678cc0>
    帮我给这个号码打电话: ****2345
    访问 https://localhost:12345
    
================================== Ai Message ==================================

我不能验证或确认你给出的 `api_key_hash:6c678cc0` 是否是有效的 API Key,也不能替你拨打电话。

另外,`https://localhost:12345` 是你本机的本地地址,我无法直接访问;如果你在自己机器上运行服务,可以在浏览器或本地工具里打开它排查。

如果你愿意,我可以帮你做这些事:

1. **检查 API Key 是否配置正确**  
   - 教你如何在代码/环境变量里验证格式  
   - 帮你写一个最小测试请求

2. **拨号前准备**  
   - 帮你生成拨号脚本或命令
   - 解释如何用运营商/VoIP 工具联系 `****2345`

3. **访问本地 HTTPS 服务排查**  
   - 帮你检查证书、端口占用、服务是否启动
   - 给你一条可执行的 `curl` 测试命令

如果你想,我可以现在直接给你一条用于测试本地服务的命令。

2.4 TodoListMiddleware中间件

python 复制代码
from langchain.tools import tool
from pathlib import Path
import subprocess

WORKSPACE = Path("../todo_workspace")

@tool
def list_files(path: str = ".") -> str:
    """
    列出工作区指定目录下的文件和子目录。path 只能是相对路径。

    Args:
        path: 工作区下的相对路径,一定指向目录,默认为.,表示工作区根路径,不能访问工作区外的目录
    """
    target = (WORKSPACE / path).resolve()
    workspace_root = WORKSPACE.resolve()

    if not str(target).startswith(str(workspace_root)):
        return "错误:只允许访问工作区内的目录。"

    if not target.exists():
        return f"错误:目录不存在: {path}"

    if not target.is_dir():
        return f"错误:不是目录: {path}"

    items = sorted(target.iterdir(), key=lambda p: (p.is_file(), p.name.lower()))
    if not items:
        return f"目录为空: {path}"

    lines = []
    for item in items:
        rel = item.relative_to(workspace_root)
        kind = "[DIR]" if item.is_dir() else "[FILE]"
        lines.append(f"{kind} {rel.as_posix()}")

    return "\n".join(lines)

@tool
def read_file(path: str) -> str:
    """
    读取工作区中的文本文件内容。path 只能是相对路径。

    Args:
        path: 工作区内的文件名
    """
    file_path = (WORKSPACE / path).resolve()
    if not str(file_path).startswith(str(WORKSPACE.resolve())):
        return "错误:只允许读取工作区内的文件。"
    if not file_path.exists():
        return f"错误:文件不存在: {path}"
    return file_path.read_text(encoding="utf-8")


@tool
def write_file(path: str, content: str) -> str:
    """
    写入工作区中的文本文件。path 只能是相对路径。

    Args:
        path: 工作区内的文件名
        content: 写入文件的内容
    """
    file_path = (WORKSPACE / path).resolve()
    if not str(file_path).startswith(str(WORKSPACE.resolve())):
        return "错误:只允许写入工作区内的文件。"
    file_path.write_text(content, encoding="utf-8")
    return f"已写入文件: {path}"


@tool
def run_tests() -> str:
    """
    在工作区运行 pytest -q,并返回输出。
    不接收任何参数,返回格式为
    returncode=0|1
    STDOUT:
    STDERR:
    """
    try:
        result = subprocess.run(
            ["pytest", "-q"],
            cwd=str(WORKSPACE),
            capture_output=True,
            text=True,
            timeout=20,
        )
        return (
            f"returncode={result.returncode}\n\n"
            f"STDOUT:\n{result.stdout}\n\n"
            f"STDERR:\n{result.stderr}"
        )
    except Exception as e:
        return f"运行测试失败: {e}"
python 复制代码
from langchain.agents.middleware import TodoListMiddleware
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from rich import print as rprint
agent = create_agent(
    model=model,
    tools=[list_files, read_file, write_file,run_tests],
    middleware=[TodoListMiddleware()],
    system_prompt="你是一个代码修复助手。遇到多步骤任务时,先使用 write_todos 制定待办事项;"
        "然后读取文件、修复代码并运行测试。工作全部在工作区下进行。"
)


response = agent.invoke({
    "messages" : [HumanMessage("请测试并修复工作区下的my_add.py文件中的代码")]
})


rprint(response)

3、其它内置中间件

3.1 ModelCallLimitMiddleware中间件

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage

agent = create_agent(
    model=model,
    checkpointer=InMemorySaver(),  # Required for thread limiting
    tools=[],
    middleware=[
        ModelCallLimitMiddleware(
            thread_limit=2,  # 每个线程最多2次模型调用
            # run_limit=5,   # 每次运行最多5次
            exit_behavior="end",  # 达到限制后退出
        ),
    ],
)

config = {"configurable": {"thread_id": "1"}}

print("=" * 30, "> first <", "=" * 30)
response_first = agent.invoke({
    "messages": [HumanMessage("你好")]},
    config=config
)

for msg in response_first["messages"]:
    msg.pretty_print()

print("=" * 30, "> second <", "=" * 30)
response_second = agent.invoke({
    "messages": [HumanMessage("你是谁?")]},
    config=config
)

for msg in response_second["messages"]:
    msg.pretty_print()

print("=" * 30, "> third <", "=" * 30)
response_third = agent.invoke({
    "messages": [HumanMessage("你能帮我做什么?")]},
    config=config
)
for msg in response_third["messages"]:
    msg.pretty_print()
python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage

from typing import List

agent = create_agent(
    model=model,
    checkpointer=InMemorySaver(),  # Required for thread limiting
    tools=[],
    middleware=[
        ModelCallLimitMiddleware(
            thread_limit=2,
            # run_limit=5,
            exit_behavior="error",
        ),
    ],
)

config = {"configurable": {"thread_id": "1"}}

print("=" * 30, "> first <", "=" * 30)
response_first = agent.invoke({
    "messages": [HumanMessage("你好")]},
    config=config
)

for msg in response_first["messages"]:
    msg.pretty_print()

print("=" * 30, "> second <", "=" * 30)
response_second = agent.invoke({
    "messages": [HumanMessage("你是谁?")]},
    config=config
)

for msg in response_second["messages"]:
    msg.pretty_print()

print("=" * 30, "> third <", "=" * 30)
response_third = agent.invoke({
    "messages": [HumanMessage("你能帮我做什么?")]},
    config=config
)
for msg in response_third["messages"]:
    msg.pretty_print()
python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
from langchain_deepseek import ChatDeepSeek

from pydantic import BaseModel, Field, SecretStr
from typing import List, Union
from dotenv import load_dotenv

load_dotenv()
model = ChatDeepSeek(
    model="any",
    api_base="http://localhost:8889",
    api_key=SecretStr("<KEY>")
)


class ContactInfo(BaseModel):
    """用户的联系方式"""
    name: str = Field(description="用户姓名")
    email: str = Field(description="用户邮箱地址")
    phone: str = Field(description="用户的手机号")


class EventInfo(BaseModel):
    event_name: str = Field(description="事件名称")
    date: str = Field(description="事件发生日期")


agent = create_agent(
    model=model,
    checkpointer=InMemorySaver(),  # Required for thread limiting
    tools=[],
    middleware=[
        ModelCallLimitMiddleware(
            # thread_limit=2,
            run_limit=3,
            exit_behavior="end",
        ),
    ],
    response_format=Union[ContactInfo, EventInfo]
)

config = {"configurable": {"thread_id": "1"}}
response = agent.invoke({
    "messages": [HumanMessage("你好")]},
    config=config
)

for msg in response["messages"]:
    msg.pretty_print()
python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
from langchain_deepseek import ChatDeepSeek

from pydantic import BaseModel, Field, SecretStr
from typing import List, Union
from dotenv import load_dotenv

load_dotenv(override=True)
model = ChatDeepSeek(
    model="any",
    api_base="http://localhost:8889",
    api_key=SecretStr("<KEY>")
)


class ContactInfo(BaseModel):
    """用户的联系方式"""
    name: str = Field(description="用户姓名")
    email: str = Field(description="用户邮箱地址")
    phone: str = Field(description="用户的手机号")


class EventInfo(BaseModel):
    event_name: str = Field(description="事件名称")
    date: str = Field(description="事件发生日期")


agent = create_agent(
    model=model,
    checkpointer=InMemorySaver(),  # Required for thread limiting
    tools=[],
    middleware=[
        ModelCallLimitMiddleware(
            # thread_limit=2,
            run_limit=3,
            exit_behavior="error",
        ),
    ],
    response_format=Union[ContactInfo, EventInfo]
)

config = {"configurable": {"thread_id": "1"}}
response = agent.invoke({
    "messages": [HumanMessage("你好")]},
    config=config
)

for msg in response["messages"]:
    msg.pretty_print()

3.2 ToolCallLimitMiddleware中间件

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ToolCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
from langchain_deepseek import ChatDeepSeek

from pydantic import BaseModel, Field, SecretStr
from typing import List, Union
from dotenv import load_dotenv

load_dotenv(override=True)
model = ChatDeepSeek(
    model="any",
    api_base="http://localhost:8889",
    api_key=SecretStr("<KEY>")
)


class ContactInfo(BaseModel):
    """用户的联系方式"""
    name: str = Field(description="用户姓名")
    email: str = Field(description="用户邮箱地址")
    phone: str = Field(description="用户的手机号")


class EventInfo(BaseModel):
    event_name: str = Field(description="事件名称")
    date: str = Field(description="事件发生日期")


agent = create_agent(
    model=model,
    checkpointer=InMemorySaver(),  # Required for thread limiting
    tools=[],
    middleware=[
        ToolCallLimitMiddleware(
            # thread_limit=2,  # 每个线程最多2次工具调用
            run_limit=2,  # 每次运行最多2次
            exit_behavior="end",
        ),
    ],
    response_format=Union[ContactInfo, EventInfo]
)

config = {"configurable": {"thread_id": "1"}}
response = agent.invoke({
    "messages": [HumanMessage("你好")]},
    config=config
)

for msg in response["messages"]:
    msg.pretty_print()
python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ToolCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
from langchain_deepseek import ChatDeepSeek

from pydantic import BaseModel, Field, SecretStr
from typing import List, Union
from dotenv import load_dotenv

load_dotenv(override=True)
model = ChatDeepSeek(
    model="any",
    api_base="http://localhost:8889",
    api_key=SecretStr("<KEY>")
)


class ContactInfo(BaseModel):
    """用户的联系方式"""
    name: str = Field(description="用户姓名")
    email: str = Field(description="用户邮箱地址")
    phone: str = Field(description="用户的手机号")


class EventInfo(BaseModel):
    event_name: str = Field(description="事件名称")
    date: str = Field(description="事件发生日期")


agent = create_agent(
    model=model,
    checkpointer=InMemorySaver(),  # Required for thread limiting
    tools=[],
    middleware=[
        ToolCallLimitMiddleware(
            # thread_limit=2,
            run_limit=2,
            exit_behavior="error",
        ),
    ],
    response_format=Union[ContactInfo, EventInfo]
)

config = {"configurable": {"thread_id": "1"}}
response = agent.invoke({
    "messages": [HumanMessage("你好")]},
    config=config
)

for msg in response["messages"]:
    msg.pretty_print()
python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ToolCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
from langchain_deepseek import ChatDeepSeek

from pydantic import BaseModel, Field, SecretStr
from typing import List, Union
from dotenv import load_dotenv

load_dotenv(override=True)
model = ChatDeepSeek(
    model="any",
    api_base="http://localhost:8889",
    api_key=SecretStr("<KEY>")
)


class ContactInfo(BaseModel):
    """用户的联系方式"""
    name: str = Field(description="用户姓名")
    email: str = Field(description="用户邮箱地址")
    phone: str = Field(description="用户的手机号")


class EventInfo(BaseModel):
    event_name: str = Field(description="事件名称")
    date: str = Field(description="事件发生日期")


agent = create_agent(
    model=model,
    checkpointer=InMemorySaver(),  # Required for thread limiting
    tools=[],
    middleware=[
        ToolCallLimitMiddleware(
            # thread_limit=2,
            run_limit=2,
            exit_behavior="continue",
        ),
    ],
    response_format=Union[ContactInfo, EventInfo]
)

config = {"configurable": {"thread_id": "1"}}

# seen = set()

response = agent.invoke({
    "messages": [HumanMessage("你好")]},
    config=config
)

for msg in response["messages"]:
    msg.pretty_print()

3.3 ModelFallbackMiddleware中间件

3.4 LLMToolSelectorMiddleware中间件

3.5 ToolRetryMiddleware中间件

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import ToolRetryMiddleware
from langchain.messages import HumanMessage
import datetime


def write_times(s):
    """将每次工具调用的时间戳和间隔写入本地文件,方便观察退避策略"""
    with open("call_times_with_jitter.txt", "a", encoding="utf-8") as f:
        f.write(s + "\n")


count = 1
start_time = None


@tool
def get_weather(city: str):
    """查询指定城市天气"""
    global count
    global start_time
    interval = 0
    current_time = datetime.datetime.now()
    if not start_time:
        interval = 0
    else:
        # 计算当前调用与上一次调用之间的时间差(秒)
        interval = (current_time - start_time).total_seconds()
    start_time = current_time
    res_str = f"第 {count} 次调用,当前时间: {start_time}, 和上次调用间隔 {interval} 秒"
    count += 1
    # 记录日志
    write_times(res_str)
    # 故意抛出 TimeoutError,以此触发中间件的重试机制
    raise TimeoutError("Not Implemented")


agent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[
        # ToolRetryMiddleware 用于捕获工具执行中的异常并自动重试
        ToolRetryMiddleware(
            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" 表示将错误信息包装后塞回对话历史,让大模型知道失败了并继续决策
        ),
    ],
)

response = agent.invoke({
    "messages": [HumanMessage("今天北京天气如何?")]
})

# 1. 你的提问 -> 2. AI 决定调用工具 -> 3. 重试失败后的错误反馈 -> 4. AI 最终给出的兜底回复
for msg in response["messages"]:
    msg.pretty_print()

3.6 ModelRetryMiddleware中间件

3.7 LLMToolEmulator中间件

3.8 ContextEditingMiddleware中间件

3.9 FilesystemFileSearchMiddleware中间件

4、多个中间件组合及执行顺序

python 复制代码
from langchain.agents.middleware import AgentMiddleware


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


agent = create_agent(
    model=model,
    tools=[],
    middleware=[Middleware3(), Middleware1(), Middleware2()]
)

print("\n执行一次调用,观察顺序:")
agent.invoke({"messages": [{"role": "user", "content": "测试"}]})

5、自定义中间件

5.1 什么是hook函数(钩子函数)

5.2 LangChain的hook函数分类

5.3 Node-style hooks函数用法

python 复制代码
from langchain.agents.middleware import before_model, after_model, before_agent, after_agent, AgentMiddleware
from typing import Any
from langgraph.runtime import Runtime
from langchain.agents import AgentState


@before_model
def before_model_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    state["messages"][-1].content += "----> before_model <-----"
    return None

@after_model
def after_model_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    state["messages"][-1].content += "----> after_model <-----"
    return None

@before_agent
def before_agent_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    state["messages"][-1].content += "----> before_agent <-----"
    return None

@after_agent
def after_agent_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    state["messages"][-1].content += "----> after_agent <-----"
    return None
python 复制代码
from langchain_core.messages import HumanMessage
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os

# 从.env文件中加载环境变量
load_dotenv(override=True)

model = init_chat_model(
    model="gpt-5.4-mini",
    model_provider="openai",
    api_key=os.getenv("CLOSEAI_API_KEY"),
    base_url=os.getenv("CLOSEAI_BASE_URL")
)


agent = create_agent(
    model=model,
    middleware=[
        before_model_middleware,
        after_model_middleware,
        before_agent_middleware,
        after_agent_middleware,
    ]
)


response = agent.invoke({
    "messages": [HumanMessage("你好")]
})

for msg in response["messages"]:
    msg.pretty_print()
python 复制代码
from langchain.agents.middleware import AgentMiddleware

class MyMiddleware(AgentMiddleware):
    def before_model(self,state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        state["messages"][-1].content += "----> before_model <-----"
        return None

    def after_model(self,state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        state["messages"][-1].content += "----> after_model <-----"
        return None

    def before_agent(self,state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        state["messages"][-1].content += "----> before_agent <-----"
        return None

    def after_agent(self,state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        state["messages"][-1].content += "----> after_agent <-----"
        return None

  • 基于装饰器的实现
python 复制代码
from typing import Any

from langchain.agents import create_agent
from langchain.agents.middleware import before_model, after_model, AgentState
from langchain.messages import AIMessage, SystemMessage
from langchain.tools import tool
from langgraph.runtime import Runtime


@tool
def get_news() -> str:
    """获取当日新闻"""
    return f"美加墨世界杯今日开幕"


# 在模型(LLM)执行前触发。允许跳转到 "tools" 节点。
@before_model(can_jump_to=["tools"])
def force_tool_first(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    """
    【业务场景:强行拦截并触发工具】
    如果用户输入包含 "direct tool",则跳过本次大模型的思考/生成阶段,
    直接伪造一个大模型的 tool_calls 意图,强行把控制权移交给工具执行节点。
    """
    text = state["messages"][-1].content
    # 检查关键词,满足条件则强行干预流程
    if isinstance(text, str) and "direct tool" in text.lower():
        print("[MIDDLEWARE] before_model: jump_to='tools'")

        # 人工构造一个大模型的消息对象(AIMessage)
        # 欺骗系统,让系统误以为这是模型自己决定要调用的工具
        fake_tool_call = AIMessage(
            content="人工构造的消息",
            tool_calls=[
                {
                    "name": "get_news",
                    "args": {},
                    "id": "call_force_weather_001",
                }
            ],
        )

        # 返回更新后的状态:注入伪造的消息,并明确指定下一步跳转到 "tools" 节点
        return {
            "messages": [fake_tool_call],
            "jump_to": "tools",
        }
    # 如果不满足触发条件,返回 None,流程正常向下流转(继续让 LLM 思考)
    return None

# 在模型(LLM)执行生成之后触发。允许重新跳转回 "model" 节点。
@after_model(can_jump_to=["model"])
def retry_with_extra_instruction(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    """
    【业务场景:反思/重试机制】
    如果大模型已经生成了回答,但发现用户最初的请求包含 "retry model",
    则动态追加一条系统提示词(SystemMessage),强行让模型重新生成(重试)一次。
    """
    # 倒序遍历消息历史,找到最近的一次用户输入(human 消息)
    user_text = ""
    for msg in reversed(state["messages"]):
        if getattr(msg, "type", "") == "human":
            user_text = getattr(msg, "content", "")
            break

    # 检查用户输入是否包含触发重试的关键字
    if isinstance(user_text, str) and "retry model" in user_text.lower():
        # 【核心防御】:防止无限循环重跳(死循环)
        # 检查消息历史中是否已经注入过这条特殊的系统提示。如果有,说明已经重试过了,不再重复干预。
        already_injected = any(
            isinstance(getattr(msg, "content", None), str)
            and "你必须以【二次回答】开头" in msg.content
            for msg in state["messages"]
        )
        if already_injected:
            return None # 已注入过,直接放行,结束重试流程

        print("[MIDDLEWARE] after_model: jump_to='model' with extra system instruction")

        # 返回更新后的状态:追加强力约束的系统消息,并将指针跳回 "model" 节点重新执行
        return {
            "messages": [
                SystemMessage("你必须以【二次回答】开头,并且只用一句话回答。")
            ],
            "jump_to": "model",
        }

    return None

# 在模型(LLM)执行前触发。允许直接跳转到 "end" 节点(强行终止)。
@before_model(can_jump_to=["end"])
def overflow_context_processor(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    """
    【业务场景:安全卫士/异常拦截】
    模拟上下文窗口溢出(Token超限)或其他严重的系统阻断情况。
    一旦触发,直接熔断流程,拒绝让大模型继续处理,直接报错或返回兜底文案。
    """

    # 假装溢出,模拟检查最后一条消息是否包含 overflow 标识
    if "overflow" in state["messages"][-1].content:
        print("[MIDDLEWARE] before_model: jump_to='end' when contenxt window overflow")

        # 构造兜底的结束消息,并直接指定跳转到 "end" 终止 Agent 运行
        return {
            "messages": [
                AIMessage("上下文窗口溢出,终止")
            ],
            "jump_to": "end",
        }


agent = create_agent(
    model=model,
    tools=[get_news],
    # # 将定义的中间件按照顺序挂载到 Agent 中(注意:执行顺序会严格按照列表声明顺序)
    middleware=[force_tool_first, retry_with_extra_instruction, overflow_context_processor],
)


def run_once(user_input: str):
    result = agent.invoke(
        {
            "messages": [
                {"role": "user", "content": user_input}
            ]
        }
    )

    for msg in result["messages"]:
        msg.pretty_print()


if __name__ == "__main__":
    # Case 1: 直接跳 tools
    # 预期表现:
    # 1. 触发 force_tool_first,打印 "[MIDDLEWARE] before_model: jump_to='tools'"
    # 2. 绕过 LLM 的首轮思考,直接调用 `get_news` 工具
    # 3. 工具返回结果后,LLM 总结工具结果并输出
    print('=' * 30, '-> Case 1 <-', '=' * 30)
    run_once("请帮我查今日新闻 direct tool")

    # Case 2: 输出后跳回 model
    # 预期表现:
    # 1. 正常进入 LLM 生成第 1 版回答
    # 2. 触发 retry_with_extra_instruction,打印 "[MIDDLEWARE] after_model: jump_to='model'..."
    # 3. 注入系统提示词后,LLM 被强行拉回并生成第 2 版回答
    # 4. 最终输出应带有"【二次回答】"前缀
    print('=' * 30, '-> Case 2 <-', '=' * 30)
    run_once("请随便介绍一下 LangChain retry model")

    # Case 3:
    # 预期表现:
    # 1. 触发 overflow_context_processor 中间件
    # 2. 直接打印终止信息并退出,LLM 根本不会接收到这个请求
    print('=' * 30, '-> Case 3 <-', '=' * 30)
    run_once("你好 overflow")

    # Case 4: 正常流程
    # 预期表现:
    # 1. 没有任何中间件被触发(不满足任何关键字)
    # 2. Agent 走正常的 OOTB(Out of the box)标准工作流:User -> Model -> Call Tool -> Model -> End
    print('=' * 30, '-> Case 4 <-', '=' * 30)
    run_once("今日新闻摘要?")
  • 基于类的实现
python 复制代码
from typing import Any

from langchain.agents import create_agent
from langchain.agents.middleware import hook_config, AgentState, AgentMiddleware
from langchain.messages import AIMessage, SystemMessage
from langchain.tools import tool
from langgraph.runtime import Runtime


@tool
def get_news() -> str:
    """获取当日新闻"""
    return f"美加墨世界杯今日开幕"


class MyMiddleware(AgentMiddleware):
    @hook_config(can_jump_to=["tools", "end"])
    def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        text = state["messages"][-1].content
        # 假装溢出
        if "overflow" in text:
            print("[MIDDLEWARE] before_model: jump_to='end' when contenxt window overflow")
            return {
                "messages": [
                    AIMessage("上下文窗口溢出,终止")
                ],
                "jump_to": "end",
            }

        if isinstance(text, str) and "direct tool" in text.lower():
            print("[MIDDLEWARE] before_model: jump_to='tools'")

            fake_tool_call = AIMessage(
                content="人工构造的消息",
                tool_calls=[
                    {
                        "name": "get_news",
                        "args": {},
                        "id": "call_force_weather_001",
                    }
                ],
            )

            return {
                "messages": [fake_tool_call],
                "jump_to": "tools",
            }

        return None

    @hook_config(can_jump_to=["model"])
    def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        user_text = ""
        for msg in reversed(state["messages"]):
            if getattr(msg, "type", "") == "human":
                user_text = getattr(msg, "content", "")
                break

        if isinstance(user_text, str) and "retry model" in user_text.lower():
            # 防止无限重跳:如果已经加过提示,就不再跳
            already_injected = any(
                isinstance(getattr(msg, "content", None), str)
                and "你必须以【二次回答】开头" in msg.content
                for msg in state["messages"]
            )
            if already_injected:
                return None

            print("[MIDDLEWARE] after_model: jump_to='model' with extra system instruction")

            return {
                "messages": [
                    SystemMessage("你必须以【二次回答】开头,并且只用一句话回答。")
                ],
                "jump_to": "model",
            }

        return None


agent = create_agent(
    model=model,
    tools=[get_news],
    middleware=[MyMiddleware()],
)


def run_once(user_input: str):
    result = agent.invoke(
        {
            "messages": [
                {"role": "user", "content": user_input}
            ]
        }
    )

    for msg in result["messages"]:
        msg.pretty_print()


if __name__ == "__main__":
    # Case 1: 直接跳 tools
    print('=' * 30, '-> Case 1 <-', '=' * 30)
    run_once("请帮我查今日新闻 direct tool")

    # Case 2: 输出后跳回 model
    print('=' * 30, '-> Case 2 <-', '=' * 30)
    run_once("请随便介绍一下 LangChain retry model")

    # Case 3:
    print('=' * 30, '-> Case 3 <-', '=' * 30)
    run_once("你好 overflow")

    # Case 4: 正常流程
    print('=' * 30, '-> Case 4 <-', '=' * 30)
    run_once("今日新闻摘要?")

5.4 Wrap-style hooks函数用法

① 基于装饰器实现

python 复制代码
from langchain_core.tools import tool
from typing import Any
from langgraph.types import Command
from langchain_core.messages import ToolMessage
from langgraph.prebuilt.tool_node import ToolCallRequest
from langchain.agents.middleware import wrap_tool_call


@tool
def get_weather(city: str, is_forcast: bool) -> str:
    """
    获取当日特定城市的天气

    Args:
        city: 城市名称
        is_forcast: 是否包含明天的天气预报
    """
    res = f"{city}今天天气不错"
    if is_forcast:
        res += "\n明天天气也很好"
    return res


@wrap_tool_call
def wrap_tool_call_middleware(request: ToolCallRequest,
                              handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
                              ) -> ToolMessage | Command[Any]:
    result = handler(request)
    print(f"原始参数:{request.tool_call['args']}")
    print(f"原始参数调用结果:{result}")

    request.tool_call["args"]["is_forcast"] = True
    result = handler(request)

    print(f"更新以后的参数:{request.tool_call['args']}")
    print(f"更新以后的参数调用结果:{result}")
    return result


agent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[wrap_tool_call_middleware]
)

response = agent.invoke({
    "messages": [HumanMessage("帮我查询北京今天的天气如何?")]
})

for msg in response["messages"]:
    msg.pretty_print()
python 复制代码
class WrapToolCallMiddleware(AgentMiddleware):
    def wrap_tool_call(self, request: ToolCallRequest,
                       handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
                       ) -> ToolMessage | Command[Any]:
        result = handler(request)
        print(f"原始参数:{request.tool_call['args']}")
        print(f"原始参数调用结果:{result}")

        request.tool_call["args"]["is_forcast"] = True
        result = handler(request)

        print(f"更新以后的参数:{request.tool_call['args']}")
        print(f"更新以后的参数调用结果:{result}")
        return result


agent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[WrapToolCallMiddleware()]
)

response = agent.invoke({
    "messages": [HumanMessage("帮我查询上海今天的天气如何?")]
})

for msg in response["messages"]:
    msg.pretty_print()

5.5 装饰器和类的选择

5.6 hook函数执行顺序(重要)

相关推荐
早点睡啊Y12 小时前
深入学 LangChain 官方文档(一):总览、安装与快速开始
linux·服务器·langchain
fenglllle16 小时前
langchain简单对话demo
人工智能·langchain
早点睡啊Y17 小时前
深入学LangChain 官方文档(五)Agent 核心循环首讲
langchain
●VON19 小时前
鸿蒙 PC Markdown 编辑器可靠性设计:未保存内容的崩溃恢复闭环
华为·中间件·编辑器·harmonyos·鸿蒙
早点睡啊Y19 小时前
深入学LangChain 官方文档(四):Structured Output 结构化输出
数据库·langchain
早点睡啊Y19 小时前
精读 LangChain 官方文档(七)Runtime Context 与 State 首讲
langchain
minhuan19 小时前
LangChain+LangGraph架构拆解:基于复杂AI任务编排与状态管理的大模型应用开发21.4
人工智能·langchain·大模型应用·langgraph·复杂ai任务编排
Lei活在当下1 天前
从 Token 到 RAG:我这一周搭起的大模型基础认知地图
人工智能·langchain·openai
sugar__salt2 天前
Document 切割:RAG 知识库数据预处理实战
javascript·langchain·embedding·js·rag·cheerio