惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LangChain Blog
人人都是产品经理
人人都是产品经理
D
DataBreaches.Net
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
小众软件
小众软件
The Register - Security
The Register - Security
C
Check Point Blog
Engineering at Meta
Engineering at Meta
The GitHub Blog
The GitHub Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
爱范儿
爱范儿
有赞技术团队
有赞技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
Vercel News
Vercel News
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
美团技术团队
P
Proofpoint News Feed
IT之家
IT之家
Martin Fowler
Martin Fowler
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
V2EX
MyScale Blog
MyScale Blog
Y
Y Combinator Blog
博客园 - 【当耐特】
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
S
Schneier on Security
G
Google Developers Blog
Hugging Face - Blog
Hugging Face - Blog
F
Full Disclosure
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - Franky
T
The Exploit Database - CXSecurity.com
罗磊的独立博客
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
Latest news
Latest news
GbyAI
GbyAI
P
Privacy International News Feed
Last Week in AI
Last Week in AI
T
The Blog of Author Tim Ferriss
H
Hacker News: Front Page
K
Kaspersky official blog

掘金

Win 安装Claude Code FastAPI 的 CORSMiddleware 跨域中间件 Java 自研 ReAct Agent 半年后,我用 LangGraph 验证了这些设计取舍 🚀AI编程工作流终极形态:GitNexus!零Token消耗实现代码知识图谱化!让Claude Code和Codex拥有上帝视角彻底告别盲目改代码,复杂项目重 LeetCode 72. 编辑距离:动态规划经典题解 被The Graph的GraphQL查询坑了三天,我用一个真实DeFi项目把链上数据索引彻底搞懂了 (AI) 编写简单 AI 助手 (ds-agent) 别再让 pnpm 跟着 nvm 跑了!独立安装终极指南 Claude Code 为什么这么顺?Anthropic 最新复盘:真正撑住它的不是模型,而是缓存 从 /simplify 指令深挖 Claude Code 多 Agent 协同机制 Function-Calling与工具使用 新手上路(六):Claude code装上ECC全家桶:38 个子代理、156 个技能、生产级 Hooks 与 Rules 体系 我在 Claude、Kimi、opencode 三个 AI 之间搭了一条自动协作管道 【技能篇】OpenClaw Skill 详解:给 AI 装上"专业外挂" wagmi v2 多链钱包切换:一个 Uniswap 仿盘项目让我踩了三天坑 两周浅学 RAG 我把 Python re 模块比喻成摸金手套 新手上路(三):Claude Code Skills 装了一堆没用?20+ 个 Skill 横向对比 + 三套组合方案,按需抄 K2.6、DeepSeek V4、GPT-5.5 都来了,组合拳打起来 Claude Code 进阶之路:从记忆系统到子代理编排 [java] 编译之后的记录类(Record Classes)长什么样子(上) 国产大模型能力大比拼,社区有话说 我研读了 500 个 Spring Boot 生产级代码库,90% 都犯了这 7 个致命错误 JAVA重点难点 转发-中央网信办部署开展“清朗·整治AI应用乱象”专项行动 合同同步逻辑 【合并已排序数组的三种实现策略,哪一种更可取?】 30天减20斤挑战:少一斤发100红包(2) 我竟然被JavaScript的隐式类型转换坑了三天! 二十五.Electron 初体验与进阶 本地到生产,解决 AI 全栈最后一公里——构建&部署&运维 程序员创业半年:顺的事、不顺的事,和我一直没想清楚的事 UI组件库elementplus 像使用 Redis 一样操作 LocalStorage 向量检索的流程是怎样的?Embedding 和 Rerank 各自的作用? LangChain DeepAgents 速通指南(七)—— DeepAgents使用Agent Skill 为什么越来越多的大厂抛弃MCP,转向CLI? 【节点】[SquareRoot节点]原理解析与实际应用 juejin.cn juejin.cn 从 “存得下” 到 “算得快”:工业物联网需要新一代时序数据平台越来越多工业用户开始意识到一个问题:**数据是存下来了, - 掘金 放弃 Claude 订阅?我用 8 年前的服务器,强跑 Google 最强开源模型 Gemma 4 真实测评! Python开发者狂喜!200+课时FastAPI全栈实战合集,10大模块持续更新中🔥 从 Claw-Code 看 AI 驱动的大型项目开发:2 人 + 10 个自治 Agent 如何产出 48K 行 Rust 代码 秒级创建实例,火山引擎 Milvus Serverless 让 AI Agent 开发更快更省火山引擎MilvusSer MediaPlayer 播放器架构:NuPlayer 的 Source/Decoder/Renderer 三驾马车 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn OrbStack:一键将你的 Mac 变为本地服务器 NginxPulse:Nginx日志监控革命!实时洞察Web流量与安全态势的智能利器引言:当Nginx日志成为运维的“数 - 掘金 juejin.cn 大V说’AI替代不了你’,但现实是——用AI的人正在替代你2026年是AI落地的元年,自从Claude Code爆火之后 - 掘金 juejin.cn 你以为是技术问题,其实是流程问题:工程效率的真相引言 在软件工程领域,效率问题始终是团队管理者和工程师们关注的焦点。当项 - 掘金 大模型工程三驾马车:Prompt Engineering、Context Engineering 与 Harness Engineering 深度解析 juejin.cn 4.响应式系统基础:从发布订阅模式的角度理解 Vue3 的数据响应式原理本文从发布订阅模式的核心思想出发,深入剖析了 V - 掘金 慌了!Android 17 取消图标文字,你的 App 可能要找不到了用户终于可以隐藏桌面图标下面的文字了。 这个功能在 juejin.cn 我用 AI 搓了一个"比谁更持久"的微信小游戏,AI实现只用了一天,微信审核却用了一个月!!!起因:一个沙雕想法的诞生 - 掘金 juejin.cn 第12章 工具(Tools)与函数调用(LangChain实战)在前几章中,我们搭建的RAG系统、对话链,核心能力局限 - 掘金 juejin.cn CmComposeUI —— 基于 Kotlin Multiplatform Compose 的 UI 组件库 Android 开发的 AI coding 与 AI debugging在目前整个行业都在大规模使用 AI coding juejin.cn juejin.cn juejin.cn juejin.cn 一文搞懂Harness Engineering与Meta-Harness 越用越强不是广告语:拆解 Hermes Agent 的三层学习机制 P2G-Python字符串方法完全指南-split、join、strip、replace的Python编程利器 AI 周刊【2026.04.06-04.12】:Anthropic 藏起最强模型、AI 社会矛盾激化、"欢乐马"登顶 从 AI Skills 学实战技能(六):让 AI 帮你总结网页、PDF、视频 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 详解 karpathy 的 microgpt:实现一个浏览器运行的 gpt 不用 Tailscale:3 步把 Mac mini 通过 FRP 暴露到公网(稳定开机自启) P2B-Python可迭代对象完全指南-从列表到生成器的Python编程利器 手把手带你部署本地模型,让你Token自由(小白专属) juejin.cn 10分钟掌握 JSON-RPC 协议,面试加分、设计不踩坑 ReAct:让大模型学会边想边做 聊聊AI的发展史,AI的爆发并不是偶然 Python的列表推导式里藏了个坑,差点让我加班到凌晨 重排、重绘与合成——浏览器渲染性能的底层逻辑 podman与docker的区别和生产环境最佳实践 juejin.cn ConcurrentHashMap线程安全实现原理全解析 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn OpenAI Codex深度解析:终端里的AI代码特工,一个指令重构整个项目 UE5.6 Cesium 插件编译踩坑记录(UE 5.6 + MSVC 14.38 + CMake 3.31)
Agent系列(六):记忆管理——让 Agent 记住重要的事
冬奇Lab · 2026-05-27 · via 掘金

