☰
Agent记忆四层架构:Context、Session、State与Memory的电商客服实战
2026/10/28 4:35:37 网站建设 项目流程

很多 Agent 项目的通病是:模型越换越大,能力却没见长进。原因往往不在模型,而在记忆。用户第二次来咨询,Agent 不记得他上次买过什么;对话超过几十轮,模型开始把早期指令忘掉;两个用户同时提问,会话串线。这些问题的根子,都指向四个词:Context、Memory、Session、State。

这次我们就围绕这四个概念,把 Agent 记忆拆开讲清楚,然后手把手落地一个电商客服助手。这个助手的核心不是单纯调用大模型,而是让大模型具备短期工作状态、会话隔离能力、长期用户记忆,以及上下文超限时的自动处理。做到这四件事,Agent 才谈得上“越用越聪明”。文章会覆盖架构设计、代码实现、功能测试、API封装和批量任务,适合正在做 Agent 开发、任务编排、客服机器人或知识库问答的读者。

1. Agent 记忆核心能力速览

在动手之前,先把四个概念放进一张表里。它们不是同义词,而是四层不同生命周期的记忆结构。

记忆层级对应概念存储位置生命周期典型用途
Context上下文窗口模型请求体内部单次请求结束即消失系统提示词、最近对话、检索结果
State工作状态编排框架的图状态或内存一次任务流从开始到结束任务进度、临时变量、当前意图
Session会话服务端存储,如 Redis、SQLite一次会话周期会话消息历史、会话隔离
Memory长期记忆数据库、向量库跨会话长期保留用户偏好、历史订单、身份信息

四个层级的关系可以用一句话概括:Context 是模型“眼前能看到的东西”,Session 决定“这次对话从哪里来到哪里去”,State 是 Agent 执行任务时的临时工作台,Memory 是沉淀下来跨会话复用的业务事实。

在电商客服场景里,四者的落点非常清晰。Context 放当前轮系统提示词和最近几条消息;Session 标记是哪位用户在哪个会话中提问,避免串线;State 记录订单查询进度、是否已经索取订单号、是否转人工;Memory 记住用户常买的品类、默认收货地址、历史售后记录。下一节先明确这个设计适合谁用。

2. 适用场景与使用边界

这套 Agent 记忆方案最适合多轮、个性化、需要跨会话复用信息的业务场景。典型例子就是电商客服:用户不会每次都完整说出“我上周在你们店买了一个黑色手机壳,现在裂了”,他更可能只说“我上周买的手机壳坏了”。如果 Agent 有 Memory,它能把“上周买的手机壳”自动解析成具体的订单记录;如果只有 Context 没有 Memory,这一句话就会让模型无从下手。

它同时适合知识库问答、售后工单处理、用户运营助手、教育辅导机器人等场景。这些场景的共同点是:用户之间存在明显差异,且历史信息对当前回答有决定性影响。

不适合的场景也很明确。单轮一次性任务,比如“把这段英文翻译成中文”,不需要 Session 和 Memory,引入反而增加延迟和存储成本。对数据隐私极其敏感、不允许存储任何用户信息的场景,也不能照搬这套设计。另外,如果业务要求严格实时、必须把响应压到几百毫秒以内,那么长期记忆检索和上下文压缩带来的额外耗时需要仔细评估。

使用边界必须强调:电商客服会接触到订单号、收货地址、手机号等个人信息。长期记忆存储不是无上限收集数据,而是要有授权、有脱敏、有过期清理策略。记忆分层设计的意义之一就是可控:该记的记,不该记的一律不落库。

3. 环境准备与前置条件

这套方案的代码不绑定特定平台,Windows、macOS、Linux 都可以跑。核心依赖包括:

  • Python 3.10 及以上,具体版本以依赖包要求为准。
  • 一个可用的大模型服务,可以是 OpenAI 兼容接口,也可以是本地部署的模型。
  • 任务编排框架,推荐使用 LangGraph 或自研状态机。本文示例用 LangGraph 风格编写,但核心思想不依赖任何框架。
  • 存储组件:SQLite 用于本地测试,Redis 用于 Session 缓存,向量数据库用于记忆检索。
  • 可选 Docker,用于统一运行环境。

