做多轮对话 Agent,为什么我建议把短期记忆放到 Redis

前言

做 Agent 系统时,大家最先关注的往往是模型、Tool、工作流和 Prompt,但真正把系统跑到线上之后,最先暴露问题的通常不是模型,而是上下文。

一个用户连续聊了 10 轮,Agent 需要记住最近几轮消息;对话太长了,又要做截断和摘要;服务一旦扩成多实例,用户下一次请求还可能落到另一台机器上。这时候如果你还把会话上下文放在单机内存里,问题会立刻出现:

  • 实例之间无法共享会话状态。
  • 服务重启后短期记忆直接丢失。
  • 对话读写频率高,落数据库太重,延迟也不划算。
  • 会话闲置后还要自己清理,维护成本高。

这也是为什么在一套完整的 Agent 架构里,通常会把"记忆"拆成两层:

  • 短期记忆:承载最近消息、摘要、临时状态,要求低延迟、可过期、可跨实例共享。
  • 长期记忆:承载永久消息、历史会话、知识片段、向量检索结果,要求可归档、可追溯、可分析。

短期记忆这一层,Redis 几乎就是天然解法。

它不是因为"缓存很快"这么简单,而是因为它同时具备了 低延迟读写、TTL 自动过期、多实例共享、灵活数据结构 这四个关键特性。

本期我们就围绕一个具体目标来讲透它:如何用 Redis 为 Agent 构建一套真正可运行的短期记忆层,并和长期记忆存储做好分工。

读完后,大家会得到 3 个结果:

  • 知道为什么 Agent 的短期记忆更适合放在 Redis,而不是直接塞进应用内存或数据库。
  • 能基于 ioredis + LangChain 跑通一个最小可运行示例。
  • 明白 List / Hash / String / TTL 各自适合承载什么,以及实际工程里该如何分层。

一、短期记忆层:为 Agent 会话上下文争取最低延迟

图 1:在线对话优先依赖 Redis 承载短期记忆,而永久消息、历史检索与长期记忆则交给 PostgreSQL。

1.1 为什么需要短期记忆层?

如果把 Agent 看成一个正在持续对话的"运行时程序",那最近几轮消息、当前任务状态、临时摘要,本质上都是它的运行时内存。问题在于,线上服务的运行时内存并不稳定。

在本地 CLI 演示里,我们完全可以把上下文保存在进程变量里。因为:

  • 只有一个用户。
  • 只有一个进程。
  • 进程生命周期和会话生命周期几乎一致。

但一旦到了后端服务,情况就变了:

  1. 请求会被负载均衡分发到不同实例。
  2. 单机内存不能天然共享。
  3. 对话属于高频热数据,读写非常频繁。
  4. 大量闲置会话会持续占用资源。
  5. 历史消息还要做截断、摘要和重新拼装。

这时如果还把所有上下文都放进 PostgreSQL,你会遇到两个问题:

  • 一是每轮对话都要做多次数据库读写,代价太重。
  • 二是很多上下文本质上只是"临时运行态",并不适合永久保存。

所以短期记忆层的价值,不是"再建一个缓存",而是给 Agent 提供一个高频、低延迟、可过期、可共享 的运行时状态空间。

1.2 短期记忆层适用场景

Redis 这一层,特别适合下面几类场景:

  1. 多轮对话上下文 最近 5 到 20 轮消息需要被持续读取、拼接和截断,典型的高频热数据。

  2. 对话摘要缓存 当消息数增长到阈值后,把老消息压缩成摘要,后续继续复用。摘要并不一定要永久保存,但在当前会话阶段必须能快速读取。

  3. 多实例 Agent 服务 用户本轮请求打到 A 实例,下轮请求打到 B 实例,Redis 能让两台机器读到同一份会话上下文。

  4. 工具执行中的临时状态 比如当前任务步骤、中间结果、最近调用的 Tool 输出,这类状态适合短期保留,不适合直接归档。

  5. 需要自动失效的会话 用户 30 分钟没说话,会话记忆自动过期,完全符合 Redis 的 TTL 模型。