记忆,让 Agent 从"工具"变成"助手"

一个没有记忆的 Agent,每次对话都是从零开始。你告诉它你叫李雷、你是 Python 工程师、你喜欢动手实践——下次对话,它完全不记得。

这不是 Bug,是架构缺失。

LLM 本身没有持久记忆,每次调用都是无状态的。要让 Agent 记住事情,需要在架构层显式地存储、管理和读取记忆。这就是记忆管理模块要解决的问题。

本篇从四个维度拆解 Agent 记忆:记忆类型的分类模型、上下文管理的三种策略、LangGraph 提供的两种记忆机制(checkpointer 和 store)、以及超长对话的自动压缩方案。


四种记忆类型:从认知科学到工程实现

借鉴认知科学对人类记忆的分类,Agent 的记忆也可以分为四层,每一层在 LangGraph 中都有对应的实现方式:

┌──────────────────────────────────────────────────────────┐
│                     记忆层级                              │
├──────────────────────┬───────────────────────────────────┤
│ 感觉记忆 (Sensory)   │ 当前 Turn 的 in-flight 消息       │
│                      │ 生命周期:单次 LLM 调用            │
├──────────────────────┼───────────────────────────────────┤
│ 工作记忆 (Working)   │ 对话历史 MessageHistory(有限 K 轮)│
│                      │ 实现:messages 列表注入 Prompt     │
├──────────────────────┼───────────────────────────────────┤
│ 情景记忆 (Episodic)  │ 向量化/摘要化的历史片段            │
│                      │ 实现:摘要压缩 + VectorStore 检索  │
├──────────────────────┼───────────────────────────────────┤
│ 语义记忆 (Semantic)  │ 长期存储的用户偏好、事实           │
│                      │ 实现:LangGraph store (KV Store)  │
└──────────────────────┴───────────────────────────────────┘

