从零搭建博客RAG知识库问答——基于Embedding语义检索的实践
一句话总结
在纯静态 Hexo 博客上零后端服务搭建 RAG 知识库问答系统:用 Embedding API 做向量化 + Cloudflare Worker 做代理 + IndexedDB 做缓存,实现用户自然语言提问即可检索 1500+ 篇博客内容并生成回答。
问题背景
我在做什么
我有一个基于 Hexo + Butterfly 主题的个人技术博客 kukudelin.top,已经积累了 1500+ 篇 Markdown 文档。我希望在博客页面上加一个问答窗口,让访客可以用自然语言提问,系统自动从所有博客文章中检索相关内容并给出回答——也就是给静态博客加一个 RAG(检索增强生成)能力。
遇到了什么问题
核心矛盾:Hexo 是一个纯静态博客框架,没有后端服务。传统的 RAG 方案需要起一个后端来做向量存储和检索,但我——
- 不想单独启动一个后端服务
- 不想维护数据库
- 不想花钱租服务器
我想让整个系统”无需部署任何服务”就能跑起来。
AI 分析过程
AI 的定位思路
整个对话是一条逐步探索的路径,AI 引导我完成了架构选型:
- 第一步:可行性分析 — AI 分析了项目结构,确认知识库问答在技术上完全可行
- 第二步:架构拍板 — 确定”前端本地向量搜索 + Cloudflare Worker 代理 LLM API”的架构,完全避免自建后端
- 第三步:建模选型 — Embedding 模型选了
Qwen/Qwen3-Embedding-4B,对话模型选了deepseek-ai/DeepSeek-V4-Flash,都通过硅基流动 API 调用 - 第四步:分块策略 — 从固定字符分块改为按 Markdown 标题段落智能分段
- 第五步:CORS 地狱 — 从本地 localhost 到线上 kukudelin.top,CORS 一路踩坑
- 第六步:前端时序问题 — 15MB 向量文件加载 + IndexedDB 缓存 + Edge 浏览器兼容性
关键发现
- Cloudflare Worker 可以做 API 代理,隐藏 API Key,同时处理 CORS
- Embedding 向量可以预计算、打包成二进制文件,前端直接 fetch,不需要向量数据库
- IndexedDB 在 Edge 的跟踪防护下会被阻止,需要 fallback 到内存存储
- CSS 选择器名和 JS ID 不一致(
togglevstrigger)会导致功能完全失效 - CORS 白名单不能只写
localhost,线上域名也需要加进去
涉及的技术概念
- RAG (Retrieval-Augmented Generation) — 先检索相关文档,再把文档内容塞进 prompt 让 LLM 回答
- Embedding 向量 — 把文本转换成高维向量(2560 维),语义相近的文本向量距离也相近
- 余弦相似度 — 用两个向量的夹角余弦值衡量它们的相似程度
- Cloudflare Worker — 在 CDN 边缘节点运行的 Serverless 函数,用于代理 API 请求
- IndexedDB — 浏览器端的结构化存储,可以存大量向量数据避免重复下载
- CORS (跨域资源共享) — 浏览器安全策略,Worker 需要返回正确的
Access-Control-Allow-Origin头
AI 解决方案
最终方案
整体架构分四层:
1 | 用户提问 → 前端本地向量搜索(找相关文档) |
关键设计决策:
- 向量不存数据库 —
build_kb.js脚本预计算所有文章的 embedding,pack_vectors.js打包成embeddings.bin(二进制)+index.json(元数据),直接放在source/kb/下,Hexo 构建时自动拷贝到public/ - 前端本地搜索 —
kb-chat.js从public/kb/下载向量文件,用户提问时调用 Worker 的/api/embed获取问题向量,然后在前端用余弦相似度遍历所有文档向量找 Top 5 - Worker 只做代理 — 隐藏 API Key,转发
/api/chat和/api/embed请求到硅基流动
核心代码片段
1 | // 前端本地向量搜索 |
为什么这样做能解决问题
核心思路是”计算前置,检索后置”:
- Embedding 计算是最耗资源的步骤(每篇文章需要调一次 API),但它是离线的——可以在构建时一次性完成
- 检索是在线的,但只需要做向量点积运算,前端 JavaScript 完全能胜任 1500 × 2560 维的计算(约几十毫秒)
- LLM 调用通过 Worker 代理,前端无需知道 API Key
这套架构的本质是把”重计算”从在线挪到离线,把”轻计算”留在前端。
本次知识萃取
显性知识
- Embedding 模型选型:中文场景优先选 Qwen 系列,
Qwen3-Embedding-4B输出 2560 维,比BAAI/bge-large-zh-v1.5的 1024 维语义更丰富 - Markdown 分块策略:不要固定字符数切分,应该按
##标题段落自然分段,保持语义完整性 - Cloudflare Worker 部署:
npx wrangler deploy一键部署,配置写在wrangler.toml - CORS 三件套:
Access-Control-Allow-Origin+Access-Control-Allow-Methods+Access-Control-Allow-Headers,OPTIONS 预检要单独处理
隐性知识
- Edge 跟踪防护会静默阻止 IndexedDB,没有任何可见报错,只能通过
try-catch发现并 fallback fetch大文件(15MB)超时后 Promise 可能永远 pending,必须加AbortController- CSS 类名和 JS ID 必须精确匹配,
toggle和trigger一字之差功能全废 - PowerShell 不支持
&&,构建脚本要用;串联命令 - npm 全局安装可能触发 EPERM,需要管理员权限或换用
npx - 硅基流动 API 的 401 和 500 错误要区分处理:401 是 token 问题,500 可能是内容审核不通过
关联知识
- RAG → LangChain / LlamaIndex — 成熟的 RAG 框架,本项目是它们的”极简实现”
- 前端向量搜索 → Transformers.js — 可以在浏览器里直接跑模型,但本项目文件太大不适用
- Cloudflare Worker → Cloudflare Pages / Vercel Edge Functions — 同类 Serverless 平台
- Embedding → 向量数据库 (Milvus / Pinecone / Qdrant) — 工业级方案,本项目用纯文件替代
安全思考
- API Key 泄露风险:Key 存在 Worker 环境变量中,前端不可见。但 Worker 代码本身在 Cloudflare 控制台可见,需要确保控制台权限安全
- CORS 白名单:已经配置了
localhost:4000和kukudelin.top,避免被其他域名滥用 - 速率限制:Worker 代码中配置了
rateLimit,防止恶意刷 API 导致费用暴涨 - 无 DoS 风险:所有重计算(embedding)在构建时完成,Worker 只做轻量转发
- 内容审核:硅基流动 API 自带内容审核,敏感内容会返回 500,已做好 fallback 跳过处理
延伸学习路线
1 | Node.js 脚本(build_kb.js) |
面试视角
可能会怎么问
- “你们项目的 RAG 是怎么实现的?” — 考察架构理解和选型理由
- “为什么不用向量数据库?” — 考察工程权衡能力
- “Embedding 模型怎么选的?” — 考察模型评估能力
- “CORS 为什么会出问题?怎么解决的?” — 考察浏览器安全模型理解
- “15MB 的向量文件前端加载不会慢吗?” — 考察性能优化意识
- “API Key 怎么保护的?” — 考察安全意识
你可以怎么答
回答框架:先讲架构全景(离线 Embedding + 前端搜索 + Worker 代理),再解释为什么这样选(零成本、零运维、纯静态),最后补充踩过的坑(IndexedDB、CORS、大文件加载)。
对于”为什么不加重向量数据库”这个问题,核心回答逻辑是:1500 篇文档 × 2560 维 = 约 15MB,浏览器遍历一遍不到 50ms,完全不需要额外数据库。量上去了(10 万+ 文档)才需要引入向量数据库。
我的收获
这次从零搭建博客 RAG 知识库问答,最大的收获不是技术本身,而是一种“离线计算 + 在线检索”的架构思维。
以前总觉得 AI 功能 = 后端服务 + 数据库 + GPU,这次证明了即使是纯静态博客,也能通过巧妙的计算前移实现完整的 RAG 能力。
另外印象很深的是调试过程——CORS 白名单、CSS 类名匹配、IndexedDB 兼容性、PowerShell 语法差异……每一个都是踩一脚才知道的坑。技术的”最后一公里”往往不是算法,而是这些琐碎的工程量细节。
最后,自己博客右下角出现一个能回答”我的 Docker 博客讲了什么”的聊天窗口时,那种满足感是无法替代的。

