从零到一!前端搭建本地轻量化 RAG 问答系统
1. 引言大模型虽强但面对私有知识库、企业内部文档时往往存在「幻觉」和「知识过时」的问题。RAGRetrieval-Augmented Generation检索增强生成正是解决这一痛点的主流方案先检索出与问题相关的文档片段再交给大模型生成回答。传统 RAG 方案往往依赖后端服务、向量数据库和复杂的部署链路。而随着浏览器能力的增强前端完全可以在本地搭建一套轻量化的 RAG 问答系统——无需服务器、无需安装数据库数据不出浏览器隐私安全。本文将从零到一带你用纯前端技术栈Vite React Transformers.js localStorage搭建一套可运行的本地 RAG 问答系统。2. 技术选型与整体架构2.1 技术栈模块选型说明构建工具Vite极快的开发体验UI 框架React组件化开发向量化模型Transformers.js all-MiniLM-L6-v2纯浏览器端运行无需后端向量存储localStorage 自研余弦相似度检索轻量、零依赖生成模型可选本地模型 / 云端 API按需接入2.2 整体流程用户上传文档文本切分向量化 Embedding向量存入 localStorage用户提问问题向量化余弦相似度检索 Top-K拼接上下文 问题调用大模型生成回答3. 环境准备与项目初始化首先确保本机已安装 Node.js 18然后执行npmcreate vitelatest local-rag ----templatereact-tscdlocal-ragnpminstallnpminstallhuggingface/transformers安装完成后启动开发服务器npmrun dev4. 文本切分把长文档拆成小块RAG 检索的粒度很关键。整篇文档直接向量化检索精度差切得太碎又丢失上下文。这里实现一个简单的按段落 字符数控制的切分器。// src/utils/chunker.tsexportinterfaceChunk{id:string;text:string;source:string;}exportfunctionchunkText(text:string,source:string,maxLen500):Chunk[]{constparagraphstext.split(/\n\s*\n/);constchunks:Chunk[][];letbuffer;for(constparaofparagraphs){consttrimmedpara.trim();if(!trimmed)continue;if((buffer\ntrimmed).lengthmaxLenbuffer){chunks.push({id:crypto.randomUUID(),text:buffer.trim(),source,});buffertrimmed;}else{bufferbuffer?buffer\ntrimmed:trimmed;}}if(buffer.trim()){chunks.push({id:crypto.randomUUID(),text:buffer.trim(),source,});}returnchunks;}5. 向量化在浏览器中运行 Embedding 模型借助 Transformers.js我们可以在浏览器端直接加载并运行 MiniLM 模型把文本转成 384 维向量。// src/utils/embedder.tsimport{pipeline}fromhuggingface/transformers;letembedder:anynull;exportasyncfunctiongetEmbedder(){if(!embedder){embedderawaitpipeline(feature-extraction,Xenova/all-MiniLM-L6-v2);}returnembedder;}exportasyncfunctionembedTexts(texts:string[]):Promisenumber[][]{constmodelawaitgetEmbedder();constoutputsawaitmodel(texts,{pooling:mean,normalize:true});returnoutputs.tolist();}提示首次加载模型需要下载约 90MB 文件之后浏览器会缓存二次访问秒开。6. 向量存储与检索localStorage 余弦相似度6.1 存储层localStorage 无法直接存高维数组这里用 JSON 序列化存储并封装读写接口。// src/utils/store.tsimporttype{Chunk}from./chunker;interfaceStoredChunkextendsChunk{vector:number[];}constSTORAGE_KEYlocal-rag-chunks;exportfunctionsaveChunks(chunks:StoredChunk[]):void{localStorage.setItem(STORAGE_KEY,JSON.stringify(chunks));}exportfunctionloadChunks():StoredChunk[]{constrawlocalStorage.getItem(STORAGE_KEY);returnraw?JSON.parse(raw):[];}exportfunctionclearChunks():void{localStorage.removeItem(STORAGE_KEY);}6.2 余弦相似度检索// src/utils/search.tsimporttype{StoredChunk}from./store;functioncosineSimilarity(a:number[],b:number[]):number{letdot0,normA0,normB0;for(leti0;ia.length;i){dota[i]*b[i];normAa[i]*a[i];normBb[i]*b[i];}returndot/(Math.sqrt(normA)*Math.sqrt(normB)1e-8);}exportfunctionsearchChunks(queryVector:number[],chunks:StoredChunk[],topK3):StoredChunk[]{returnchunks.map((chunk)({chunk,score:cosineSimilarity(queryVector,chunk.vector),})).sort((a,b)b.score-a.score).slice(0,topK).map((item)item.chunk);}7. 组装问答把检索结果交给大模型检索到 Top-K 相关片段后把它们拼进 Prompt再调用大模型生成回答。这里以 OpenAI 兼容接口为例你也可以替换为本地 Ollama 或其他模型。// src/utils/qa.tsimport{searchChunks}from./search;import{loadChunks}from./store;exportasyncfunctionaskQuestion(question:string,queryVector:number[],apiKey:string,baseUrlhttps://api.openai.com/v1):Promisestring{constchunksloadChunks();consttopChunkssearchChunks(queryVector,chunks,3);constcontexttopChunks.map((c,i)【片段${i1}】\n${c.text}).join(\n\n);constprompt请基于以下资料回答问题。如果资料中没有相关信息请直接说明资料中未找到相关内容。 资料${context}问题${question}回答;constrespawaitfetch(${baseUrl}/chat/completions,{method:POST,headers:{Content-Type:application/json,Authorization:Bearer${apiKey},},body:JSON.stringify({model:gpt-4o-mini,messages:[{role:user,content:prompt}],temperature:0.3,}),});constdataawaitresp.json();returndata.choices[0].message.content;}8. 前端界面串联完整流程最后用 React 把以上模块串起来实现「上传文档 → 建立索引 → 提问 → 得到回答」的完整交互。// src/App.tsx import { useState } from react; import { chunkText } from ./utils/chunker; import { embedTexts } from ./utils/embedder; import { saveChunks, loadChunks, clearChunks } from ./utils/store; import { askQuestion } from ./utils/qa; export default function App() { const [docText, setDocText] useState(); const [question, setQuestion] useState(); const [answer, setAnswer] useState(); const [loading, setLoading] useState(false); const [indexed, setIndexed] useState(false); const handleIndex async () { setLoading(true); try { const chunks chunkText(docText, 本地文档); const vectors await embedTexts(chunks.map((c) c.text)); saveChunks(chunks.map((c, i) ({ ...c, vector: vectors[i] }))); setIndexed(true); alert(索引完成共 ${chunks.length} 个片段); } finally { setLoading(false); } }; const handleAsk async () { if (!question.trim()) return; setLoading(true); try { const [queryVector] await embedTexts([question]); const apiKey prompt(请输入 API Key本地运行仅存于内存) || ; const res await askQuestion(question, queryVector, apiKey); setAnswer(res); } finally { setLoading(false); } }; return ( div style{{ maxWidth: 720, margin: 0 auto, padding: 24 }} h1本地轻量化 RAG 问答/h1 section h21. 上传文档/h2 textarea value{docText} onChange{(e) setDocText(e.target.value)} rows{8} style{{ width: 100% }} placeholder粘贴你的文档内容... / button onClick{handleIndex} disabled{loading || !docText.trim()} {loading ? 处理中... : 建立索引} /button {indexed ( button onClick{() { clearChunks(); setIndexed(false); }} 清空索引 /button )} /section section h22. 提问/h2 input value{question} onChange{(e) setQuestion(e.target.value)} style{{ width: 100%, padding: 8 }} placeholder输入你的问题... / button onClick{handleAsk} disabled{loading || !indexed} 提问 /button /section {answer ( section h2回答/h2 div style{{ whiteSpace: pre-wrap, background: #f5f5f5, padding: 16, borderRadius: 8 }} {answer} /div /section )} /div ); }9. 效果演示与运行在项目根目录执行npm run dev浏览器打开http://localhost:5173粘贴一段产品文档或技术资料点击「建立索引」等待模型加载与向量化首次约 10~30 秒输入问题点击「提问」系统检索相关片段并调用大模型生成回答。10. 总结与优化方向本文实现了一套完全运行在浏览器端的轻量化 RAG 系统核心亮点零后端向量化、存储、检索全部在前端完成隐私安全文档数据不离开本地浏览器轻量依赖仅需 Transformers.js 一个核心库。后续可优化方向接入 Web Worker避免向量化阻塞 UI 线程使用 IndexedDB 替代 localStorage突破 5MB 存储上限引入重排序Rerank模型提升检索精度支持 PDF、Word 等格式解析接入本地 Ollama 模型实现完全离线运行。希望这篇文章能帮你快速上手前端 RAG 开发动手试试吧

