인셔셔RSS 관심 있는 블로그, 뉴스, 기술 정보를 효율적으로 추적하고 읽으세요
원문 읽기 InertiaRSS에서 열기

추천 피드

WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
T
The Blog of Author Tim Ferriss
Engineering at Meta
Engineering at Meta
Jina AI
Jina AI
Last Week in AI
Last Week in AI
I
InfoQ
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
人人都是产品经理
人人都是产品经理
MongoDB | Blog
MongoDB | Blog
The Cloudflare Blog
月光博客
月光博客
爱范儿
爱范儿
D
Docker
罗磊的独立博客
博客园 - 叶小钗
博客园 - 司徒正美

掘金

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
Agent 시리즈 (6): 메모리 관리 - Agent가 중요한 일을 기억하게 하기
冬奇Lab · 2026-05-27 · via 掘金

기억, Agent을 "도구"에서 "도우미"로 바꿔줍니다.

기억이 없는 Agent은 각 대화가 처음부터 시작합니다. 그에게 '이름은 리레이이고 파이썬 엔지니어이며 손으로 직접 해보는 것을 좋아한다'고 말해도 다음 대화에서는 전혀 기억하지 못합니다.

이것은 버그가 아니라 아키텍처의 결함입니다.

LLM 자체에는 영속적인 기억이 없으며, 각 호출은 무상태입니다. Agent이 일을 기억하려면 아키텍처 레이어에서 명시적으로저장하고 관리하고 읽어들여야 합니다.기억

이것이 기억 관리 모듈이 해결해야 할 문제입니다.


이 글은 Agent 기억을 네 가지 차원으로 분석합니다: 기억 유형의 분류 모델, 맥락 관리의 세 가지 전략, LangGraph가 제공하는 두 가지 기억 메커니즘(checkpointer와 store), 그리고 초장 대화의 자동 압축 방안입니다.__JHSNS_SEG_a04323c2_8__네 가지 기억 유형: 인지 과학에서 공학 실현까지

인지 과학에서 인간의 기억의 분류를 참고하여, Agent의 기억도 네 개의 레벨로 나눌 수 있습니다. 각 레벨은 LangGraph에서 대응되는 구현 방식이 있습니다:

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

감각 기억: 현재 턴의 메시지

가장 짧은 기억. 한 번의 LLM 호출의 입력과 출력은 사용하고 버립니다:

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

감각 기억을 "관리"하기 위해 어떤 메커니즘도 필요하지 않습니다 - 그것은 LLM 호출 자체입니다.

작업 기억: 제한된 대화 이력

이전 몇 라운드의 대화 메시지를 프롬프트에 연결하는 것이 가장 직접적인 기억 구현입니다. 효과는 즉각적입니다:

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

실제 비교:

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

차이는 매우 직관적입니다. 작업 기억의 한계는 토큰 비용이 대화 길이에 비례하여 선형적으로 증가하는 것입니다. 잘라내거나 요약을 결합하여 관리해야 합니다.

상황 기억: 요약된 역사적 조각

대화 이력이 매우 길 때, 전체 이력을 직접 프롬프트에 넣는 비용이 너무 높습니다. 맥락 기억 방법은 먼저 압축한 다음, 저장 입니다:

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

16개의 메시지를 28자로 압축하고, 다음 라운드에는 요약을 원본 이력 대신 사용하여 토큰 소모를 크게 줄입니다.

의미 기억: 회의 간의 사용자 사실

가장 오래 지속되는 기억 계층입니다. 대화가 끝나도 사라지지 않으며, 사용자에 대한 장기적인 사실(이름, 직업, 선호 등)을 전용으로 저장합니다:

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

맥락 관리 세 가지 전략: 잘라내기 / 요약 / 검색

대화 이력이 점점 길어지면서, 맥락 창이 감당하지 못하면 어떡해요? 세 가지 전략은 각각의 선택 사항이 있습니다:

전략 1: 잘라내기 (Truncation)

가장 간단하고 무뚝뚝한 방법 - 최근 N개의 메시지를 유지하고 나머지는 모두 버립니다:

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

실제로 8개의 주제(16개의 메시지)의 역사를 사용하고, 최근 4개를 잘라내서 "Python 리스트는 무엇인가요"라고 묻는 것:

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

적용 시나리오:역사의 연속성 요구가 높지 않은 시나리오 또는 역사 자체가 중요하지 않은 순수 질의응답 유형의 에이전트.

전략 2: 요약(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

전략 3: 검색(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)

두 번째와 세 번째 턴의 답변은 이전의 이력에 의존했으며, 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에서, 에이전트는 대화에서 사용자 정보를 자동으로 추출하여 저장소에 저장합니다:

[会话 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))

생산 환경에서 InMemoryStorePostgresStore 또는 RedisStore로 대체하면 실제 영구 저장이 가능합니다.


자동 요약 압축: RemoveMessage + 요약 순환

메시지 수가 임계값을 초과하면 자동 압축이 트리거됩니다—이것은 무한한 대화에서 에이전트가 정신을 차리도록 하는 핵심 메커니즘입니다.

그래프 구조

[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
  • 할당 설정历史 자르기 임계값, 토큰 무한 증가 방지

장기 기억 (store)

  • 네임스페이스별로 사용자 데이터를 구성:(类型, user_id)("user_facts", uid)
  • 에서 기억을 추출할 때 신뢰도 필터링을 적용하여 의미 없는 노이즈를 저장하지 않도록 해야 합니다.
  • 생산 환경을 PostgresStore / RedisStore 로 대체합니다.

컨텍스트 압축

  • 압축 임계값을 결정합니다 (추천 8-20개의 메시지, 시나리오에 따라 다름).
  • 요약 Prompt에서 보존해야 할 정보를 명확히 지정합니다 (주제 이름, 핵심 결정, 사용자 선호도).
  • 요약 체인 테스트: N차례 압축된 요약이 1차례의 핵심 정보를 포함하고 있는지 확인합니다.
  • RemoveMessage 를 전체 메시지 목록을 대체하는 대신 사용합니다 (후자는 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 파이프라인의 본질적인 차이, 다수의 지식베이스를 라우팅하는 방법, Agent가 언제 검색해야 할지, 무엇을 검색해야 할지, 몇 번 검색해야 할지 결정하는 방법입니다


참고자료


제 개인 홈페이지로 방문해 주세요더 많은 유용한 지식과 재미있는 제품을 찾으실 수 있습니다