AI对话为什么需要RAG

本质上是通过rag解决达模型回答不可靠的问题,前端的核心价值在于将检索结果结构化展示,并与流式生成过程融合。

什么是RAG

rag(检索增强生成)是一种将强大信息检索技术与生成式大语言模型(LLM)相结合 的框架。核心思想是在 让LLM回答问题或生成文本之前,先从一个大规模的知识库如(数据库、文档集合)中检索出相关的上下文信息,然后将这些信息与原始问题一并提供给LLM,来增强生成能力,产出更准确、更具时效性、更符合特定领域知识的问答

为什么需要RAG

解决大模型的知识截止(预训练的大模型的知识库有截止时间)、数据孤岛(企业内部私有文档)、幻觉问题(编造不符合事实的信息)。

RAG工作原理

分为索引和检索两个阶段

**索引阶段-离线预处理:**文档加载、清洗、分段切为小块chunk;通过embedding模型转换为向量;存入向量数据库,构建语义索引

**检索生成阶段-在线实时:**将用户问题转换为向量;从数据库中找到最相近的文档块,拼接为上下文交给LLM生成答案

应用场景-科研助手

论文公式推导:求解一篇论文的公式,需要参考几篇相关的论文同时求解

文献查阅:没有rag的话,ai会凭空捏造文献

论文写作:例如写文献综述,可以生成更为准确的报告

支持私有知识问答:个人文献库中问答

前端目标拆解

能展示答案及其来源

支持引用高亮

与流式输出融合

长对话的稳定

技术调研

为什么选rag而不是fintune

fin tune成本高,更新慢;知识频繁更新、私有敏感文档、需要答案可溯源、不想大量标注数据、控制幻觉 ,RAG更好。

检索方案对比

1.关键词检索BM25:简单成本低,语义理解差

2.向量检索embedding:语义匹配强是主流,embedding负责将文本转换为稠密向量,予以相近的文本在空间中距离更近,需要embedding服务

常用embedding模型

|------------------------------------------|------|------|-------------------|
| 模型 | 维度 | 适用语言 | 特点 |
| text-embedding-3-small (OpenAI) | 1536 | 多语言 | 性价比高,适合大规模索引 |
| text-embedding-3-large (OpenAI) | 3072 | 多语言 | 精度最高,成本较高 |
| BAAI/bge-m3 | 1024 | 中英文 | 开源,中文效果优秀,支持多语言 |
| sentence-transformers/all-MiniLM-L6-v2 | 384 | 英文 | 体积小,速度快,适合本地极轻量部署 |

3.混合检索BM25+向量:效果好,实现复杂

向量检索:embedding+相似度计算

检索的核心是度量距离 。最常用的是余弦相似度(Cosine Similarity),它计算两个向量的夹角余弦值,值域 [-1, 1],越接近 1 越相似。此外还有点积(Dot Product)和欧氏距离(L2 Distance)。

为了在百万级向量中实现毫秒级检索,数据库通常采用近似最近邻(ANN)算法 (如 HNSW、IVF)。HNSW 是目前最主流的算法,它通过构建多层跳跃图网络,牺牲极少的精度换取了数量级的搜索速度提升。

前端展示方案 检索结果如何展示

只给答案:简单,不可信

内联引用(类似论文):可解释性强,实现复杂

采用内联引用+高亮标注效果最佳

流式/非流式输出

一次性输出,简单,但是延迟很高,用户体验很差:流式输出sse,实时体验效果好,但是前端处理比较复杂

方案设计(核心链路)

用户输入->检索服务(向量数据库)-(topk文档)->拼接prompt-> LLM生成-(sse)->前端解析与渲染->答案+引用高亮展示

前端实现

RAG数据结构设计

后端返回实例

复制代码
type RagChunk = {
  content: string
  source: string
  id: string
}

消息结构

复制代码
type Message = {
  role: 'user' | 'assistant'
  content: string
  references?: RagChunk[]
}

引用插入 策略(难)

方案一:

后端:插入引用标记,返回结构化引用源数据,"这是答案内容[1],这里引用了资料[2]";