感觉记忆:当前 Turn 的消息

最短暂的记忆。一次 LLM 调用的输入和输出,用完即弃:

q = "Python 中 len([1, 2, 3]) 等于多少"
answer = llm.invoke([HumanMessage(q)])
# answer.content → "len([1, 2, 3]) 的结果等于 3。"
# 这次 invoke 结束后,这个 answer 就消失了

不需要任何机制来"管理"感觉记忆——它是 LLM 调用本身。

工作记忆:有限的对话历史

把之前几轮对话消息拼接进 Prompt,是最直接的记忆实现。效果立竿见影:

history = [
    HumanMessage("我叫李雷,是一名 Python 工程师"),
    AIMessage("你好,李雷!很高兴认识你。"),
    HumanMessage("我最近在学习 LangGraph"),
    AIMessage("LangGraph 很强大,特别适合构建有状态的 Agent。"),
]
test_q = "我之前告诉你我叫什么名字?"

实测对比:

有历史 → "是的,你之前告诉我你的名字是李雷,并且你是一名 Python 工程师..."
无历史 → "抱歉,我无法回忆起您之前告诉我的名字,因为作为一个 AI,我没有
          持久的记忆功能来存储个人数据..."

差距非常直观。工作记忆的限制是 Token 成本随对话长度线性增长,需要结合截断或摘要来管理。

情景记忆:摘要化的历史片段

当对话历史很长时,把全部历史直接塞进 Prompt 代价太高。情景记忆的做法是先压缩,再存储

long_history = history * 4  # 16 条消息
summary = llm.invoke([
    SystemMessage("将以下对话压缩为 60 字以内的摘要,保留关键信息"),
    HumanMessage(str([m.content for m in long_history])),
])
# → "李雷,Python工程师,积极学习LangGraph,赞其强大,适合构建有状态Agent。"

16 条消息压缩成 28 个字,下一轮用摘要代替原始历史,Token 消耗大幅下降。

语义记忆:跨会话的用户事实

最持久的记忆层。不随对话结束而消失,专门存储关于用户的长期事实(姓名、职业、偏好等):

