一句话总结

在纯静态 Hexo 博客上零后端服务搭建 RAG 知识库问答系统:用 Embedding API 做向量化 + Cloudflare Worker 做代理 + IndexedDB 做缓存,实现用户自然语言提问即可检索 1500+ 篇博客内容并生成回答。


问题背景

我在做什么

我有一个基于 Hexo + Butterfly 主题的个人技术博客 kukudelin.top,已经积累了 1500+ 篇 Markdown 文档。我希望在博客页面上加一个问答窗口,让访客可以用自然语言提问,系统自动从所有博客文章中检索相关内容并给出回答——也就是给静态博客加一个 RAG(检索增强生成)能力。

遇到了什么问题

核心矛盾:Hexo 是一个纯静态博客框架,没有后端服务。传统的 RAG 方案需要起一个后端来做向量存储和检索,但我——

  1. 不想单独启动一个后端服务
  2. 不想维护数据库
  3. 不想花钱租服务器

我想让整个系统”无需部署任何服务”就能跑起来。


AI 分析过程

AI 的定位思路

整个对话是一条逐步探索的路径,AI 引导我完成了架构选型:

  1. 第一步:可行性分析 — AI 分析了项目结构,确认知识库问答在技术上完全可行
  2. 第二步:架构拍板 — 确定”前端本地向量搜索 + Cloudflare Worker 代理 LLM API”的架构,完全避免自建后端
  3. 第三步:建模选型 — Embedding 模型选了 Qwen/Qwen3-Embedding-4B,对话模型选了 deepseek-ai/DeepSeek-V4-Flash,都通过硅基流动 API 调用
  4. 第四步:分块策略 — 从固定字符分块改为按 Markdown 标题段落智能分段
  5. 第五步:CORS 地狱 — 从本地 localhost 到线上 kukudelin.top,CORS 一路踩坑
  6. 第六步:前端时序问题 — 15MB 向量文件加载 + IndexedDB 缓存 + Edge 浏览器兼容性

关键发现

  1. Cloudflare Worker 可以做 API 代理,隐藏 API Key,同时处理 CORS
  2. Embedding 向量可以预计算、打包成二进制文件,前端直接 fetch,不需要向量数据库
  3. IndexedDB 在 Edge 的跟踪防护下会被阻止,需要 fallback 到内存存储
  4. CSS 选择器名和 JS ID 不一致toggle vs trigger)会导致功能完全失效
  5. CORS 白名单不能只写 localhost,线上域名也需要加进去

涉及的技术概念

  • RAG (Retrieval-Augmented Generation) — 先检索相关文档,再把文档内容塞进 prompt 让 LLM 回答
  • Embedding 向量 — 把文本转换成高维向量(2560 维),语义相近的文本向量距离也相近
  • 余弦相似度 — 用两个向量的夹角余弦值衡量它们的相似程度
  • Cloudflare Worker — 在 CDN 边缘节点运行的 Serverless 函数,用于代理 API 请求
  • IndexedDB — 浏览器端的结构化存储,可以存大量向量数据避免重复下载
  • CORS (跨域资源共享) — 浏览器安全策略,Worker 需要返回正确的 Access-Control-Allow-Origin

AI 解决方案

最终方案

整体架构分四层:

1
2
3
4
用户提问 → 前端本地向量搜索(找相关文档)
→ Cloudflare Worker(代理 /api/chat,拼接 system prompt + 上下文)
→ 硅基流动 LLM API(生成回答)
→ 前端展示 + 参考来源

关键设计决策

  1. 向量不存数据库build_kb.js 脚本预计算所有文章的 embedding,pack_vectors.js 打包成 embeddings.bin(二进制)+ index.json(元数据),直接放在 source/kb/ 下,Hexo 构建时自动拷贝到 public/
  2. 前端本地搜索kb-chat.jspublic/kb/ 下载向量文件,用户提问时调用 Worker 的 /api/embed 获取问题向量,然后在前端用余弦相似度遍历所有文档向量找 Top 5
  3. Worker 只做代理 — 隐藏 API Key,转发 /api/chat/api/embed 请求到硅基流动

核心代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 前端本地向量搜索
function cosineSimilarity(a, b) {
let dot = 0, na = 0, nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
return dot / (Math.sqrt(na) * Math.sqrt(nb));
}

// 遍历所有预计算向量找 Top 5
const scored = index.map((item, i) => ({
...item,
score: cosineSimilarity(queryVec, getVector(i))
})).sort((a, b) => b.score - a.score).slice(0, 5);

为什么这样做能解决问题

核心思路是”计算前置,检索后置”

  • 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 必须精确匹配toggletrigger 一字之差功能全废
  • 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:4000kukudelin.top,避免被其他域名滥用
  • 速率限制:Worker 代码中配置了 rateLimit,防止恶意刷 API 导致费用暴涨
  • 无 DoS 风险:所有重计算(embedding)在构建时完成,Worker 只做轻量转发
  • 内容审核:硅基流动 API 自带内容审核,敏感内容会返回 500,已做好 fallback 跳过处理

延伸学习路线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Node.js 脚本(build_kb.js)

Embedding 向量原理(余弦相似度、KNN)

RAG 架构(检索 + 生成的 pipeline)

Cloudflare Workers(Serverless 边缘计算)

IndexedDB + 浏览器存储(前端缓存策略)

CORS 安全模型(跨域资源共享)

LLM API 调用与 Prompt Engineering

从 RAG → Agent 演进(工具调用、多轮推理)

面试视角

可能会怎么问

  • “你们项目的 RAG 是怎么实现的?” — 考察架构理解和选型理由
  • “为什么不用向量数据库?” — 考察工程权衡能力
  • “Embedding 模型怎么选的?” — 考察模型评估能力
  • “CORS 为什么会出问题?怎么解决的?” — 考察浏览器安全模型理解
  • “15MB 的向量文件前端加载不会慢吗?” — 考察性能优化意识
  • “API Key 怎么保护的?” — 考察安全意识

你可以怎么答

回答框架:先讲架构全景(离线 Embedding + 前端搜索 + Worker 代理),再解释为什么这样选(零成本、零运维、纯静态),最后补充踩过的坑(IndexedDB、CORS、大文件加载)

对于”为什么不加重向量数据库”这个问题,核心回答逻辑是:1500 篇文档 × 2560 维 = 约 15MB,浏览器遍历一遍不到 50ms,完全不需要额外数据库。量上去了(10 万+ 文档)才需要引入向量数据库。


我的收获

这次从零搭建博客 RAG 知识库问答,最大的收获不是技术本身,而是一种“离线计算 + 在线检索”的架构思维

以前总觉得 AI 功能 = 后端服务 + 数据库 + GPU,这次证明了即使是纯静态博客,也能通过巧妙的计算前移实现完整的 RAG 能力。

另外印象很深的是调试过程——CORS 白名单、CSS 类名匹配、IndexedDB 兼容性、PowerShell 语法差异……每一个都是踩一脚才知道的坑。技术的”最后一公里”往往不是算法,而是这些琐碎的工程量细节

最后,自己博客右下角出现一个能回答”我的 Docker 博客讲了什么”的聊天窗口时,那种满足感是无法替代的。