前端:通过正则表达式匹配解析序号标签,进行样式高亮展示,绑定鼠标悬浮事件,实现鼠标经过[1]->高亮+弹出tooltip(鼠标悬浮文本框,使用ui组件库中的Tooltip组件),展示对应知识库来源

方案二:

前端匹配,文本匹配chunk,性能差,精度差

流式输出融合(重点)

流式输出是逐个chunk返回的,引用怎么对齐呢

后端传输规则:

  • 提示词工程 :强制要求 LLM 在回答中为每个事实性陈述添加内联引用标记,例如 [1]〔2〕。明确禁止跨 token 分割引用(如将 [1] 拆成 [1])。

  • 结构化协议:流式响应中,将纯文本与引用元数据分离传输。典型 SSE 设计:

    event: text
    data: {"content": "海外营收增长23%[1]。"}

    event: citation
    data: {"id": "1", "doc": "年报2024", "page": 12, "preview": "..."}

  • 确定性映射:后端维护一个引用清单,每个引用编号对应完整的元数据(文档名、页码、原文片段等)。

前端核心流程

  1. 维护三个"容器"
  • 文本缓冲池 (一个字符串变量):
    每收到一个 text 块,就把它拼接到这个字符串末尾。
    作用:保留完整的答案原文,方便后续用正则找出所有 [数字]
  • 待渲染队列 (另一个字符串变量):
    每收到一个 text 块,也把它拼接到这个队列里。
    作用:累积一小批文本,等合适的时机再一次性画到屏幕上。
  • 引用映射表 (一个字典/Map):
    每收到一个 citation 事件,就把编号和对应的文档信息存起来。
  1. 渲染文本流:批量、与屏幕刷新同步
  • 不用"收到一个字立刻改一次 DOM",那样会频繁触发浏览器重排。
  • 而是采用 requestAnimationFrame(请求动画帧)机制:
    • 每次收到新文本,先放进"待渲染队列"。
    • 如果当前没有安排渲染任务,就请求浏览器在下一帧绘制前执行一个函数。
    • 这个函数会把"待渲染队列"里所有的文本一次性追加 到页面上,然后清空队列。
  • 浏览器通常每秒刷新 60 次(约 16.6 毫秒一次),因此用户感知到的延迟极低,依然觉得是"逐字输出",但 CPU 负担大大降低。
  1. 引用对齐:延迟解析 + 局部更新
  • 因为 [1] 可能被拆成两个网络包([1]),所以不能一收到字符就立刻去解析。
  • 前端会定期 (比如每 100 毫秒,或者每次收到一批新文本后)对"文本缓冲池"做一次扫描:
    • 用正则表达式找出所有已经完整的 [数字]
    • 检查每个编号是否已经在"引用映射表"里。
    • 如果已存在,就把这个 [1] 替换成一个可交互的组件(角标)。
    • 如果还不存在,就保留原样,等以后元数据来了再处理。
  • 替换时,不会整个页面重新渲染,而是只更新变化的那一小段(Vue/React 的虚拟 DOM diff 会自动完成)。

最终对齐

  • 当收到 done 事件后,再做一次完整的全量解析,确保所有引用都变成组件。
  • 如果有某个引用编号始终没等到元数据,就显示一个灰色的 [?] 或保留原文本,不会报错。

部分参考菜鸟教程:RAG 与知识检索 | 菜鸟教程以及ai生成

相关推荐
C澒2 小时前
IntelliPro 企业级产研协作平台:低代码实时预览与可视化编辑技术调研
前端·低代码·ai编程
霍理迪2 小时前
TS类型断言和类型守卫
前端
木斯佳2 小时前
前端八股文面经大全:京东前端实习一面(2026-04-16)·面经深度解析
前端
墨心@2 小时前
pytorch 与资源核算
pytorch·语言模型·大语言模型·datawhale·组队学习
chenxu98b2 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
Bigger2 小时前
第十章:我是如何剖析 CLI 里的终极 Agent 能力的(电脑控制与浏览器接管)
前端·claude·源码阅读
kyriewen2 小时前
代码写成一锅粥?这5种设计模式让你的项目“起死回生”
前端·javascript·设计模式
蓝色的雨2 小时前
基于Babylonjs的WEBGPU渲染器源码架构
前端·javascript
浇头面加面2 小时前
📊 流式输出实现总结
前端