# 把用户信息存入 KV Store,下一次会话直接读取
user_profile = {
    "name": "李雷",
    "role": "Python 工程师",
    "interests": ["LangGraph", "Agent 开发"],
    "level": "中级",
}
# 基于这些信息,Agent 能给出个性化的回答
# "下一步学习方向" → 推荐 LangGraph 进阶用法而不是 Python 入门

上下文管理三策略:截断 / 摘要 / 检索

当对话历史越来越长,上下文窗口撑不住怎么办?三种策略各有取舍:

策略一:截断(Truncation)

最简单粗暴——保留最近 N 条消息,其余全部丢弃:

# 保留最近 4 条消息
truncated = history[-4:]
resp = llm.invoke(truncated + [HumanMessage(test_q)])

实测用 8 个主题(16 条消息)的历史,截断到最近 4 条后问"Python 列表是什么":

截断后最早可见:'解释一下 Python 装饰器'(第 5 个主题,"列表"在第 1 个)
回答:Python 中的列表是一种内置的数据结构... (靠 LLM 自身知识回答)
⚠ 丢失了我们"学过"列表这个事实,LLM 只是在用通用知识回答

适用场景:对历史连贯性要求不高的场景,或历史本身就不重要的纯问答类 Agent。

策略二:摘要(Summarization)

用 LLM 把长历史压缩成一段摘要,后续对话以摘要代替原始历史:

summary_resp = llm.invoke([
    SystemMessage("将对话历史压缩为一段摘要(不超过 80 字),保留所有已介绍的主题名称"),
    HumanMessage("\n".join([f"{m.type}: {m.content}" for m in history])),
])
# → "Python列表可变有序,元组不可变省内存,字典键值对映射,集合唯一元素,
#    函数封装逻辑,类面向对象,装饰器函数包装,生成器惰性计算。"
# 16 条消息 → 66 字摘要

同样问"Python 列表是什么",摘要方案的回答能体现"这是我们讨论过的话题",而不只是通用知识。

三种策略对比

策略Token 消耗信息保留实现复杂度适用场景
截断最低只有近期极低纯问答、历史不重要
摘要全局脉络教学、咨询、长期对话
检索最低精准相关高(需向量库)知识库、多领域 Agent

策略三:检索(Retrieval)

只拉取与当前问题语义相关的历史片段——是效果最好也最复杂的方案:

# 简化演示:按关键词过滤(生产中用向量相似度)
relevant = [m for m in history if "列表" in m.content or "list" in m.content.lower()]
# 16 条 → 3 条相关历史
resp = llm.invoke(relevant + [HumanMessage(test_q)])

适用场景:知识库型 Agent、用户有大量历史记录的个人助手。


LangGraph checkpointer:会话内状态持久化

LangGraph 的 MemorySaver(checkpointer)用 thread_id 区分不同会话,在同一会话内自动积累对话历史:

from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent

checkpointer = MemorySaver()
agent = create_react_agent(model=llm, tools=[get_weather], checkpointer=checkpointer)

# 同一 thread_id = 同一个会话,状态持久化
config_a = {"configurable": {"thread_id": "weather_001"}}

跨轮引用实测

会话内连续三轮问天气:

[Turn 1] 用户: 北京今天天气怎么样?
         Agent: 北京今天的天气是晴,温度为 25°C,东北风 3 级,空气良好。
         (当前状态中消息数: 4)

[Turn 2] 用户: 那上海呢?          ← "那" 字没有明确指代,需要上文理解
         Agent: 上海今天的天气是多云,22°C,东南风 2 级,轻度雾霾。
         (当前状态中消息数: 8)

[Turn 3] 用户: 这两个城市哪个更适合今天出行?   ← 需要前两轮的查询结果
         Agent: 考虑到上海的雾霾情况,建议您在北京出行。
         (当前状态中消息数: 10)

Turn 2 和 Turn 3 的回答都依赖了前面的历史,checkpointer 自动完成了跨轮上下文管理。

会话隔离

不同 thread_id 之间完全独立:

