LangChain 实践笔记:中间件与服务化

从中间件机制出发,梳理 before_model、wrap_model_call 等钩子的触发位置与两种自定义写法,再用 add_routes 注册出 /invoke 与 /stream 接口。

到这里,已经具备了一条完整 Agent 链路需要的核心能力:上下文记忆、工具调用和多轮执行。

但是要真正进入工程化阶段,还需要补上两类能力:一是执行过程的扩展与治理,例如日志、重试、权限校验、提示词动态调整;二是对外提供稳定调用入口,让 Agent 能被其他服务或应用接入。

前者对应 Middleware,后者对应服务化。

#一、Agent Middleware 的作用与适用场景

链的行为如果一直都写在核心逻辑,那么想加日志、重试、权限校验、提示词调整,都要直接修改 Agent 或者工具代码。功能一朵,主流程很快就会变乱。

中间件就是解决这个问题的,把这些模型横切逻辑从核心流程代码中拆分出来,挂到 Agent 执行过程中的特定位置。

在 LangChain 里,中间件可以在模型调用、工具调用等阶段前后介入,读取或者修改执行状态,也可以提前终止、重试或者替换部分行为,而不改 Agent 本身。

常见用途包括:

  • 观测:日志、Tracing、调试;
  • 调整:动态修改提示词、工具选择、模型输入和输出格式;
  • 控制:重试、回退、提前终止、人工审批;
  • 治理:限流、权限校验、敏感信息处理。

这些场景看起来不同,但代码层面做的都是同一件事,在 Agent 执行流程的某个节点前后插入一段自定义逻辑。

#二、Agent Middleware 的生命周期钩子

LangChain 提供了六个常用钩子。它们不是并列执行的六个步骤,而是分布在一次 Agent 执行流程的不同位置。

钩子触发位置
before_agent一次 Agent 调用开始时
before_model每次模型调用之前
wrap_model_call包裹模型调用,可在调用前后介入
after_model每次模型调用之后
wrap_tool_call包裹工具调用,可在调用前后介入
after_agent整轮 Agent 执行结束时

一次典型调用大致是:

before_agent
    ↓
before_model
    ↓
wrap_model_call
    ↓
Model
    ↓
after_model
    ↓
如果需要工具
    ↓
wrap_tool_call
    ↓
Tool
    ↓
回到 before_model
    ↓
...
    ↓
after_agent

请求进入后先经过 before_agent,接着进入模型调用阶段。wrap_model_call 包住真正的模型调用,因此可以在调用前后执行自定义逻辑;模型返回后再经过 after_model。

如果模型需要调用工具,wrap_tool_call 会包住工具执行。工具结果返回后,Agent 会重新进入模型调用阶段,让模型继续判断下一步。

因此,只要一次 Agent 执行中发生了多轮模型调用,before_model、wrap_model_call 和 after_model 就可能重复触发;wrap_tool_call 则会随着工具调用次数触发。

而 before_agent 和 after_agent 面向的是整次 Agent 调用,通常各执行一次。

某些钩子如果不需要修改状态,可以直接返回 None,表示不对当前执行过程做额外变更。

#三、Middleware的内置能力与自定义实现

LangChain 已经提供了一批常用 Middleware,用来处理对话摘要、人工审批、调用限额、失败回退、敏感信息处理、工具筛选和重试等问题。下边表格列举了一些:

中间件作用
SummarizationMiddleware对话历史接近上限时自动汇总
HumanInTheLoopMiddleware暂停执行,等人工审核工具调用
ModelCallLimitMiddleware限制模型调用次数,防止成本过高
ToolCallLimitMiddleware限制工具调用次数,可全局也可限定某个工具
ModelFallbackMiddleware主模型失败时自动回退到备用模型
PIIDetectionMiddleware检测并处理个人身份信息
ToDoListMiddleware为 Agent 配备任务规划与跟踪
LLMToolSelectorMiddleware调用主模型之前,先由 LLM 筛选相关工具
ToolRetryMiddleware工具调用失败时按指数退避自动重试
LLMToolEmulator用 LLM 模拟工具执行,用于测试
ContextEditingMiddleware通过精简或清除工具使用记录来管理上下文
ShellToolMiddleware向 Agent 公开持久化的 shell 会话
FilesystemFileSearchMiddleware提供对文件的 Glob 与 Grep 搜索工具

自定义 Middleware 通常有两种方式:

  • 装饰器:适合只需要一个 Hook 的简单逻辑;
  • 类:继承 AgentMiddleware,适合同时使用多个 Hook,或者需要在 Middleware 内维护配置和共享逻辑。

例如,消息脱敏需要每次在调用模型前处理输入,可以实现 before_model:

from langchain.agents.middleware import AgentMiddleware

class DesensitizedDataMiddleware(AgentMiddleware):
    def before_model(self, state, runtime):
        ...
        return {"messages": new_messages}

before_model 拿到当前 Agent 状态,在模型真正调用之前完成处理;如果不需要修改状态,也可以返回 None。

多个 Middleware 可以同时挂到 create_agent 的 Middleware 列表中。不同 Hook 会在各自的生命周期位置执行,而 warp_model_call、warp_tool_call 这类包装型 Hook 会形成嵌套调用。

#四、把 Runnable 暴露为 HTTP 服务

中间件解决的是 Agent 内部的执行扩展,服务化解决的是另一件事:让这条链能够被其他应用通过网络调用。

前面的 Chain 或 Agent 还只是进程内对象:本地可以 invoke,但前端、其他服务或其他进程还不能直接访问它。

LangServe 可以把一个 Runnable 挂到 FastAPI 上,对外暴露成 HTTP 接口:

from fastapi import FastAPI
from langserve import add_routes

chain = prompt_template | client | parser

app = FastAPI(title="LangChain Service", version="1.0")
add_routes(app, chain, path="/langChain")

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

add_routes 会根据 Runnable 的输入输出结构自动注册一组接口,比如:

POST /langchain/invoke
POST /langchain/batch
POST /langchain/stream
...

同时还提供:

/langchain/playground/
/docs

其中 /playground/ 用来交互式测试 Runnable,/docs 是 FastAPI 自动生成的 OpenAPI 文档。

这里 FastAPI 和 LangServe 各负责一层:

  • FastAPI 提供 Web 服务、路由、参数校验、依赖注入和 OpenAPI;
  • LangServe 负责把 Runnable 的 invoke / batch / stream 等能力映射成 HTTP 接口。

因此,Runnable 只负责业务执行,HTTP 层负责对外暴露。调用只需要遵守接口约定,不需要了解链内部是怎么实现的。

#五、总结

  1. Middleware 把横切逻辑从核心代码里拆出来。 六类 Hook 分布在 Agent 执行生命周期的不同位置,模型调用和工具调用相关的Hook 可能在一次执行中重复触发;
  2. 服务化负责把 Runnable 变成网络接口。 FastAPI 提供 Web 服务基础,LangServe 通过 add_routes 把 invoke / batch / stream 等能力映射成 HTTP 接口;
  3. 接口建立后,调用方不再需要了解链的内部实现。 前端、其他服务或其他语言的程序,都可以按照统一的 HTTP 协议调用。

到这里,这个系列已经走过五篇:从一次模型调用开始,把组件组合成链,接入自己的知识库,再让他具备记忆和工具调用能力,最后通过 Middleware 扩展执行过程,并把能力暴露成服务。

这五篇是一条连续的工程路径:会调用、会组合、会接知识库、会调用外部能力、会做为服务运行。

后一阶段始终是建立在前一阶段之上,最后落到的,仍然是一条可以被组合、扩展和部署的执行链。