✦ 大道至简 · 时光是画在卷上的河流 · 行到水穷处,坐看云起时

redis2026.09.05 · 42 分钟阅读

Agent 的短期记忆,Redis

大模型本身不带记忆,多轮对话靠的是外部给它「递上下文」。这份最热、最临时、每轮都要读写的记忆,业界几乎都交给 Redis:内存级读写快、TTL 到点自动忘、数据结构又刚好够用。这篇从 Redis 7 种数据类型讲起,讲透为什么短期记忆的最佳解是 Redis,并用「摘要 + 截断中间件 + Redis 存取」落地一个能续聊的会话记忆,最后讲清它与 PostgreSQL 长期记忆如何分工

L

Leo

2026.09.05 · 更新于 2026.09.23

3 次浏览
Agent 的短期记忆,Redis

Agent 的短期记忆,Redis 一个就够:从 7 种数据类型到记忆落地全解

为什么写这篇:LLM 是金鱼,Agent 得帮它「过目不忘」

大模型本身是无状态的——你问完上一句,它转头就忘干净。多轮对话能聊下去,靠的不是模型记得,而是每次提问时,由外部把历史上下文重新塞给它。

于是 Agent 天然分裂出两种记忆需求:

  • 短期记忆:这一会话里最近几轮说了什么、刚才推理到哪一步。它要热——每轮都被完整读一遍、写一遍,读写得快不快直接决定对话手感;
  • 长期记忆:用户上次聊过的偏好、跨会话的完整历史。它要久——可能隔几天、隔几个会话才用一次,要用时最好还能按语义搜回来。

大多数人做的第一版长这样:内存里开个数组装着历史。但一旦服务重启、进程一挂,记忆全没了——模型记住的,还不如你自己记得多。

Redis 就是来补这个窟窿的。一句话先说结论:

短期记忆 = 现用现取 + 到点自动忘。Redis 内存级读写、自带 TTL 过期、数据结构又刚好覆盖消息存取的几种形态,所以它是 Agent 短期记忆的事实默认选择。


先看全景:短期记忆和长期记忆怎么分工

不要一上来就纠结用哪个库。先把两段记忆的生命周期画出来,选型就顺了。

两种记忆不是二选一,而是各管一段生命周期:

维度短期记忆(Redis)长期记忆(PostgreSQL)
管什么最近几轮对话、会话摘要完整历史消息、用户画像、可复用事实
生命周期分钟 ~ 小时级,自动过期永久,除非显式删除
读的频率每轮都全量读一遍按需召回(新会话开场、用户提问触发)
检索方式按 key 直取关系查询 + 向量语义检索
该有的性子快、轻、过期干净稳、全、能 JOIN 能搜
代价关注点内存贵,只放「热」的磁盘便宜,放得下全部历史

后面你会看到:这套分工恰好把 Redis 和 PostgreSQL 各自的强项都用满了。


第一步:先把 Redis 跑起来(30 秒)

短期记忆的场景,用官方镜像起一个就够:

# 一份 docker-compose:Redis + 可选的可视化面板 RedisInsight(类似 pgAdmin)
services:
  redis:
    image: redis:7-alpine
    ports: ["6379:6379"]
    command: redis-server --appendonly yes   # 开持久化,防宕机丢短期记忆

  redisinsight:
    image: redis/redisinsight:2.50
    ports: ["5540:5540"]

Node 侧连它用的是社区最流行的 ioredis:

npm i ioredis
import Redis from "ioredis";

const redis = new Redis({ host: "localhost", port: 6379, db: 0 });
redis.on("connect", () => console.log("✅ Redis 已连接"));

要点:

  • 官方镜像 + 一行配置就是完整可用的 Redis,redis-server --appendonly yes 让内存数据带持久化保险;
  • 下面的示例统一用 redis-cli 讲命令(在容器里 redis-cli 就能进),读起来最直接;写代码时换成 ioredis 的方法名,语义一一对应;
  • RedisInsight 是官方 Web GUI,看 key、看 TTL、跑命令都方便,相当于记忆区的「体检面板」。

第二步:Redis 的 7 种数据类型,谁在 Agent 记忆里是主角

先给一张「是什么」的总表,后面逐个说。带 ⭐ 的,是短期记忆的主力数据类型,其余更多用在 Agent 的后台与周边业务上。