[新会话 - thread_id: weather_002]
用户: 我刚才问的是哪个城市?
Agent: 您刚才问的是"哪个城市",但是没有提供具体的城市名称。
      如果您需要查询某个城市的天气,请告诉我具体的城市名称。
→ 新 thread_id 没有任何历史,完全不知道刚才问过什么

会话继续

同一 thread_id 隔一段时间再回来,历史依然存在:

[会话 A 继续 - 同一 thread_id]
用户: 刚才查的两个城市,再查一下深圳对比一下
Agent: 深圳今天的天气是阵雨,27°C,西南风 2 级,同时还有雷暴预警。
       (进行了北京/上海/深圳三城比较)
→ 记住了前面查过北京和上海

MemorySaver 是内存实现,进程重启后数据丢失。生产中用 SqliteSaver(本地文件)或 PostgresSaver(数据库)代替。


LangGraph InMemoryStore:跨会话长期记忆

checkpointer 解决的是单次会话内的记忆。跨会话的长期记忆需要 store

checkpointer  →  绑定 thread_id,会话生命周期内有效
store         →  绑定 user_id,跨会话永久存在

核心 API

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

# 写入:(namespace, key, value)
store.put(("user_facts", user_id), key, {"fact": "李雷,后端工程师"})

# 读取:搜索某个 namespace 下的所有条目
facts = store.search(("user_facts", user_id))
for item in facts:
    print(item.value["fact"])

# 精确读取
item = store.get(("user_facts", user_id), specific_key)

跨会话记忆实测

在会话 A 中,Agent 自动从对话中提取用户信息并存入 store:

[会话 A] 用户说了三句话 → 自动提取并存储:
  • 李雷,后端工程师
  • Python, Go, LangGraph, Agent 开发
  • 动手实践,不喜欢纯看文档

在完全不同的会话 B(新 thread_id,但同一 user_id)中问"你认识我吗?":

[会话 B - 全新 thread_id]
用户: 你好,你认识我吗?
Agent: 你好!根据你提供的信息,我认识你。你是李雷,一位后端工程师,
       擅长使用 Python、Go、LangGraph 和 Agent 进行开发。
       你更喜欢动手实践,而不是仅仅阅读文档。有什么可以帮助你的吗?
→ 虽然是全新 thread_id,但 store 中的用户信息跨会话持久

不同 user_id 的数据完全隔离,互不干扰。

checkpointer vs store 对比

# 短期记忆:checkpointer — 绑定 thread_id,会话内有效
app = graph.compile(checkpointer=MemorySaver())
result = app.invoke(input, config={"configurable": {"thread_id": "abc"}})

# 长期记忆:store — 绑定 user_id,跨会话有效
store = InMemoryStore()
app = graph.compile(store=store, checkpointer=MemorySaver())
# 在 node 中操作 store
store.put(("user_facts", user_id), key, {"fact": "..."})
stored = store.search(("user_facts", user_id))

生产中 InMemoryStore 替换为 PostgresStoreRedisStore 即可获得真正的持久化。


自动摘要压缩:RemoveMessage + 摘要轮替

当消息数超过阈值时,触发自动压缩——这是让 Agent 在无限长对话中保持清醒的核心机制。

图结构

[chat 节点]
    │
    ├─ 消息数 ≤ 阈值 → END
    └─ 消息数 > 阈值 → [compress 节点] → END

compress 节点:RemoveMessage 删除旧消息

def compress_node(state: SummaryState) -> dict:
    messages = state["messages"]
    to_compress = messages[:-2]   # 保留最新 2 条,其余全部压缩
    keep = messages[-2:]

    # 旧消息 → 新摘要
    new_summary = llm.invoke([
        SystemMessage("将以下内容压缩为 120 字以内的摘要"),
        HumanMessage(existing_summary + old_messages_text),
    ]).content

    # RemoveMessage:告诉 add_messages reducer 删除这些消息
    remove_ops = [RemoveMessage(id=m.id) for m in to_compress]
    return {"messages": remove_ops, "summary": new_summary}

