第34期 | RAG前端实现
第34期 | RAG前端实现 今天你将学会理解 RAG检索增强生成的完整流程——不只是概念是前端要实现什么实现知识库管理界面上传文档 → 分片 → embedding → 存储实现向量搜索交互用户输入 → 搜索相关文档 → 展示结果 引用来源理解前端在 RAG 系统中的角色——不是调用 API而是管理整个数据流 核心知识RAG 是什么为什么需要它LLM 有一个致命缺陷幻觉——它会编造不存在的事实自信满满地给你错误答案。RAG 解决这个问题让 AI 在回答前先从真实文档库中搜索相关内容基于真实数据回答而不是凭记忆编造。没有 RAG 的回答“React useEffect 的 cleanup 在组件重新渲染前执行。” ← 这句话是编造的实际是组件卸载和下次 effect 执行前。有 RAG 的回答“根据 React 官方文档useEffect 的 cleanup 函数在组件卸载时和下次 effect 执行前运行。” ← 基于真实文档附引用来源。RAG 的完整流程前端视角1. 知识库构建阶段离线/管理界面 上传文档 → 文档分片(chunking) → 生成 embedding → 存入向量数据库 2. 查询阶段在线/用户交互 用户提问 → 问题生成 embedding → 向量搜索找最相关文档片段 → 构建 Promptsystem 检索到的文档 问题→ LLM 生成回答 → 前端展示回答 引用来源前端在 RAG 系统中的职责环节前端做什么后端做什么文档上传上传界面 进度显示 文档列表管理接收文件 → 分片 → embedding → 存储向量搜索搜索输入 结果展示 引用标注问题 embedding → 向量搜索 → 返回结果LLM 回答聊天界面 Markdown渲染 来源链接RAG Prompt → LLM → 流式返回知识库管理界面核心组件KnowledgeBase知识库管理页面 ├── DocumentUpload文档上传组件 │ ├── DropZone拖拽上传区域 │ └── UploadProgress上传 处理进度 ├── DocumentList文档列表 │ ├── DocumentCard单文档卡片名称/状态/操作 │ └── ChunkPreview文档分片预览 └── SearchBar知识库内搜索DocumentUpload 组件// features/knowledge/components/DocumentUpload.tsx import { useState, useCallback } from react; import { Upload, FileText, Loader2 } from lucide-react; interface DocumentUploadProps { onUpload: (files: File[]) Promisevoid; } interface UploadStatus { fileName: string; status: uploading | processing | completed | error; progress: number; error?: string; } export function DocumentUpload({ onUpload }: DocumentUploadProps) { const [uploadStatuses, setUploadStatuses] useStateUploadStatus[]([]); const [isDragging, setIsDragging] useState(false); const handleFiles async (files: File[]) { // 支持的文件类型 const validTypes [text/plain, text/markdown, application/pdf]; const validFiles files.filter(f validTypes.includes(f.type) || f.name.endsWith(.md)); if (validFiles.length 0) { alert(请上传 Markdown、PDF 或纯文本文件); return; } // 初始化状态 const statuses: UploadStatus[] validFiles.map(f ({ fileName: f.name, status: uploading, progress: 0, })); setUploadStatuses(statuses); try { // 逐个上传 for (let i 0; i validFiles.length; i) { // 上传阶段 setUploadStatuses(prev prev.map((s, j) j i ? { ...s, status: uploading, progress: 30 } : s )); // 等后端处理分片 embedding await new Promise(resolve setTimeout(resolve, 500)); // 模拟 setUploadStatuses(prev prev.map((s, j) j i ? { ...s, status: processing, progress: 60 } : s )); // 等 embedding 完成 await new Promise(resolve setTimeout(resolve, 1000)); // 模拟 setUploadStatuses(prev prev.map((s, j) j i ? { ...s, status: completed, progress: 100 } : s )); } await onUpload(validFiles); } catch (error) { setUploadStatuses(prev prev.map(s s.status ! completed ? { ...s, status: error, error: 上传失败 } : s )); } }; return ( div {/* 拖拽上传区域 */} div onDragOver{(e) { e.preventDefault(); setIsDragging(true); }} onDragLeave{() setIsDragging(false)} onDrop{(e) { e.preventDefault(); setIsDragging(false); handleFiles([...e.dataTransfer.files]); }} className{border-2 rounded-lg p-8 text-center cursor-pointer transition-colors ${isDragging ? border-blue-500 bg-blue-50 : border-gray-200 hover:border-gray-300}} onClick{() { const input document.createElement(input); input.type file; input.multiple true; input.accept .md,.txt,.pdf; input.onchange (e) handleFiles([...(e.target as HTMLInputElement).files!]); input.click(); }} Upload size{32} classNamemx-auto mb-2 text-gray-400 / p classNametext-gray-500拖拽文件到此处或点击上传/p p classNametext-xs text-gray-400 mt-1支持 Markdown、PDF、纯文本/p /div {/* 上传进度 */} {uploadStatuses.length 0 ( div classNamemt-4 space-y-2 {uploadStatuses.map((status) ( div key{status.fileName} classNameflex items-center gap-2 text-sm FileText size{16} / span classNameflex-1{status.fileName}/span {status.status uploading ( span classNametext-blue-500上传中 {status.progress}%/span )} {status.status processing ( span classNametext-yellow-500处理中.../span )} {status.status completed ( span classNametext-green-500✅ 完成/span )} {status.status error ( span classNametext-red-500❌ {status.error}/span )} {(status.status uploading || status.status processing) ( Loader2 size{16} classNameanimate-spin text-gray-400 / )} /div ))} /div )} /div ); }向量搜索交互界面用户在聊天时提问 → 后端同时做向量搜索 → 返回回答 引用来源。核心在聊天界面中展示引用来源// features/chat/components/SourceReference.tsx interface SourceReference { id: string; title: string; content: string; // 引用的文档片段 source: string; // 文档来源文件名/URL relevance: number; // 相关度分数 (0-1) } interface SourceReferenceProps { sources: SourceReference[]; } export function SourceReference({ sources }: SourceReferenceProps) { const [expanded, setExpanded] useState(false); if (sources.length 0) return null; return ( div classNamemt-3 border-t border-gray-100 pt-2 button onClick{() setExpanded(!expanded)} classNametext-xs text-blue-500 hover:underline flex items-center gap-1 BookOpen size{12} / {expanded ? 收起引用来源 : 查看 ${sources.length} 个引用来源} /button {expanded ( div classNamemt-2 space-y-2 {sources.map((source, idx) ( div key{source.id} classNamerounded border border-gray-200 p-3 text-xs dark:border-gray-600 div classNameflex items-center justify-between mb-1 span classNamefont-medium text-blue-600{source.title}/span span classNametext-gray-400 相关度: {Math.round(source.relevance * 100)}% /span /div p classNametext-gray-600 line-clamp-3{source.content}/p span classNametext-gray-400 mt-1 block来源: {source.source}/span /div ))} /div )} /div ); }RAG 聊天的后端接口设计前端需要一个新的 API 接口同时返回 LLM 回答 引用来源// app/api/ai/rag-chat/route.tsimportOpenAIfromopenai;exportasyncfunctionPOST(req:NextRequest){const{message,messages}awaitreq.json();// 1. 向量搜索找到最相关的文档片段constsearchResultsawaitsearchKnowledgeBase(message,{topK:3});// 2. 构建 RAG PromptconstsystemPromptbuildRAGPrompt(searchResults);// 3. 流式调用 LLMconststreamawaitopenai.chat.completions.create({model:gpt-4o-mini,messages:[{role:system,content:systemPrompt},...messages,{role:user,content:message},],stream:true,});// 4. 流式返回回答 最后附带引用来源// SSE 流中两种数据// data: { type: content, content: ... } — AI 回答内容// data: { type: sources, sources: [...] } — 引用来源最后一条}functionbuildRAGPrompt(searchResults:SearchResult[]):string{return你是一个技术助手。回答问题时请基于以下参考资料。如果参考资料中没有相关信息请说明根据现有文档没有找到相关信息。 参考资料${searchResults.map((r,i)[${i1}]${r.title}\n${r.content}).join(\n\n)}回答时请标注引用来源格式[1] [2] 等。;}前端处理 RAG 流式响应RAG 的 SSE 流有两种数据类型content sources前端需要区分处理// 解析 RAG SSE 流asyncfunctionhandleRAGStream(stream:ReadableStreamstring){letfullContent;letsources:SourceReference[][];constreaderstream.getReader();while(true){const{done,value}awaitreader.read();if(done)break;// RAG SSE 可能包含两种类型的数据try{constparsedJSON.parse(value);if(parsed.typecontent){fullContentparsed.content;// 更新 UI追加文字到消息updateLastAssistantMessage(fullContent);}elseif(parsed.typesources){sourcesparsed.sources;// 更新 UI显示引用来源updateSourceReferences(sources);}}catch{// 无法解析的行跳过}}return{content:fullContent,sources};}知识库的状态管理// features/knowledge/store/knowledgeStore.tsinterfaceKnowledgeState{documents:Document[];searchQuery:string;searchResults:SearchResult[];isSearching:boolean;isUploading:boolean;fetchDocuments:()Promisevoid;uploadDocument:(file:File)Promisevoid;deleteDocument:(id:string)Promisevoid;search:(query:string)Promisevoid;}常见误区误区1RAG 只需要后端实现前端需要管理文档上传、展示搜索结果、标注引用来源——这些是用户体验的关键部分。误区2所有文档都直接丢给 LLM文档太大 → token 超限。必须分片chunking只搜索最相关的片段传给 LLM。误区3引用来源不重要没有引用来源 → 用户无法验证 AI 回答的可靠性 → RAG 的核心价值就没了。 AI协作实战实战场景设计知识库管理界面的完整交互我给 AI 的 prompt设计一个技术文档知识库管理界面的完整交互流程 1. 文档上传区支持拖拽上传 点击上传支持的格式md/pdf/txt上传后显示处理进度 2. 文档列表每个文档卡片显示名称、状态、分片数量、上传时间支持搜索和删除 3. 分片预览点击文档可以看到它的分片列表每个分片是一段 500 字左右的文本 4. 搜索测试在知识库中搜索一个关键词看到最相关的分片和相似度分数 用 React TypeScript Tailwind shadcn/ui 风格。 每个组件给出 Props 类型定义和核心渲染逻辑。AI 输出的核心组件代码经过审查修改后✅ 上传区拖拽 点击进度条完整✅ 文档列表带搜索、状态标签、删除按钮❌ 分片预览太简单——追加要求分片内容可编辑用户可以微调分片内容以提高搜索精度✅ 搜索测试输入关键词 → 显示相关分片 相似度分数 来源文档名学到了什么AI 生成的知识库界面基本完整但分片编辑功能是我追加的——因为实际使用中自动分片可能切得不理想用户需要微调。 动手练习练习1简单实现文档上传界面用 DocumentUpload 组件实现拖拽上传 进度显示。先不连后端用 setTimeout 模拟上传进度。练习2中等实现知识库搜索 引用展示在前端实现搜索输入框 → 调用搜索 API → 展示搜索结果分片内容 相似度分数在聊天消息中展示 SourceReference引用来源面板练习3挑战实现完整的 RAG 聊天组合所有组件聊天界面 RAG 搜索 引用来源用户提问 → 后端同时做向量搜索 LLM 回答前端流式渲染回答 最后展示引用来源引用来源可展开/收起显示相关度分数和原始文档片段 本期要点RAG 解决幻觉让 AI 基于真实文档回答而不是凭记忆编造前端职责文档上传/管理界面、向量搜索交互、引用来源展示知识库管理界面拖拽上传 进度显示 文档列表 分片预览 搜索测试引用来源是 RAG 的灵魂没有引用来源 → 用户无法验证 → RAG 的核心价值没了RAG SSE 流有两种数据contentAI回答 sources引用来源前端要区分处理 下期预告下一期进入 AI Agent 前端交互——工具调用展示、思考过程可视化、多轮对话。你将让 AI 不仅会「说话」还能「做事」——调用工具、执行操作并把整个过程可视化展示给用户。如果你没有苹果电脑需要上传ios到APPStore可以访问以下网站iPA上传工具 - IPA解析与AppStore提交

