臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

一、💡 一句话理解

第 8 天核心结论

大模型本身不会记住上一轮对话。后端要用一个稳定的会话 ID把同一段对话归类,把消息保存为聊天记录,并在下一次提问前取回最近上下文;本篇用 Redis Stack 保存这份短期记忆。

本篇严格对应 0-学习路线 第 8 天:会话 ID、聊天记录、Redis。它接在 3.Spring AI ChatClient 和 7.SSE 流式响应 之后学习;今天先完成普通聊天接口的“记住上下文”能力,不提前学习 Tool Calling、RAG、限流或分布式锁。

完成后应能验证下面这段连续对话:

第 1 次:我叫小王。
第 2 次:我叫什么?
结果:模型能回答“小王”。

二、🧭 理论:它是什么

2.1 会话 ID:给一段对话的“档案编号”

会话 ID(conversationId)是标识一段对话的字符串。例如:user-42:550e8400-e29b-41d4-a716-446655440000。

它不是用户 ID,也不应该是前端随便传来的固定值:一个用户可以有多段对话;不同用户绝不能共用同一个 ID。

名称负责回答的问题示例
用户 ID“是谁在使用系统?”user-42
会话 ID“这是他的哪一段对话?”550e...000
实际存储键“Redis 到哪里找消息?”chat-memory:user-42:550e...000

Spring AI 的官方定义是:所有 ChatMemory 操作都按会话 ID 进行读、追加和删除。多用户系统应由服务器根据当前登录用户和会话生成 ID,避免串话。

2.2 聊天记录和聊天记忆不是一回事

概念白话解释本篇怎么处理
聊天记录(Chat History)所有消息的完整留档,用于审计、翻页和展示本篇不做长期归档;后续可写入 PostgreSQL。
聊天记忆(Chat Memory)本次请求真正放进 Prompt、让模型“看见”的上下文Redis 中保留一个最近消息窗口。

两者不能混为一谈。完整记录会越来越长,全部传给模型既增加 Token 成本,也会超过上下文窗口;而记忆只保留当前回答需要的最近几轮。

2.3 Redis 在这里做什么

Redis 是内存数据存储。这里不用它做“通用缓存”,而是把每个会话最近的消息存成可快速读取的短期记忆。

客户端携带 conversationId 和本轮问题
          ↓
ChatClient 的 Memory Advisor 按 ID 读取 Redis 中的历史消息
          ↓
历史消息 + 本轮问题 一起交给模型
          ↓
模型回答后,用户消息和助手消息写回 Redis

官方事实

Spring AI 的 RedisChatMemoryRepository 使用 Redis Stack 的 Redis Query Engine 与 RedisJSON,将消息存为 JSON 文档,可设置 TTL;它不是普通 Redis Server 的无额外模块方案。

2.4 为什么还要设置“最近 10 条”与 TTL

MessageWindowChatMemory 是一个滑动窗口:超过 maxMessages 的旧消息会被移出记忆,系统消息会保留。maxMessages 是上限,不一定恰好等于最终条数,因为 Spring AI 会按完整对话轮次裁剪,避免把一次问答截断。

本篇设置 10 条消息和 24h TTL:前者控制每次模型调用携带的上下文,后者让临时会话在 24 小时后自动过期。它们都是示例值,应根据业务对“记住多久”和 Token 成本的要求调整。

三、⚙️ 理论:它是怎么工作的

3.1 从请求到写回 Redis 的完整链路

3.2 Spring AI 中各组件的职责

组件职责不负责什么
RedisChatMemoryRepository在 Redis Stack 中读写消息不决定保留多少消息。
MessageWindowChatMemory按窗口策略决定哪些消息留在记忆里不直接调用模型。
MessageChatMemoryAdvisor调用前取历史、调用后存消息不生成会话 ID。
ChatClient组织 Prompt 并调用模型不会自动猜测会话 ID。

每次带 Memory Advisor 调用 ChatClient 时,必须通过 ChatMemory.CONVERSATION_ID 传入会话 ID;省略它会在运行时抛出 IllegalArgumentException。

3.3 会话 ID 的安全边界

示例为了便于用 curl 验证,允许请求带入 conversationId。真实项目中必须改为:从 JWT 或登录态取得当前用户 ID,在服务端生成 用户 ID + 新 UUID,并且查询、继续和删除会话时都校验归属。

错误:所有用户都使用 "default"
结果:A 的历史消息可能被送给 B 的模型请求 —— 串话与数据泄露。
 