数据类型一句话最擅长Agent 记忆里
String 字符串 ⭐key 对一段文本缓存、计数、带过期的小数据整段会话序列化后塞一个 key
Hash 哈希 ⭐一个 key 下多个字段结构化对象、购物车一个会话拆多个字段存
List 列表 ⭐有序可追加的数组队列、聊天记录每轮消息按序入队、按需截断
Set 集合无序去重黑名单、签到、标签「已经讲过的、处理过的」去重
ZSet 有序集合 ⭐带分数的排序集合排行榜score 存时间戳 → 按时间窗口取记忆
Bitmap 位图一字节 8 个 0/1海量布尔状态用户活跃日、实验分组标记
Geo 地理位置存经纬度附近检索本地生活类 Agent 找「附近」,跟记忆无关

2.1 String —— 整段记忆就塞一个 key

String 是最简单的「key → 一段值」。看家本领是能设过期,而「到点自动忘」正是短期记忆的第一诉求。

# 手机验证码:5 分钟过期
SETEX verification:mobile:13800138000 300 666888

# 登录 token:24 小时过期
SETEX session:token:adf245k 86400 userid:1001

# 计数器自增:文章/视频阅读量
INCR counter:article:1024

# 分布式锁:10 秒过期防死锁
SET lock:order:2001 locked NX EX 10

# Agent 记忆:某个会话最近的对话摘要,1 小时过期
SETEX agent:memory:u1001:s12:summary 3600 "用户想学 PostgreSQL 向量检索"

要点:

  • SETEX key 秒 值 = 存值 + 设过期一步到位;没有过期时间的东西在这里没有存在感;
  • INCR/DECR 自增自减原子完成,适合给会话轮次、token 用量计数;
  • SET key val NX EX 秒 是分布式锁标准写法——多 Agent 并发跑同一个任务时,用它保证「只有一个在执行」;
  • 短期记忆最朴素的形态就是它:把整段消息历史 JSON 序列化,作为一个 String 存起来。第三步的落地实现就是这么干的。

2.2 Hash —— 一个会话,拆成多个字段

Hash 是一个 key 底下挂多个 field,像「一张 JSON 只让你改其中一个字段」。适合存结构化了但还要局部更新的对象。

# 用户资料:几个字段一次写入
HSET user:info:1001 name 张三 age 28 phone 13800138000

# 电商购物车:字段=商品 ID,值=数量
HSET cart:user:1001 product:10086 2 product:10087 1

# 一个 Agent 会话拆字段存:messages、summary、last_active 各自可独立更新
HSET agent:session:u1001:s12 messages "{...}" summary "对话摘要" updated_at 1728000000

要点:

  • HGETALL 一次拿全字段,HGET key field 只取一个字段,避免为了改一行而把整个对象读写一遍;
  • HINCRBY 可以对某个字段原子自增——比如给会话里的某个计数 hincrby key turn 1;
  • 相比 String 整段存,Hash 的收益是局部更新:messages 变长时不用把 summary 字段也带着读写一遍。

2.3 List —— 把每轮消息按顺序推入队尾

List 是有序、可重复、支持两边进出的数组。消息历史天生就是「一个接一个的追加序列」,List 几乎是为它而生的。它还有个记忆场景的杀手锏:LTRIM 直接截断。

# 聊天/浏览历史:左侧压入最新
LPUSH user:history:1001 看了AI课程 看了Redis教程

# 消息队列:右侧入队,worker 左侧取出
RPUSH queue:task 生成对话摘要 向量入库

# 订单队列
RPUSH queue:order order_1001 order_1002

# 只保留最近 20 条:旧消息自动滚出(短期记忆的“截断”就长这样)
LTRIM queue:order -20 -1

# 取全部:正序
LRANGE user:history:1001 0 -1

要点:

  • RPUSH 追加 + LRANGE 正序取回 = 消息「一条一条进来、按序读出」的标准姿势;
  • LTRIM key -N -1 把列表削到最近 N 条——比摘要更暴力的短期记忆策略就是它:超了阈值直接丢最老的;
  • LPOP/RPOP 一头进一头出就成了任务队列,Agent 后台要异步处理的消息(比如「去把对话摘要落库」)可以排这里。

2.4 Set —— 记住「哪些已经处理过」

Set 无序、自动去重。记忆类 Agent 不常直接用它存上下文,但常拿它做账本:记录「这个我已经知道 / 做过了」。

# 当日签到用户:同一用户加第二次不会重复
SADD sign:20250820:user 1001 1002 1003