相关新闻

全网最强 Gin 教程 | 认识 Gin 框架

全网最强 Gin 教程 | 认识 Gin 框架

认识gin 框架是一系列工具的集合,能让开发变的便捷。 学习框架的目的就是为了提供项目的开发效率,使我们更加专注业务,而不是和业务无关的底层代码。 1. go流行的web框架 如果学习过其他语言,可能知道Java用的比较多的是Spring框架…

2026/6/27 4:04:25阅读更多 →
把 Agent 的 “Loop Engineering“一次性讲透

把 Agent 的 “Loop Engineering“一次性讲透

我之前写过一个项目:how-ai-agents-remember——逆向工程 5 个开源 Bot 的记忆系统,源码级拆解每一条数据流。 于是我顺着同一条线往下挖:Agent 怎么记住事情搞清楚了,那它怎么持续推进任务呢? 这就是第二个项目&…

2026/6/27 3:59:25阅读更多 →
2026年Esri用户大会将聚焦利用GIS创造更智能的世界

2026年Esri用户大会将聚焦利用GIS创造更智能的世界

预计将有超过18,000名与会者参加在圣迭戈举行的全球最大GIS会议 • 2026年Esri用户大会将于7月13日至17日在加州圣迭戈举行。 • 今年大会的主题是“GIS——创造更智能的世界”。 • Esri总裁兼创始人Jack Dangermond与Tompkins Conservation总裁兼联合创始人Kristine Tompkins…

