这是本文档旧的修订版!


第十章:核心能力详解 - Memory

“记忆”是很多 LLM 应用最容易被误用的能力之一。很多初学者会把 Memory 理解成“把历史聊天全塞进去”,但工程上真正的问题不是“有没有保存记录”,而是:系统应该记住什么、保存多久、以什么形式保存、在何时取回使用。

现代 LangChain 和 LangGraph 语境下,Memory 与其说是一个“会话缓存功能”,不如说是 状态管理 + 上下文压缩 + 长期档案设计 的组合。本章将系统讲解短期记忆、长期记忆、摘要记忆、档案记忆、线程状态、检查点,以及如何把这些概念落到具体代码里。

在一次性问答里,Memory 的收益很小;但在以下场景中,记忆会显著提升体验:

  • 多轮客服;
  • 长周期任务协作;
  • 项目跟进助手;
  • 个性化办公助手;
  • 带状态的 Agent 工作流。

没有记忆,系统每轮都像“第一次见你”;记忆用错,则系统会变得啰嗦、昂贵、容易泄露信息,还可能记住过期事实。

短期记忆解决的是当前会话的连续性,例如:

  • 用户刚刚给出的订单号;
  • 当前对话中确认过的条件;
  • 本次任务的中间步骤。

长期记忆保存跨会话依然有价值的信息,例如:

  • 用户所属部门;
  • 常用语言偏好;
  • 经常关注的项目;
  • 已确认的操作习惯。

不是保存原文,而是压缩成关键结论,例如:

  • 用户负责华东区渠道业务;
  • 当前项目目标是搭建知识库问答系统;
  • 偏好简洁回答,最好带表格。

档案记忆更像结构化档案或用户画像,适合存:

  • 账号属性;
  • 权限等级;
  • 组织归属;
  • 固定配置。

一个实用原则是:只存对未来决策仍有帮助的信息。

适合保存:

  • 稳定偏好;
  • 已确认事实;
  • 当前任务摘要;
  • 下轮仍会用到的状态字段。

不适合保存:

  • 冗长闲聊;
  • 未确认猜测;
  • 敏感原文全文;
  • 一次性噪声输出。
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
 
history = [
    SystemMessage(content="你是企业 IT 助手。"),
    HumanMessage(content="VPN 连不上怎么办?"),
    AIMessage(content="先检查网络和账号状态,如果仍失败联系 IT 服务台。"),
    HumanMessage(content="那我昨天已经重置过密码了。")
]

这种方式最适合:

  • 原型阶段;
  • 较短会话;
  • 临时 Demo。
def trim_history(messages, keep_last=8):
    system_messages = messages[:1]
    others = messages[1:]
    return system_messages + others[-keep_last:]

但要注意,这种方式很快会遇到两个问题:

  • 旧消息越来越长;
  • 重要结论会在裁剪时丢失。

对于较长会话,更好的方式通常是“近期原文 + 历史摘要”。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
 
summary_prompt = ChatPromptTemplate.from_template("""
请把以下对话总结成给后续助手看的会话摘要。
要求保留:
1. 用户目标
2. 已确认事实
3. 未解决问题
4. 用户偏好
 
对话:
{conversation}
""")
 
summarizer = summary_prompt | ChatOpenAI(model="gpt-4o-mini", temperature=0)
summary_message = SystemMessage(
    content="会话摘要:用户为财务部员工,正在咨询差旅报销规则,已确认出差日期为 4 月 1 日至 4 月 3 日。"
)

之后把 `summary_message` 插入消息列表,再拼接最近几轮原文,就能在有限 token 预算里保持连续性。

长期记忆更适合存成结构化字段,而不是大量历史原文。

user_profile = {
    "user_id": "u_001",
    "department": "finance",
    "language": "zh-CN",
    "preferred_response_style": "concise",
    "frequent_topics": ["报销", "预算", "采购流程"]
}
from langchain_core.prompts import ChatPromptTemplate
 
prompt = ChatPromptTemplate.from_template("""
你是企业办公助手。
用户档案:{profile}
请根据用户档案和问题给出回答。
 
问题:{question}
""")

这类设计的优点是:

  • 稳定;
  • 易于审计;
  • 容易更新和失效;
  • 不会像聊天原文那样越来越冗长。

根据 LangChain 官方短期记忆文档,现代 Agent 往往借助 checkpointer + thread_id 来实现线程级状态保存。

from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents import create_agent
 
checkpointer = InMemorySaver()
 
agent = create_agent(
    model="openai:gpt-4o-mini",
    tools=[],
    checkpointer=checkpointer,
    system_prompt="你是一个支持多轮对话的企业助手。"
)
 
config = {"configurable": {"thread_id": "thread-001"}}
 
agent.invoke(
    {"messages": [{"role": "user", "content": "我来自财务部。"}]},
    config=config,
)
 
agent.invoke(
    {"messages": [{"role": "user", "content": "我刚才说我是哪个部门的?"}]},
    config=config,
)

在这个例子里:

  • `thread_id` 用于标识同一个会话线程;
  • `checkpointer` 负责保存状态;
  • 第二次调用时,Agent 可以基于第一次的状态继续回答。

在复杂 Agent 或 LangGraph 中,记忆往往不只是消息,还包括结构化状态字段。