1.3 短期记忆层核心原理

可以把 Redis 理解成 Agent 身边的一张"工作台",而 PostgreSQL 则是后面的"档案室"。

  • 工作台上放的是当前正在处理的材料:最近消息、会话摘要、临时状态。
  • 档案室里放的是最终归档材料:永久消息、历史记录、长期记忆。

一次标准的短期记忆流程,大致是这样:

  1. 输入是什么 输入是一条新的用户消息,以及它所属的 sessionId

  2. 触发条件是什么 用户发起一次对话请求,系统需要组装上下文并调用 Agent。

  3. 中间处理了什么 服务先从 Redis 读取当前会话历史,再把用户消息拼进去;如果消息过长,则用摘要中间件压缩旧消息;最后把新的消息集合写回 Redis,并刷新 TTL。

  4. 输出结果是什么 输出是一条 Agent 回复,以及一份更新后的短期记忆。

  5. 对整体系统产生什么影响 会话状态不再依赖单机内存,实例可以横向扩展;历史消息又不会无限膨胀,因为 TTL 和摘要压缩会一起控制规模。

在这个模型里,Redis 解决的是"当前会话如何持续运行 "的问题,而不是"所有聊天记录如何永久保存"的问题。这个边界一定要清楚。

二、快速上手:构建第一个可运行示例

这一节我们直接基于一个真实示例来搭建:

  • 用 Docker 启动 Redis 和 RedisInsight。
  • ioredis 连接 Redis。
  • RedisMessageStore 管理消息读写。
  • 在 Agent 调用前后自动读取和写回会话记忆。
  • summarizationMiddleware 在消息过多时做压缩。

2.1 环境准备

这个示例使用的技术栈如下:

  • Redis 7
  • ioredis
  • langchain
  • @langchain/openai
  • dotenv

安装依赖:

bash 复制代码
pnpm add ioredis langchain @langchain/openai dotenv

如果你还想直接复用完整示例里的依赖,也可以保留这些包:

json 复制代码
{
  "dependencies": {
    "@langchain/langgraph": "^1.3.7",
    "@langchain/openai": "^1.4.7",
    "deepagents": "^1.10.2",
    "dotenv": "^17.4.2",
    "ioredis": "^5.11.1",
    "langchain": "^1.4.4",
    "zod": "^4.4.3"
  }
}

准备 .env

env 复制代码
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
OPENAI_API_KEY=sk-xxxx
MODEL_NAME=qwen-plus

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
MEMORY_TTL_SECONDS=1800
MEMORY_KEY_PREFIX=agent:short_memory
MEMORY_SESSION_ID=demo_user_001

这里最关键的几个参数是:

  • MEMORY_TTL_SECONDS:短期记忆保留多久。
  • MEMORY_KEY_PREFIX:会话记忆的 Redis key 前缀。
  • MEMORY_SESSION_ID:当前会话 ID。

2.2 启动 Redis

先看 docker-compose.yml

yaml 复制代码
services:
  # Redis 主服务,负责承载 Agent 短期记忆
  redis:
    image: redis:7-alpine
    container_name: agent_redis
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - ./volumes/redis:/data
    # 开启 AOF,降低极端情况下的数据丢失风险
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

  # RedisInsight 图形界面,便于查看 key、TTL 和数据结构
  redisinsight:
    image: redis/redisinsight:2.50
    container_name: redis_insight
    restart: always
    ports:
      - "5540:5540"
    volumes:
      - ./volumes/redisinsight:/data
    environment:
      - RI_HOST=0.0.0.0
    depends_on:
      - redis

执行:

bash 复制代码
docker compose up -d

这套配置的整体作用很简单:

  • redis 负责承载会话短期记忆。
  • redisinsight 用来可视化查看 key、TTL 和数据结构。
  • appendonly yes 表示开启 AOF,降低极端情况下的数据丢失风险。

2.3 先认识 Redis:7 种核心数据类型怎么选?

很多人学 Redis 时,第一反应是"它是一个缓存",第二反应是"我只会 set/get"。但如果你真的要用它承载 Agent 的短期记忆,只会 String 远远不够。