先检查基础环境:

python --version pip --version

如果使用 LangGraph 生态,可以按需安装依赖。下面只是示例命令,缺失哪个模块装哪个:

pip install langgraph langchain-openai fastapi uvicorn redis chromadb

不需要严格照抄整条命令。更稳妥的做法是根据项目需要,逐个安装并验证。安装完成后,下一步是先把 Agent 记忆的四层架构理解透,再进入代码实现。

4. 四层记忆架构拆解

4.1 Context:模型唯一能看到的“眼前”

Context 是大模型一次推理能接收的全部内容,包括系统提示词、历史聊天记录、检索回来的用户信息、工具返回结果。模型本身没有持久记忆,它能依赖的只有请求中的 Context。因此所有上层记忆,最终都要在合适的时机被转换成 Context 的一部分,模型才能看到。

Context 管理直接决定 Agent 是“聪明”还是“糊涂”。Context 太短,关键信息放不进去,模型只能猜;Context 太长,超过模型的最大窗口,就会报出类似maximum context length exceeded的错误。这是 Agent 开发群里最常见的报错之一,问题根源通常不是模型不行,而是没有做上下文裁剪和压缩。

4.2 Session:会话隔离的边界

Session 表示一段连续对话。同一个用户连续咨询,通常放在同一个 Session 中;不同用户之间的 Session 必须隔离。电商客服助手最常见的串线事故,就是因为只用了一个全局消息列表,A 用户的订单被 B 用户看到了。

实现 Session 的常见方式是用 Redis 或 SQLite 保存消息,以session_id作为 key,并设置过期时间。Session 存储的消息可以理解为“会话的原始素材”,但真正进入模型 Context 的,往往只是经过裁剪后的最新片段。

4.3 State:Agent 任务流的工作台

在 LangGraph 这类编排框架中,State 是节点之间传递数据的核心载体。它既保存用户输入,也保存任务过程中的临时结果,比如“是否已经查过订单”“当前匹配到哪个订单”“是否需要转人工”。

很多初学者问“LangGraph 如何在节点函数改变 state 状态值”,答案很简单:节点函数接收当前状态,返回一个字典,返回的字段会更新到状态中。没有返回的字段保持原样。State 的生命周期通常只在一次任务流内,任务结束后要么归档,要么丢弃。它不等同于长期记忆。

4.4 Memory:跨会话的长期沉淀

Memory 是“用户过去是谁、买过什么、有什么偏好”。它不直接进 Context,而是在需要时被检索出来,拼接到当前上下文中。Memory 的存储通常分两类:结构化数据存数据库表,比如订单记录、用户地址;非结构化偏好存向量库,比如“用户喜欢深色系商品”“用户倾向于先问价格”。

Memory 写入需要克制。不是每句话都值得进入长期记忆。一次闲聊中的“我今天心情不好”不应该被写进用户画像,但“以后别给我发短信了”就是明确的偏好信号。记忆写入要设计判据,不然后续每一次请求都会把无关信息拉进 Context,白白浪费 Token,还会干扰模型判断。

5. 电商客服助手实战:从短期状态到长期记忆

下面进入代码实现。这里用 LangGraph 风格展示核心逻辑,代码是演示性的,具体 API 以你当前安装的版本为准。

5.1 定义 State 结构

先定义 Agent 的工作状态。状态里既要有会话基本信息,也要有任务执行过程中的临时变量。

from typing import TypedDict, List class AgentState(TypedDict): user_id: str # 用户标识 session_id: str # 会话标识 messages: List[dict] # 当前会话消息 memory: dict # 从长期记忆加载的用户信息 intent: str # 当前用户意图 matched_order: dict # 匹配到的订单 need_human: bool # 是否需要转人工

这里有一个容易忽视的点:messages是短期会话消息,但它不能无限制增长。每次请求前都需要裁剪,否则列表会越来越大,最终触发模型上下文超限。

5.2 Session 初始化与消息记录

Session 需要持久化到存储层。用 SQLite 做本地测试最简单,Redis 适合生产环境。