2026/6/27 3:59:25阅读更多 →
Spring Boot 实现 HTML 转 PDF

Spring Boot 实现 HTML 转 PDF

一、接口概述在实际业务开发中,我们经常遇到将 HTML 内容转换为 PDF 文件的需求,比如生成合同、报告、证书等文档。本文介绍一个基于 Spring Boot 实现的 HTML 转 PDF 接口,支持动态渲HTML 并生成 PDF 文件供用户下载。二、接口定义RequestMa…

2026/6/27 5:34:31阅读更多 →
亚马逊关键词不会挖?从词库搭建到广告投放,一篇讲透

亚马逊关键词不会挖?从词库搭建到广告投放,一篇讲透

做亚马逊运营,很多卖家一开始都会把精力放在选品、图片、价格和广告预算上。但真正跑起来之后才发现:Listing 写得好不好,广告投得准不准,流量能不能持续进来,背后都离不开一个基础动作——关键词研究。关键词找得不准…

2026/6/27 5:34:31阅读更多 →
AI率太高怎么降?10款降AI率软件实测(含免费降ai率工具)真实避坑指南

AI率太高怎么降?10款降AI率软件实测(含免费降ai率工具)真实避坑指南

最近这半年,我敢说,被“论文降aigc”折磨的同学,绝对比被查重折磨的还多。 说实话,现在这情况太难了。 你是不是也一样?有时候,你就是用AI帮你润色个摘要和前言,没写几个字,都可能被…