RemoveMessage 是 LangGraph 专用的消息删除操作符,add_messages reducer 看到它会从状态中删除对应 id 的消息。

实测效果

11 轮对话,阈值设为 8 条消息:

[Turn 1-4]  消息数:  2/4/6/8  | 摘要: ○ 无

  [压缩触发] 10 条 → 压缩 8 条,保留 2 条
  [新摘要]   Python列表常用方法包括查找、排序、添加删除等。
             `dict.get()` 避免 `KeyError`,返回默认值。
             `*args` 接收任意位置参数,`**kwargs` 接收任意关键字参数...

[Turn 5]    消息数:  2  | 摘要: ✓ 已压缩  ← 压缩后从 2 条重新开始

  [压缩再次触发] 10 条 → 压缩 8 条,保留 2 条

[Turn 11]  最终汇总:
           "根据我们之前的讨论,以下是您掌握的 Python 知识点汇总:
            1. Python 列表推导式...
            2. 集合推导式...  ← 通过摘要链条传承,第 1 轮的内容还在
            3. Lambda 函数..."

关键结果:11 轮对话始终只有 2-8 条 active 消息,但通过摘要链,所有历史知识都传承了下来。

摘要状态设计

class SummaryState(TypedDict):
    messages: Annotated[list, add_messages]  # add_messages 处理 RemoveMessage
    summary: Optional[str]                   # 累积的历史摘要,注入 system prompt

def chat_node(state: SummaryState) -> dict:
    summary = state.get("summary") or ""
    system_parts = ["你是助手。"]
    if summary:
        system_parts.append(f"\n\n【历史摘要】{summary}")  # 摘要注入 system prompt
    resp = llm.invoke([SystemMessage("".join(system_parts))] + state["messages"])
    return {"messages": [resp]}

记忆管理设计清单

实现一套完整的 Agent 记忆系统需要考虑的点:

短期记忆(checkpointer)

  • 选择合适的 checkpointer 后端(开发用 MemorySaver,生产用 SqliteSaver/PostgresSaver)
  • 为每个用户/会话分配唯一的 thread_id
  • 设置历史截断阈值,防止 Token 无限增长

长期记忆(store)

  • 按 namespace 组织用户数据:(类型, user_id)("user_facts", uid)
  • 提取记忆时要有置信度过滤,避免存入无意义的噪声
  • 生产环境替换为 PostgresStore / RedisStore

上下文压缩

  • 确定压缩阈值(建议 8-20 条消息,视场景而定)
  • 摘要 Prompt 中明确要保留哪些信息(主题名称、关键决策、用户偏好)
  • 测试摘要链:第 N 轮压缩的摘要是否包含了第 1 轮的关键信息
  • RemoveMessage 而不是替换整个 messages 列表(后者在有 checkpointer 时会有问题)

记忆的读写时机

  • 读记忆:在 chat_node 开头,注入 system prompt
  • 写记忆:在 chat_node 结尾,提取用户新信息
  • 避免每轮都写(设置提取置信度或内容长度过滤)

本篇小结

几个核心观点:

  1. 四种记忆类型各司其职:感觉记忆是 LLM 调用本身;工作记忆是对话历史;情景记忆是压缩摘要;语义记忆是跨会话的 KV Store
  2. checkpointer 管会话内,store 管跨会话:thread_id 是会话维度,user_id 是用户维度,两个维度分开管理
  3. 摘要压缩是长对话的核心方案:RemoveMessage + 摘要注入,让 Agent 在无限长对话中保持 Token 可控
  4. 会话隔离是基础:不同 thread_id 的历史互不干扰,不同 user_id 的长期记忆互不干扰
  5. 从 InMemoryStore 到 PostgresStore 只需换一行:架构不变,后端可插拔

下一篇:知识库集成——RAG 作为 Agent 工具和 RAG Pipeline 的本质区别,多知识库路由,Agent 如何决定什么时候检索、检索什么、检索几次。


参考资料


欢迎来我的个人主页找到更多有用的知识和有趣的产品