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)
相关推荐
阳区欠7 小时前
【LangChain】LLM基础介绍
开发语言·python·langchain
动能小子ohhh9 小时前
DocForge平台的设计与开发--文件上传接口的实现
开发语言·人工智能·python·langchain·ocr·fastapi
颜酱10 小时前
LangChain LCEL Chain 零基础入门指南
langchain
颜酱12 小时前
LangChain调用向量模型,存入向量数据库
python·langchain
wuhen_n13 小时前
RAG 核心:向量嵌入与本地向量数据库实战
前端·langchain·ai编程
冷小鱼13 小时前
LangChain 系统性科普:从入门到架构设计
langchain
wuhen_n13 小时前
RAG 关键环节:文本分块策略与最优参数配置
前端·langchain·ai编程
矩阵科学18 小时前
Langchain.js 实战四:工具的使用
langchain·node.js
P-ShineBeam20 小时前
智能体-LangChain框架-Tools工具的使用指南
数据库·人工智能·语言模型·自然语言处理·langchain
易小染1 天前
AI-Agent学习-LangChain-01
学习·langchain