import sqlite3 from datetime import datetime def save_message(session_id: str, role: str, content: str): conn = sqlite3.connect("agent_memory.db") conn.execute( "INSERT INTO messages(session_id, role, content, created_at) VALUES (?, ?, ?, ?)", (session_id, role, content, datetime.utcnow().isoformat()), ) conn.commit() conn.close()

Session ID 的设计直接影响隔离效果。推荐使用user_id + session_id的组合作为查询条件,而不是只依赖其中一个。否则可能出现同一个用户开多个会话时数据互相干扰,或者不同用户因为 session 生成规则不良而读到对方消息。

5.3 加载长期记忆

每个用户进入会话时,先加载他的长期记忆。这一步发生在调用大模型之前。

def load_memory(state: AgentState) -> AgentState: user_id = state["user_id"] profile = memory_db.get_profile(user_id) recent_orders = memory_db.get_recent_orders(user_id, top_k=5) preferences = vector_memory.search(f"user:{user_id}", top_k=3) return { "memory": { "profile": profile, "recent_orders": recent_orders, "preferences": preferences, } }

注意这里的返回字典里包含memory字段,LangGraph 会把它更新到状态中。如果节点函数没有返回这个字段,状态中的memory就会保持原样,这也是很多“状态没更新”问题的根源:返回了结果,但字段名和状态定义不一致。

5.4 组装 Context

加载完记忆后,把长期记忆、系统提示词、最近消息组装成完整的模型输入。

def assemble_context(state: AgentState) -> AgentState: system_prompt = "你是电商客服助手,只能基于用户订单和售后政策回答,不要编造订单信息。" memory_text = format_memory_text(state["memory"]) recent_messages = state["messages"][-6:] # 默认只保留最近6条 messages = [ {"role": "system", "content": system_prompt + memory_text}, ] + recent_messages return {"messages": messages}

这里有一个关键经验:系统提示词中必须把业务约束写死,否则早期对话被压缩后,模型可能忘记自己只能基于订单回答。压缩的是对话历史,不是系统指令。

5.5 记忆写入:什么值得记

长期记忆不能全量写入。设计一个简单的判据,只有命中关键信号才写库。

def store_memory(state: AgentState): user_id = state["user_id"] user_message = state["messages"][-1]["content"] if any(keyword in user_message for keyword in ["地址", "喜欢", "不要", "习惯", "希望"]): memory_db.upsert_profile(user_id, user_message)

这个判据非常朴素,实际生产中可以换成更细的意图识别规则,也可以用一个小模型做信息抽取。核心原则是:写入长期记忆的内容必须对后续对话有复用价值。

5.6 上下文超限处理

电商客服对话天然长,用户反复追问物流、售后、价格,消息很容易堆积。超限处理有四种常用手段:

  • 截断:只保留最近 N 条消息。实现简单,但早期指令容易丢失。
  • 滑动窗口:保留最近 K 轮消息加上关键业务字段。适合高频短对话。
  • 摘要压缩:把早期对话交给模型生成摘要,替代原始消息。适合长会话。
  • 检索增强:把历史记忆向量化,按需取回。适合跨会话长期记忆。

下面是一个典型的压缩逻辑:

def compress_if_needed(messages, max_tokens=8000): estimated_tokens = sum(len(m["content"]) for m in messages) if estimated_tokens <= max_tokens: return messages head = messages[:1] # 系统提示词不变 tail = messages[-6:] # 保留最近6条 middle = messages[1:-6] # 中间部分需要压缩 summary = llm.summarize(middle) return head + [ {"role": "system", "content": f"早期对话摘要:{summary}"} ] + tail

这个方案解决的是“单次请求内 Context 超限”的问题。如果多次请求后早期摘要本身变得很长,还需要对摘要做二次压缩,或者把摘要转入长期记忆库,按需检索。

5.7 完整任务图

把以上节点连接起来,形成完整执行流程。

from langgraph.graph import StateGraph, START, END graph = StateGraph(AgentState) graph.add_node("load_memory", load_memory) graph.add_node("assemble_context", assemble_context) graph.add_node("call_llm", call_llm) graph.add_node("store_memory", store_memory) graph.add_edge(START, "load_memory") graph.add_edge("load_memory", "assemble_context") graph.add_edge("assemble_context", "call_llm") graph.add_edge("call_llm", "store_memory") graph.add_edge("store_memory", END) app = graph.compile()

