✅派聪明 RAG 系统的聊天助手模块设计方案 – 朝汐の小站
✅派聪明 RAG 系统的聊天助手模块设计方案
本文最后更新于 259 天前,如有错误请邮件至 zhiligyi222na@gmail.com

聊天助手模块是派聪明系统的核心组件之一,承载了用户与系统之间的主要交互能力。

模块通过 WebSocket 协议实现双向通信,支持大语言模型(接入了 DeepSeek)输出内容的流式返回;为支持多轮连续对话,该模块集成了 Redis 用于存储和维护用户会话上下文,确保大模型在生成回答时能够“记住”前文内容,维持语义连贯性。

同时,模块深度集成了 Elasticsearch,可以为用户提供结构化文本的全文索引和关键词匹配,通过这套混合检索机制,派聪明能在海量本地知识中快速定位与用户问题相关的信息片段。

为了更好地引导大语言模型生成高质量回答,系统特别强化了 Prompt 构建与模板管理能力

  • 根据检索结果动态生成 Prompt;
  • 支持多种 Prompt 模板配置与调优;
  • 确保内容组织清晰、有重点,引导模型围绕核心信息生成响应。

这一机制是实现 RAG 的关键保障,确保模型回答既有语义逻辑,又有知识依据。

一、功能需求

二、技术选型

功能模块技术选型备注
实时通信WebSocket(基于Spring WebSocket)支持STOMP子协议
对话上下文存储Redis(使用Spring Data Redis)高性能缓存,支持TTL
本地知识库(当前)Elasticsearch支持混合检索
本地知识库(规划)Faiss提升向量检索性能
语言模型调用DeepSeek API通过WebClient调用
Prompt管理自研模板引擎支持动态模板和变量替换
异步处理Spring WebFlux支持响应式编程
安全认证JWT确保WebSocket连接安全

三、关键流程

01、用户发起对话流程

当用户在页面上开始一次对话时,系统的第一步是由客户端主动发起一个 WebSocket 连接请求,这个请求里会带上用户的 JWT 身份认证信息。

服务端收到请求后,会先验证用户的身份和权限,确认无误后,就会和客户端建立一个稳定的 WebSocket 长连接,用于后续的实时对话。

连接建立之后,用户可以开始提问了。客户端会把用户输入的问题通过 WebSocket 发给服务端。服务端这边接收到消息后,会先解析内容,然后根据情况获取一个当前的会话 ID,如果是新的对话,就创建一个。

接着系统会启动知识库检索流程。它会调用内部的 /api/search/hybrid 接口,执行一轮“混合检索”,也就是结合关键词匹配和语义匹配的方式,快速从本地知识库中找出和用户问题最相关的文档。这些结果还会再经过筛选、排序,并提取出关键内容和出处信息,为后面生成回答做准备。

在拿到检索结果后,系统会开始构建 Prompt,也就是发送给大模型的提问模板。它会根据问题类型选择一个合适的 Prompt 模板,然后把刚刚检索到的内容填进去,同时还会加上一些系统级的指令或限制条件。这个过程中还会管理好上下文的长度,保证多轮对话的连贯性,最终生成一份结构化的 Prompt。

准备好 Prompt 之后,系统会把它发送给大语言模型的 API(比如 DeepSeek)。大模型会开始生成回答,系统这边则以流式的方式逐段接收内容。为了保证体验,还会处理模型返回中的异常或错误,比如超时、内容为空等问题。

生成内容后,系统会把这些文本切分成一段一段,再通过 WebSocket 实时地推送给客户端。这样用户就能一边看到内容一边继续等待剩下的生成,体验上就像在“实时对话”一样流畅。客户端也会一段段渲染这些返回的内容,提升整体交互体验。

最后,为了支持后续的上下文对话,系统会把当前这轮的用户提问和模型回答完整地存进 Redis 中,更新对话历史记录。同时也会设置或刷新这个会话的过期时间,以便未来再次使用或者进行归档。

02、新建会话流程

当用户打开对话页面,准备开始一次新的交流时,客户端会先通过一个 REST 接口向服务端发送“创建会话”的请求。这时候,服务端首先会对用户的身份进行校验,确保这是一个合法登录的用户。

验证通过后,系统会为这次新对话生成一个全局唯一的 conversationId,用作这轮会话的身份标识。同时,会为这次对话准备一份空的历史记录结构,方便后续存储每轮提问和回答内容。

接下来,系统会在 Redis 里建立用户和这个会话 ID 之间的映射关系,也就是说:这个会话是属于哪个用户的。为了防止会话无限制增长,系统还会给这个会话设置一个过期时间,比如 24 小时或 7 天,超时后自动清理。