正确:服务端生成 "当前用户 ID:随机会话 UUID"
结果:一人多会话,且用户之间隔离。

四、🚀 实践:从准备到验证

4.1 前置准备

4.1.1 沿用已有模型项目

先完成 3.Spring AI ChatClient 的 Spring Boot 项目与模型 API Key 配置。本篇不重复配置模型。以下示例以 Spring AI 官方当前稳定版 2.0.1 文档为准;如果现有项目使用其他 Spring AI 大版本,应先以该版本的官方文档核对 starter 名称和配置项。

4.1.2 启动 Redis Stack

RedisChatMemoryRepository 需要 Redis Stack 7.0+,不是只启动基础 Redis 镜像。开发机安装 Docker 后执行:

docker run -d --name ai-redis-stack -p 6379:6379 redis/redis-stack-server:latest

检查容器正在运行:

docker ps

预期能看到 ai-redis-stack,并且端口映射包含 6379。停止和重新启动它可分别使用 docker stop ai-redis-stack、docker start ai-redis-stack。

4.1.3 在 pom.xml 增加 Redis 聊天记忆依赖

保留已有的模型 starter,另增加这一项:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-redis</artifactId>
</dependency>

它会带入官方实现所需的 Jedis 客户端。修改后在 IntelliJ IDEA 重新加载 Maven 依赖。

4.1.4 配置 Redis 连接和记忆窗口

文件位置:src/main/resources/application.properties。不需要把密码写死;若 Redis 设置了密码,将它写为环境变量引用。

# Redis Stack 连接
spring.ai.chat.memory.repository.redis.host=localhost
spring.ai.chat.memory.repository.redis.port=6379
# spring.ai.chat.memory.repository.redis.password=${REDIS_PASSWORD}
 
# 会话记忆的 Redis 存储策略
spring.ai.chat.memory.repository.redis.key-prefix=chat-memory:
spring.ai.chat.memory.repository.redis.time-to-live=24h
spring.ai.chat.memory.repository.redis.initialize-schema=true

initialize-schema=true 会在启动时创建 Redis 搜索索引;生产环境应由部署流程统一管理这类结构变更,并视情况设为 false。

4.1.5 注册 ChatMemory 和 ChatClient

这一步必须在调用聊天接口前完成:它把 Redis 记忆窗口注册给 ChatClient。文件位置:src/main/java/com/afyke/ai/config/ChatMemoryConfig.java。如果你的项目基础包名不是 com.afyke.ai,把 package 改成自己的基础包名。

package com.afyke.ai.config;
 
import java.time.Duration;
 
import redis.clients.jedis.RedisClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.redis.RedisChatMemoryRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class ChatMemoryConfig {
 
    @Value("${spring.ai.chat.memory.repository.redis.host:localhost}")
    private String redisHost;
 
    @Value("${spring.ai.chat.memory.repository.redis.port:6379}")
    private int redisPort;
 
    @Value("${spring.ai.chat.memory.repository.redis.key-prefix:chat-memory:}")
    private String redisKeyPrefix;
 
    @Value("${spring.ai.chat.memory.repository.redis.time-to-live:24h}")
    private Duration redisTimeToLive;
 
    @Bean
    RedisClient redisClient() {
        return RedisClient.builder().hostAndPort(redisHost, redisPort).build();
    }
 
    @Bean
    RedisChatMemoryRepository redisChatMemoryRepository(RedisClient redisClient) {
        return RedisChatMemoryRepository.builder()
                .jedisClient(redisClient)
                .keyPrefix(redisKeyPrefix)
                .timeToLive(redisTimeToLive)
                .build();
    }
 
    @Bean
    ChatMemory chatMemory(RedisChatMemoryRepository repository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)
                .maxMessages(10)
                .build();
    }
 
    @Bean
    ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
        return builder
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }
}

这段配置先用 @Value 读取 application.properties 中的 Redis 地址、端口、键前缀和 TTL,再显式创建 RedisClient 与 RedisChatMemoryRepository;最后把仓库交给“最多保留 10 条消息”的 ChatMemory,并创建已注册 MessageChatMemoryAdvisor 的 ChatClient。

为什么不能只注入 RedisChatMemoryRepository

自定义了 ChatMemory Bean 时,Redis 自动配置可能因“已有 ChatMemory”条件而不再创建 RedisChatMemoryRepository,于是出现“找不到 Bean”。此时像上例一样显式注册仓库即可,不能只依赖自动配置。

4.2 可以拿来干什么

4.2.1 让模型记住刚刚说过的话

