Agent 类应用真正难的地方,不是怎么让大模型把话说对,而是怎么让它记住“上一次发生了什么”。在电商客服、企业助手、工单系统这类真实业务场景里,用户不会每次重复一遍自己的订单号、收货地址和售后诉求,更不会接受 AI 频繁反问“您之前反馈过什么”。如果你正在做一个客服 Agent、销售助手或者内部知识问答机器人,迟早会发现:模型能力是底座,记忆系统才是决定体验的上限。
这篇文章会从四个最容易被混为一谈的概念讲起——Context、Memory、Session 和 State,然后结合一个电商客服助手项目,带你从零实现“短期会话状态 + 长期用户记忆 + 上下文超限处理”的分层记忆架构。读完之后,你能回答这几个问题:Agent 的记忆到底该存在哪里?多轮对话里哪些数据该放内存、哪些该落库?当上下文长度超限时,如何在不丢失关键信息的前提下继续对话?以及,一个带记忆的电商客服 Agent,代码上到底应该怎么组织。
先说一个判断:不要把“记忆”当成一个插件或一个字段,而要把它当成一套数据架构来设计。这篇文章的核心,就是拆解这套数据架构。
1. 这篇文章真正要解决的问题
很多开发者第一次接触 Agent 开发时,看到的示例都是“单轮问答”:用户提问,模型回答,结束。这类 Demo 给人一个错觉——只要调用一下大模型 API,Agent 就完成了。
但真实业务不是这样的。
以电商客服为例,用户可能在一段对话里完成以下操作:
- 咨询某个商品有没有货;
- 询问自己 3 天前的订单为什么还没发货;
- 要求修改收货地址;
- 投诉某个商品的售后进度。
这些信息散落在多轮对话里,而且部分必须跨会话保留:比如用户 ID、历史订单、退款偏好。如果 Agent 每次收到消息都只把当前这一句发给大模型,它会立刻“失忆”,不仅答非所问,还会让用户觉得对面是个极其不专业的机器人。
这篇文章会重点解决以下三个问题:
- 状态混乱:对话过程中,哪些变量要临时保存?怎么在多个步骤之间传递?
- 记忆丢失:用户下次再来,如何认出他、加载他的历史画像?
- 上下文超限:当聊天记录太长、超过模型上下文窗口时,怎么裁剪、压缩,又不丢关键信息?
适合读这篇文章的读者,是已经了解 Prompt 和 API 调用、准备上手做真实 Agent 项目的开发者。如果你还没写过一行调用大模型的代码,建议先跑通一个最基础的对话接口再回来看。
2. 基础概念:Context、Session、State 到底有什么区别
这四个词在中文技术讨论里经常混用,但它们指向的是完全不同的层级。先看一张对比表:
| 概念 | 本质 | 存活周期 | 典型载体 | 会不会消耗 Token |
|---|---|---|---|---|
| Context(上下文) | 发送给大模型的消息内容集合 | 单次请求 | Prompt / Messages 数组 | 会 |
| Session(会话) | 一次完整对话过程的唯一标识 | 短则几分钟,长则数小时 | Session ID | 不会直接消耗 |
| State(状态) | Agent 在处理过程中读取和更新的内部变量 | 随任务生命周期变化 | 内存字典、状态图持久化字段 | 不确定 |
| Memory(记忆) | 跨会话可复用的信息存储 | 长期存在 | 数据库、向量库、Redis | 注入上下文时才会消耗 |
逐个展开解释。
Context 是“当前窗口里能看到的信息”。大模型本身没有记忆,你每次调用 API 时,把历史消息、系统提示词、检索到的知识全部拼成一个 messages 数组,模型基于这些内容生成回复。这个数组就是 Context。它是 Token 消耗的来源,也是“超限”问题发生的唯一位置。
Session 是“一次对话过程的身份标识”。它的价值在于:把一个用户连续发出的多条消息归到同一个业务单元里。Web 开发中常见的 Session 机制,在 Agent 场景里同样适用。你可以用一个 UUID 作为 Session ID,也可以把用户 ID 和会话序号拼接起来。Session 通常存放在服务端内存、Redis 或数据库里。
State 是“Agent 运行时的内部状态”。大多数 Agent 框架会定义一个状态对象,例如:
{ "user_id": "u_10001", "session_id": "s_8848", "pending_action": "confirm_refund", "selected_order": "order_20240815" }State 与 Session 的区别在于:Session 强调对话过程的边界,State 强调任务推进所必需的变量。一个 Session 可以包含多次状态更新,State 的值也可以影响下一步决策。
Memory 是“跨会话的信息沉淀”。它存储那些你想长期记住的东西:用户名字、购买偏好、历史订单、未解决的售后问题。Memory 可以放在 MySQL、PostgreSQL、Redis,也可以做成向量库用于语义检索。Memory 不直接发送给模型,而是通过检索或组装,变成 Context 的一部分。
一句话总结:Session 管对话,State 管任务,Memory 管沉淀,Context 管输入。四个概念在架构上是递进关系:Memory 被检索出来 → 组装进 Context → Context 发给模型 → 模型根据 State 完成任务 → 任务结果写回 Session 和 Memory。
3. Agent 记忆系统的架构设计
我在实际项目里倾向于把 Agent 记忆分成四层。你在设计自己的客服助手时,也可以参照这个分层思路,避免把所有信息塞进一个大杂烩表。
3.1 第一层:窗口上下文(Context Window)
窗口上下文是模型能看到的最新消息,通常包含最近 5 到 20 轮对话。这一层的特点是:每次都全量发送,但只保留“最近”。超过窗口上限的消息,会进入下一层进行摘要或裁剪。
实现上一般用 deque 或列表维护:
messages = [] messages.append({"role": "user", "content": "你好,我想查一下订单"}) messages.append({"role": "assistant", "content": "好的,请提供订单号"}) # 发送时只取最后 N 条 recent_messages = messages[-10:]3.2 第二层:会话状态(Session State)
会话状态是当前会话内的临时数据。比如用户在客服对话里选择了“我要退货”,Agent 需要记住 pending_action 是 return_request,并等待用户补充订单号。
这一层适合放在内存或 Redis 中,设置过期时间。这样对话结束一段时间后,状态自动清理,避免内存泄漏。
3.3 第三层:用户画像存储(User Memory)
用户画像是跨会话长期记忆,记录用户的身份信息、偏好和历史行为。在电商客服场景里包括:会员等级、常用收货地址、购买记录、退款偏好、上次投诉的处理结果。
这一层存储在数据库或 Redis,比如:
{ "user_id": "u_10001", "level": "gold", "address": "广东省深圳市南山区xx路xx号", "last_issue": "2024-08-15 订单缺货退款" }3.4 第四层:业务知识库(Business Knowledge)
业务知识库不是“记忆”,但它是 Agent 回答专业问题时必须引用的外部信息。在电商客服里,就是商品信息表、退换货政策、物流公司接口返回的轨迹数据。
这一层通常用向量数据库做语义检索,把命中结果拼进 Context。严格来说,这属于 RAG(检索增强生成),但它和记忆系统经常一起使用,共同决定了 Agent 的“专业度”。
3.5 分层架构的功能图
下面用文字描述这个分层流程,开发者可以据此设计自己的数据表:
- 用户发送消息 → Agent 根据 Session ID 加载会话状态。
- 根据用户 ID 从 Memory 加载用户画像。
- 将最近对话从窗口上下文取出。
- 如果候选内容超过模型 Context 上限,执行摘要或裁剪。
- 合并系统提示词 + 用户画像信息 + 检索到的知识 + 最近对话,发给大模型。
- 模型返回结果后,把当前轮次写入消息历史,并更新 State。
4. 环境准备与前置条件
在动手写代码之前,先准备好运行环境。本文的示例使用 Python 3.10+,版本请以你本机为准,重点是演示通用思路。
需要安装的库:
openai:调用大模型接口(也可以替换成其他兼容 OpenAI 协议的 SDK)。fastapi+uvicorn:提供 HTTP 服务。redis:缓存 Session 状态和短期记忆。chromadb或faiss-cpu:向量检索(可选,用于知识库检索)。pydantic:定义数据模型(FastAPI 自带)。
安装命令:
pip install openai fastapi uvicorn redis chromadb pydantic配置环境变量:
export OPENAI_API_KEY="your-api-key" export REDIS_URL="redis://localhost:6379/0"如果你没有 Redis,也可以用 Python 字典临时模拟。但生产环境不建议这样做,后面会解释原因。
5. 完整示例:构建一个带记忆的电商客服助手
下面我们用 FastAPI 构建一个最小可运行的电商客服后端。它包含:
- Session 管理:给每个用户分配一个会话 ID。
- 短期状态:用 Redis 保存当前对话状态。
- 长期记忆:用 SQLite(或 Redis Hash)保存用户画像和历史消息。
- 上下文组装:把用户画像、知识库检索结果、最近对话拼成 Context。
- 超限处理:截断历史消息,保留关键摘要。
5.1 定义数据模型
先定义一个user.py,用来描述用户画像:
# user.py from datetime import datetime class UserProfile: def __init__(self, user_id: str, name: str, level: str, address: str): self.user_id = user_id self.name = name self.level = level self.address = address self.created_at = datetime.utcnow() def to_dict(self): return { "user_id": self.user_id, "name": self.name, "level": self.level, "address": self.address, "created_at": self.created_at.isoformat(), }真实项目中,这份数据应该从用户系统或订单系统同步过来,而不是作为独立文件维护。这里为了最小演示,直接用 Python 类模拟。
5.2 Session 与 State 管理
Session 的核心是“给谁用、哪次对话”。我们用 Redis 存储 Session 到 State 的映射,并设置过期时间:
# session_store.py import redis import uuid redis_client = redis.Redis.from_url("redis://localhost:6379/0") class SessionManager: @staticmethod def create_session(user_id: str) -> str: session_id = str(uuid.uuid4()) redis_client.hset( f"session:{session_id}", mapping={ "user_id": user_id, "state": "init", "pending_action": "", }, ) # 会话 30 分钟无操作自动过期 redis_client.expire(f"session:{session_id}", 1800) return session_id @staticmethod def get_state(session_id: str): data = redis_client.hgetall(f"session:{session_id}") if not data: return None return {k.decode(): v.decode() for k, v in data.items()} @staticmethod def update_state(session_id: str, **kwargs): redis_client.hset(f"session:{session_id}", mapping=kwargs)为什么用 Redis?因为 Session 数据往往需要快速读写,而且不需要复杂关系查询。30 分钟过期也好控制,用户长时间不发言后自动清理,避免服务端堆积无用的临时状态。
5.3 上下文组装与超限裁剪
这是整个系统的核心。我们要把以下内容拼成最终发给模型的 Context:
- 系统提示词(说明客服角色和回复规则);
- 用户画像(长期记忆);
- 检索到的商品信息(业务知识);
- 最近 N 轮对话(短期会话历史);
- 一个“历史摘要”字段(当对话过长时启用)。
先实现一个简单的 Token 估算器,用来判断当前消息是否超限。这里用粗略估算:1 个汉字约等于 1 到 2 个 Token,英文单词约等于 1 到 2 个 Token。你也可以用tiktoken做精确统计,但最小实现不需要。
# context_builder.py def estimate_tokens(text: str) -> int: # 粗略估算:中文按 1.5 token/字,英文按 1 token/词 import re chinese_chars = len(re.findall(r"[\u4e00-\u9fff]", text)) other_words = len(re.findall(r"[a-zA-Z0-9]+", text)) return int(chinese_chars * 1.5 + other_words) def build_context(system_prompt: str, user_profile: dict, history: list, max_tokens: int = 3000): messages = [] # 1. 系统提示词 messages.append({"role": "system", "content": system_prompt}) # 2. 用户画像说明 if user_profile: profile_text = f"用户信息:{user_profile}" messages.append({"role": "system", "content": profile_text}) # 3. 历史消息,从最早到最新 history_messages = [{"role": m["role"], "content": m["content"]} for m in history] # 4. 计算当前总 token current_tokens = sum(estimate_tokens(m["content"]) for m in messages + history_messages) # 5. 如果超限,丢弃最早的历史,直到满足上限 while current_tokens > max_tokens and history_messages: removed = history_messages.pop(0) current_tokens -= estimate_tokens(removed["content"]) messages.extend(history_messages) return messages这段代码的思路是:先把不可压缩的内容(系统提示词、用户画像)放进去,然后从最旧的历史消息开始淘汰,直到总 Token 数低于上限。
但“丢弃最早消息”有一个问题:用户可能在第 1 轮就提供了订单号,第 10 轮还在问这个订单。如果我们把第 1 轮删掉,模型就丢失了订单号。所以更完整的做法是:把被删除的历史记录做成摘要,放进系统提示词。
5.4 摘要式超限处理
我们维护一个summary字段,当历史消息即将超限时,把最早的消息合并成摘要。可以用大模型生成摘要,也可以直接用简单规则提取关键实体(订单号、商品名、地址)。
下面是一个基于规则的简化版,适合演示,实际项目建议调用大模型做摘要:
# summarizer.py import re def extract_key_info(text: str) -> str: orders = re.findall(r"订单[号:\s]*([A-Za-z0-9]+)", text) phones = re.findall(r"1[3-9]\d{9}", text) keywords = [] if orders: keywords.append("订单号" + ",".join(orders[:3])) if phones: keywords.append("电话" + phones[0]) if not keywords: return "" return ";".join(keywords) def summarize_history(removed_messages: list) -> str: removed_text = " ".join(m["content"] for m in removed_messages) return extract_key_info(removed_text)在build_context中,如果发生了删除操作,就把summary作为一条 system 消息放在用户画像之后:
def build_context_with_summary(system_prompt, user_profile, history, max_tokens=3000): messages = [ {"role": "system", "content": system_prompt}, {"role": "system", "content": f"用户信息:{user_profile}"}, ] history_messages = [{"role": m["role"], "content": m["content"]} for m in history] removed_messages = [] current_tokens = sum(estimate_tokens(m["content"]) for m in messages + history_messages) while current_tokens > max_tokens and history_messages: removed_messages.append(history_messages.pop(0)) current_tokens -= estimate_tokens(history_messages[-1]["content"]) if history_messages else 0 if removed_messages: summary_text = summarize_history(removed_messages) if summary_text: messages.append({"role": "system", "content": f"历史关键信息:{summary_text}"}) messages.extend(history_messages) return messages这里有一个细节:current_tokens的更新逻辑在真实项目里要更精细,建议精确计算每条消息的 Token。示例代码更关注思路,但你实际使用时应使用tiktoken或模型对应的 Tokenizer。
5.5 组装 FastAPI 服务
最后把各部分拼起来:
# main.py from fastapi import FastAPI, Request from pydantic import BaseModel from openai import OpenAI from session_store import SessionManager from context_builder import build_context_with_summary from user import UserProfile app = FastAPI() client = OpenAI() # 需要设置 OPENAI_API_KEY # 简单模拟用户画像查询 def load_user_profile(user_id: str): # 实际项目从数据库读取 return UserProfile( user_id=user_id, name="张三", level="gold", address="广东省深圳市南山区xx路xx号", ).to_dict() class ChatRequest(BaseModel): user_id: str message: str session_id: str | None = None SYSTEM_PROMPT = """ 你是一个电商客服助手。请根据用户信息、历史订单和当前问题,给出简洁、专业的回复。 如果需要用户提供订单号或地址,请明确询问。 如果用户要求退款或售后,请确认退款原因和订单状态。 """ @app.post("/chat") async def chat(req: ChatRequest): # 1. 获取或创建会话 if req.session_id: session_id = req.session_id else: session_id = SessionManager.create_session(req.user_id) state = SessionManager.get_state(session_id) if state is None: SessionManager.create_session(req.user_id) state = SessionManager.get_state(session_id) # 2. 加载用户画像 profile = load_user_profile(req.user_id) # 3. 从消息存储中读取本轮历史(实际项目用 Redis List 或数据库) history = [ {"role": "user", "content": "我想查一下我的订单状态"}, {"role": "assistant", "content": "您好,请提供订单号。"}, ] history.append({"role": "user", "content": req.message}) # 4. 构建上下文 messages = build_context_with_summary(SYSTEM_PROMPT, profile, history) # 5. 调用模型 resp = client.chat.completions.create( model="gpt-4o-mini", messages=messages, temperature=0.3, ) answer = resp.choices[0].message.content # 6. 更新会话状态 SessionManager.update_state(session_id, last_answer=answer, updated_at="now") return {"session_id": session_id, "answer": answer}这里为了避免过度复杂,历史消息用硬编码列表代替。真实项目中,你可以用 Redis List 存储每个 Session 的消息记录,每次请求时读取最新 N 条,再用build_context_with_summary做超限处理。
6. 上下文超限的三种处理策略对比
电商客服助手在实际运行中一定会碰到上下文超限。常见错误信息包括:
This model's maximum context length is 1048576 tokens...context length exceededContext is too large and auto-compaction could not recover this turn
遇到这些情况,有两种处理方向:要么给模型“减肥”,要么让模型“分次看”。
6.1 策略一:滑动窗口裁剪
适合对“最近对话”依赖度高的场景。实现简单,节省 Token,但会丢掉早期信息。
适用场景:闲聊型客服、一般性问题咨询。早期信息对当前回答影响不大。
6.2 策略二:历史摘要压缩
适合对话复杂、早期信息可能仍然重要的场景。系统把早期对话压缩成一段摘要,再作为系统提示词传入。优点是保留关键实体;缺点是摘要本身也消耗 Token,而且摘要可能有损。
实现建议:当对话轮数超过阈值时,自动触发摘要任务。你可以用一个独立的“摘要模型”或同一模型,使用一个summarize_prompt完成。
6.3 策略三:向量检索式记忆
适合长期知识依赖型客服。所有历史对话和知识文档都写入向量库,每次请求时根据用户当前问题做语义检索,只把命中的片段拼进上下文。
这种方式最像“真正的长期记忆”,但实现成本最高,需要向量库、嵌入模型和检索流程。在电商客服场景中,建议第一阶段用“滑动窗口 + 摘要”就能满足大部分需求,等数据量上来了再引入向量检索。
三种策略对比:
| 策略 | 实现难度 | Token 消耗 | 信息保留 | 适用场景 |
|---|---|---|---|---|
| 滑动窗口裁剪 | 低 | 低 | 只保留最近 | 简单咨询 |
| 历史摘要压缩 | 中 | 中 | 保留关键实体 | 售后、订单处理 |
| 向量检索式记忆 | 高 | 高 | 按语义召回 | 复杂知识库、长期画像 |
7. 运行结果与效果验证
启动服务:
uvicorn main:app --reload --port 8000然后模拟一次带 Session 的客服对话:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{ "user_id": "u_10001", "message": "你好,我想退掉昨天买的手机壳" }'预期响应(实际输出取决于模型):
{ "session_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "answer": "您好,您提到的手机壳订单,我需要确认一下订单号和购买时间,方便为您处理退款。请问您方便提供订单号吗?" }再发一条消息,带上刚才返回的 session_id:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{ "user_id": "u_10001", "session_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "message": "订单号是 20240815A1003" }'此时,服务端应该能通过 Session 上下文看到用户上一轮提到的“退手机壳”,并结合用户画像里地址信息给出专业回复。
验证要点:
- 同一个 session_id 下,模型能理解多轮上下文;
- 不同 session_id 之间互不干扰;
- 当历史消息超过设定的 max_tokens 时,服务不会报 Context 超限错误;
- Redis 中能查到该 Session 的 state 数据;
- 重启服务后,只要 Redis 没清空,Session 依然可用。
如果运行失败,先看服务端日志有没有报错,再按下一节的内容排查。
8. 常见问题与排查思路
我把实际项目中高频出现的记忆类问题整理成一张排查表:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型回复“我不记得之前的对话” | 没有把历史消息传入 messages | 查看发送给模型的完整 payload | 确认 build_context 中历史消息已拼接 |
| 同一用户换了设备后记忆丢失 | Session 绑定在浏览器或客户端,而不是用户 ID | 查看 Redis Key 是否存在 | 在服务端统一维护 user_id 与 session 的映射 |
| 上下文超限报错 | 历史消息数量过多,未触发裁剪 | 打印 estimate_tokens 的结果 | 降低 max_tokens,或增加摘要逻辑 |
| Session 状态混乱 | 多个请求并发更新同一个 State | 查看 Redis 中的 state 字段 | 引入版本号或原子操作,避免并发覆盖 |
| 用户画像信息不更新 | 长期记忆只在首次创建时写入 | 检查数据库更新逻辑 | 在会话结束时对比画像差异并回写 |
| Token 费用增长过快 | 每轮都全量发送所有历史 | 打印 messages 长度 | 使用滑动窗口或摘要压缩 |
| Redis 内存持续增长 | Session 没有设置过期时间 | redis-cli keys 'session:*'数量 | 给 Session Key 设置 TTL |
这里特别想强调一个容易被忽略的问题:并发更新 State。在客服场景中,用户可能同时打开两个标签页,发出两条请求。如果没有做状态合并,一个请求写入的 pending_action 会被另一个请求覆盖。处理思路有:
- 用 Redis 的 WATCH / MULTI / EXEC 做事务;
- 简化状态模型,让 State 不要存放临时性太强的字段;
- 更彻底一点,把 Agent 状态机放到支持持久化的工作流引擎里,比如 LangGraph 的持久化 Checkpointer,而不是自己用散落的字段维护。
9. 最佳实践与工程建议
9.1 把记忆分成“可丢”和“不可丢”
写代码之前,先给所有信息分级:
- 可丢:闲聊内容、临时猜测、过程性状态;
- 不可丢:用户 ID、订单号、收货地址、退款原因、身份认证信息。
可丢信息只放在 Context 中,超限直接删除。不可丢信息必须落到 Redis 或数据库,并且每次组装 Context 时重新注入。
9.2 区分“用户会话”和“用户记忆”
我见过很多项目把所有对话历史永久存成一个 JSON 数组,读到模型里导致超限。正确做法是:
- 短期对话历史按 Session 存储,设置 30 分钟到 7 天不等的过期时间;
- 用户画像、订单、偏好等长期信息单独建表,按 user_id 索引;
- 长期信息更新时,记录更新时间,方便回溯。
9.3 给每条记忆加元数据
Memory 不只是 content 字符串,至少要包含:
- user_id:属于谁;
- session_id:来自哪次会话;
- created_at / updated_at:时间戳;
- memory_type:是偏好、订单、售后还是情绪状态;
- source:来自用户声明、系统推导还是模型摘要。
有了这些字段,后续做检索、清理和召回才能有的放矢。
9.4 设置明确的 Token 预算
不要把整个上下文窗口用完。建议预留 20% 给模型的 system 输出、函数调用结果和临时注入内容。假设模型窗口是 4000 Token,设置 max_tokens=3000 给历史消息和用户画像,留 1000 作为安全边界。
9.5 注意数据合规与安全边界
电商客服涉及用户隐私,在设计和实现记忆系统时必须遵循最小权限原则:
- 只存储业务必需的信息,不采集与对话无关的隐私数据;
- 对外输出时对手机号、地址等敏感信息做脱敏;
- 给用户提供“清除记忆”的机制,这是很多商业产品上线前必须考虑的功能;
- 数据库账号使用独立的最小权限账号,应用服务不要使用 root / admin 权限。
9.6 日志与可观测性
给每次模型调用记录以下日志:
- session_id、user_id;
- 送入模型的 messages 数量与总 Token;
- 是否触发了摘要或裁剪;
- 模型返回耗时;
- 上下文超限时的备用策略。
这样即使线上出了问题,也能快速定位是记忆加载出错、检索失效还是 Prompt 设计问题。
9.7 从“规则记忆”升级到“语义记忆”的时机
如果只是订单号、地址、用户偏好这类结构化信息,用数据库表就足够了,不要为了“智能”而强行上向量库。向量检索的价值在于处理非结构化的长文本:比如“用户之前抱怨过物流太慢”“用户对某个品牌有特殊偏好”。当你的业务积累了大量这类非结构化对话,再引入向量检索是合理的。早期阶段,先跑通结构化记忆,再逐步增加非结构化检索。
后续可以继续深入的方向
这篇文章给出的方案是一个相对完整的起点:Session 管理短期对话,State 维护任务进度,Memory 沉淀用户画像,Context 负责组装输入,超限时通过裁剪和摘要保护对话不中断。
接下来你可以尝试:
- 用 LangGraph 的状态图重写这个客服 Agent,把分支判断、人工接管、退款确认等步骤变成显式节点;
- 引入真正的向量检索,将商品知识库和用户历史对话都接入 RAG 流程;
- 把 Session 和 Memory 从 Redis 迁移到 PostgreSQL,增加事务和审计能力;
- 为 Agent 增加“主动记忆写入”能力,在对话结束后自动提取需要长期保存的事实;
- 做一组记忆质量评测用例,验证不同的摘要窗口、检索 TopK 和 Prompt 设计对回复准确率的影响。
真实的 Agent 不是“大模型 + 定时器”写出来的,而是像设计数据库一样,把每一份数据放在它应该在的位置。只要你把 Context、Session、State、Memory 四者的边界理清楚,电商客服只是其中一个很简单的应用形态。