最后,系统会把新生成的 conversationId 返回给客户端,表示这轮对话已经正式创建成功,用户可以开始提问啦。

03、查询历史对话流程

当用户想要查看之前的聊天记录时,客户端会向服务端发送一个查询历史记录的 REST 请求。服务端收到请求后,第一步还是先对用户的身份进行校验,确认用户是合法且有权限访问对应数据的。

接着,系统会去 Redis 中查找当前用户对应的 conversationId,也就是这位用户当前正在使用的那一轮对话的标识。如果 Redis 中没有查到,或者这条会话已经过期失效,系统会及时返回提示信息,避免出现无效请求。

private List<Map<String, String>> getConversationHistory(String conversationId) {
        String key = "conversation:" + conversationId;
        String json = redisTemplate.opsForValue().get(key);
        try {
            if (json == null) {
                logger.debug("会话 {} 没有历史记录", conversationId);
                return new ArrayList<>();
            }
            
            List<Map<String, String>> history = objectMapper.readValue(json, new TypeReference<List<Map<String, String>>>() {});
            logger.debug("读取到会话 {} 的 {} 条历史记录", conversationId, history.size());
            return history;
        } catch (JsonProcessingException e) {
            logger.error("解析对话历史出错: {}, 会话ID: {}", e.getMessage(), conversationId, e);
            return new ArrayList<>();
        }
    }

如果会话是有效的,那系统就会继续从 Redis 中读取这个会话对应的聊天历史记录,包括之前用户问过什么、系统是怎么回答的。这些内容会经过一轮格式化处理,比如按时间顺序排列、结构整理清晰,最后统一打包成接口返回数据发回给客户端,方便前端展示成对话列表,帮助用户快速回顾之前的交流内容。

四、Redis 结构设计

01、用户到会话的映射

  • Key: user:{userId}:current_conversation
  • Value: 当前用户的 conversationId
  • TTL: 7 天
  • 用途: 快速查找某个用户的当前会话 ID
  • 示例:
Key: user:12345:current_conversation
Value: abcdef123456

02、对话历史记录:

  • Key: conversation:{conversationId}
  • Value: JSON 格式的对话历史记录数组,每个元素包含 role、content、timestamp 字段
  • TTL: 7 天
  • 用途: 存储用户的对话上下文,支持多轮对话,限制最多保存 20 条消息
  • 示例:
{
"messages": [
    {"role": "user", "content": "人工智能是什么?"},
    {"role": "assistant", "content": "人工智能是模拟人类智能的技术。"}
  ]
}

03、历史会话列表:

  • Key: user:{userId}:conversations
  • Value: 用户的所有 conversationId 列表(JSON 格式)
  • TTL: 7 天
  • 用途: 支持用户查看历史会话记录

04、Prompt 模板缓存:

  • Key: prompt_templates:{templateName}
  • Value: 模板内容
  • TTL: 无(或较长时间)
  • 用途: 存储系统定义的 Prompt 模板
  • 示例:
{
  "name": "knowledge_qa",
  "template": "你是派聪明,一个基于本地知识库的智能助手。\n\n当回答问题时,请遵循以下规则:\n1. 优先基于提供的参考信息回答\n2. 如果参考信息不足,清楚地表明\n3. 回答要简洁、准确、客观\n4. 引用来源时使用[文档X]格式\n\n参考信息:\n{{context}}\n\n对话历史:\n{{history}}\n\n用户问题:{{query}}\n\n请用中文回答。",
  "variables": ["context", "history", "query"],
  "max_tokens": 4000
}

五、接口设计

01、WebSocket 接口

  • URL/chat/{token}
  • 协议: WebSocket
  • 功能: 用户通过 WebSocket 发送消息,服务端逐段返回回答内容

客户端发送消息格式:

①、普通聊天消息,**格式**为纯文本字符串,比如:”沉默王二是沙雕吗?”

②、停止响应指令,格式为 JSON 对象(需要先获取停止令牌)示例:

{
  "type": "stop",
  "_internal_cmd_token": "WSS_STOP_CMD_123456"
}

服务端返回格式:

①、AI 响应内容块(流式),示例:

{"chunk": "沉默王二是"}
{"chunk": "一名帅气的"}
{"chunk": "技术博主。"}

②、响应完成通知,格式: JSON对象,示例:

{
  "type": "completion",
  "status": "finished",
  "message": "响应已完成",
  "timestamp": 1703123456789,
  "date": "2025-05-26T09:04:16.789Z"
}

