杠精派
派聪明
0/512

热门评论

阿白 2026年01月27日 09:54
引用原文:首先,在检索这层,我们会给 Elasticsearch 设置一个相关性评分的门槛。比如我们会把低于 0.3 分的结果全部过滤掉。也就是说,如果一个文档跟用户的问题只有一点点关联关系,分数达不到要求,它就根本不会进入到下一步的上下文构建中。
@派聪明 我们不是相似性检索和关键词检索综合评分吗?这个最后的0.3过滤是根据什么分数过滤的?
点赞
回复3 取消回复
派聪明 2026年01月27日 09:54
根据参考资料,系统在Elasticsearch检索层采用**混合检索策略**(结合相似性检索和关键词检索),最终生成的相关性评分是一个**综合得分**。0.3分是系统设定的**最低相关性阈值**,具体依据如下: --- ### 1. **评分机制** - **混合检索评分**: 系统会同时进行**向量相似性检索**(如余弦相似度)和**关键词检索**(如BM25算法),并将两种结果按预设权重加权融合,生成一个0-1区间的综合相关性分数。 - **阈值过滤**: 低于0.3分的文档被视为与用户问题关联度不足,直接排除,避免低质量信息进入后续上下文构建。 --- ### 2. **0.3阈值的设定依据** - **经验性平衡**: 0.3是系统通过多轮测试确定的平衡点: - **查全率 vs. 查准率**: 过低阈值(如0.1)可能召回过多无关文档,导致LLM混淆;过高阈值(如0.5)可能遗漏有效信息,降低回答覆盖率。 - **业务场景需求**: 专业领域(如技术文档)要求更高相关性,而通用场景可适当放宽阈值。 - **动态调整可能性**: 阈值可根据不同知识库特性或用户反馈微调(如高敏感领域采用更高阈值)。 --- ### 3. **为什么过滤低分文档?** - **避免LLM“胡说八道”**: 参考资料明确要求,若上下文无足够信息,LLM需回答“无法回答”。低分文档可能包含错误或无关信息,干扰模型判断。 - **优化性能**: 减少冗余数据传递,降低大模型处理负担和响应延迟。 --- ### 总结: 0.3分是**混合检索后的综合相关性阈值**,用于筛选与问题强相关的文档。这一数值通过平衡查全率、查准率及业务需求确定,确保进入LLM的上下文信息既相关又可靠。
点赞
回复 取消回复
qq92159 2026年03月30日 17:38
@派聪明 这个最小分数阈值只有在纯文本搜索时才会起作用吧,混合索引也会用到吗?
根据参考资料,系统在Elasticsearch检索层采用**混合检索策略**(结合相似性检索和关键词检索),最终生成的相关性评分是一个**综合得分**。0.3分是系统设定的**最低相关性阈值**,具体依据如下: --- ### 1. **评分机制** - **混合检索评分**: 系统会同时进行**向量相似性检索**(如余弦相似度)和**关键词检索**(如BM25算法),并将两种结果按预设权重加权融合,生成一个0-1区间的综合相关性分数。 - **阈值过滤**: 低于0.3分的文档被视为与用户问题关联度不足,直接排除,避免低质量信息进入后续上下文构建。 --- ### 2. **0.3阈值的设定依据** - **经验性平衡**: 0.3是系统通过多轮测试确定的平衡点: - **查全率 vs. 查准率**: 过低阈值(如0.1)可能召回过多无关文档,导致LLM混淆;过高阈值(如0.5)可能遗漏有效信息,降低回答覆盖率。 - **业务场景需求**: 专业领域(如技术文档)要求更高相关性,而通用场景可适当放宽阈值。 - **动态调整可能性**: 阈值可根据不同知识库特性或用户反馈微调(如高敏感领域采用更高阈值)。 --- ### 3. **为什么过滤低分文档?** - **避免LLM“胡说八道”**: 参考资料明确要求,若上下文无足够信息,LLM需回答“无法回答”。低分文档可能包含错误或无关信息,干扰模型判断。 - **优化性能**: 减少冗余数据传递,降低大模型处理负担和响应延迟。 --- ### 总结: 0.3分是**混合检索后的综合相关性阈值**,用于筛选与问题强相关的文档。这一数值通过平衡查全率、查准率及业务需求确定,确保进入LLM的上下文信息既相关又可靠。
点赞
回复 取消回复
派聪明 2026年03月30日 17:40
是的,这个最小分数阈值(如0.3)在**混合检索(文本+向量)时同样适用**,且是关键过滤机制。以下是具体说明: --- ### **1. 混合检索的评分机制** 在混合索引中,Elasticsearch会综合两种评分: - **文本相似度**:基于BM25算法(传统关键词匹配)。 - **向量相似度**:基于余弦相似度(语义匹配)。 最终通过`rank_feature`或`neural`等查询类型,将两种分数**加权融合**为统一的`_score`(综合相关性分数)。 --- ### **2. 阈值的作用场景** 无论纯文本还是混合检索,阈值的作用都是**过滤低相关性结果**: - **纯文本检索**:直接过滤BM25分数低于阈值的文档。 - **混合检索**:过滤**综合分数(文本+向量)** 低于阈值的文档。 **示例**: 若某文档文本匹配度低(BM25=0.2),但语义高度相关(向量相似度=0.9),混合分数可能超过阈值(如0.8)保留;反之,若两者均低(如混合分数=0.2),则直接过滤。 --- ### **3. 为何混合检索仍需阈值?** - **避免噪音干扰**:向量检索可能引入语义相关但文本无关的文档(如“苹果”指水果或公司),阈值可过滤此类低质量结果。 - **控制上下文长度**:防止过多低相关文档挤占Token预算,提升大模型回答质量。 - **用户信任度**:确保引用的文档与问题强相关,减少“一本正经地胡说八道”。 --- ### **4. 实现方式(代码逻辑)** 在Elasticsearch查询中,阈值通常通过`min_score`参数实现: ```json { "query": { "neural": { "content_embedding": { "query_text": "用户问题", "model_id": "sentence-transformers__all-MiniLM-L6-v2" } } }, "min_score": 0.3 // 仅返回综合分数≥0.3的结果 } ``` 即使混合检索,最终返回的文档仍需满足`_score >= 阈值`。 --- ### **总结** - **纯文本检索**:阈值基于BM25分数过滤。 - **混合检索**:阈值基于**文本+向量融合后的综合分数**过滤,确保结果同时满足语义和关键词的相关性。 - **必要性**:阈值是混合检索质量控制的核心手段,避免低质量污染上下文。
@派聪明 这个最小分数阈值只有在纯文本搜索时才会起作用吧,混合索引也会用到吗?
点赞
回复 取消回复

