短期记忆与上下文管理

短期记忆与上下文管理

2026年9月21日·#编程学习/langchain学习笔记LangChain/AI·5272 字 26 分钟
浏览量加载中...
AI 摘要

模型本身无状态,记忆要靠 State + Checkpointer + Thread ID 三件套;InMemorySaver 与 PostgresSaver 的用法、thread_id 隔离的实测、上下文变长后的裁剪/删除/摘要三种管理手段

为什么需要记忆#

大模型本身是”无状态”的——它不记忆任何上下文。也就是说,每次 agent.invoke() 都是全新的开始,不记得之前的对话。

# 第一次调用
agent.invoke({"messages": [HumanMessage("你好,我叫小明")]})
# 第二次调用(同一个 agent 对象!)
agent.invoke({"messages": [HumanMessage("我叫什么?")]})
# 模型:我不知道你叫什么 ← 因为历史消息根本没传给它

而我们平时用 ChatGPT 那种”多轮对话”体验,背后其实是应用层在每次请求时把历史消息一起发给模型

图:多轮对话里模型能接上前文,靠的是把历史消息一起送过去

上下文工程与三种上下文#

在 LangChain 里:

  • 记忆(Memory) 是专门负责”存储历史交互信息”的组件,核心作用是「保存上下文」和「提供上下文」
  • 上下文工程(Context Engineering) 负责”合理组织”这些记忆和任务信息,让 LLM 的响应更连贯、更贴合需求——这是 Agent 能实现复杂多轮交互的核心基础

图:无记忆的调用 vs 有记忆、有上下文优化的调用(LangChain 实现)

LangGraph 提供了三种管理上下文的方法(上下文工程是构建在 LangGraph 之上的):

上下文类型描述可变性生命周期访问方法
动态运行时上下文单次运行中会演变的可变数据动态单次运行LangGraph state 对象
动态跨会话上下文对话间共享的持久数据(用户偏好、历史洞察、知识条目)动态跨对话LangGraph store 对象
静态运行时上下文启动时传入的用户元数据、工具、数据库连接静态单次运行LangGraph context 对象

记忆分为两类

类型别名作用范围
短期记忆会话级记忆 / thread-scoped memory单个对话线程(Thread)内,换了 thread_id 记忆即消失
长期记忆跨会话级记忆会话线程间共享,范围是任意自定义命名空间(下一篇)
Note

版本变化:LangChain v0.x 用专用的 xxxMemory 类管理记忆;v1.x 因为 Agent 构建在 LangGraph 图结构之上,改用 state(短期)store(长期) 构建记忆系统——更简单、功能更统一。

图:上下文工程做的事——从全部历史里筛出最相关的几轮,再把老的压成摘要

短期记忆的三件套#

LangChain 1.x 的短期记忆是三者的组合

组件职责
State(会话内部状态)默认存储历史消息列表 messages,通过 State 管理历史消息
Checkpointer(持久化机制)把 State 作为检查点持久化保存,检查点是某个时刻的 State 快照
Thread ID(会话作用域)唯一标识 State,运行时按 thread_id 读写 State 快照

像玩 RPG 游戏的”自动存档”:你不需要手动保存,系统在关键节点自动记录,下次进入游戏随时可以从上次的存档点继续。

基于内存:InMemorySaver#

最便捷的方式,适合快速测试或调试:

from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
checkpointer = InMemorySaver()
agent = create_agent(
model=model,
checkpointer=checkpointer, # ← 1. 加检查点
)
config = {"configurable": {"thread_id": "1"}} # ← 2. 指定会话 ID
agent.invoke({"messages": [HumanMessage("你好,我叫小明")]}, config=config)
agent.invoke({"messages": [HumanMessage("我叫什么?")]}, config=config) # ← 3. 用同一个 config
# 这次它就能答上来了
Tip

实测记忆的效果:同一个 thread_id 的第二轮,发给模型的消息是 ['你好,我叫小明', '好的。', '我叫什么?']——历史被自动带上了。 换成 thread_id="2" 再问同样的问题,发给模型的只剩 ['我叫什么?']——记忆是按 thread_id 隔离的,新会话就是一张白纸。