# IP 黑名单:判断某 IP 在不在里面
SADD blacklist:ip 192.168.1.100
SISMEMBER blacklist:ip 192.168.1.100   # 返回 1 表示命中

# 共同好友:两个集合求交集
SINTER user:friend:1001 user:friend:1002

# 给某个会话标记「已去重的知识点」
SADD agent:seen:u1001:s12 分布式锁 Redis过期策略
SISMEMBER agent:seen:u1001:s12 分布式锁   # 1 = 讲过,别再重复科普

要点:

  • SADD 天然去重,重复写入不报错、不产生重复成员;
  • SISMEMBER 是 O(1) 的「有没有」,最适合做已读/已处理判断;
  • SINTER(交集)/ SUNION(并集)/ SDIFF(差集) 让你能对多组记忆做集合运算,比如「两个用户共同收藏过的 Agent 主题」。

2.5 ZSet —— score 存时间戳,记忆就带上了时间轴

ZSet = Set + 每个成员带一个分数(score),按分数自动排序。排行榜是它最出名的用法;但在记忆场景里,让 score = 时间戳,它就变成了「带时间轴的记忆」。

# 排行榜:score 就是热度
ZADD rank:course 98 PostgreSQL实战 95 AI-Agent开发 92 Redis从入门到精通

# 按名次取:降序前 3
ZREVRANGE rank:course 0 2

# 记忆场景:score=消息发生的时间戳,按时间窗口取“最近 30 分钟说了什么”
ZADD agent:timeline:u1001:s12 1728000000 "用户问:Redis 怎么存记忆"
ZADD agent:timeline:u1001:s12 1728000060 "助手答:按会话 key + JSON..."
ZRANGEBYSCORE agent:timeline:u1001:s12 1727998200 1728000060

要点:

  • ZADD key score member、ZSCORE 查分、ZRANK/ZREVRANGE 查名次,一套就够日常用;
  • score 换成本质是数字的时间戳,就能用 ZRANGEBYSCORE 圈出一个时间窗——比 List「只能从两端截」灵活;
  • 想给某条记忆加权(比如用户重复提到的事实热度 +1),用 zadd key score member 覆盖写即可提升排序。

2.6 Bitmap —— 一个字节当 8 个开关用

Bitmap 是 String 的位级视图,每个 bit 表示一个布尔值,极其省内存:百万用户各一个 bit,也就 100 多 KB。适合「海量用户 × 少状态」。

# 用户当月签到:第 5 天、第 10 天各置 1
SETBIT user:sign:1001:202508 5 1
SETBIT user:sign:1001:202508 10 1

# 统计这个月签到了几天
BITCOUNT user:sign:1001:202508

# Agent 运营:某用户在 3 号、17 号用过助手
SETBIT agent:active:1001:202508 3 1
SETBIT agent:active:1001:202508 17 1

要点:

  • SETBIT key 偏移量 1 / GETBIT key 偏移量,偏移量即「第几天/第几个」;
  • BITCOUNT 一行统计出「总共置了多少个 1」——海量签到天数、活跃天数这类问题,用它内存省到极致;
  • 它不参与上下文记忆,但 Agent 后台的活跃度统计、A/B 实验分组很常见。

2.7 Geo —— 给「本地生活类 Agent」用的,不是给记忆的

Geo 存经纬度并提供距离与附近检索。它是给「帮我找附近的奶茶店」这类带位置的 Agent 用的,跟上下文记忆没关系——但既然讲了 7 种,你至少要知道「有这把锤子」。

# 记录门店位置:注意参数顺序是 经度 纬度 名称
GEOADD shop:location 116.481028 39.921983 北京总店
GEOADD shop:location 121.473701 31.230416 上海分店

# 两家店直线距离,单位 km
GEODIST shop:location 北京总店 上海分店 km

要点:

  • GEOADD 存的是「经度 纬度 名称」,顺序和直觉相反,最容易写反;
  • GEODIST 算两点直线距离,GEOSEARCH 能查「某点半径内的店」;
  • 一个直觉:短期记忆是「时间敏感」,Geo 是「空间敏感」——后者属于会查位置的 Agent,而不是记忆层。

2.8 小结一张速查表