用途:用户分两次发送“我叫小王”“我叫什么”,第二次请求通过同一个会话 ID 取到第一次的记忆。

前置:ChatClient 已注册 MessageChatMemoryAdvisor,本次调用传入了 ChatMemory.CONVERSATION_ID。

String answer = chatClient.prompt()
        .user("我叫什么?")
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
        .call()
        .content();

输入是本轮问题与 conversationId;Advisor 自动补上历史消息;输出仍是本轮模型回答。不要手动把历史字符串拼进每一个 Prompt,这会让角色、顺序和窗口裁剪难以统一维护。

4.2.2 给短期对话自动过期

用途:客服临时咨询、未登录体验等不需要永久保存的对话,可以在一段时间后由 Redis 自动清理。

前置:已配置 spring.ai.chat.memory.repository.redis.time-to-live=24h。24 小时内有存储的消息可用于记忆;过期后该会话不再有该部分上下文。

spring.ai.chat.memory.repository.redis.time-to-live=24h

TTL 不是数据合规策略的替代品。涉及用户隐私或审计时,还要明确完整聊天记录的存储地点、访问权限、删除流程和保留期限。

4.3 完整实践:带 Redis 记忆的聊天接口

目标:从注册 Redis 记忆到新增 POST /api/chat 接口,完整跑通连续对话。首次不传 conversationId 时,后端生成一个;之后客户端带回这个 ID,就能继续同一段对话。

4.3.1 配置 ChatMemory 和 ChatClient

前面的 4.1.5 已经说明了为什么这一步必须先做;为保证本节可以从头独立完成,这里再次给出完整配置。文件位置:src/main/java/com/afyke/ai/config/ChatMemoryConfig.java。如果你的项目基础包名不是 com.afyke.ai,把 package 改成自己的基础包名。

package com.afyke.ai.config;
 
import java.time.Duration;
 
import redis.clients.jedis.RedisClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.redis.RedisChatMemoryRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class ChatMemoryConfig {
 
    @Value("${spring.ai.chat.memory.repository.redis.host:localhost}")
    private String redisHost;
 
    @Value("${spring.ai.chat.memory.repository.redis.port:6379}")
    private int redisPort;
 
    @Value("${spring.ai.chat.memory.repository.redis.key-prefix:chat-memory:}")
    private String redisKeyPrefix;
 
    @Value("${spring.ai.chat.memory.repository.redis.time-to-live:24h}")
    private Duration redisTimeToLive;
 
    @Bean
    RedisClient redisClient() {
        return RedisClient.builder().hostAndPort(redisHost, redisPort).build();
    }
 
    @Bean
    RedisChatMemoryRepository redisChatMemoryRepository(RedisClient redisClient) {
        return RedisChatMemoryRepository.builder()
                .jedisClient(redisClient)
                .keyPrefix(redisKeyPrefix)
                .timeToLive(redisTimeToLive)
                .build();
    }
 
    @Bean
    ChatMemory chatMemory(RedisChatMemoryRepository repository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)
                .maxMessages(10)
                .build();
    }
 
    @Bean
    ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
        return builder
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }
}

这段代码把 Redis 记忆窗口注册给 ChatClient:调用前自动按会话 ID 取历史消息,调用后自动写回用户消息和模型回答。

4.3.2 编写请求、响应与 Controller

文件位置:src/main/java/com/afyke/ai/controller/ChatController.java。

package com.afyke.ai.controller;
 
// 校验 @RequestBody 中的字段规则。
import jakarta.validation.Valid;
// 声明字符串不能为空、不能只包含空格。
import jakarta.validation.constraints.NotBlank;
// 调用大模型的高级客户端。
import org.springframework.ai.chat.client.ChatClient;
// 读取 Spring AI 规定的会话 ID 参数名。
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.UUID;
 
// 声明这是 REST Controller,方法返回值会自动转成 JSON。
@RestController
// 为本类所有接口统一加上 /api/chat 路径前缀。
@RequestMapping("/api/chat")
public class ChatController {
 
    // 复用已注册聊天记忆 Advisor 的 ChatClient。
    private final ChatClient chatClient;
 
    // Spring 自动注入配置类创建的 ChatClient,无须手动 new。
    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
 