基于外部存储:PostgresSaver#

内存里的检查点在进程结束就没了,生产环境不可接受,所以要换成持久化的外部存储(如 PostgreSQL)。

LangChain 支持的 checkpointer 后端列表见官方文档「Persistence」。以 PostgreSQL 为例:

Terminal window
pip install langgraph-checkpoint-postgres
Note

数据库环境准备:课程是在**云服务器(Ubuntu 系统)**上装 PostgreSQL 的,具体步骤见资料包 02-资料 下的《Linux云服务器安装与PostgreSQL安装》。连接串里的用户名、密码、IP 都要换成自己的:

postgresql://langchain_user:abcd1234@118.195.128.47:5432/langchain_db?sslmode=disable

118.195.128.47 是课程云服务器的 IP,本地跑就换成 localhost

from langgraph.checkpoint.postgres import PostgresSaver
DB_URL = "postgresql://langchain_user:abcd1234@<你的IP>:5432/langchain_db?sslmode=disable"
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:
checkpointer.setup() # ← 初始化数据库表结构(第一次要跑)
agent = create_agent(model=model, checkpointer=checkpointer)
...
Warning

课程强调:依赖版本可能冲突,课程环境的依赖都在 requirements.txt 里预置了,不要重复安装。自己项目里装之前先看版本。

两种方式的对比#

InMemorySaverPostgresSaver
存储位置进程内存外部数据库
进程重启❌ 数据丢失✅ 保留
跨进程共享❌ 不支持✅ 支持
适用开发调试、快速验证生产环境

上下文变长了怎么办:三种管理手段#

内存和上下文都会无限膨胀——消息越来越多(token 消耗增加,甚至超过模型的 token 限制),响应变慢、成本上升。课程把”为什么必须治理”归纳成四条:

#理由
1LLM 的上下文窗口是有限的,完整历史可能无法装入,导致上下文丢失或错误
2即便模型的上下文窗口够大,多数 LLM 在长上下文场景仍然表现不佳——模型会被陈旧或离题的内容”分散注意力
3高昂的 token 花费
4需要对历史记录进行压缩、清理、重组等管理动作

三种处理手段:

手段时机特点适合
裁剪调用模型控制”模型能看到多少”,通常保留系统初始消息 + 最近若干条成本敏感、对旧上下文依赖不强
删除模型调用完成后永久从状态里移除消息明确要遗忘、清理、重置某段历史
摘要达到阈值时把早期历史压缩成摘要替换原文(保语义不保原文)长会话的折中方案

裁剪:before_model + REMOVE_ALL_MESSAGES#

from typing import Any
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import before_model
from langchain_core.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langgraph.runtime import Runtime
@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
messages = state["messages"]
if len(messages) <= 3:
return None
first_msg = messages[0] # 保留开头(比如系统提示)
recent_messages = messages[-3:] # 保留最近几条
new_messages = [first_msg, *recent_messages]
return {
"messages": [
RemoveMessage(id=REMOVE_ALL_MESSAGES), # 先全部清掉
*new_messages, # 再放回要保留的
]
}
agent = create_agent(model=model, middleware=[trim_messages], checkpointer=InMemorySaver())
Tip

实测效果keep=最近 3 条):消息数涨到 5 条时触发裁剪 → 裁剪后仍回到 5 条,之后每轮都稳定在 5 条,不再无限增长。

裁剪的坑:人机消息要成对保留#

消息列表是人机交替的(HumanMessageAIMessageHumanMessage → ……)。如果裁剪时不管这一层,切出来的片段很可能是”从一条 AIMessage 开头”的——模型看到的第一条就是自己的话,等于上下文对不齐。课程用一个小技巧解决:按奇偶决定切几条