数据类型一句话典型业务短期记忆里的角色
Stringkey → 一段值验证码、Token、计数器、锁整段历史 JSON / 一条摘要,TTL 自动过期
Hashkey → 多字段用户、购物车会话拆字段存,局部更新
List有序追加队列、历史消息逐条入队 + LTRIM 截断
Set无序去重黑名单、签到已处理/已讲过去重
ZSet带 score 排序排行榜score=时间戳,按时间窗取
Bitmap位级开关海量签到活跃度、实验分组
Geo经纬度附近检索位置型 Agent,非记忆

给记忆场景排个座次: 短期内存消息,String、List、Hash、ZSet 是四种可用姿势(下文逐个比);Set / Bitmap / Geo 更多是「Agent 产品要有的后台能力」,知道存在、知道命令,就够。


第三步:记忆的形态——先把「无限变长的历史」这个问题摆出来

无论用哪种 Redis 类型存,存的内容本质是一份消息数组:一条消息有角色(user / assistant / tool)和内容,工具调用还夹着参数与结果。它长这样(结构示意):

[
  { role: "user", content: "记住:我的猫叫小橘" },
  { role: "assistant", content: "好的,已记住" },
  { role: "user", content: "我猫叫什么?" },
  // ... 继续膨胀
]

问题来了:这份数组每聊一轮就变长一点,而且每轮都要整份送给 LLM。于是它无限膨胀会带来一串连锁反应:

  1. 超过模型的上下文窗口——直接报错,聊不下去;
  2. token 费用线性上涨——历史越长,每轮都按「全部历史」计费;
  3. 变慢——输入越长,首字延迟越高;
  4. 注意力稀释——最新意图被淹没在几千行旧消息里,反而答得更差。

所以任何长期运行的 Agent 都要回答同一个问题:历史太长了怎么办? 业界答案是「摘要 + 截断」组合拳,由 Agent 的中间件自动完成:

这套动作在 LangChain / DeepAgents 里是一个叫 summarizationMiddleware(摘要中间件) 的东西,声明式配置即可:

summarizationMiddleware({
  model,
  summaryPrompt,          // 告诉摘要模型要提炼哪些信息
  trigger: { messages: 8 },   // 历史 ≥ 8 条时触发压缩
  keep: { messages: 4 },      // 只保留最近 4 条,更早的压成摘要
})

要点:

  • trigger 控制「何时压缩」——可以按消息条数,也可以按 token 数({ tokens: 100000 }),满足即触发;
  • keep 控制「压缩后留多少新鲜的」——被压掉的旧消息变成一条摘要消息,塞回数组最前面;
  • 压缩后的数组里,历史被一句话摘要取代,总量不再无界增长——这是所有记忆方案的前提,Redis 只是负责把这份「已压缩的小数组」存下来。

第四步:把记忆交给 Redis——一个能续聊的最小实现

现在把前两步拼起来,做一个每轮都能接着聊、重启不丢、到点自动过期的短期记忆。核心就三件事:读 Redis → 送 Agent(内部可能触发压缩)→ 写回 Redis。

// 依赖:npm i ioredis @langchain/core @langchain/openai langchain dotenv
// 环境变量:OPENAI_API_KEY / OPENAI_BASE_URL / MODEL_NAME;REDIS_HOST 等可选
import "dotenv/config";
import Redis from "ioredis";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, HumanMessage, summarizationMiddleware } from "langchain";
import {
  mapChatMessagesToStoredMessages,    // LangChain 消息对象 → 可 JSON 化的纯对象
  mapStoredMessagesToChatMessages,    // 反向:纯对象 → 消息对象
} from "@langchain/core/messages";
import * as readline from "node:readline/promises";

// ---------- 1. 一个极简的「Redis 记忆仓库」 ----------
class RedisMessageStore {
  constructor({ redis, ttlSeconds }) {
    this.redis = redis;
    this.ttlSeconds = ttlSeconds;          // 短期记忆的“寿命”
  }

  key(sessionId) {
    // key 设计:agent:memory:{谁}:{哪个会话}:messages
    return `agent:memory:${sessionId}:messages`;
  }

  async load(sessionId) {
    const raw = await this.redis.get(this.key(sessionId));   // String 整段取
    if (!raw) return [];
    return mapStoredMessagesToChatMessages(JSON.parse(raw)); // 还原成消息对象
  }

  async save(sessionId, messages) {
    const payload = JSON.stringify(mapChatMessagesToStoredMessages(messages));
    // EX 让每次写入都刷新 TTL —— 只要还在聊,记忆就一直续期
    await this.redis.set(this.key(sessionId), payload, "EX", this.ttlSeconds);
  }