10 条评论

心已指南 2026年03月29日 15:04
@派聪明 怎么理解这句话:基于 Redis 实现会话管理,支持 7 天对话历史存储,并在向量 API 调用失败时自动降级至文本搜索保障可用。
点赞
回复1 取消回复
派聪明 2026年03月29日 15:04
这句话描述了一个智能对话系统的会话管理机制,核心要点如下: ### 1. **Redis 实现会话管理** - **存储方式**:使用 Redis 存储用户对话历史(参考资料第5、6、7节)。 - **数据结构**: - `user:{userId}:current_conversation_id` → 记录用户当前会话ID(UUID)。 - `conversation:{conversationId}` → 存储历史对话的JSON列表(包含用户消息和AI回复)。 - **优势**:Redis 内存读写速度快(微秒级),适合高频读写场景(参考资料第6节)。 ### 2. **7天对话历史存储** - **TTL 自动过期**:通过 Redis 的 `EXPIRE` 命令设置7天过期时间(参考资料第5节)。 - **数据清理**:过期后自动删除,无需手动维护(对比MySQL需定时任务)。 - **滑动窗口**:仅保留最近20条消息(参考资料第8节),避免上下文过长。 ### 3. **向量 API 失败时降级至文本搜索** - **正常流程**:优先调用向量 API 进行语义检索(如 Elasticsearch 向量搜索)。 - **降级机制**: - 当向量服务不可用时(如网络故障、服务宕机),自动切换到 **文本关键词搜索**(如 ES 的全文检索)。 - 确保核心功能(知识检索)仍可用,提升系统鲁棒性(参考资料第9节)。 --- ### 设计意图 - **性能与成本平衡**:Redis 高速读写支撑实时对话,7天TTL控制数据量。 - **容错能力**:降级机制保障服务可用性(参考资料第3、10节强调稳定性)。 - **上下文管理**:通过会话ID关联历史,实现多轮对话连贯性(参考资料第5节)。 > 示例流程: > 用户提问 → 检索知识(向量API → 失败 → 降级文本搜索) → 结合Redis历史 → 生成回答 → 存入Redis(7天后自动清除)。
点赞
回复 取消回复
小叮当 2026年03月25日 10:09
引用原文:将用户的原始问题,提示词和聊天记录一并发给 DeepSeek
@派聪明 聊天记录也发给deepseek是在哪一步做的呢?代码实现是什么
点赞
回复1 取消回复
派聪明 2026年03月25日 10:10
根据参考资料,聊天记录作为上下文信息是在**调用DeepSeek之前**,与用户问题和提示词一起被整合并发送的。具体实现步骤如下: --- ### **核心步骤** 1. **获取历史记录** 当用户发送新消息时,后端通过`conversationId`从Redis中读取最近20条对话记录(见参考资料第5节): ```java // 获取历史记录(JSON格式) String historyJson = getConversationHistory(conversationId); ``` 2. **构建提示词(Prompt)** 将历史记录、用户当前问题、知识库检索结果等组合成结构化的提示词(参考资料第11节): ```java // 示例:将历史记录拼接到System指令中 String systemPrompt = "历史对话记录:" + historyJson + "\n\n当前问题:" + userQuestion; ``` 3. **调用DeepSeek API** 将完整提示词(包含历史记录)通过流式API发送给DeepSeek(参考资料第1节): ```java // 使用WebClient发送流式请求 WebClient.create() .post() .bodyValue(promptWithHistory) // 包含历史记录的完整提示词 .retrieve() .bodyToFlux(String.class) // 流式接收响应 .subscribe(chunk -> { // 实时推送内容到前端 sendToFrontend(chunk); }); ``` --- ### **关键设计** - **数据来源**:历史记录存储在Redis中,通过`conversationId`关联(参考资料第5、7节)。 - **上下文管理**:仅保留最近20条消息,避免Token超限(参考资料第8节)。 - **实时传递**:历史记录与当前问题同时发送,确保DeepSeek理解对话上下文。 > **代码位置**:参考资料第5节的`updateConversationHistory`方法负责存储历史记录,而调用DeepSeek的流式请求逻辑在参考资料第1节的`WebClient`部分(具体代码片段未完全展示,但流程已明确)。
点赞
回复 取消回复
过时的草莓 2026年03月20日 17:29
引用原文:后端这边我们遵循的是“无状态连接”原则。比如用户重连后重新发一条消息,我们会通过消息中携带的会话 ID 去缓存中找回上下文,然后接着处理,就好像这条连接从来没断过一样。
消息中有携带会话id吗?@派聪明
点赞
回复1 取消回复
派聪明 2026年03月20日 17:30
是的,根据上下文描述,**用户重连后重新发送的消息中会携带会话ID**。具体实现逻辑如下: 1. **消息携带会话ID** 当用户因网络波动断开重连后,前端在重新发送消息时,会在消息中**包含会话ID**(`conversationId`)。这个ID用于标识当前对话的上下文(如历史记录、用户身份等)。 2. **后端恢复上下文** 后端收到消息后,会解析其中的会话ID,并以此作为键值从缓存(如Redis)中**检索对应的对话历史**: - 通过 `conversation:{conversationId}` 获取历史记录(JSON格式)。 - 将历史记录与当前问题拼接,重新构建Prompt发送给大模型。 3. **无状态连接的体现** 后端不依赖WebSocket连接本身保存状态,而是通过消息中的会话ID动态恢复上下文,实现“连接中断后无缝续接”的效果。 > ✅ **关键点**:会话ID是消息的**显式组成部分**,而非依赖连接属性。这样即使连接断开重连,只要消息携带正确的ID,后端仍能恢复对话状态。
点赞
回复 取消回复
H 2026年03月16日 18:20
引用原文:在动态构建提示词的时候,我们首先会构建一个 system 指令,然后将检索到的知识片段包裹在引用开始/结束符中,并且要求模型遵守我们自定义的规则。
@派聪明 所以一共有几个sistem 指令
点赞
回复3 取消回复
派聪明 2026年03月16日 18:21
根据您提供的参考资料原文,在动态构建提示词时,**只构建了一个 `system` 指令**。 ### 关键依据: 1. **原文明确表述**: > "我们首先会构建一个 `system` 指令,然后将检索到的知识片段包裹在引用开始/结束符中..." (参考资料第11条) **关键词是“一个”**,说明 `system` 指令在流程中仅创建一次。 2. **技术逻辑**: - `system` 指令用于定义模型的基础行为规则(如引用格式、输出规范),属于**静态框架**。 - 后续动态添加的是 `user` 指令(包含历史对话和当前问题),与 `system` 指令**组合**形成完整提示词,而非重复创建 `system` 指令。 ### 流程总结: ```mermaid graph LR A[构建system指令] --> B[定义规则与引用格式] B --> C[包裹知识片段] C --> D[添加user指令] D --> E[组合成完整提示词] ``` **结论**:整个流程中仅存在 **1 个 `system` 指令**,用于固定规则,后续动态内容通过 `user` 指令补充。
点赞
回复 取消回复
H 2026年03月16日 18:24
@派聪明 这个怎么 要求的呢
根据您提供的参考资料原文,在动态构建提示词时,**只构建了一个 `system` 指令**。 ### 关键依据: 1. **原文明确表述**: > "我们首先会构建一个 `system` 指令,然后将检索到的知识片段包裹在引用开始/结束符中..." (参考资料第11条) **关键词是“一个”**,说明 `system` 指令在流程中仅创建一次。 2. **技术逻辑**: - `system` 指令用于定义模型的基础行为规则(如引用格式、输出规范),属于**静态框架**。 - 后续动态添加的是 `user` 指令(包含历史对话和当前问题),与 `system` 指令**组合**形成完整提示词,而非重复创建 `system` 指令。 ### 流程总结: ```mermaid graph LR A[构建system指令] --> B[定义规则与引用格式] B --> C[包裹知识片段] C --> D[添加user指令] D --> E[组合成完整提示词] ``` **结论**:整个流程中仅存在 **1 个 `system` 指令**,用于固定规则,后续动态内容通过 `user` 指令补充。
点赞
回复 取消回复
派聪明 2026年03月16日 18:27
派聪明的核心要求围绕**实时交互体验**和**知识准确性**展开,具体技术实现如下: --- ### 1. **流式响应(打字机效果)** - **技术路径**: 前端通过 **WebSocket** 建立长连接 → 后端调用 **DeepSeek 流式 API** → 使用 **Spring WebFlux 的 WebClient** 订阅数据流 → 分段接收内容 → 通过 WebSocket 实时推送给前端。 - **关键点**: LLM 生成内容时,每完成一小段就立即推送,用户看到文字逐字显示。 --- ### 2. **多轮对话记忆** - **存储方案**: - 每个用户分配唯一 `conversationId`(存储于 Redis 的 `user:{userId}:current_conversation_id`)。 - 对话历史以 JSON 格式存入 Redis(Key: `conversation:{conversationId}`),保留最近 **20 条消息**。 - **上下文传递**: 每次请求时,从 Redis 加载历史对话,拼接进 Prompt 发送给 LLM。 --- ### 3. **知识库检索与引用** - **混合检索流程**: 用户提问 → 在 Elasticsearch 检索相关文档 → 过滤相关性低于 **0.3 分** 的结果 → 将文档片段格式化(如 `[编号] 内容`)并加入 Prompt。 - **引用规则**: 要求 LLM 在回答时标注来源(如 `[1]`),前端解析编号后生成可点击链接。 --- ### 4. **异常处理与安全** - **网络断线**: - 前端用 `@vueuse` 实现 **自动重连**(指数退避策略)+ **心跳检测**。 - 后端 **无状态设计**:重连后通过 `conversationId` 恢复上下文。 - **权限控制**: 身份认证通过 **JWT Token**(WebSocket 请求头传递),知识库检索接口严格校验用户权限(如组织、文档公开性)。 --- ### 5. **上下文窗口管理** - **滑动窗口**: 超过 20 条消息时截断最新记录,避免 Token 超限。 - **未来优化**: 计划引入 **对话摘要压缩** 或 **向量数据库语义检索**,平衡上下文长度与信息完整性。 --- ### 总结 派聪明通过 **WebSocket 实时通信**、**Redis 会话管理**、**ES 知识检索** 和 **流式 API 调用**,实现了“像真人对话”的交互体验,同时通过严格的过滤和引用机制确保回答的准确性。
@派聪明 这个怎么 要求的呢
点赞
回复 取消回复
H 2026年03月16日 17:20
引用原文:5.聊天助手能记住我们上一轮聊天的内容。这种‘多轮对话’的能力,背后需要什么样的技术来支撑?
@派聪明 这是前端做的还是后端做的呢
点赞
回复1 取消回复
派聪明 2026年03月16日 17:22
根据参考资料,**多轮对话能力完全由后端实现,前端仅负责展示**。具体技术支撑如下: ### 后端核心实现: 1. **会话管理** - 为每个用户分配唯一 `conversationId`(通过 Redis 存储 `user:{userId}:current_conversation_id`),关联历史对话。 - 每次新请求时,通过 `conversationId` 从 Redis 加载历史记录(JSON 格式)。 2. **上下文维护** - 将历史对话(用户提问+模型回答)拼接进当前请求的 Prompt,发送给大模型(如 DeepSeek)。 - 更新时追加新对话到 Redis,并保留最近 **20 条消息**(滑动窗口策略)。 3. **数据存储** - 使用 Redis 存储对话历史(高频读写场景),而非 MySQL,因其读写速度更快(微秒级)且支持自动 TTL 清理。 ### 前端角色: - 仅负责接收后端返回的流式响应,并实时显示在聊天界面(打字机效果)。 - **不参与**历史数据的存储或上下文逻辑。 ### 关键设计: - **无状态连接**:前端断线重连后,后端通过 `conversationId` 恢复上下文,用户无感知。 - **权限隔离**:前端仅传递用户 Token,身份校验由后端完成。 > ✅ **结论**:多轮对话是典型的后端业务逻辑,前端仅负责交互展示。
点赞
回复 取消回复
未来可期 2026年03月13日 14:20
引用原文: key 用于定位用户的会话,格式为 user:{userId}:current_conversation_id,value 是一个 UUID 的字符串
@派聪明,这个value存储的是上一次会话的ID是吗
点赞
回复1 取消回复
派聪明 2026年03月13日 14:20
是的,根据参考资料中的描述,这个 `value` 存储的是**用户当前活跃会话的 ID**(即上一次成功建立的会话 ID),而非历史会话。具体实现逻辑如下: ### 关键点解析: 1. **`key` 格式**:`user:{userId}:current_conversation_id` - `{userId}` 是用户的唯一标识(如 `itwanger`)。 - 该 key 用于定位用户当前正在进行的会话。 2. **`value` 的含义**: - `value` 是一个 **UUID 字符串**,代表当前会话的唯一标识符(`conversationId`)。 - **它指向用户最近一次发起对话的会话**,而非所有历史会话。 - 若用户首次对话或上次会话已过期(超过 7 天),系统会生成**新的 UUID** 作为当前会话 ID。 - 若用户连续对话(未超时),则复用**同一个 UUID** 保持会话连续性。 3. **与 Redis 数据结构的关联**: - 通过这个 `current_conversation_id`,系统进一步查找真正的对话历史: ```redis conversation:{conversationId} → 存储 JSON 格式的最近 20 条对话记录 ``` - 示例: - 用户 `itwanger` 的当前会话 ID 是 `abc-123`(存储在 `user:itwanger:current_conversation_id` 中)。 - 完整对话历史则通过 `conversation:abc-123` 获取。 ### 为什么不是“上一次会话”? - **设计目的**:`current_conversation_id` 是**实时状态标识**,用于快速关联用户与最新会话。 - **会话管理**: - 用户每次发起新对话时,若 Redis 中无有效会话 ID(或过期),则创建新会话并更新此 key。 - 若会话未过期(7 天内),则直接复用该 ID,确保上下文连续。 > ✅ **总结**:`value` 存储的是用户**当前活跃的会话 ID**,是连接用户与最近对话历史的桥梁,而非泛指所有历史会话。
点赞
回复 取消回复
LibW*y 2026年03月11日 16:24
引用原文:接着去调用 DeepSeek,我们调用的是流式响应的 API。
使用的协议是什么@派聪明
点赞
回复1 取消回复
派聪明 2026年03月11日 16:24
DeepSeek流式响应使用的是Server-Sent Events (SSE)协议。这是一种基于HTTP/1.1的流式传输协议,采用分块传输编码(chunked transfer encoding),允许服务器逐步推送数据到客户端,实现"打字机效果"的实时响应。
点赞
回复 取消回复
活力的抽屉 2026年03月08日 14:27
引用原文:既然用到了WebSocket
@派聪明 为什么使用websocket?和大模型对话主流使用的一般是SSE
1
回复1 取消回复
派聪明 2026年03月08日 14:29
根据参考资料,选择WebSocket而非SSE(Server-Sent Events)实现大模型流式响应的核心原因如下: ### 1. **双向实时通信需求** - **WebSocket优势**:WebSocket是全双工协议,支持服务端主动向客户端实时推送数据(如大模型逐字生成的流式内容),同时允许客户端随时发送新消息(如用户连续提问)。这种双向能力完美匹配聊天场景的实时交互需求。 - **SSE局限**:SSE本质是单向通信(服务端→客户端),仅支持服务端推送。若用户需在模型生成过程中主动打断或发送新消息,SSE无法直接实现,需额外机制(如轮询或新HTTP请求),增加复杂度。 ### 2. **流式传输效率** - **WebSocket**:通过持久化长连接,后端可直接将DeepSeek返回的流式数据块(如每生成100字符)立即通过WebSocket推送给前端,实现“打字机效果”。 - **SSE**:虽也支持流式响应,但依赖HTTP协议,每次数据推送需包含HTTP头部(即使复用连接),开销略高于WebSocket的二进制帧传输。 ### 3. **连接稳定性与异常处理** - **WebSocket**:参考资料中明确提到,通过心跳检测和自动重连机制(如`@vueuse`库)保障连接稳定性。后端采用无状态设计,断线重连后通过会话ID恢复上下文,无缝继续对话。 - **SSE**:基于HTTP,连接更易因网络波动中断,且原生不提供重连机制,需手动实现重连逻辑,增加开发成本。 ### 4. **大模型交互的特殊性** - 在大模型流式生成过程中,用户可能随时输入新问题(如追问细节)。WebSocket的双向特性允许用户在模型回答时实时发送消息,后端可立即处理并终止当前流式响应,切换新对话——这是SSE难以高效支持的。 ### 总结 WebSocket因**全双工通信、低延迟流式推送、强健的重连机制**,成为大模型实时对话场景的理想选择。SSE虽适合简单推送(如新闻通知),但在需要双向交互、上下文保持的复杂对话中,WebSocket的技术优势更显著。
1
回复 取消回复
Y 2026年03月07日 19:59
引用原文:前端会解析回答内容中的这些编号
@派聪明 这里后端是怎么把编号和文档链接做的转换@
点赞
回复1 取消回复
派聪明 2026年03月07日 20:00
根据参考资料,后端实现编号与文档链接的转换过程如下: 1. **编号分配** 后端在从 Elasticsearch 检索文档时,会为每条知识片段分配**临时编号**(如 `[1]`、`[2]`),编号从 1 开始递增。这些编号与文档内容、标题、链接等元数据一起构成结构化的知识片段(示例:`[1] 标题(链接)内容`)。 2. **Prompt 构建规则** 后端在构建提示词时,通过 System 指令明确要求大模型: > "如果参考了某段知识,请在句末加上它的编号(如 `[1]`)。" 大模型在生成回答时,会自动在引用内容后附加对应的编号。 3. **前端解析与链接绑定** - 前端收到大模型的回答后,解析文本中的编号(如 `[1]`)。 - 前端通过编号**索引后端提供的文档元数据**(在检索时已包含标题、链接等)。 - 将编号替换为可点击的链接(如 `[1]` → `<a href="链接">标题</a>`)。 **关键点**: - 后端仅传递**编号**给大模型,避免暴露原始链接。 - 前端通过编号反查**本地缓存的元数据**(链接、标题),实现动态绑定。 - 这种设计符合单一职责:后端负责编号分配和规则约束,前端负责解析和渲染。
点赞
回复 取消回复
Y 2026年03月07日 17:29
引用原文:加上后端的“无状态设计+会话恢复
@派聪明 状态恢复的代码在哪里?
点赞
回复1 取消回复
派聪明 2026年03月07日 17:32
根据参考资料,**会话恢复的核心逻辑通过 Redis 存储和检索对话历史实现**,具体代码位置和实现逻辑如下: --- ### **会话恢复的实现位置** 1. **会话 ID 获取与恢复**(参考资料第 5 点) - **方法名**:`getOrCreateConversationId(String userId)` - **逻辑**: - 根据用户 ID 在 Redis 中查找当前会话 ID(Key: `user:{userId}:current_conversation_id`)。 - 若不存在或过期,生成新会话 ID 并存入 Redis(TTL 为 7 天)。 - **代码片段**: ```java private String getOrCreateConversationId(String userId) { String key = "user:" + userId + ":current_conversation_id"; String conversationId = redisTemplate.opsForValue().get(key); if (conversationId == null) { conversationId = UUID.randomUUID().toString(); redisTemplate.opsForValue().set(key, conversationId, Duration.ofDays(7)); } return conversationId; } ``` 2. **历史对话的加载与拼接**(参考资料第 5、11 点) - **方法名**:`getConversationHistory(String conversationId)` - **逻辑**: - 通过会话 ID 从 Redis 获取历史对话(Key: `conversation:{conversationId}`)。 - 将历史记录与当前问题、知识库检索结果拼接成 Prompt,发送给大模型。 - **代码片段**(隐含逻辑): ```java private List<Map<String, String>> getConversationHistory(String conversationId) { String key = "conversation:" + conversationId; String json = redisTemplate.opsForValue().get(key); return objectMapper.readValue(json, List.class); // 反序列化 JSON } ``` --- ### **会话恢复的完整流程** 1. **用户重连** → 前端通过 WebSocket 重新连接,携带用户 Token。 2. **后端恢复会话**: - 通过 `getOrCreateConversationId` 获取会话 ID(从 Redis 读取或新建)。 - 通过 `getConversationHistory` 加载历史对话(从 Redis 读取 JSON 数据)。 3. **构建新请求**: - 将历史对话 + 当前问题 + 知识库检索结果 → 拼接成 Prompt 发送大模型。 4. **更新历史**: - 大模型返回后,调用 `updateConversationHistory` 将新对话追加到 Redis(参考资料第 5 点)。 --- ### **关键设计** - **无状态设计**:后端不保存 WebSocket 连接状态,所有会话数据存储在 Redis。 - **会话隔离**:通过 `conversationId` 关联用户历史,确保多用户数据互不干扰。 - **自动清理**:Redis 设置 TTL(如 7 天),避免历史数据堆积。 > 💡 **总结**:会话恢复的核心代码在 **Redis 的读写操作**(`getOrCreateConversationId` 和 `getConversationHistory`),通过会话 ID 从 Redis 加载历史上下文,实现“断连后无缝恢复”。
点赞
回复 取消回复

目录