Redis 的真正价值之一,在于它提供了多种针对不同场景优化过的数据结构。它不是单纯把数据"存进去",而是允许你根据访问模式,选择最合适的承载方式。

图 2:从通用缓存到会话记忆,Redis 的 7 种核心数据类型各自覆盖了不同的数据组织方式。

这 7 种类型里,和 Agent 短期记忆最相关的是 StringHashList,但其余类型在周边能力上也很有价值。下面按实战视角快速过一遍。

String 字符串

  • 适用场景:验证码、Token、登录会话、计数器、分布式锁、文本类短期记忆。
  • 核心命令:setgetsetexincr
  • 真实业务示例:
bash 复制代码
setex verification:mobile:13800138000 300 "666888"
setex session:token:adf245kjndsa3 86400 "userid:1001"
incr counter:article:1024
set lock:order:2001 "locked" nx ex 10
setex agent:memory:user:1001 3600 "用户想学习 PostgreSQL 向量检索"

对 Agent 来说,String 最适合承载对话摘要、临时状态、整段序列化消息 。示例里的 RedisMessageStore,本质上就是把一整段消息数组序列化后放进一个 String。

Hash 哈希

  • 适用场景:用户信息、商品资料、电商购物车、结构化对话上下文。
  • 核心命令:hsethgetallhkeyshincrby
  • 真实业务示例:
bash 复制代码
hset user:info:1001 name "张三" age 28 phone "13800138000"
hset cart:user:1001 product:10086 2 product:10087 1
hset agent:session:user:1001 messages "最近 5 轮对话" summary "对话摘要"

Hash 特别适合存结构化会话元信息 ,比如 modellocaleuser_idsummary_versiontool_state。它的优势是字段级读写,不需要每次把整个 JSON 反序列化再整体改回去。

List 列表

  • 适用场景:消息队列、任务队列、操作日志、聊天历史、有序记录。
  • 核心命令:lpushrpushlrangelpopllen
  • 真实业务示例:
bash 复制代码
rpush queue:order "order_1001" "order_1002"
lpush user:history:1001 "查看了 AI 课程" "查看了 Redis 教程"
rpush queue:task "生成对话摘要" "向量入库"

如果你希望短期记忆按"最近消息队列"的方式组织,List 就很自然。比如把最近 20 条对话按顺序压进一个 List,再配合 ltrim 保持固定长度,这是一种非常常见的会话窗口设计。

Set 集合

  • 适用场景:数据去重、每日签到、IP 黑名单、共同好友、权限标签。
  • 核心命令:saddsmemberssismembersinter
  • 真实业务示例:
bash 复制代码
sadd sign:20250820:user 1001 1002 1003
sadd blacklist:ip "192.168.1.100" "192.168.1.101"
sinter user:friend:1001 user:friend:1002

Set 不一定直接承载聊天消息,但非常适合做 Agent 周边能力,比如已执行 Tool 去重、会话标签、权限集合、已看过的知识片段 ID 集合

ZSet 有序集合

  • 适用场景:排行榜、热度排序、积分排名、权重队列。
  • 核心命令:zaddzrangezrevrangezrank
  • 真实业务示例:
bash 复制代码
zadd rank:course 98 "PostgreSQL 实战" 95 "AI Agent 开发" 92 "Redis 从入门到精通"
zadd rank:user:points 1000 "张三" 850 "李四"
zadd hot:article 1200 "article:1024" 980 "article:1025"

对 Agent 而言,ZSet 特别适合做优先级任务队列、最近活跃会话排序、召回结果打分缓存。如果你希望某些任务"按权重执行",ZSet 会比 List 更灵活。

Bitmap 位图

  • 适用场景:海量用户签到、在线状态统计、布尔状态存储。
  • 核心命令:setbitgetbitbitcount
  • 真实业务示例:
bash 复制代码
setbit user:sign:1001:202508 5 1
setbit user:sign:1001:202508 10 1
bitcount user:sign:1001:202508

