给 AI 产品加上 RAG:前端知识库检索增强实战

发布时间:2026/7/25 4:27:09

给 AI 产品加上 RAG:前端知识库检索增强实战 点击上方 前端Q关注公众号回复加群加入前端Q技术交流群前面 15 篇我们从零搭了 Agent、给它加了工具和记忆、学了 Graph 架构、搞了多 Agent 协作——Agent 已经很能干了。但有一个关键问题始终没解决Agent 只知道训练数据里的东西。用户问我们公司的退货政策是什么——Agent 不知道。问上周会议决定了什么——Agent 更不知道。它不是不聪明是根本没看过这些资料。怎么办把公司所有文档塞进 PromptToken 炸了。微调模型成本太高、更新太慢。答案是RAGRetrieval-Augmented Generation检索增强生成——先从知识库里搜到相关内容再喂给 LLM 生成回答。为什么需要 RAGLLM 有三个硬伤RAG 恰好能补硬伤一知识截止。GPT-4o 的训练数据截止到某个时间点之后的事它不知道。你的公司文档、产品手册、内部 Wiki 更不用说——根本没在训练集里。硬伤二幻觉。LLM 不知道的东西会编。问它一个不存在的 API它能编得有模有样。在客服、医疗、法律等场景幻觉是致命的。硬伤三上下文窗口有限。就算你把所有文档塞进去128k token 也装不下一个中型知识库。而且 token 越多费用越高、注意力越分散。RAG 的思路很直觉不把所有知识都塞给 LLM而是先搜到相关的几段只把这几段当上下文传给 LLM。用前端类比——RAG 就像搜索引擎 AI 的组合▸用户提问 → 先在知识库里搜一搜类似 Elasticsearch▸搜到最相关的 3~5 段内容▸把这些内容连同用户问题一起发给 LLM▸LLM 基于真实资料回答不瞎编RAG 的完整流水线RAG 分两个阶段索引阶段离线和检索生成阶段在线。▎索引阶段Indexing——把文档变成可搜索的向量原始文档 → 加载 → 分块 → Embedding 向量化 → 存入向量数据库文档加载Loading读取各种格式——PDF、Markdown、HTML、Notion 导出、数据库文档分块Chunking把长文档切成小段通常 200~1000 token 一段向量化Embedding用 Embedding 模型把每段文字转成一个数字向量比如 1536 维存储Storage把向量存入向量数据库Pinecone、Chroma、Milvus 等▎检索生成阶段Retrieval Generation——用户提问时实时执行用户提问 → Embedding → 在向量库中检索相似文档 → 拼装 Prompt → LLM 生成回答查询向量化把用户的问题也转成向量相似度检索在向量库中找到最相近的 top-K 段上下文拼装把检索到的内容塞进 PromptLLM 生成基于真实资料生成回答动手用 LangChain.js 搭一个 RAG 系统用 LangChain.js 从零搭一个完整的 RAG 流水线。场景给前端团队做一个内部文档问答助手让同事可以问我们的组件库怎么用、部署流程是什么之类的问题。▎安装依赖bashnpm install langchain langchain/openai langchain/community cheerio▎Step 1文档加载与分块typescriptimport { RecursiveCharacterTextSplitter } from langchain/text_splitter;import { Document } from langchain/core/documents;// 模拟加载内部文档const docs [new Document({pageContent: # 组件库使用指南## 安装\\\bashnpm install our-company/ui-kit\\\## Button 组件Button 组件支持 primary、secondary、danger 三种类型。使用方式Button typeprimary提交/ButtonProps 包括type、size、disabled、loading、onClick。size 支持 small、medium、large 三个值。## Modal 组件Modal 用于弹窗确认。必须传入 visible 和 onClose。使用方式Modal visible{show} onClose{() setShow(false)}内容/Modal支持 title、footer、width、maskClosable 等 Props。,metadata: { source: component-guide.md, category: 组件库 },}),new Document({pageContent: # 部署流程## 测试环境1. 提交代码到 feature 分支2. 在 GitLab 上创建 MR3. MR 合并后自动触发 CI/CD 部署到测试环境4. 测试环境地址https://test.our-app.com## 生产环境1. 测试通过后从 develop 合并到 master2. 在 Jenkins 上点击「构建生产」3. 构建产物自动上传到 CDN4. 需要运维确认后才会正式切流5. 回滚方式在 Jenkins 选择上一个成功的构建版本重新部署## 注意事项- 生产部署时间窗口工作日 10:00-18:00- 部署前需在群里通知 all- 大版本上线需提前一天在 TAPD 提上线申请单,metadata: { source: deploy-guide.md, category: 部署 },}),];// 分块每段 500 字符重叠 50 字符const splitter new RecursiveCharacterTextSplitter({chunkSize: 500,chunkOverlap: 50,});const splitDocs await splitter.splitDocuments(docs);console.log(E6DB74原始文档 ${docs.length} 篇 → 分块后 ${splitDocs.length} 段);分块策略是 RAG 效果的关键。几个核心参数参数说明推荐值chunkSize每段最大字符数500~1000中文建议偏小chunkOverlap相邻段重叠字符数chunkSize 的 10%~20%分割符优先级按什么切\n\n\n. 空格为什么要重叠假设一段话被切成两半前半段讲Button 的 Props后半段讲Props 包括 type、size、disabled——不重叠的话搜到前半段就拿不到具体 Props 了。重叠保证上下文不断裂。▎Step 2Embedding 向量存储typescriptimport { OpenAIEmbeddings } from langchain/openai;import { MemoryVectorStore } from langchain/vectorstores/memory;// 1. 创建 Embedding 模型const embeddings new OpenAIEmbeddings({modelName: text-embedding-3-small,});// 2. 向量化 存入内存向量库const vectorStore await MemoryVectorStore.fromDocuments(splitDocs, embeddings);// 3. 测试检索const results await vectorStore.similaritySearch(Button 组件怎么用, 3);console.log(检索到的相关文档);results.forEach((doc, i) {console.log(\n--- 第 ${i 1} 段 (来源: ${doc.metadata.source}) ---);console.log(doc.pageContent.slice(0, 100) ...);});similaritySearch背后做了什么把Button 组件怎么用转成 1536 维向量跟向量库里所有文档段落计算余弦相似度返回最相似的 top-3 段生产环境该用什么向量数据库向量数据库特点适合场景MemoryVectorStore纯内存不持久化开发测试、小数据量Chroma轻量支持本地部署中小项目、快速验证Pinecone全托管免运维不想管基础设施Milvus高性能支持亿级数据大规模生产系统pgvectorPostgreSQL 扩展已有 PG不想加新依赖▎Step 3检索 LLM 生成typescriptimport { ChatOpenAI } from langchain/openai;import { ChatPromptTemplate } from langchain/core/prompts;import { createStuffDocumentsChain } from langchain/chains/combine_documents;import { createRetrievalChain } from langchain/chains/retrieval;const llm new ChatOpenAI({ modelName: gpt-4o, temperature: 0 });// 1. 构建 RAG Promptconst prompt ChatPromptTemplate.fromTemplate(你是公司内部文档助手。根据以下参考资料回答用户问题。如果资料中没有相关信息请诚实说我在文档中没有找到相关信息。不要编造不存在的内容。参考资料{context}用户问题{input});// 2. 创建 RAG Chainconst documentChain await createStuffDocumentsChain({ llm, prompt });const retriever vectorStore.asRetriever({ k: 3 });const ragChain await createRetrievalChain({combineDocsChain: documentChain,retriever,});// 3. 提问const response await ragChain.invoke({input: Button 组件支持哪些 Props,});console.log(回答, response.answer);console.log(引用来源, response.context.map(d d.metadata.source));输出回答Button 组件支持以下 Props- type按钮类型可选 primary、secondary、danger- size按钮尺寸可选 small、medium、large- disabled是否禁用- loading是否显示加载状态- onClick点击回调函数使用方式Button typeprimary提交/Button引用来源[component-guide.md]LLM 的回答完全基于检索到的文档不会瞎编。而且还返回了引用来源用户可以追溯。在 Dify 中搭建 RAG零代码方案如果你不想写代码搭 RAG 管线Dify是目前最好的开源替代——它提供了完整的知识库管理 RAG 编排 API 发布前端直接调接口就行。▎Dify 的 RAG 流程上传文档 → 自动分块 → 自动 Embedding → 创建应用 → 关联知识库 → 发布 API▎Step 1创建知识库在 Dify 控制台点「知识库」→「创建知识库」上传文档支持 PDF、Markdown、TXT、Notion 同步、网页抓取配置分块策略——自动 or 自定义选择 Embedding 模型——推荐text-embedding-3-small点「保存并处理」等待索引完成▎Step 2创建应用并关联知识库创建一个「聊天助手」应用在「上下文」中关联刚创建的知识库配置检索策略——向量检索 / 全文检索 / 混合检索设置 System Prompt让 LLM 基于检索结果回答▎Step 3前端调用 Dify APItypescriptasync function askDify(question: string, conversationId?: string) {const response await fetch(https://api.dify.ai/v1/chat-messages, {method: POST,headers: {Authorization: Bearer ${DIFY_API_KEY},Content-Type: application/json,},body: JSON.stringify({inputs: {},query: question,response_mode: streaming,conversation_id: conversationId || ,user: frontend-user-001,}),});const reader response.body!.getReader();const decoder new TextDecoder();while (true) {const { done, value } await reader.read();if (done) break;const chunk decoder.decode(value);const lines chunk.split(\n).filter(line line.startsWith(data: ));for (const line of lines) {const data JSON.parse(line.slice(6));if (data.event message) {process.stdout.write(data.answer);}}}}await askDify(部署到生产环境的流程是什么);Dify 的优势▸零代码搭 RAG——上传文档就能用▸可视化编排——拖拽式搭建 RAG 工作流▸开箱即用的 API——前端直接调支持流式▸自带评估工具——可以测试 RAG 效果、调优参数前端集成用 AI SDK 做流式 RAG在实际前端项目中推荐用Vercel AI SDK做流式集成——用户体验最好。▎Next.js API Route RAGtypescript// app/api/rag/route.tsimport { NextRequest } from next/server;import { streamText } from ai;import { openai } from ai-sdk/openai;import { getVectorStore } from /lib/vector-store;export async function POST(req: NextRequest) {const { messages } await req.json();const lastMessage messages[messages.length - 1].content;// 1. 检索相关文档const vectorStore await getVectorStore();const relevantDocs await vectorStore.similaritySearch(lastMessage, 3);const context relevantDocs.map(doc doc.pageContent).join(\n\n---\n\n);// 2. 流式生成const result streamText({model: openai(gpt-4o),system: 你是公司内部文档助手。根据以下参考资料回答问题。如果资料中没有相关信息诚实说不知道。参考资料${context},messages,});return result.toDataStreamResponse();}▎前端聊天组件tsx// app/page.tsxuse client;import { useChat } from ai/react;export default function RAGChat() {const { messages, input, handleInputChange, handleSubmit, isLoading } useChat({api: /api/rag,});return (div classNamemax-w-2xl mx-auto p-4h1 classNametext-2xl font-bold mb-4 内部文档助手/h1div classNamespace-y-4 mb-4{messages.map((msg) (divkey{msg.id}className{p-3 rounded-lg ${msg.role user? bg-blue-100 ml-auto max-w-xs: bg-gray-100}}{msg.content}/div))}{isLoading div classNametext-gray-400思考中.../div}/divform onSubmit{handleSubmit} classNameflex gap-2inputvalue{input}onChange{handleInputChange}placeholder问点什么...classNameflex-1 border rounded-lg px-4 py-2/buttontypesubmitdisabled{isLoading}classNamebg-blue-500 text-white px-4 py-2 rounded-lg disabled:opacity-50发送/button/form/div);}几行代码就做了一个带 RAG 的聊天界面——用户输入问题后端检索知识库、流式返回前端实时渲染。进阶 RAG 技术基础 RAG 管用但效果还能更好。以下是几个生产中常用的优化手段。▎1. Re-ranking二次排序基础检索用向量相似度排序但向量匹配不等于语义最相关。Re-ranking 在检索结果上再做一轮精排效果显著提升。typescriptimport { CohereRerank } from langchain/cohere;const reranker new CohereRerank({model: rerank-v3.5,topN: 3,});// 先检索 top-10再用 Reranker 精排到 top-3const initialResults await vectorStore.similaritySearch(query, 10);const rerankedResults await reranker.compressDocuments(initialResults, query);▎2. 混合检索Hybrid Search单纯向量检索有时候搜不到精确匹配——比如搜ERR_CONNECTION_REFUSED这种错误码关键词匹配比语义匹配更准。混合检索 向量检索 关键词检索两路结果合并排序。typescript// Dify 中直接选「混合检索」模式即可// 代码层面示例const vectorResults await vectorStore.similaritySearch(query, 5);const keywordResults await bm25Search(query, documents, 5);// 用 Reciprocal Rank Fusion 合并排序const merged reciprocalRankFusion([vectorResults, keywordResults]);▎3. 查询改写Query Rewriting用户的问题往往不够精确。先让 LLM 改写查询再去检索命中率更高。typescriptconst rewritePrompt ChatPromptTemplate.fromTemplate(请将用户问题改写为更适合知识库检索的查询。- 去掉口语化表达- 提取关键术语- 如果问题模糊拆成 2-3 个子查询用户问题{question}改写后的查询每行一个);// 这个 Button 的 loading 咋用 → Button 组件 loading 属性 用法▎4. Self-RAG让 LLM 自己判断要不要检索不是每个问题都需要检索。你好不需要查知识库部署流程是什么才需要。typescriptconst needRetrieval await llm.invoke([{role: system,content: 判断以下问题是否需要查询知识库。回答 YES 或 NO。,},{ role: user, content: userQuestion },]);if (needRetrieval.content YES) {const docs await retriever.invoke(userQuestion);// 走 RAG 流程...} else {const answer await llm.invoke([...messages]);}RAG 效果评估RAG 搭完不是完事了——怎么知道效果好不好需要量化评估。▎核心指标指标衡量什么怎么算检索命中率检索到的文档是否包含答案正确文档在 top-K 中的比例回答准确率LLM 的回答是否正确人工标注 or LLM-as-Judge忠实度回答是否忠于检索到的资料检查答案是否有超出资料的内容回答延迟从提问到回答的时间P50 / P99 延迟▎用 LLM-as-Judge 自动评估typescriptconst evalPrompt ChatPromptTemplate.fromTemplate(你是 RAG 系统质量评估员。根据以下信息打分1-5 分用户问题{question}参考资料{context}系统回答{answer}标准答案{groundTruth}评分维度1. 回答准确性是否正确回答了问题2. 忠实度是否基于参考资料没有编造3. 完整性是否覆盖了关键信息输出 JSON{{accuracy: 1-5, faithfulness: 1-5, completeness: 1-5, explanation: ...}});避坑指南▎坑 1分块太大或太小❌ chunkSize: 2000 → 检索到一大段很多内容不相关浪费 token❌ chunkSize: 100 → 上下文太碎LLM 拼不出完整答案✅ chunkSize: 500 → 刚好一个完整的段落或知识点经验法则chunk 大小让一段包含一个完整的知识点。中文文档建议 300~500 字符英文 500~1000。▎坑 2没有 metadata 导致无法追溯检索到了答案但用户问这是哪个文档说的——你答不上来。typescript// ❌ 没有 metadatanew Document({ pageContent: Button 支持 primary 和 secondary 类型 });// ✅ 带上 metadatanew Document({pageContent: Button 支持 primary 和 secondary 类型,metadata: {source: component-guide.md,section: Button 组件,lastUpdated: 2025-03-01,category: 组件库,},});metadata 不仅用于追溯还能做过滤检索——比如只搜部署相关的文档。▎坑 3Embedding 模型和内容语言不匹配用中文文档Embedding 却用了纯英文模型——检索效果打折。场景推荐 Embedding 模型英文为主text-embedding-3-small中文为主text-embedding-3-small支持中文或BAAI/bge-m3多语言text-embedding-3-large追求性价比text-embedding-3-small维度 1536价格最低▎坑 4只用向量检索忽略关键词用户搜ERR_MODULE_NOT_FOUND 错误向量检索可能返回模块加载机制的文档——语义相关但不是用户要的。解法用混合检索。向量找语义相关的关键词找精确匹配的两路合并效果最好。▎坑 5文档更新了但向量库没更新知识库是活的——文档会修改、新增、删除。但向量库是拍快照的——索引建好后不会自动更新。解法▸增量索引文档更新时只重新索引变化的部分▸定时全量重建每天凌晨跑一次全量索引▸用 Dify 的文档管理内置了文档版本管理和自动重索引总结RAG 的核心思想不让 LLM 凭记忆回答而是先查资料再回答——就像开卷考试比闭卷考试更靠谱。搭一套 RAG 系统的关键步骤文档加载 分块——用RecursiveCharacterTextSplitterchunk 大小 300~500Embedding 向量存储——text-embedding-3-small性价比最高生产用 Chroma / Pinecone / pgvector检索 生成——createRetrievalChain自动串联检索和 LLM前端集成——AI SDKuseChat streaming体验最好进阶优化——Re-ranking、混合检索、查询改写效果提升显著两条路线选其一▸快速落地用 Dify 零代码搭 RAG前端调 API▸深度定制用 LangChain.js 全链路自建灵活度最高下一篇我们聊AI 应用的前端性能优化——流式渲染怎么做、Token 怎么省、缓存策略怎么设计。RAG 搭好了怎么让它跑得又快又省才是生产级的关键。推荐资源▸Dify 官方文档https://docs.dify.ai▸LangChain.js RAG 指南https://js.langchain.com/docs/tutorials/rag▸Vercel AI SDKhttps://sdk.vercel.ai/docs▸向量数据库选型指南https://github.com/erikbern/ann-benchmarks往期推荐Multi-Agent Teams让多个专家 Agent 像团队一样协作AI Agent 是怎么想一步做一步的拆解 ReAct 模式从零开始用 LangChain.js 构建你的第一个 Tool-Calling Agent最后点个在看支持我吧

相关新闻