  async clear(sessionId) {
    await this.redis.del(this.key(sessionId));
  }
}

// ---------- 2. 摘要中间件:把会无限变长的历史“锁死” ----------
const summaryPrompt = `你是对话摘要助手。请用中文总结对话,务必保留用户明确说过的
姓名、偏好、日期等关键事实。待摘要的对话:{messages} 摘要:`;

const model = new ChatOpenAI({
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  configuration: { baseURL: process.env.OPENAI_BASE_URL },
  temperature: 0,
});

const agent = createAgent({
  model,
  systemPrompt: "你是会话助手。记住用户提到的关键事实,若见摘要请据此继续。",
  middleware: [
    summarizationMiddleware({
      model,
      summaryPrompt,
      trigger: { messages: 8 },   // 历史 ≥ 8 条 → 压缩
      keep: { messages: 4 },      // 只留最近 4 条
    }),
  ],
});

// ---------- 3. 每一轮:读 Redis → invoke → 写回 Redis ----------
const redis = new Redis({ host: "localhost", port: 6379 });
const store = new RedisMessageStore({ redis, ttlSeconds: 1800 }); // 半小时记忆
const SESSION = "u1001:s12";                                       // 谁 + 哪个会话

async function chat(userText) {
  const history = await store.load(SESSION);
  const { messages } = await agent.invoke(
    { messages: [...history, new HumanMessage(userText)] },
    { recursionLimit: 30 }
  );
  await store.save(SESSION, messages);   // 注意:存的是压缩后的、有界的那份
  return messages.at(-1).content;
}

// 命令行聊几句做演示;也可循环读入
for (const q of ["我叫小明,家住北京", "我住哪?我的名字?"]) {
  const answer = await chat(q);
  console.log("Q:", q, "\nA:", answer);
}
await redis.quit();

跑起来就是这个效果:第一轮它记住,第二轮它答得出,中间把 Redis 里那个 key 的 TTL 一查,能看到它一直在滚动续期:

# 聊过之后,Redis 里真实存在的记忆
> GET agent:memory:u1001:s12:messages
"[{\"role\":\"human\",...}]"            # 一份已压缩过的消息数组

> TTL agent:memory:u1001:s12:messages
1559                                 # 还剩 ~26 分钟,每次对话都会重置

要点:

  • key 设计带上身份和会话:agent:memory:{用户}:{会话}:messages,一个用户多个会话天然隔离,不同用户互不串味;
  • EX + 每次写入 = 滚动 TTL:只要还在聊就续命,半小时没人理自动消失——「短期」是被 Redis 的过期机制物理保证的,不用自己写清理任务;
  • 序列化两个 map 函数是唯一的“胶水”代码:LangChain 消息对象不能直接塞 Redis,先转成纯 JSON 对象再存,读回再还原;
  • 存的是压缩后的那份:摘要中间件在 invoke 内部把历史压成「摘要 + 最近 4 条」再返回,写回 Redis 的记忆总量永远是 有界的;
  • 服务重启不丢(Redis 在跑)、换机器不丢、多实例共享——内存数组做不到的三件事,它都做到了。

第五步:让 Redis 记得更讲究——四种存法怎么挑

第四步用的是 String 整段存,最简单。但 Redis 有 7 种类型,为什么消息不挑别的?因为它们各有取舍,适合不同诉求:

存法每轮怎么读写优势代价
String + JSON 整段GET 整份 / SET 整份 + EX最简;压缩中间件返回的本来就是整份数组,天然匹配会话大了整读整写;改动一个字段也要整份搬运
List 逐条入队每轮 RPUSH 一条;读 LRANGE 0 -1天生有序;LTRIM 直接实现“只留最近 N 条”的纯截断摘要是一整段,塞进去会打乱“一条=一轮”的对应;要自己按轮次归档
Hash 分字段HGET messages / summary / updated_at 各自读写摘要和原文分离,可单独刷新摘要不碰原文;结构清晰每条消息仍是序列化的一坨,没比 String 省多少,代码稍多
ZSet score=时间戳每轮 ZADD(score=now);ZRANGEBYSCORE 按时间窗取能精确表达「最近 30 分钟说了什么」这类时间窗口语义中间件返回的整份数组要重新摊平成分条写入

怎么选,取决于你的记忆策略:

要点:

  • 别过度设计:第一步用 String 整段存就够了——它和摘要中间件的产物天然对齐,TTL 一键过期,是最短路径;
  • 想要「能局部更新」再上 Hash(把摘要和原文拆字段),想要「纯截断」再上 List + LTRIM,想要「按时间窗」再上 ZSet;
  • 上面四种都是同一个 key 命名空间下的事,存量换形态只是把存取代码换掉,记忆的读写语义不用动。

第六步:短期记忆只负责「这一阵」——历史与长期记忆交给 PostgreSQL

Redis 再好,它的定位决定了它不适合做历史沉淀:内存贵、放不下全部历史;要语义检索它更无从谈起(它不是为「按意思找」设计的)。而每轮聊过的内容本身很有价值——用户下个星期可能回来问「我上次是不是让你查过向量数据库?」。

这就轮到 PostgreSQL 上场,两套库形成分工:

  • Redis:当前会话的「热」上下文,毫秒级读写 + TTL 自动过期——短期记忆;
  • PostgreSQL:每条消息永久落库,带 user_id / conversation_id 外键,再给内容挂一列向量、配一个 HNSW 索引——历史 + 长期记忆。想找「上个会话里和向量有关的」,一条带 WHERE conversation_id = ? 和 ORDER BY embedding <=> $1 的 SQL 就能按语义召回。

关系上长这样(简化的三张表):

CREATE TABLE conversations (
  id BIGSERIAL PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title TEXT
);

CREATE TABLE messages (
  id BIGSERIAL PRIMARY KEY,
  conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('user','assistant','system')),
  content TEXT NOT NULL,
  embedding vector(1024),          -- 向量就是一列,不是另一套库
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX ON messages USING hnsw (embedding vector_cosine_ops);

于是两种记忆串成一条完整的流水线:

要点:

  • 双写但不冲突:Redis 存「本轮要用的压缩版」,PostgreSQL 存「全量的唯一事实源」——Redis 丢了、过期了,历史还在 PG,随时能重建;
  • 长期召回不是靠 Redis 翻聊天记录,而是 PG 里按向量找「意思最接近的旧消息」,再把命中的几段塞回本轮 prompt;
  • Redis 负责让这轮快、让记忆会过期;PG 负责让记忆永存、让历史可搜。 两个库的选型理由各用其长,谁也不抢谁的活。

收个尾:为什么「短期记忆 = Redis」是个默认解

把全篇的逻辑收成一句话:短期记忆要的是「现用现取 + 到点自动忘 + 多实例共享」,而这三点 Redis 都是原生答案:

  1. 快:内存级读写,每轮全量读写的操作是 μs~ms 级,对话手感不受历史拖累;
  2. 会过期:EX / SETEX / TTL 让「短期」有物理保证,不用自己写清理任务、不怕“记性太好”泄漏隐私;
  3. 数据结构刚好够用:整段存用 String、逐条存用 List、拆字段用 Hash、按时间窗用 ZSet——4 种形态覆盖消息存取的所有姿势,Set/Bitmap/Geo 留给周边业务;
  4. 好扩展:多实例、多进程共享同一份记忆;配合摘要中间件,存进 Redis 的永远是「有界的那份」。

最后一张「该把记忆放哪」的决策图,方便你按需取用:

给你的行动清单: 跑一个 Redis(官方镜像一行配置)→ 用 String 把一个会话的历史 JSON 存进去、带上 EX → 挂上 summarizationMiddleware 让历史永远有界 → 再补一张 PostgreSQL 表把每轮消息完整落库、配一列向量做长期召回。到这一步,你的 Agent 就同时拥有了「这一阵记得住」和「很久以前也想得起」的完整记忆——而其中最短、最热的那一段,Redis 一个就够。


附:7 种数据类型命令速查

类型写读常用判断/删除
StringSET / SETEX / INCRGETDEL、TTL
HashHSETHGET / HGETALL / HKEYSHDEL
ListLPUSH / RPUSHLRANGE / LPOP / RPOPLTRIM、LLEN
SetSADDSMEMBERSSISMEMBER、SINTER
ZSetZADDZRANGE / ZREVRANGE / ZSCOREZRANK
BitmapSETBITGETBITBITCOUNT
GeoGEOADDGEODIST / GEOSEARCH—
标签 / TAGSredis
L

Leo

博主

独立开发者 / Blogger,原博客「大道至简」维护者。正在把 WordPress 上攒了几年的文章与拾语迁移到 Next.js。

读者留言

COMMENTS · 0

发表留言

评论经审核后展示 · 请友善发言0/100