@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
messages = state["messages"]
if len(messages) <= 3:
return None
first_msg = messages[0]
# 总数是偶数 → 最后 3 条正好是 [H, A, H];总数是奇数 → 取 4 条才是 [H, A, H, A]
recent_messages = messages[-3:] if len(messages) % 2 == 0 else messages[-4:]
new_messages = [first_msg] + recent_messages
return {"messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES), *new_messages]}

课程用 [H1, A1, H2, A2, H3](5 条,奇数)演示:messages[-4:] 取出 [A1, H2, A2, H3],加上第一条 H1 之后,模型看到的还是 [H1, A1, H2, A2, H3]——一条都没剪掉。这正说明”随便切几条”会出问题:该剪的没剪,不该丢的反而丢了。

Tip

本机假服务端实测(同一份中间件跑四轮对话,抓每次真正发往模型的 messages):

轮次用户输入裁剪前 state 长度切出的片段实际发给模型的消息
1你好,我是老王1不触发[H: 你好,我是老王]
2从现在起,你叫小王3不触发[H, A, H]
3今天天气不错5(奇数)[-4:][H1, A1, H2, A2, H3]——5 条,等于没剪
4告诉我,你是谁?我是谁?7(奇数)[-4:][H1, A2, H3, A4, H5]——这次真剪掉了

换掉奇偶判断、固定写 messages[-3:] 再跑一遍,第 3 轮发给模型的消息变成 [H1, H2, A2, H3](4 条)——开头是一条 HumanMessage,后面紧跟着又是 HumanMessage,人机不再交替,这正是要避免的。

结论:len(messages) % 2 这个判断不是”玄学”——它保证切出来的片段永远从 HumanMessage 开始,人机成对。

删除:after_model + RemoveMessage(id=…)#

from langchain.agents.middleware import after_model
from langchain_core.messages import RemoveMessage
@after_model
def delete_old_messages(state: AgentState, runtime: Runtime) -> dict | None:
messages = state["messages"]
if len(messages) > 5:
to_delete = len(messages) - 5
# 用 RemoveMessage 标记删除,返回出去让框架真正移除
return {"messages": [RemoveMessage(id=m.id) for m in messages[:to_delete]]}
return None
agent = create_agent(model=model, middleware=[delete_old_messages], checkpointer=InMemorySaver())

关键区别:裁剪是”这次调用不给模型看”,删除是”从状态里彻底抹掉”(永久生效)。

图:两条路线——Summarize 把早期历史压成摘要,Filter 只保留要用的消息

摘要:官方推荐的做法#

消息裁剪和删除都会导致上下文缺失,影响回答质量和用户体验。相比之下,摘要是更适合长会话的折中方案:保语义,不保原文

官方推荐直接用内置的 SummarizationMiddleware(第 19 篇详细讲过它的参数和实测效果),不必自己写。

自定义过滤策略#

裁剪、删除、摘要只是三种”教科书套路”。课程强调:通过中间件可以随意更改消息列表,因此可以实现任意的过滤策略(课程未演示,留作练习)。

这一点很关键——因为 before_model / after_model 拿到的是完整的 state["messages"],返回的又是”新的消息列表”,所以”怎么筛”完全由你决定:

过滤策略思路
按角色筛只保留 HumanMessage / 丢掉中间的工具往返消息
按内容筛命中关键词(订单号、身份证、金额)的消息永远保留,其余按时间裁剪
按重要性筛给消息打标记,重要消息进”白名单”,裁剪时优先保住
按类型筛丢掉 ToolMessage 这类冗长的中间过程,只留人机对话
Warning

自定义过滤没有内置保护:删错了就真的丢了。判断”哪条重要”这件事,最终还是得靠你自己写规则(或者像下一篇那样,把重要事实搬进长期记忆里——那才是它真正该待的地方)。

了解一下 state#

state 是 Agent 底层有状态运行图的状态信息,是 AgentState 类型的实例:

class AgentState(TypedDict, Generic[ResponseT]):
"""State schema for the agent."""
messages: Required[Annotated[list[AnyMessage], add_messages]]
jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]
structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]

AgentStateTypedDict 的子类,所以可以按字典的方式读写。它有三个字段(实测 AgentState.__annotations__ 正是这三个):

字段说明
messages截止当前节点的历史会话消息记录,Required(必填)
jump_to跳转到运行图的指定节点(第 21 篇的流程跳转),可以为 None
structured_response启用结构化输出时,结构化后的内容记录在这里(第 16 篇)

亲眼看看 jump_to 和 structured_response 的变化#