call_llm节点可以自行实现,它负责把组装好的 messages 发给模型,并把回复写回状态。整个流程的路径很清晰:先加载记忆,再组装上下文,再调用模型,最后沉淀新记忆。

6. 功能测试与效果验证

完成代码实现后,建议按下面的测试用例验证。这些用例覆盖了新老用户、会话隔离、超限恢复和记忆修正五个关键场景。

编号测试场景输入示例预期结果通过标准
T1新用户咨询“我要退货”Agent 询问订单号或手机号不抛出无根据的订单匹配
T2老用户咨询“我上周买的手机壳坏了”Agent 自动匹配近期订单返回订单号并给出售后入口
T3会话隔离两个用户同时提问各自看到自己的订单不串线、不串数据
T4上下文超限恢复长对话后再问售后进度摘要保留关键售后信息仍能回答早期订单问题
T5记忆修正“收货地址改成新地址”长期记忆更新下一轮使用新地址

验证时建议写一个脚本,用不同user_id和session_id连续跑多轮,检查每轮的返回内容。重点关注三件事:是否调用了订单查询、是否出现订单串线、上下文压缩后是否丢失了关键业务信息。

测试中发现 T4 失败,优先检查压缩逻辑。常见问题是早期对话中包含了用户明确授权信息,比如“可以查看我的订单”,压缩时这个授权被丢掉了,后续模型就不敢继续查订单。解决方案是把授权状态提升到 State 的字段中,不依赖聊天历史保存。

7. 接口 API 与批量任务

电商客服助手最终要接入业务系统,建议封装成 HTTP 接口。使用 FastAPI 非常简单:

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class ChatRequest(BaseModel): user_id: str session_id: str message: str @app.post("/chat") def chat(req: ChatRequest): result = app_graph.invoke({ "user_id": req.user_id, "session_id": req.session_id, "messages": [{"role": "user", "content": req.message}], }) return {"reply": result["reply"], "intent": result["intent"]}

启动服务:

uvicorn main:app --host 127.0.0.1 --port 8000

调用示例:

curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{"user_id": "u10001", "session_id": "s20001", "message": "我上周买的手机壳坏了"}'

接口能跑通,后面就可以接到自己的客服页面、企业微信机器人、小程序或者工单系统里。

批量任务的典型场景是离线构建长期记忆。把历史客服工单按user_id分组,重放一次消息,让 Agent 提取每轮值得记忆的信息写入长期库。

def rebuild_memory_from_logs(logs): for user_id, messages in group_by_user(logs).items(): for msg in messages: extract_and_store_memory(user_id, msg)

批量重放要设计幂等性。同一个工单如果重复重放,不能重复写入同一条记忆。实现上可以给记忆记录加source_id唯一索引,存在则跳过。没有幂等保护,批量任务跑第二遍时记忆库会膨胀,检索质量也会下降。

8. 资源占用与性能观察

Agent 记忆方案比单次调用大模型多出来的开销,主要在存储、Token 和检索延迟三个方向。

Token 消耗是最大的隐性成本。每次请求进入模型前,要统计输入 Token 数。可以在assemble_context节点中打印日志:

estimated_tokens = sum(len(m["content"]) for m in messages) print(f"input_messages={len(messages)}, estimated_tokens={estimated_tokens}")

长期记忆检索会增加响应时间。向量检索通常需要几十到几百毫秒,如果记忆库很大,要设置检索超时,不能因为检索失败拖垮整个对话。更稳妥的做法是:检索失败时返回空记忆,不阻断主流程。

存储增长需要监控。SQLite 中的消息表会随会话数量持续增长,要给 Session 设置清理策略。比如 Redis 中只保留最近 7 天的会话,超过后自动删除;SQLite 中定期清理三个月前的临时消息,长期记忆保留更久。

压缩逻辑触发时,会额外调用一次模型做摘要,这同样消耗延迟和 Token。性能观察时要分别记录:正常请求耗时为多少、触发压缩后耗时为多少、记忆检索耗时为多少。如果压缩频繁触发,优先优化滑动窗口的大小,而不是每次都做摘要压缩。