Bitmap 不适合直接存会话消息,但在高并发系统里可以承担海量布尔状态统计,比如某个知识片段是否已处理、某个会话是否触发过某类动作。

Geo 地理位置

  • 适用场景:附近门店、附近的人、距离计算、位置检索。
  • 核心命令:geoaddgeodist
  • 真实业务示例:
bash 复制代码
geoadd shop:location 116.481028 39.921983 "北京总店"
geodist shop:location "北京总店" "上海分店" km

Geo 和 Agent 短期记忆关系没那么直接,但一旦你做本地生活、地图服务、到店导购 Agent,它就会立刻变得有用。

为了方便大家快速决策,可以先记住这个简单结论:

数据类型 典型业务场景
String 验证码、Token、计数器、分布式锁、文本记忆
Hash 用户信息、商品数据、购物车、结构化会话
List 消息队列、任务队列、浏览/聊天历史
Set 签到、数据去重、黑名单、好友关系
ZSet 排行榜、热度排序、积分排名
Bitmap 批量签到、海量布尔状态统计
Geo 位置检索、距离计算、附近门店/人群

如果把这张表进一步翻译成 Agent 场景,其实就是一句话:

短期记忆主用 String / Hash / List,状态控制和周边能力按需补 Set / ZSet / Bitmap / Geo

2.4 完整示例:带短期记忆的 Agent 调用

下面这段代码,就是一个最小可运行的短期记忆实现。它来自实际项目示例,做了适度裁剪,但流程保持一致。

js 复制代码
import dotenv from "dotenv";
// 读取 .env 配置,并覆盖当前进程环境变量,方便本地调试
dotenv.config({ override: true });

import Redis from "ioredis";
import { ChatOpenAI } from "@langchain/openai";
import {
  mapChatMessagesToStoredMessages,
  mapStoredMessagesToChatMessages,
} from "@langchain/core/messages";
import { createAgent, HumanMessage, summarizationMiddleware } from "langchain";

// Redis 连接配置
const REDIS_HOST = process.env.REDIS_HOST ?? "localhost";
const REDIS_PORT = Number(process.env.REDIS_PORT ?? 6379);
const REDIS_DB = Number(process.env.REDIS_DB ?? 0);

// 会话短期记忆的 TTL,默认 30 分钟
const MEMORY_TTL = Number(process.env.MEMORY_TTL_SECONDS ?? 1800);

// Redis key 前缀,避免和别的业务 key 冲突
const KEY_PREFIX = process.env.MEMORY_KEY_PREFIX ?? "agent:short_memory";

class RedisMessageStore {
  constructor({ redis, keyPrefix, ttlSeconds }) {
    this.redis = redis;
    this.keyPrefix = keyPrefix;
    this.ttlSeconds = ttlSeconds;
  }

  // 统一生成某个会话的消息 key
  messagesKey(sessionId) {
    return `${this.keyPrefix}:${sessionId}:messages`;
  }

  // 从 Redis 读取历史消息,并反序列化成 LangChain 可识别的消息对象
  async loadMessages(sessionId) {
    const raw = await this.redis.get(this.messagesKey(sessionId));
    if (!raw) return [];
    return mapStoredMessagesToChatMessages(JSON.parse(raw));
  }

  // 将消息重新序列化后写回 Redis,同时刷新 TTL
  async saveMessages(sessionId, messages) {
    const payload = JSON.stringify(mapChatMessagesToStoredMessages(messages));
    await this.redis.set(
      this.messagesKey(sessionId),
      payload,
      "EX",
      this.ttlSeconds
    );
  }

  // 清空某个会话的短期记忆
  async clear(sessionId) {
    await this.redis.del(this.messagesKey(sessionId));
  }

  // 查看当前会话还剩多少秒过期
  async ttl(sessionId) {
    return this.redis.ttl(this.messagesKey(sessionId));
  }
}