课程用一个”伪造工具调用 + 三个钩子打点”的例子,把这两个字段在一轮运行中的取值变化抓了出来。核心是:用 @before_model(can_jump_to=["tools"]) 人工造一条带 tool_callsAIMessage,然后 jump_to="tools" 跳过模型、直奔工具节点。

from typing import Any, Callable
from pydantic import BaseModel, Field
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import after_agent, before_model, wrap_tool_call
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langchain.tools import tool
from langchain.tools.tool_node import ToolCallRequest
from langgraph.runtime import Runtime
from langgraph.types import Command
class WeatherInfo(BaseModel):
"""城市天气情况"""
city: str = Field(description="城市名称")
temperature: str = Field(description="气温")
desc: str = Field(description="当日天气概述")
@tool(parse_docstring=True)
def get_weather(city: str):
"""获取当日天气
Args:
city: 城市名称
"""
return f"[{city}] 今天气温9~16度,万里无云,天气不错适合外出"
@before_model(can_jump_to=["tools"])
def direct_tool_call(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
last_msg = state["messages"][-1]
if isinstance(last_msg, HumanMessage) and "天气" in last_msg.text and "北京" in last_msg.text:
fake_tool_call = AIMessage( # 人工伪造一条"模型想调工具"的消息
content="人工构造的消息",
tool_calls=[{"name": "get_weather", "args": {"city": "北京"}, "id": "direct_call_id"}],
)
return {"messages": [fake_tool_call], "jump_to": "tools"} # ← 直接跳工具节点
return None
@wrap_tool_call
def first_check(
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage | Command],
) -> ToolMessage | Command:
print("=" * 30, '-> In first_check Middleware <-', "=" * 30)
print(f"{request.state.get('jump_to', None) = }")
print(f"{request.state.get('structured_response', None) = }")
return handler(request)
@after_agent
def final_check(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
print("=" * 30, '-> In final_check Middleware <-', "=" * 30)
for msg in state["messages"]:
msg.pretty_print()
print(f"{state.get('jump_to', None) = }")
print(f"{state.get('structured_response', None) = }")
return None
agent = create_agent(
model=model,
response_format=WeatherInfo, # ← 开启结构化输出
middleware=[direct_tool_call, first_check, final_check],
tools=[get_weather],
)
agent.invoke({"messages": [HumanMessage("请帮我查询北京当日天气")]})

输出(课程实测,两个打点各打一次):

============================== -> In first_check Middleware <- ==============================
request.state.get('jump_to', None) = 'tools'
request.state.get('structured_response', None) = None
============================== -> In final_check Middleware <- ==============================
================================ Human Message =================================
请帮我查询北京当日天气
================================== Ai Message ==================================
人工构造的消息
Tool Calls:
get_weather (direct_call_id)
Call ID: direct_call_id
Args:
city: 北京
================================= Tool Message =================================
Name: get_weather
[北京] 今天气温9~16度,万里无云,天气不错适合外出
================================== Ai Message ==================================
{"city":"北京","temperature":"9~16℃","desc":"万里无云,天气不错适合外出"}
============================== -> 消息打印完毕 <- ==============================
state.get('jump_to', None) = None
state.get('structured_response', None) = WeatherInfo(city='北京', temperature='9~16℃', desc='万里无云,天气不错适合外出')

课程的分析(三条):

  1. 我们在模型调用之前通过 jump_to 直接跳转至工具节点
  2. 工具调用前打印状态信息,此时的 jump_to 非空
  3. Agent 执行完毕之后打印状态信息,此时的 structured_response 非空,而 jump_to 为空
Tip

本机假服务端实测jump_to 的两段式变化完全复现——

============================== -> In first_check Middleware <- ==============================
request.state.get('jump_to', None) = 'tools'
request.state.get('structured_response', None) = None
...
============================== -> 消息打印完毕 <- ==============================
state.get('jump_to', None) = None

说明这两个字段都是**“过程性”的**:jump_to临时跳转意图EphemeralValue + PrivateStateAttr,跳完就清掉,所以 after_agent 里已经是 None);structured_response跑完才写进去的最终产物OmitFromInput,只在输出侧可见)。