2026/6/27 5:34:31阅读更多 →
AI 智能体的身份与权限挑战Uber和Auth0如何重新思考访问控制

AI 智能体的身份与权限挑战Uber和Auth0如何重新思考访问控制

最近,Uber 描述了一种用于在多智能体 AI 工作流中传播智能体身份的内部架构。该设计的目标是,在智能体委派任务并调用内部工具时,能够保留原始的用户上下文、智能体的来源信息以及限定范围的访问权限。Uber 的案例研究印证了 Auth0 的观点&am…

2026/6/27 5:34:31阅读更多 →
CyberSources:一个网络安全人的工具箱

CyberSources:一个网络安全人的工具箱

文章目录CyberSources:一个网络安全人的工具箱覆盖了哪些方向学习资源也有和其他资源列表有什么不同适合谁CyberSources:一个网络安全人的工具箱 做网络安全这行,工具散落各处是个老问题。GitHub 上搜一个,论坛里翻一个&#xff…

2026/6/27 5:34:31阅读更多 →
资阳黄金白银回收铂金旧金回收无套路门店 TOP 榜单 实地测评资料整理

资阳黄金白银回收铂金旧金回收无套路门店 TOP 榜单 实地测评资料整理

资阳街头巷尾的黄金白银回收门店鳞次栉比,看似选择众多实则鱼龙混杂,不少市民在变现旧金时遭遇压价、虚报成色等套路。为帮大家甄选靠谱渠道,小编实地走访多家门店,逐一核验资质与口碑,整理出这份正规回收门店清单。收…