async function invokeWithMemory(agent, store, sessionId, userText) {
  // 第一步:从 Redis 拿到历史消息
  const history = await store.loadMessages(sessionId);
  console.log(`↳ 从 Redis 加载 ${history.length} 条历史`);

  // 第二步:把本轮用户消息拼接到历史后面,再交给 Agent
  const result = await agent.invoke(
    { messages: [...history, new HumanMessage(userText)] },
    { recursionLimit: 30 }
  );

  // 第三步:把 Agent 返回后的最新消息集合写回 Redis
  await store.saveMessages(sessionId, result.messages);

  // 第四步:读取 TTL,确认这次写入已经刷新了会话过期时间
  const ttl = await store.ttl(sessionId);
  console.log(`↳ 写回 Redis ${result.messages.length} 条 (TTL ${ttl}s)`);

  return result;
}

// 初始化 Redis 客户端
const redis = new Redis({ host: REDIS_HOST, port: REDIS_PORT, db: REDIS_DB });

// 创建短期记忆存储实例
const store = new RedisMessageStore({
  redis,
  keyPrefix: KEY_PREFIX,
  ttlSeconds: MEMORY_TTL,
});

// 初始化对话模型
const model = new ChatOpenAI({
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  configuration: { baseURL: process.env.OPENAI_BASE_URL },
  temperature: 0,
});

// 创建 Agent,并挂上摘要中间件
const agent = createAgent({
  model,
  tools: [],
  systemPrompt: "你是会话助手。记住用户提到的关键事实,中文简短回答。",
  middleware: [
    summarizationMiddleware({
      model,
      // 当消息过多时,用中文摘要保留关键上下文
      summaryPrompt: `请用中文总结对话,保留关键事实。\n\n{messages}\n\n摘要:`,
      // 消息达到 8 条时触发压缩
      trigger: { messages: 8 },
      // 压缩后保留最近 4 条消息,其余交给摘要承载
      keep: { messages: 4 },
    }),
  ],
});

// 发起一次带短期记忆的 Agent 调用
const { messages } = await invokeWithMemory(
  agent,
  store,
  "demo_user_001",
  "我上次问过你 Redis 和 PostgreSQL 的分工,你还记得吗?"
);

// 取最后一条消息,通常就是 Agent 的最终回复
console.log(messages.at(-1)?.content);

// 结束前主动关闭 Redis 连接
await redis.quit();

这段代码的整体作用,是把一次 Agent 对话变成一个固定流程:

  1. 先从 Redis 取出会话历史。
  2. 把当前用户输入拼进消息列表。
  3. 调用 agent.invoke()
  4. 在需要时自动触发摘要压缩。
  5. 把新的消息集合写回 Redis。
  6. 刷新 TTL,确保会话在活跃期间持续存在。

2.5 关键依赖与核心 API

这套示例最核心的依赖只有 3 个:

  • ioredis:负责 Redis 读写。
  • ChatOpenAI:负责模型调用。
  • summarizationMiddleware:负责在消息过长时压缩历史。

其中最关键的 API 有这些:

  • redis.get():读取当前会话历史。
  • redis.set(key, value, "EX", ttl):写入历史并设置过期时间。
  • redis.ttl():查看当前会话剩余存活时间。
  • agent.invoke():执行一次完整 Agent 调用。
  • summarizationMiddleware():按阈值压缩旧消息。

2.6 运行结果会看到什么?

假设你连续对话几轮,控制台大致会看到类似输出:

text 复制代码
↳ 从 Redis 加载 6 条历史
↳ 写回 Redis 8 条 (TTL 1800s)

助手:记得。Redis 负责最近消息、短期摘要和会话运行态,PostgreSQL 负责永久消息与长期检索。

如果消息数量增长过快,压缩中间件还可能触发类似效果:

text 复制代码
当前消息数: 6
⚡ 已触发压缩

这说明短期记忆并不是无上限堆消息,而是在"保留必要上下文"和"控制上下文体积"之间动态平衡。

图 3:在生产场景里,短期记忆通常不会只有一种结构,而是会把消息、元数据、摘要拆分到不同 key 中。

三、核心配置 / 关键流程详解

这一节我们不只讲"能跑",而是讲清楚真正影响稳定性的 5 个点。