Note

print(f"{state.get('jump_to', None) = }") 用的是 f-string 的 = 说明符——它会连”表达式本身”一起打出来,所以输出长这样:state.get('jump_to', None) = None。调试状态时非常省事。 顺带一提:f-string 表达式里嵌套同种引号f"{d.get("k")}")在 Python 3.12+(PEP 701) 才是合法的,老版本会报 SyntaxError。本课环境是 Python 3.12+,可以放心写。

常见问题#

Q1:为什么我的 Agent 不记得? 逐条检查:

检查项错误示例
checkpointer 了吗?create_agent(model=model, tools=[]) → 不会记住
调用时传 config 了吗?有 checkpointer 但 invoke 不传 config → 不会记住
两次的 thread_id 一样吗?thread_id="1"thread_id="2" 是两个会话

Q2:InMemorySaver 会丢数据吗? 会!它只存在内存里:同一进程内有效,程序重启后丢失、不同进程无法共享。要持久化就换 SQLite / PostgreSQL。

Q3:内存会无限增长吗? 会!默认保存所有消息,所以必须做上下文管理(裁剪 / 删除 / 摘要)。

Q4:怎么清空某个会话的历史? 目前 InMemorySaver 没有提供删除 API,临时方案是换一个新的 thread_id(或重新创建 Agent)。

相关#

练习题#

一、回忆填空(写完再展开对答案)#

  1. 大模型本身是____的,每次 agent.invoke() 都是全新的开始,所以要靠应用层把____一起发过去
  2. LangGraph 的三种上下文:动态运行时上下文用 ____ 对象、动态跨会话上下文用 ____ 对象、静态运行时上下文用 ____ 对象
  3. 短期记忆又称____记忆,换了 thread_id 记忆就会____;长期记忆在会话线程间____
  4. 短期记忆三件套:(会话内部状态)、(持久化机制)、____(会话作用域)
  5. 创建 Agent 时加 ____ 参数启用检查点;invoke 时用 config={"configurable": {"____": ...}} 指定会话
  6. 基于外部存储的持久化器以 ____ 为例,使用时先调用 checkpointer.____() 初始化表结构
  7. 上下文管理三手段:(调用前,控制模型可见范围)、(调用后,永久移除)、____(保语义不保原文)
  8. 裁剪用 before_model 钩子,配合 RemoveMessage(id=____) 先清空再放回;删除用 after_model 钩子,配合 RemoveMessage(id=消息的____)
  9. AgentState 是____的子类,三个字段是 messages(必填)、____structured_response
  10. 清空某个会话历史的临时方案是换一个新的____,因为 InMemorySaver 没有提供____ API
填空答案(做完再点开)
  1. 无状态 / 历史消息 2. state / store / context 3. 会话级 / 消失 / 共享 4. State / Checkpointer / Thread ID 5. checkpointer / thread_id 6. PostgresSaver / setup 7. 裁剪 / 删除 / 摘要 8. REMOVE_ALL_MESSAGES / id 9. TypedDict / jump_to 10. thread_id / 删除

二、裸写题#

  • 2-1 让 Agent 记住你 创建带 checkpointer=InMemorySaver() 的 Agent,用 thread_id="1" 连续 invoke 两轮:“你好,我叫小明” → “我叫什么?“,确认它能答上来;再用 thread_id="2" 问同样的问题,观察它是不是”失忆”了。

    提示(先自己想,实在想不出再点开)

    一级 · 思路:记忆 = 检查点 + 会话 ID 的组合 二级 · 方法create_agent(model=model, checkpointer=InMemorySaver()) + config={"configurable": {"thread_id": "1"}} 三级 · 骨架:打印 len(response["messages"]),能直观看到历史在累积

  • 2-2 让上下文别无限膨胀(裁剪) 写一个 @before_model 钩子:当消息数超过 4 条时,保留第一条 + 最近 3 条。跑 5 轮对话,观察消息数是否被”按住”。

    提示

    一级 · 思路:裁剪发生在”模型调用前”,通过返回新的 messages 列表实现 二级 · 方法RemoveMessage(id=REMOVE_ALL_MESSAGES) + 新的消息列表 三级 · 骨架:打印每轮的消息数,看它是不是稳定在同一个值附近

  • 2-3 用删除实现”永久遗忘” 写一个 @after_model 钩子:消息数超过 5 条就删掉最早的几条。跑 3 轮,观察状态里的消息总数。

    提示

    一级 · 思路:删除是”从状态里真删”,所以下一轮也看不到 二级 · 方法return {"messages": [RemoveMessage(id=m.id) for m in messages[:to_delete]]} 三级 · 骨架:对比一下”裁剪”和”删除”在 state 里的差异——谁能真正减小状态?

