LangChain-9-中间件middleware

Agent中间是什么?

简单来说,Agent中间件是一种软件设计模式,它像一个"套娃"一样包裹住Agent核心的执行流程。它在Agent执行的不同阶段(如收到用户请求时、调用大模型前/后、调用工具前/后)设置"钩子"(Hooks),让你可以插入自定义的逻辑来拦截、检查、修改、增强或终止Agent的行为。

它不仅解决单次执行的可靠性问题,其思想和机制已被各大主流框架采纳,作为构建多智能体协作系统的核心技术。

一个基础的Agent通常只负责"接收输入 -> 调用LLM -> 调用工具 -> 返回输出"。但在真实应用中,我们几乎总是需要处理一些与核心业务逻辑无关的"横切关注点",例如:

  • 日志记录:记录每次Agent调用的输入、输出、耗时,用于调试和监控。

  • 权限校验:在Agent执行动作前,检查当前用户是否有权限调用某个特定工具。

  • 缓存:对于相同的或相似的LLM请求,直接返回缓存结果,节省成本和延迟。

  • 限流/熔断:防止Agent过度调用昂贵的LLM或外部API。

  • 输入/输出预处理/后处理:例如对用户输入进行敏感词过滤,或将Agent的输出格式化为统一的JSON结构。

  • 错误处理与重试:当LLM调用或工具调用失败时,统一捕获异常并执行重试或降级策略。

如果没有中间件,这些逻辑就需要直接写在Agent的核心执行循环里,导致代码臃肿、难以维护、无法复用。中间件提供了一个优雅的解决方案:解耦横切关注点,并支持插件化、可组合的架构。

Middleware的核心工作原理

Agent中间件大多基于责任链模式(Chain of Responsibility) 实现。你可以把它想象成洋葱:

  • 请求从外向内,经过层层中间件的预处理。

  • 核心Agent执行完毕。

  • 响应再从内向外,经过层层中间件的后处理。

LangChain 官方内置中间件

LangChain 官方提供了丰富的预置中间件供你按需选用。下表汇总了目前常用的官方中间件,方便你查阅和快速找到需要的功能:

中间件名称 核心作用 典型应用场景
SummarizationMiddleware 自动压缩历史对话,防止上下文超限 客服机器人、长期对话,避免因上下文过长而报错或费用激增
HumanInTheLoopMiddleware 敏感操作前暂停,等待人工审批 金融转账、删除数据库等高危操作,增加安全阀
ModelCallLimitMiddleware 限制模型调用次数,防止无限循环 控制成本,防止 Agent 陷入无意义的自我循环
ToolCallLimitMiddleware 限制工具调用次数 避免高频调用第三方付费 API(如天气、股票接口)导致成本失控
ModelFallbackMiddleware 主模型故障时自动切换到备用模型 提升服务的高可用性,当 GPT-4 宕机时可降级到 GPT-3.5
ModelRetryMiddleware 模型调用失败时自动重试 应对网络波动或 LLM 服务商的临时限流
ToolRetryMiddleware 工具调用失败时自动重试 第三方 API 不稳定时,自动重新尝试获取数据
PIIMiddleware 识别并自动脱敏或屏蔽个人敏感信息 合规审查,自动掩盖日志中的手机号、身份证等信息
TodoListMiddleware 为 Agent 装备任务规划和追踪能力 处理多步骤复杂任务时,保持条理和专注,不遗漏关键步骤
LLMToolSelectorMiddleware 在调用主模型前,用小模型筛选相关工具 当注册给 Agent 的工具数量非常多时,减少每次请求的 Token 消耗
ContextEditingMiddleware 在对话流中自动编辑或修剪上下文 自动删除旧的工具调用结果,只保留最新的有效信息
OpenAIModerationMiddleware 集成 OpenAI 审核端点,检查内容安全性 对用户输入、模型输出进行安全合规审查,适用于需要内容安全的场景

这些内置中间件覆盖了从稳定性、安全性、成本控制到上下文管理等多个核心维度。使用它们就像"搭积木",极大地简化了构建可靠生产级 Agent 的复杂性。