3.1 MEMORY_TTL_SECONDS:它是什么?

它是什么:

短期记忆的会话过期时间,示例里默认是 1800 秒,也就是 30 分钟。

它控制什么行为:

它决定一段会话在用户停止交互后还能保留多久。

常见配置方式:

  • 900:15 分钟,适合强交互、低成本场景。
  • 1800:30 分钟,比较常见。
  • 3600:1 小时,适合复杂任务型 Agent。

推荐使用场景:

大多数在线聊天 Agent,都应该给短期记忆设置 TTL,而不是无限保留。

容易踩坑的地方:

TTL 过短,用户切回页面时记忆已经没了;TTL 过长,会堆积大量闲置会话,占用内存。

3.2 RedisMessageStore:它控制什么?

它是什么:

一个基于 Redis 的消息存储层,负责会话消息的加载、保存、清空和 TTL 查看。

它控制什么行为:

它定义了"会话历史在 Redis 里以什么 key 组织、以什么格式读写"。

常见配置方式:

  • 简单模式:像示例这样,把整个消息数组序列化成一个 String。
  • 拆分模式 :消息用 List,元数据用 Hash,摘要用 String

推荐使用场景:

示例里的 String 模式很适合快速验证;正式项目如果会话结构更复杂,建议拆 key。

容易踩坑的地方:

把所有内容都塞进一个超大的 JSON String,后期会造成单 key 过大、局部更新困难。

3.3 summarizationMiddleware:为什么它是关键能力?

它是什么:

LangChain 提供的摘要中间件,用来在消息超阈值后压缩旧历史。

它控制什么行为:

它避免上下文窗口无限膨胀,降低 Token 消耗,同时尽量保留关键信息。

常见配置方式:

js 复制代码
summarizationMiddleware({
  model,
  summaryPrompt,
  trigger: { messages: 8 },
  keep: { messages: 4 },
})

推荐使用场景:

一旦你的 Agent 是多轮对话,就应尽早考虑摘要压缩,否则上下文成本会迅速失控。

容易踩坑的地方:

  • 触发阈值太低,会导致摘要过于频繁。
  • 摘要 Prompt 写得太弱,会丢失关键事实。
  • 只做截断不做摘要,用户的重要偏好和任务目标容易被删掉。

3.4 invokeWithMemory:一次完整调用如何串起来?

它是什么:

这个函数把"加载历史 -> 调用 Agent -> 写回记忆"串成一次完整事务流。

它控制什么行为:

它决定每次请求如何和短期记忆交互。

常见配置方式:

js 复制代码
async function invokeWithMemory(agent, store, sessionId, userText) {
  const history = await store.loadMessages(sessionId);

  const result = await agent.invoke({
    messages: [...history, new HumanMessage(userText)]
  });

  await store.saveMessages(sessionId, result.messages);
  return result;
}

推荐使用场景:

所有需要"带记忆调用"的对话接口,都可以抽成这种统一模式。

容易踩坑的地方:

  • 调用前忘记加载历史,Agent 每轮都会失忆。
  • 调用后忘记写回,下一轮还是拿到旧状态。
  • 只写消息不刷新 TTL,会导致活跃会话被提前过期。

图 4:一次对话请求并不是单纯调用模型,而是一个带状态读写、摘要压缩和 TTL 刷新的完整流程。

3.5 List / Hash / String:生产里怎么选?

它是什么:

这是 Redis 最常用的 3 种短期记忆承载方式。

它控制什么行为:

它决定你如何拆分消息、元数据和摘要。

常见配置方式:

  • List:存最近消息序列,适合按顺序追加和截断。
  • Hash:存会话元信息,比如模型名、语言、当前状态、用户偏好。
  • String:存摘要、压缩文本、序列化后的整段上下文。

推荐使用场景:

如果只是做 Demo,直接一个 String 就够了;如果已经进入工程阶段,建议拆成多结构。

容易踩坑的地方:

不要因为 Redis 数据结构丰富,就把每个字段都拆成单独 key。过度碎片化会让调试和维护都变得很累。

四、运行效果与扩展思考