③、停止确认通知,格式: JSON对象,示例:

{
  "type": "stop",
  "message": "响应已停止",
  "timestamp": 1703123456789,
  "date": "2025-05-26T09:04:16.789Z"
}

④、错误消息,格式: JSON 对象,示例:

{
  "error": "AI服务暂时不可用,请稍后重试"
}

02、获取中止回答 Token

  • URL/api/chat/websocket-token
  • Method: GET
  • 请求头:
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
  • Response:
{
  "code": 200,
  "message": "获取WebSocket停止指令Token成功",
  "data": {
    "cmdToken": "WSS_STOP_CMD_123456"
  }
}

03、获取对话历史

  • URL/api/v1/users/conversation
  • Method: GET
  • 请求头:
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
  • 查询参数(可选):
start_date (string, 可选): 开始日期时间,格式支持:
  ● 2023-01-01T12:00:00 (完整格式)
  ● 2023-01-01T12:00 (不带秒)
  ● 2023-01-01T12 (不带分钟和秒)
  ● 2023-01-01 (仅日期)
end_date (string, 可选): 结束日期时间,格式同上
  • Response:
{
  "code": 200,
  "message": "获取对话历史成功",
  "data": [
    {
      "role": "user",
      "content": "沉默王二是沙雕吗?",
      "timestamp": "2025-01-26T10:30:15"
    },
    {
      "role": "assistant", 
      "content": "沉默王二是一名帅气的技术博主...",
      "timestamp": "2025-01-26T10:30:15"
    }
  ]
}
  • 说明: 根据用户的userId获取其当前会话的对话历史

04、获取对话历史(admin)

  • URL/api/v1/admin/conversation
  • Method: GET
  • 请求头:
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
  • 查询参数(可选):
userid: string     (可选) - 目标用户ID(数字),不填则获取所有用户的对话历史
start_date: string (可选) - 开始日期时间,多种格式支持
end_date: string   (可选) - 结束日期时间,多种格式支持

yyyy-MM-dd              例: 2025-01-26
yyyy-MM-ddTHH:mm        例: 2025-01-26T10:30
yyyy-MM-ddTHH:mm:ss     例: 2025-01-26T10:30:15
  • Response:
{
  "code": 200,
  "message": "获取对话历史成功",
  "data": [
    {
      "role": "user",
      "content": "沉默王二是沙雕吗?",
      "timestamp": "2025-01-26T10:30:15",
      "username": "test"
    },
    {
      "role": "assistant", 
      "content": "沉默王二是一名帅气的技术博主,编程星球已有 5 个实战项目:编程喵、技术派、mydb、PmHub 和派聪明。",
      "timestamp": "2025-01-26T10:30:15",
      "username": "test"
    },
    {
      "role": "user",
      "content": "沉默王二有什么特殊癖好吗?",
      "timestamp": "2025-01-26T11:15:30",
      "username": "admin"
    },
    {
      "role": "assistant",
      "content": "沉默王二喜欢美女,喜欢篮球、喜欢足球、喜欢技术、喜欢撒野。",
      "timestamp": "2025-01-26T11:15:30",
      "username": "admin"
    }
  ]
}

六、Prompt构建与管理设计

派聪明 Prompt 设计方案

七、小结

聊天助手模块是派聪明系统中最关键的交互中枢,它整合了 WebSocket 实时通信、Redis 对话存储、本地知识库检索以及大语言模型(如 DeepSeek)的强大能力,为用户提供了一种基于知识库的智能问答体验。

这个模块的最大特点在于,它不仅能“回答问题”,更能结合知识库内容,生成有依据、有深度的回答。这背后,得益于我们对 Prompt 构建与管理机制 的深度设计——系统内置了一整套结构化的 Prompt 模板体系,能够智能拼接检索结果、对话上下文和系统指令,从而构建出更适合语言模型理解和输出的 Prompt。

这一整套机制的存在,使得派聪明真正具备了 RAG 的完整能力:

  1. 回答更精准、相关性更强:通过把知识库中最相关的信息“喂”给大模型,回答内容更聚焦、更靠谱。
  2. 合理引用知识内容,增强可信度:回答中会引用具体文档或段落,让用户清楚回答来源,提升可信度和专业性。
  3. 支持多轮连续对话,理解上下文:借助 Redis 保存历史记录,系统可以持续理解用户上下文,做到“前后呼应”。
  4. 适配多种问题类型与场景:无论是专业知识问答、业务流程咨询,还是日常闲聊,Prompt 模板都能灵活适配。
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