    // 接收 POST /api/chat 请求。
    @PostMapping
    // 正常处理时返回 HTTP 200。
    @ResponseStatus(HttpStatus.OK)
    // @Valid 启用字段校验;@RequestBody 将请求 JSON 转为 ChatRequest。
    public ChatResponse chat(@Valid @RequestBody ChatRequest request) {
        // 先取得客户端带回的 ID;新旧两个变量分开,避免后续 Lambda 捕获一个被重新赋值的变量。
        String requestedConversationId = request.conversationId();
 
        // Lambda 中使用的局部变量必须是 final 或“只赋值一次”。
        final String conversationId;
        if (requestedConversationId == null || requestedConversationId.isBlank()) {
            // 新对话:生成随机 ID。实际项目应把 demo-user 替换为登录态中的用户 ID。
            conversationId = "demo-user:" + UUID.randomUUID();
        } else {
            // 继续已有对话:使用客户端带回的 ID。
            conversationId = requestedConversationId;
        }
 
        // 发起本轮模型调用;记忆 Advisor 会在调用前后自动处理 Redis 中的聊天记录。
        String answer = chatClient.prompt()
                // 放入用户本轮问题。
                .user(request.message())
                // 指明本轮属于哪个会话;缺少它,Advisor 无法读取或保存对应记忆。
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
                // 执行同步模型调用。
                .call()
                // 从模型响应中取出纯文本回答。
                .content();
 
        // 把会话 ID 和回答一起返回;客户端必须保存 ID,下一次继续携带它。
        return new ChatResponse(conversationId, answer);
    }
 
    // 请求 JSON:conversationId 可省略(表示新建对话),message 必须非空。
    public record ChatRequest(String conversationId, @NotBlank String message) {
    }
 
    // 响应 JSON:返回本次会话 ID 和模型回答。
    public record ChatResponse(String conversationId, String answer) {
    }
}

输入 JSON 包含 message 和可选的 conversationId。首次请求生成 ID;每次调用都把 ID 交给 Advisor;返回 JSON 让客户端保存 ID。@NotBlank 防止空问题进入模型调用。若项目尚未有校验依赖,在 pom.xml 增加 spring-boot-starter-validation。

4.3.3 启动并验证

先确认 Redis Stack 已运行,再从 IDEA 启动 Spring Boot 应用。打开新终端,第一次请求不要带会话 ID:

curl -X POST 'http://localhost:8080/api/chat' \
  -H 'Content-Type: application/json' \
  -d '{"message":"我叫小王,请记住。"}'

预期响应结构如下;其中 conversationId 每次新建会话都不同:

{
  "conversationId": "demo-user:550e8400-e29b-41d4-a716-446655440000",
  "answer": "好的,我记住了,你叫小王。"
}

复制上一步的真实 conversationId,发起第二次请求:

curl -X POST 'http://localhost:8080/api/chat' \
  -H 'Content-Type: application/json' \
  -d '{"conversationId":"替换为上一步真实的 conversationId","message":"我叫什么?"}'

预期:answer 能识别“小王”。若换成一个全新的 conversationId,模型没有前一轮消息可用,不能期待它知道名字。

4.3.4 常见报错排查

现象常见原因处理方式
启动时连不上 RedisRedis Stack 未运行、端口不是 6379执行 docker ps,核对容器和 application.properties。
RedisChatMemoryRepository 找不到 Bean未添加 starter、Maven 未刷新,或自定义 ChatMemory 使自动配置退让核对 4.1.3 的依赖;按 4.1.5 显式创建 RedisClient 与 RedisChatMemoryRepository。
提示缺少 conversationId调用中漏了 .advisors(...) 参数传入 ChatMemory.CONVERSATION_ID,不要只在请求对象中保存它。
第二次回答不记得第一次两次请求的 ID 不同、Redis 被清空或 TTL 已过复制同一个真实 ID,检查 Redis 容器与 TTL。
模型没有正常回答模型 API Key、模型地址或额度问题回到 2.大模型 API、请求参数与响应结构 检查既有模型配置。

五、📌 总结

  • 会话 ID 是同一段对话的隔离键;生产环境由服务端根据登录用户与会话生成并校验归属。
  • 聊天记录是完整留档,聊天记忆是给模型的有限上下文;不要把两者混为一谈。
  • Redis Stack + RedisChatMemoryRepository 负责消息存储,MessageWindowChatMemory 负责最近消息窗口。
  • MessageChatMemoryAdvisor 在调用前取历史、调用后保存消息;每次必须传 ChatMemory.CONVERSATION_ID。
  • 今天的验收标准是:两次请求使用同一个 ID 时,模型能记住第一轮信息。

一句话复述

用“用户隔离的会话 ID”从 Redis 取回最近聊天记录,再随本轮问题交给模型,模型才会表现得像在持续对话。

六、🔗 官方资料