把这套示例跑起来之后,你会看到一个很清晰的效果:

  • 第一次对话时,Redis 里几乎没有历史。
  • 第二次开始,请求会先取回上一轮上下文。
  • 对话越来越长时,旧消息会被摘要压缩。
  • 每次新对话都会刷新 TTL。
  • 用户长时间不活跃后,Redis 中的会话会自动过期。

图 5:低延迟、TTL、多实例共享和摘要压缩,是 Redis 能胜任短期记忆层的四个关键原因。

进一步看真实项目,Redis 这层通常不会独立存在,而是和长期存储协同:

  • Redis 承载"当前会话运行态"。
  • PostgreSQL 承载"永久聊天记录"。
  • 向量库或 pgvector 承载"长期语义检索"。

这背后的设计原则其实很朴素:短期记忆追求快,长期记忆追求稳。

当然,Redis 也有明确边界:

  1. 它不适合做永久归档 即便开启 AOF,Redis 也不是给你长期存几十万条历史消息用的。

  2. 它不适合无限堆大对象 消息越长、会话越多,内存成本越高,所以一定要配合摘要和 TTL。

  3. 它不能替代业务数据库 用户画像、完整消息表、可审计记录,最终还是应该写回 PostgreSQL。

如果你要继续扩展,这里有几个很自然的方向:

  • 把消息和摘要拆成 List + String + Hash 多结构组合。
  • 给不同租户、不同 Bot 设置不同 TTL 策略。
  • 在会话结束后,把 Redis 中的摘要异步同步到长期记忆层。
  • invokeWithMemory 加分布式锁,避免并发请求写乱同一会话。

五、总结

本期我们围绕一个很具体的问题展开:Agent 的短期记忆为什么适合放在 Redis,以及该怎么把它真正跑起来。

回顾一下核心结论:

  • 本文解决的问题,是多实例 Agent 场景下的上下文共享、低延迟读写、会话自动失效和消息膨胀控制。
  • Redis 的核心能力,不只是"快",而是它同时提供了 低延迟、TTL、共享状态和灵活数据结构
  • 在工程实现上,最稳妥的路线通常是:Redis 负责短期记忆,PostgreSQL 负责长期记忆,摘要中间件负责控制上下文体积。
  • 如果你现在正在做聊天 Agent、任务型 Agent 或 Tool 调度型 Agent,这几乎就是一条非常自然的基础设施路线。

下期如果继续往下讲,就很适合接着深入这几个方向:

  • 如何把 Redis 短期记忆和 PostgreSQL 长期记忆串成统一的 Memory Layer。
  • 如何设计消息压缩策略,避免摘要把关键事实"压没了"。
  • 如何在多实例和高并发下,避免同一会话被重复写乱。

很多人以为 Agent 的记忆问题,核心在"模型能不能记住"。

但到了工程层面你会发现,真正决定系统是否稳定的,往往是记忆存在哪、怎么过期、怎么压缩、怎么跨实例共享。而在短期记忆这件事上,Redis 依然是非常难被替代的那一个。

相关推荐
颜酱2 小时前
LangChain 工具调用:从原理、入门到落地
langchain·llm
程序员黑豆2 小时前
AI全栈开发之Java:什么是JDK
前端·后端·ai编程
swipe2 小时前
别再把关系库和向量库拆开了:PostgreSQL 搭建 AI 长期记忆层实战
面试·langchain·llm
阿明在折腾2 小时前
从Canvas到AI模型:我在线工具站里的图片处理实战
前端·后端
tyung3 小时前
Go 手写 Wait-Free SPSC 无界队列:无 CAS、无锁、泛型节点池
数据结构·后端·go
Lucien3233 小时前
学完 Spring Boot 再看 FastAPI,我破防了
后端
小小龙学IT3 小时前
Go 语言后端开发:从并发模型到生产落地的工程实践
开发语言·后端·golang
程序员cxuan3 小时前
Agents.md 是什么
人工智能·后端·程序员
神奇小汤圆3 小时前
将 Pi Agent 接入 HagiCode 的实践之路
面试