🚀 开篇:别让你的Agent"排队办事"
很多朋友写智能体,往往卡在两步:
| 问题 | 表象 | 根因 |
|---|---|---|
| 程序跑得慢 | 下载3张图花了6秒 | 同步I/O,CPU在等,线程在睡 |
| 项目没法上线 | 对话爆上下文、工具被滥用、出问题没日志 | 缺少中间件治理 |
异步解决第一个问题,中间件解决第二个问题。
今天咱们就把同步/异步、MCP、中间件这三个硬骨头,一次啃透。
📦 第一部分:地基——Python异步,不是"多线程"的平替
⚠️核心认知:异步是单线程任务调度,不是并行计算。
🔸 同步 = 排队办事
import time time.sleep(2) # CPU在这干瞪眼,线程被阻塞任务按顺序执行,前一个I/O没完,后面的只能等着。
🔸 异步 = 叫号系统
import asyncio await asyncio.sleep(2) # 释放线程,CPU去处理其他任务提交任务后不用傻等,I/O完成了会自动通知你。
📊 一图看懂区别
| 对比项 | 同步 | 异步 |
|---|---|---|
| 执行方式 | 顺序执行 | 任务穿插 |
| CPU状态 | 空闲等待 | 忙个不停 |
| 适用场景 | CPU密集型 | I/O密集型 |
| 典型应用 | 数学计算 | 爬虫、API调用 |
🎯记住一句话:异步不是让程序"跑得更快",而是让程序"忙起来不闲着"。
🔌 第二部分:MCP协议——Agent世界的"USB接口"
有了异步调度,接下来解决工具怎么接的问题。
以前怎么接工具?
# 每个工具都要写定制代码 def call_weather_api(city): # 定制HTTP请求... def call_database_query(sql): # 定制数据库连接...换一个Agent框架,全部重写。
现在用MCP
MCP =模型上下文协议,标准化工具的定义和调用。
from langchain_mcp_adapters.client import MultiServerMCPClient client = MultiServerMCPClient({ "weather": { "transport": "streamable_http", "url": "http://localhost:8000/mcp" }, "math": { "transport": "stdio", "command": "python", "args": ["math_server.py"] } }) tools = await client.get_tools() # 一把梭,全量拉取MCP两种传输方式
| 传输方式 | 适用场景 | 特点 |
|---|---|---|
| stdio | 本地工具 | 子进程拉起,低延迟 |
| HTTP | 跨服务调用 | 网络暴露,可分布式部署 |
🎯MCP最大价值:工具独立部署、独立升级,Agent只管调用,解耦!
🛡️ 第三部分:中间件——Agent的"生产级护城河"(重点)
如果说异步是发动机,MCP是接口,那中间件就是保障系统稳定、合规、可靠的"护城河"。
LangChain 1.0的中间件分三大类:
┌─────────────────────────────────────────────┐ │ 中间件三大件 │ ├─────────────────────────────────────────────┤ │ 1. 预置中间件 │ 开箱即用,配置就行 │ │ 2. 装饰器中间件 │ 轻量级切面,一个注解搞定 │ │ 3. 类中间件 │ 复杂逻辑,组合钩子 │ └─────────────────────────────────────────────┘
🔸 1. 预置中间件:开箱即用的"安全阀"
① SummarizationMiddleware(自动摘要)
对话一长就爆上下文窗口?这个中间件自动压缩早期历史。
from langchain.agents.middleware import SummarizationMiddleware agent = create_agent( model="gpt-4", tools=[...], middleware=[SummarizationMiddleware( trigger=("tokens", 1000), # 超过1000 token触发 keep=("messages", 5) # 保留最近5条 )] )原理:前15轮对话 → 压缩成"用户询问了退货政策" → 省Token、省成本。
② Human-in-the-loop(人工审核)
delete_file、send_email这类操作,必须人点头才能执行。
from langchain.agents.middleware import HumanInTheLoopMiddleware agent = create_agent( model="gpt-4", tools=[dangerous_tool], middleware=[HumanInTheLoopMiddleware( tools=["dangerous_write"], interrupt=True )], checkpointer=InMemorySaver() # ⚠️ 必须有,否则状态丢失 )三种决策:
| 决策 | 效果 |
|---|---|
| ✅ approve | 批准执行 |
| ✏️ edit | 修改参数后执行 |
| ❌ reject | 拒绝执行,返回提示 |
⚠️踩坑提醒:HITL必须配合
checkpointer,否则中断后无法恢复。
🔸 2. 装饰器中间件:轻量级"切面"
只想在某个节点插一脚(记日志、校验输入)?一个注解搞定。
钩子分类
| 类型 | 装饰器 | 触发时机 |
|---|---|---|
| 节点式 | @before_agent | Agent执行前 |
| 节点式 | @after_model | 模型响应后 |
| 包裹式 | @wrap_tool_call | 工具调用前后 |
| 便捷式 | @dynamic_prompt | 动态修改提示词 |
实战:拦截敏感词
from langchain.agents.middleware import after_model @after_model def block_sensitive_words(state, response): if "BLOCKED_CN" in response["output"]: return {"output": "⚠️ 检测到违规内容,已终止输出。"} return response🔸 3. 类中间件:复杂逻辑的"核武器"
需要组合多个钩子、维护状态?继承AgentMiddleware。
class LoggingMiddleware(AgentMiddleware): def before_agent(self, state, runtime): print(f"[{time.time()}] Agent开始执行") def after_agent(self, state, runtime): print(f"[{time.time()}] Agent执行完毕")关键规则:
✅ 必须继承
AgentMiddleware✅ 方法名固定:
before_agent、after_model等✅ 每次调用要new对象,不要传类
🔄 中间件执行顺序:"洋葱模型"
多个中间件共存时,执行顺序是先入后出:
注册:[A, B, C] 执行流程: before_A → before_B → before_C → 【核心调用】 → after_C → after_B → after_A
实战建议:
| 优先级 | 中间件类型 | 理由 |
|---|---|---|
| 最外层 | 安全类(HITL) | 先安检,后干活 |
| 中间层 | 日志类 | 记录完整链路 |
| 最内层 | 性能类(Summarization) | 最后优化上下文 |
⚠️ 第四部分:避坑指南——三个致命误区
| 误区 | 正确理解 | 后果 |
|---|---|---|
| 异步=多线程 | 异步是单线程调度 | 混用概念,代码写错 |
| 异步里用同步库 | async里不能用requests | 线程阻塞,异步失效 |
| 中间件顺序乱放 | HITL必须在最外层 | 安全漏洞,敏感操作漏审 |
🎯记住:异步解决I/O阻塞,中间件解决生产治理,各司其职,别混为一谈。
📝 结语
这篇文章是LangChain系列的第一篇,我们厘清了:
✅异步→ 让Agent不闲着(I/O密集场景)
✅MCP→ 让工具标准化接入(解耦部署)
✅中间件→ 让Agent可治理(安全、成本、可观测)
下次预告:我们聊LangSmith——Agent的"监控仪表盘"。怎么追踪每一次模型调用、怎么分析Token消耗、怎么定位Agent"抽风"的根本原因。
💬互动话题:你的Agent上生产了吗?遇到过哪些"坑"?HITL对接前端页面怎么搞?欢迎评论区交流!
如果觉得有用,点个👍和⭐,催更下一期!