9. 常见问题与排查方法

Agent 记忆相关的问题,集中在上下文超限、状态更新失败、会话串线和存储泄露这几个方向。下面列出一线开发中最常见的表现和排查思路。

问题现象可能原因排查方式解决方案
模型返回 maximum context length exceeded输入消息超过模型上下文窗口打印每次请求的 token 估算值触发截断或摘要压缩
报错 context length exceeded,无法继续压缩摘要本身过长且无法再次压缩检查摘要长度和保留消息数量把摘要转存到长期记忆,按需检索
多轮对话后模型丢失早期指令早期消息被裁剪或压缩查看压缩日志,确认指令是否被移除把关键指令放入系统提示词
LangGraph 节点 state 没有更新节点返回字段名与状态定义不一致检查节点返回值返回与状态字段同名的字典
多个用户订单互相串线Session key 使用不当查看消息查询 SQL 的条件用 user_id + session_id 组合隔离
Session 数据反序列化失败存储格式与读取逻辑不匹配查看存储序列化方式统一序列化工具或重置 Session
进程内存持续增长消息列表无限累积监控进程内存占用限制消息列表长度并定期清理
批量记忆重放后数据重复缺少幂等控制检查记忆表是否有重复记录加 source_id 唯一索引

其中“无法继续压缩”是比较容易踩的坑。它的本质是:早期摘要加最近消息,总长仍然超过模型窗口。此时不能再靠压缩解决,必须把早期摘要的内容搜索化,转成向量库中的记忆条目,每次只检索与当前用户问题相关的片段。

排查时建议先打日志,再改代码。日志至少包含:每次请求的总 Token 估算、压缩触发次数、记忆检索耗时、Session 查询的 user_id 和 session_id。没有日志直接猜问题,通常会把时间浪费在错误的方向上。

10. 最佳实践与使用建议

从工程落地角度,这里给出几条可复用的建议。

第一次上线建议先关闭长期记忆,只保留 Session 和 State,跑通完整对话流程后再逐步打开 Memory。这样做的好处是,出了问题时可以先排除长期记忆的干扰,确认基础链路没问题。

记忆写入要设计白名单字段。与其把用户整段输入存入长期库,不如只提取结构化字段:订单号、地址、品类偏好、售后诉求。整段入库虽然实现简单,但会让记忆库很快充满噪声,检索质量也会下降。

上下文压缩必须保留三类内容:系统指令、用户身份信息、业务授权信息。这三类信息一旦丢失,即使对话历史都在,模型也可能不敢继续操作。

涉及订单、地址、手机号等个人信息时,必须做脱敏、授权和过期策略。脱敏指存储时隐藏关键中间位;授权指用户明确同意后才能读取历史订单;过期指确定不再需要的记忆要及时清理。合规不只是避免风险,过度的信息收集本身也会降低记忆检索的准确率。

批量任务压测时,先小批量重放,比如先跑 100 条工单,检查记忆库数据是否准确,再全量执行。全量任务要有日志和失败重试,不能跑一半报错后直接重来,那样会造成大量重复记忆。

11. 总结与下一步

这个项目最值得尝试的点是:用很轻的工程手段,把“模型无状态”变成“业务有记忆”。不需要换更强的模型,电商客服的体验就能明显提升。

最先应该验证的功能是老用户二次咨询:用户说“我上周买的手机壳坏了”,Agent 能否从长期记忆里找到历史订单并给出正确售后路径。这个功能跑通,说明 Context、Memory、Session、State 四层设计已经形成了闭环。

最容易踩的坑是不加区分地存储记忆。什么内容都进长期库,会让记忆库迅速被噪声淹没,后续检索出来的内容既浪费 Token,又干扰模型判断。

后续可以继续扩展的方向有:接入向量知识库管理商品信息和售后政策,接入订单履约状态实时查询,实现多客服协同共享用户记忆,以及基于记忆做用户生命周期运营。建议先把文中第 6 节的五个测试用例跑完,再考虑扩展功能。这套记忆架构一旦跑通,后面很多业务场景都能复用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询