三、综合题#

  • 3-1 给”带记忆的客服助手”配一套上下文管理 组合出这样的 Agent:checkpointer=InMemorySaver() + 裁剪钩子(保留首条 + 最近 4 条)+ 系统提示词(“你是客服助手,记住用户告诉你的信息”)。然后:

    1. 第 1 轮:“我的订单号是 A12345”
    2. 第 2 轮:“我的订单号是多少?“(应该记得)
    3. 连续再聊 4-5 轮无关内容
    4. 最后再问一次订单号,观察裁剪之后它还记不记得,并解释原因
    提示(先自己想,实在想不出再点开)

    一级 · 思路:裁剪会”丢掉旧消息”,所以关键信息要么靠摘要,要么靠长期记忆(下一篇) 二级 · 方法middleware=[trim_messages] + checkpointer=InMemorySaver() 三级 · 骨架:把”订单号”当成一条需要长期记住的事实——这正是长期记忆要解决的问题

参考答案(做完再点开)
import os
from typing import Any
from dotenv import load_dotenv
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import after_model, before_model
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, RemoveMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langgraph.runtime import Runtime
load_dotenv(override=True)
model = init_chat_model(
model="deepseek-v4-flash",
model_provider="openai",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL"),
)
# ---------- 2-1 记住我 ----------
agent = create_agent(model=model, checkpointer=InMemorySaver())
config1 = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": [HumanMessage("你好,我叫小明")]}, config=config1)
r = agent.invoke({"messages": [HumanMessage("我叫什么?")]}, config=config1)
print("同一会话:", r["messages"][-1].content)
config2 = {"configurable": {"thread_id": "2"}}
r2 = agent.invoke({"messages": [HumanMessage("我叫什么?")]}, config=config2)
print("新会话:", r2["messages"][-1].content) # 会答不出来
# ---------- 2-2 裁剪 ----------
@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
messages = state["messages"]
if len(messages) <= 4:
return None
print(f" [裁剪] {len(messages)} 条 → 首条 + 最近 3 条")
return {
"messages": [
RemoveMessage(id=REMOVE_ALL_MESSAGES),
messages[0],
*messages[-3:],
]
}
agent_trim = create_agent(
model=model,
middleware=[trim_messages],
checkpointer=InMemorySaver(),
)
cfg = {"configurable": {"thread_id": "trim"}}
for i in range(5):
rr = agent_trim.invoke({"messages": [HumanMessage(f"第 {i} 句话")]}, config=cfg)
print(f"第 {i + 1} 轮后 state 消息数: {len(rr['messages'])}")
# ---------- 2-3 删除 ----------
@after_model
def delete_old_messages(state: AgentState, runtime: Runtime) -> dict | None:
messages = state["messages"]
if len(messages) > 5:
to_delete = len(messages) - 5
print(f" [删除] 永久删除最早的 {to_delete} 条")
return {"messages": [RemoveMessage(id=m.id) for m in messages[:to_delete]]}
return None
agent_del = create_agent(
model=model,
middleware=[delete_old_messages],
checkpointer=InMemorySaver(),
)
cfg2 = {"configurable": {"thread_id": "delete"}}
for i in range(3):
rr = agent_del.invoke({"messages": [HumanMessage(f"第 {i} 句话")]}, config=cfg2)
print(f"第 {i + 1} 轮后 state 消息数: {len(rr['messages'])}")
# ---------- 3-1 带记忆的客服助手 ----------
SYSTEM = "你是客服助手。记住用户告诉你的订单号等关键信息,并在被问到时准确回答。"
agent_cs = create_agent(
model=model,
system_prompt=SYSTEM,
middleware=[trim_messages], # 复用 2-2 的裁剪钩子
checkpointer=InMemorySaver(),
)
cfg3 = {"configurable": {"thread_id": "cs-1"}}
print("\n[1] 报订单号")
print(" AI:", agent_cs.invoke({"messages": [HumanMessage("我的订单号是 A12345")]}, config=cfg3)["messages"][-1].content[:60])
print("[2] 立刻追问(应该记得)")
print(" AI:", agent_cs.invoke({"messages": [HumanMessage("我的订单号是多少?")]}, config=cfg3)["messages"][-1].content[:60])
print("[3] 再聊几轮无关内容")
for q in ["今天天气怎么样?", "你们发货快吗?", "支持退货吗?"]:
agent_cs.invoke({"messages": [HumanMessage(q)]}, config=cfg3)
print("[4] 再问订单号(裁剪之后可能就不记得了)")
print(" AI:", agent_cs.invoke({"messages": [HumanMessage("我的订单号是多少?")]}, config=cfg3)["messages"][-1].content[:80])
# 解释:订单号那条消息被"裁剪"掉了,模型看不到 → 这就是长期记忆要解决的问题