from typing import TypedDict, NotRequired
 
class CustomState(TypedDict):
    messages: list
    user_profile: NotRequired[dict]
    current_order_id: NotRequired[str]
    last_summary: NotRequired[str]
    preferred_language: NotRequired[str]

这种方式特别适合:

  • 将短期状态显式字段化;
  • 减少模型在历史中反复“找信息”;
  • 支持图节点之间共享状态。

不是每条信息都值得写进长期记忆。建议使用规则:

  • 用户明确确认过;
  • 对未来决策有持续价值;
  • 可结构化表达;
  • 不是高风险敏感原文;
  • 有失效规则。
def should_store_as_memory(text: str) -> bool:
    keywords = ["我是", "我负责", "以后都", "偏好", "请默认"]
    return any(k in text for k in keywords)

真实项目中通常会用:

  • 规则;
  • 分类器;
  • 结构化提取;
  • 人工确认;

组合来决定是否写入长期记忆。

如果没有失效机制,系统会把过期事实当真理。例如:

  • 用户已调岗,但系统还记着旧部门;
  • 审批规则已更新,但记忆还是旧版本;
  • 用户偏好曾经有效,但现在已变。

建议你为记忆设计:

  • TTL(过期时间);
  • 版本号;
  • 最近确认时间;
  • 明确覆盖规则。
memory_record = {
    "fact": "用户偏好英文输出",
    "confirmed_at": "2026-04-03T10:00:00",
    "expires_at": "2026-07-03T10:00:00",
    "source": "user_confirmed"
}

以下场景要谨慎:

  • 法务、财务等高敏感场景,除非你有明确合规方案;
  • 单轮问答;
  • 信息必须每次实时核验的场景,如价格、库存、政策即时状态;
  • 团队尚未建立基础日志与权限控制时。
conversation_state = {
    "profile": {
        "department": "finance",
        "language": "zh-CN",
        "style": "concise"
    },
    "summary": "用户正在咨询差旅报销流程,已确认出差日期和目的地。",
    "recent_messages": [
        {"role": "user", "content": "报销单需要先给主管审批吗?"},
        {"role": "assistant", "content": "是的,通常需要主管审批后再提交财务。"}
    ]
}

然后你可以在主 Prompt 中分层使用:

  • profile → 用于风格和权限控制;
  • summary → 用于压缩长期上下文;
  • recent_messages → 用于保留最近语义连续性。
  • Memory 本质上是状态管理问题,而不只是聊天记录缓存;
  • 短期、长期、摘要、档案记忆应区别设计;
  • `checkpointer + thread_id` 是现代 Agent 的常见短期记忆方式;
  • 结构化状态字段比无脑堆消息更稳定;
  • 必须为记忆设计写入规则、更新策略和失效机制。

1. 为一个企业办公助手设计短期记忆、长期记忆和档案记忆三层结构。 2. 使用 `InMemorySaver` 搭建一个支持 thread_id 的简单多轮 Agent。 3. 设计一个“只保存已确认事实”的记忆筛选器。 4. 为你的系统写一份记忆失效规则,说明哪些信息多久失效、谁可以覆盖它。 5. 将某个多轮问答场景改造成“近期原文 + 历史摘要 + 档案字段”的结构。

下面给出一个更具体的思路:让系统从长对话里自动提取“值得长期保存”的事实。

from pydantic import BaseModel, Field
from typing import Literal
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
 
class MemoryCandidate(BaseModel):
    fact: str = Field(description="可被长期保存的事实")
    category: Literal["profile", "preference", "task", "other"]
    should_store: bool = Field(description="是否建议存入长期记忆")
    reason: str = Field(description="为什么值得存或不值得存")
 
extract_prompt = ChatPromptTemplate.from_template("""
请从下面对话中识别是否存在值得长期保存的信息。
仅保留以下类型:
1. 用户稳定身份信息
2. 用户明确确认的偏好
3. 会跨轮使用的重要任务事实
 
对话:
{conversation}
""")
 
extractor = extract_prompt | ChatOpenAI(model="gpt-4o-mini", temperature=0).with_structured_output(MemoryCandidate)
 
candidate = extractor.invoke({
    "conversation": "用户说:我是财务部员工,以后默认用简洁中文回答我。"
})
print(candidate)

这个模式的价值在于:

  • 不是无脑存所有聊天;
  • 让“记忆写入”本身也可解释;
  • 便于在写入前做人工复核或规则过滤。
from langchain_core.prompts import ChatPromptTemplate
 
assistant_prompt = ChatPromptTemplate.from_template("""
你是企业办公助手。
 
用户档案:
{profile}
 
历史摘要:
{summary}
 
当前问题:
{question}
""")
 
result = assistant_prompt.invoke({
    "profile": {"department": "finance", "style": "concise"},
    "summary": "用户正在咨询 4 月初的差旅报销流程,已确认出差城市为上海。",
    "question": "住宿发票忘开发票抬头怎么办?"
})
print(result)

这类设计很适合:

  • 对话很长但仍需保留连续性;
  • 有一些稳定档案字段需要持续生效;
  • 希望把“聊天历史”和“用户画像”分开管理。

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • langchain二次开发/核心能力详解_memory.1775196996.txt.gz
  • 最后更改: 2026/04/03 14:16
  • 张叶安