相关新闻

事务 Outbox 与幂等键:跨过“提交后崩溃”的缝隙

事务 Outbox 与幂等键:跨过“提交后崩溃”的缝隙

复用 replay.py 的恢复结论 上一篇产出的 replay.py 会从 EventStore.load 恢复 ReplayResult,并明确让 pending_commands 永远不从重放结果派发命令。这留下一个必须回答的问题:真正待执行的命令存在哪里?本篇以 replay(...).last_seq 作为乐…

2026/11/6 12:37:29 阅读更多 →
AI充电桩管理系统:智能运维与轻量化开发指南-充电桩源码解析

AI充电桩管理系统:智能运维与轻量化开发指南-充电桩源码解析

博主介绍: 所有项目都配有从入门到精通的安装教程,可二开,提供核心代码讲解,项目指导。 项目配有对应开发文档、解析等 项目都录了发布和功能操作演示视频;项目的界面和功能都可以定制,包安装运行&#xff…

2026/11/1 15:11:48 阅读更多 →
如何用AtlasOS优化Windows系统:解决卡顿、隐私泄露的终极方案

如何用AtlasOS优化Windows系统:解决卡顿、隐私泄露的终极方案

如何用AtlasOS优化Windows系统:解决卡顿、隐私泄露的终极方案 【免费下载链接】Atlas 🚀 An open and lightweight modification to Windows, designed to optimize performance, privacy and usability. 项目地址: https://gitcode.com/GitHub_Trendi…

2026/11/3 19:38:09 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/5 12:28:00 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/6 9:29:27 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/5 12:28:01 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/5 12:28:02 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/6 8:08:42 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/5 19:38:59 阅读更多 →

日新闻

周新闻

月新闻