除了选择需要的功能模块,使用中间件还需要你亲自指定两个关键方面:

  • 1. 声明顺序:定义中间件的执行逻辑

    create_agent()middleware= 参数里,传入的是一个列表。这个列表的顺序至关重要,它直接定义了中间件的执行顺序 ,是"套娃"的最外层到最内层的顺序。改变顺序会直接影响最终的行为,比如日志记录(LoggingMiddleware)和重试机制(RetryMiddleware)的先后逻辑。

  • 2. 自定义逻辑:编写业务特定的中间件

    当内置功能无法满足特定业务需求时,需要编写自定义逻辑。

    • 使用钩子函数 :LangChain 提供了 @before_agent, @before_model, @after_model, @wrap_model_call 等一系列装饰器作为钩子,能精准地将逻辑注入到 Agent 执行的不同阶段。

    • 处理复杂依赖 :如果自定义中间件需要声明与其他中间件的依赖关系(如必须在某个中间件之后执行),可以通过 afterbefore 等机制进行约束。当项目规模变大,顺序变得难以管理时,可借助 langchain-middleware-stack 这类工具,通过声明式配置解决依赖问题。

示例:

复制代码
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import before_model
from langchain.messages import RemoveMessage
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.runtime import Runtime

from agent.my_llm import llm


@tool
def get_weather(city: str) -> str:
    """
    获取指定城市的天气
    Args:
        city (str): 要查询天气的城市名称

    Returns:
        str: 包含城市天气信息的字符串
    """
    return f"{city}的天气是晴朗的,温度是25摄氏度"

@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict:
    # 打印当前消息
    print("当前state:", state)
    print("当前runtime:", runtime)

    messages = state["messages"]
    if len(messages) > 5:
        # 保留最后3条消息
        retain_msg_count = 3

        # 如果倒数第3条消息是工具调用,那么就保留最后2条消息
        if messages[-retain_msg_count].type == "tool":
            retain_msg_count =2

        #删除的消息
        print("删除的消息:", messages[:-retain_msg_count])

        # 删除消息
        return {"messages": [RemoveMessage(id=msg.id) for msg in messages[:-retain_msg_count]]}
    return None


agent = create_agent(
    model=llm,
    tools=[get_weather],
    middleware=[trim_messages],
    checkpointer=InMemorySaver()
)

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

# 模拟对话
response1 = agent.invoke({"messages": [{"role": "user", "content": "你好,我是张三"}]}, config=config)
print(response1["messages"][-1].content)
print("***"*20)
response2 = agent.invoke({"messages": [{"role": "user", "content": "今天北京天气好吗?"}]}, config=config)
print(response2["messages"][-1].content)
print("***"*20)
response3 = agent.invoke({"messages": [{"role": "user", "content": "上海天气怎么样?"}]}, config=config)
print(response3["messages"][-1].content)
print("***"*20)
final_response = agent.invoke({"messages": [{"role": "user", "content": "我的名字叫什么?"}]}, config=config)
print(final_response["messages"][-1].content)
相关推荐
哥本哈士奇8 小时前
LangChain DeepAgents 学习笔记
笔记·学习·langchain
JavaEdge.9 小时前
06-LangChain Tool 加载与使用指南:预制工具、SerpAPI、edge-tts、GraphQL
chrome·langchain·graphql
Restart-AHTCM9 小时前
LangChain学习之环境搭建与基础概念 - 练习(1/8)
学习·langchain
糖果店的幽灵9 小时前
LangChain 基于 Python 的技术- agent模块使用总结
开发语言·python·langchain
花千树-01010 小时前
SubAgent 基础:拥有自主工具的子代理
java·langchain·llm·agent·langgraph·subagent·harness
JavaEdge.10 小时前
07-LangChain Toolkit 实战:从工具函数到 Python Agent,再到 SQL Agent
python·sql·langchain
Artech10 小时前
[对比学习LangChain和MAF-01]基本编程模式的差异(上篇)
ai·langchain·agent·maf
wuxinyan12311 小时前
工业级大模型学习之路018:LangChain零基础入门教程(第一篇):LangChain架构与生态介绍
人工智能·python·学习·langchain
赢乐1 天前
大模型学习笔记:LangChain核心组件-消息(Messages)
langchain·systemmessage·消息(messages)·角色role·humanmessage·aimessage·toolmessage