让 Agent 记住对话、压缩历史、接受人工审核,最终成为可靠的智能助手。
上篇我们学会了 Agent 的概念、工具定义和 MCP 协议接入。但目前的 Agent 每次调用都是独立的------它不记得上一轮对话说了什么。同时,长对话会导致消息列表无限膨胀,Token 消耗剧增。本篇将解决这些问题:通过 Checkpointer 实现记忆,通过 中间件 实现历史压缩和人工审核,并总结最佳实践与常见问题。
四、记忆管理:让 Agent 记住过去
4.1 失忆问题
默认情况下,Agent 每次 invoke 都是一次独立的"感知→推理→行动"过程,上一次的对话内容不会自动带入下一次。
python
# 第1次调用
agent.invoke({"messages": [{"role": "user", "content": "我叫张三"}]})
# Agent: "你好张三!"
# 第2次调用(默认无记忆)
agent.invoke({"messages": [{"role": "user", "content": "我叫什么名字?"}]})
# Agent: "抱歉,我不知道你叫什么名字。" ← 失忆了
4.2 Checkpointer 机制
LangChain 通过 Checkpointer 实现记忆。每次调用结束后,自动保存本次对话的所有消息;下次调用时,自动加载历史消息拼接到新输入前面。
使用方式只需两步:
python
from langgraph.checkpoint.memory import InMemorySaver
# 1. 创建 checkpointer
checkpointer = InMemorySaver()
# 2. 创建 Agent 时传入
agent = create_agent(
model=llm,
tools=tools,
checkpointer=checkpointer, # ← 就这一行
)
4.3 Thread ID:多会话隔离
一个 Agent 通常服务多个用户,需要为每个用户维护独立的会话历史。通过 thread_id 区分:
python
# 张三的对话(thread_id = "user_zhangsan")
agent.invoke(
{"messages": [{"role": "user", "content": "我叫张三"}]},
config={"configurable": {"thread_id": "user_zhangsan"}}
)
# 李四的对话(thread_id = "user_lisi")完全独立
agent.invoke(
{"messages": [{"role": "user", "content": "我叫李四"}]},
config={"configurable": {"thread_id": "user_lisi"}}
)
相同 thread_id 的调用共享历史,不同 thread_id 互不干扰。
4.4 完整示例
python
import datetime
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
llm = init_chat_model(model="gpt-4o-mini", model_provider="openai")
checkpointer = InMemorySaver()
agent = create_agent(model=llm, tools=[], checkpointer=checkpointer)
# 第1次调用
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "今天北京天气怎么样?"}]},
config={"configurable": {"thread_id": "abc123"}}
):
print(chunk, end="\n\n")
# 第2次调用(相同 thread_id → 有记忆)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "我刚才问你什么了?"}]},
config={"configurable": {"thread_id": "abc123"}}
):
print(chunk, end="\n\n")
# Agent 会回答:"你刚才问了北京今天的天气"
# 第3次调用(不同 thread_id → 无记忆)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "我刚才问你什么了?"}]},
config={"configurable": {"thread_id": "xyz789"}}
):
print(chunk, end="\n\n")
# Agent 会回答:"这是我们第一次对话,你还没有问过任何问题"
4.5 记忆的代价
随着对话轮次增加,保存的消息列表越来越长:
-
第 1 轮:2 条消息
-
第 10 轮:20 条消息
-
第 100 轮:200+ 条消息
每次调用都要把所有历史消息发给 LLM,导致 Token 消耗剧增 ,甚至可能超出上下文窗口限制 。解决方案就是下一节的中间件。
五、中间件:在关键节点"拦截"处理
5.1 中间件概念
中间件是一种插入 Agent 执行流程中的"拦截器",可以在以下位置介入:
| 介入位置 | 时机 | 典型用途 |
|---|---|---|
before_model |
消息发给 LLM 之前 | 压缩历史、注入额外上下文 |
after_model |
LLM 返回结果之后 | 记录日志、过滤敏感内容 |
wrap_tool |
工具执行前后 | 人工审核、权限控制 |
使用方式:创建中间件实例,通过 middleware 参数传入 create_agent。
python
agent = create_agent(
model=llm,
tools=tools,
middleware=[middleware_a, middleware_b], # 列表,按顺序执行
)
5.2 消息压缩中间件(SummarizationMiddleware)
解决长对话中消息无限增长的问题。当消息量超过阈值时,自动用 LLM 将旧消息压缩成一段摘要。
python
from langchain.agents.middleware import SummarizationMiddleware
from langchain_openai import ChatOpenAI
summary_middleware = SummarizationMiddleware(
model=ChatOpenAI(model="gpt-4o-mini"),
trigger=("messages", 100), # 消息数量达到100条时触发压缩
)
压缩效果示意:
-
压缩前 (100 条消息):
[用户:你好,AI:你好,用户:天气?,AI:晴天,...,用户:最新问题] -
压缩后 (2 条消息):
[系统:以下是摘要:用户询问了天气、订单状态...,用户:最新问题]
trigger 支持三种策略:
| 触发策略 | 写法 | 含义 |
|---|---|---|
| 按消息数量 | ("messages", 100) |
消息数达 100 条时压缩 |
| 按 Token 比例 | ("fraction", 0.5) |
Token 数达上下文窗口 50% 时压缩 |
| 按 Token 绝对值 | ("tokens", 3000) |
Token 数达 3000 时压缩 |
5.3 人工审核中间件(HumanInTheLoopMiddleware)
某些高风险操作(转账、删除数据、发送邮件)即使 LLM 决定执行,也需人工确认后再真正执行。
python
from langchain.agents.middleware import HumanInTheLoopMiddleware
hitl_middleware = HumanInTheLoopMiddleware(
interrupt_on={
"transfer_money": True, # 转账 → 需要审核
"delete_record": True, # 删除 → 需要审核
"get_weather": False, # 查天气 → 不需要审核
}
)
执行流程:
-
Agent 决定调用
transfer_money; -
中间件拦截,暂停执行,返回"等待审核"状态;
-
人工通过界面查看并做出"批准"或"拒绝"决定;
-
将决定作为
Command(resume=...)传回,Agent 继续执行或放弃。
完整示例代码(见文档 5.3 节),实际开发中需结合前端交互来实现审核界面。
5.4 中间件叠加使用
多个中间件可以同时使用,按列表顺序依次执行。例如:
python
agent = create_agent(
model=llm,
tools=tools,
middleware=[summary_middleware, hitl_middleware],
)
六、Agent 最佳实践
6.1 工具设计五原则
| 原则 | 为什么重要 | 正面示例 | 反面示例 |
|---|---|---|---|
| 单一职责 | LLM 更容易理解功能明确的工具 | get_weather、get_forecast 各做一件事 |
handle_all 一个工具做太多事 |
| 描述清晰 | LLM 完全依赖描述决定何时用、怎么用 | "获取指定城市今天的实时天气" | "查天气" |
| 参数具体 | 减少 LLM 猜测参数格式的可能 | date: str = Field(description="格式YYYY-MM-DD") |
date: str(无格式说明) |
| 错误友好 | LLM 可根据错误信息调整策略 | 返回"城市'北精'无法识别,是否指'北京'?" | 抛出 KeyError: '北精' |
| 幂等安全 | 避免重试时产生副作用 | get_user(id=123) 多次调用结果相同 |
create_order() 多次调用会创建多个订单 |
经验法则:如果一个工具的 docstring 超过 3 句话才能说清功能,说明它承担了太多职责,应该拆分。
6.2 系统提示词优化
好的系统提示词应包含:角色定位、工作流程、约束条件、输出格式。
好示例:
text
你是一个专业的数据分析助手。
工作流程:
1. 理解用户分析需求
2. 使用 search 工具获取数据
3. 使用 calculate 工具进行计算
4. 用简洁语言呈现结果
注意事项:
- 计算结果保留2位小数
- 如果数据不足,主动告知用户
- 不要编造数据
差示例 :"你是一个AI助手,帮助用户解决问题。"(太模糊)
6.3 调试三层次
| 层次 | 方法 | 适用场景 |
|---|---|---|
| 第一层:开启日志 | logging.basicConfig(level=logging.DEBUG) |
快速定位调用参数 |
| 第二层:LangSmith | 设置环境变量启用追踪 | 可视化完整执行轨迹 |
| 第三层:流式输出 | 用 stream 替代 invoke |
观察每一步中间过程 |
经验:大多数 Agent 问题根源可归为三类:
-
工具描述不够清晰 → LLM 选错工具
-
系统提示词引导不足 → 执行顺序混乱
-
工具返回值格式不规范 → LLM 无法解读结果
6.4 性能优化
| 问题 | 优化方法 | 效果 |
|---|---|---|
| 响应慢 | 只加载当前任务必需的工具 | 减少 LLM 决策时间 |
| Token 消耗高 | 使用 SummarizationMiddleware |
压缩历史消息 |
| 工具调用次数多 | 优化工具返回值,一次返回完整信息 | 减少循环轮次 |
| 吞吐量不足 | 使用 ainvoke 异步调用 |
支持并发处理 |
七、课程总结与常见问题
7.1 知识回顾
| 章节 | 核心问题 | 学到了什么 |
|---|---|---|
| Agent 介绍 | Agent 是什么?和 Chain 的区别? | Agent = LLM + 自主决策 + 工具调用 |
| 工具定义 | 怎么让 Agent"做事"? | @tool 定义,create_agent 组装 |
| MCP 接入 | 怎么接入外部工具? | MCP 协议统一接入,对 LLM 透明 |
| 记忆管理 | 怎么让 Agent 记住对话? | Checkpointer + thread_id 实现多会话记忆 |
| 中间件 | 怎么压缩历史、人工审核? | SummarizationMiddleware、HumanInTheLoopMiddleware |
7.2 常见问题 Q&A
Q1:Chain 和 Agent 如何选择?
任务流程确定时用 Chain(简单可靠),任务需要动态判断时用 Agent(灵活但复杂)。两者可以结合------Agent 内部的某些子任务可以用 Chain 实现。
Q2:RAG 和 Agent 可以结合吗?
可以,而且非常常见。典型做法是把 RAG 检索封装成一个工具,Agent 在需要时自主调用它。例如,将向量数据库检索包装为 query_knowledge_base 工具,Agent 就可以在回答专业问题时自动检索相关知识。
Q3:如何优化 Agent 的响应速度?
四个方向:
-
只加载必需的工具(减少 LLM 决策开销)
-
用
SummarizationMiddleware压缩长对话(减少 Token) -
使用异步调用
ainvoke(支持并发) -
优化工具返回值格式(减少循环轮次)
Q4:什么时候需要 LangGraph?
当你需要多个 Agent 协作、复杂的条件分支和循环逻辑,或比 create_agent 更精细的流程控制时,就需要 LangGraph。它是 Agent 的进阶编排工具。
7.3 下一步建议
-
动手实践:选择一个实际场景(如个人知识库问答、自动化数据分析),用本课知识构建一个 Agent。
-
深入 LangGraph:学习状态图、条件边、多 Agent 协作,构建更复杂的工作流。
-
关注 AI 生态:MCP 社区正在快速发展,关注社区动态可以快速扩展 Agent 的能力。