评论区

[ 标签 ]
# AI37# AI 编程2# AI工具1# Ajax2# Apifox1# AstrBot3# Astro2# CC Switch1# CDN2# Claude Code1# claudecode2# ClaudeCode1# Cloudflare2# CloudFlare2# CloudFlare-ImgBed3# coc3# CSS6# DeepSeek6# deepseek2# DELETE1# Docker1# EdgeOne3# Gist1# git1# GitHub1# hexo-circle-of-friends1# HTML6# HTTP5# ImageManager1# Java23# java13# JavaScript5# JDBC3# JSON2# JUnit1# LangChain25# Logback1# Maven6# Muse Spark1# Mybatis1# MyBatis4# MySQL28# MySql1# NapCat1# Node.js1# obsidian2# Obsidian5# OpenCode4# ORM1# PathVariable1# PicGo1# PyCharm1# Python65# RequestBody1# RequestMapping1# RESTful风格1# skills1# Slf4j1# SpringBoot11# SQL2# Streamlit5# Svelte2# TailwindCSS1# Telegram3# Tlias2# Vercel1# vscode2# Vue7# Waline3# WebDAV1# Web基础6# Web开发6# WinSCP1# YAML1# 三层架构1# 中二宣言1# 书籍1# 使用文档10# 写作1# 函数2# 刷步数1# 前端32# 动态1# 动漫1# 包1# 单词2# 博客7# 博客工作流1# 博客开发2# 参数接收1# 友链1# 反思2# 图床6# 地图1# 备份2# 大模型1# 奇思妙想1# 存储1# 学习方法6# 学校1# 宝塔面板3# 宝宝10# 对象1# 导航栏1# 工具2# 开发1# 开发工具1# 开发规范1# 开心1# 异常处理1# 影视2# 微信1# 性能优化2# 总结1# 想法15# 感受1# 感悟11# 指南1# 提示词工程1# 插件5# 故障排除1# 效率工具2# 教程10# 数据分析9# 数据库27# 数据结构1# 文件操作2# 斩神1# 日常92# 日志框架1# 朋友圈1# 朱元璋1# 模块1# 模板1# 正则表达式2# 测试1# 游戏2# 爬虫7# 生活迁移1# 电影2# 电脑1# 碎碎念1# 视觉识别1# 类1# 类型注解1# 网络基础2# 网络教室1# 羊毛2# 脚本2# 脚本工具1# 自动化2# 蓝奏云1# 订阅推荐2# 记录2# 评论系统1# 词根1# 词缀1# 说说1# 足迹1# 跑步2# 路径参数1# 转载2# 运动1# 部落冲突1# 配置1# 随机图1# 面向对象5# 音乐3# 音标1# 饮食1# 驼峰命名1# 高德地图1
[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

暂未播放

0:000:00
暂无歌词
找不到相关结果。
[ contents ]
[ 全部文章 ]