2026/6/27 5:29:31阅读更多 →
【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体 一文搞定到底什么是智能体【人工智能】一文搞定到底什么是智能体一. LM,WorkFlow,Agent分别有什么么不同二. Agent的思考过程是怎样的三. Agent的五个核心部分1)LLM2)Prompt3)Me…

2026/6/26 11:03:22阅读更多 →
嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

1. 嵌入式GUI控件:从原理到实战的深度解析在嵌入式系统开发中,图形用户界面(GUI)的设计与实现往往是项目从“能用”到“好用”的关键一跃。不同于资源充沛的PC或移动平台,嵌入式设备的GUI需要在有限的CPU性能、内存空间…

2026/6/26 4:15:25阅读更多 →
Google AI Studio 300美元额度的真相与实战指南

Google AI Studio 300美元额度的真相与实战指南

1. 这300美金不是“送钱”,而是Google埋下的第一道技术门槛 你看到标题里那个醒目的“$300美金”时,第一反应可能是:又一个免费额度?领完就完事?我亲手试过——这300美金根本不是红包,而是一张入场券&…

2026/6/26 9:29:01阅读更多 →
10分钟AI语音克隆与实时变声:Retrieval-based-Voice-Conversion-WebUI完整指南

10分钟AI语音克隆与实时变声:Retrieval-based-Voice-Conversion-WebUI完整指南

10分钟AI语音克隆与实时变声&#xff1a;Retrieval-based-Voice-Conversion-WebUI完整指南 【免费下载链接】Retrieval-based-Voice-Conversion-WebUI Easily train a good VC model with voice data < 10 mins! 项目地址: https://gitcode.com/GitHub_Trending/re/Retrie…

2026/6/27 0:04:03阅读更多 →
Layerdivider:3分钟AI智能分层,彻底告别手动抠图时代

Layerdivider:3分钟AI智能分层,彻底告别手动抠图时代

Layerdivider&#xff1a;3分钟AI智能分层&#xff0c;彻底告别手动抠图时代 【免费下载链接】layerdivider A tool to divide a single illustration into a layered structure. 项目地址: https://gitcode.com/gh_mirrors/la/layerdivider 还在为复杂的图像分层工作烦…

2026/6/27 0:04:03阅读更多 →
Tomcat中X-Frame-Options配置实战:防御点击劫持的四种方法与最佳实践

Tomcat中X-Frame-Options配置实战:防御点击劫持的四种方法与最佳实践

1. 项目概述&#xff1a;为什么X-Frame-Options是Web安全的“防盗门”&#xff1f;最近在排查一个老项目的安全审计报告时&#xff0c;又被提到了“点击劫持”风险&#xff0c;矛头直指缺失的X-Frame-Options响应头。这已经不是第一次了&#xff0c;很多开发团队&#xff0c;尤…

2026/6/27 0:04:03阅读更多 →