BetterLink Logo 比邻
切换语言
切换主题

AI知识库20分钟搭完?Workers AI + Vectorize手把手教你做RAG(附完整代码)

Workers AI 和 Vectorize 构建 RAG 应用架构示意图

引言

想给公司做个智能客服,查了一圈 RAG 教程,要么讲理论云里雾里,要么让你先租个 GPU、搭环境,看着就头大。说实话,我刚开始研究这块的时候也一样,光是配置 LangChain 和向量数据库就折腾了两天,还没跑起来。 后来发现 Cloudflare 推出了一整套 AI 工具,Workers AI + Vectorize + D1,全托管的,免费额度还挺大方。我试着用它们搭了个笔记问答应用,从零到能用真的不到 20 分钟,代码也就百来行。 这篇文章会手把手带你走一遍完整流程:

  • 先讲清楚 RAG 到底是什么(不扔术语,用大白话)
  • 实战搭建一个能跑的知识库问答应用(有完整代码)
  • 优化技巧让检索更准确、成本更低
  • 部署上线真正用起来 你只需要懂点 JavaScript,有个 Cloudflare 账号(免费),跟着操作就能做出来。

RAG 是个啥?5 分钟看懂工作原理

用考试来理解 RAG

先说个直观的比喻。你考试的时候,闭卷考试只能靠脑子里记的东西答题,万一忘了就瞎编。开卷考试就不一样了,不确定的时候翻书查资料,答案准确多了。 RAG(检索增强生成)就是给 AI 开卷考试的权限。 传统 LLM 就像闭卷考试,只能基于训练时见过的数据回答。问题是:

  • 训练数据有时效性,不知道最新动态
  • 没见过你公司的内部文档
  • 记不住所有细节,容易瞎编(行话叫”幻觉”) RAG 的做法是:先从你准备好的知识库里找相关资料,然后把资料给 AI,让它基于这些内容回答。这样答案既靠谱又能结合最新信息。

RAG 的三个核心步骤

整个流程其实就三步:

第一步:把知识变成向量存起来 你有一堆文档对吧?RAG 会把每段文字转成一串数字(专业术语叫”向量”或”Embedding”),这串数字代表了文字的语义。 打个比方,“猫很可爱”和”小猫萌萌的”虽然用词不同,但意思接近,转成向量后这两串数字也会很接近。这些向量会存到 Vectorize 这样的向量数据库里。

第二步:用户提问时,找出最相关的知识片段 用户问”怎么训练猫咪”的时候,系统会先把这个问题也转成向量,然后在数据库里找”距离最近”的那几段内容——也就是语义最相关的知识。 这个过程叫”相似度搜索”,速度很快,几毫秒就能从几万条数据里找出最匹配的 3-5 条。

第三步:把检索到的内容喂给 LLM 生成答案 找到相关内容后,组装成一个 Prompt 发给 AI:

以下是相关资料:
[检索到的内容1]
[检索到的内容2]
...
用户问题:怎么训练猫咪?
请基于上述资料回答。

AI 看到这些”参考资料”,就能给出准确且有依据的答案了。

为什么选 Cloudflare 全家桶?

市面上做 RAG 的方案挺多,LangChain、LlamaIndex 啥的都行,但要自己配环境、挑向量数据库、管 GPU 资源,折腾起来挺累的。 Cloudflare 这套方案的优势是:

Workers AI - 内置十几个开源模型(Llama 3、Claude 等),调 API 就能用,不用租 GPU。免费层每天有固定的 Neurons 额度(计量单位),个人项目够用了。

Vectorize - 托管的向量数据库,不用自己搭 Milvus、Pinecone 那些。创建索引、插入向量、相似度搜索,几行代码搞定。

D1 - Cloudflare 的 SQLite 数据库,存原始文本用。向量数据库只存向量,具体文字内容还是要从这里取。

全托管 - 这是最爽的地方。不用操心服务器、扩容、备份这些破事,专心写代码就行。而且 Cloudflare 的边缘网络,全球访问都快。 2025 年 Cloudflare 还推出了 AutoRAG,进一步简化了流程——上传文档到 R2,后面的切分、向量化、检索、生成全自动。不过这篇文章咱们还是手动搭一遍,这样能学到底层原理。 说了这么多理论,接下来动手做一个。

动手实战 - 搭建你的第一个 RAG 应用

咱们做个笔记问答应用:用户可以添加笔记,然后提问,系统从所有笔记里找相关内容回答。

项目初始化和环境准备

先装 Wrangler(Cloudflare 的 CLI 工具):

npm install -g wrangler
wrangler login  # 登录你的 Cloudflare 账号

创建项目:

npm create cloudflare@latest rag-notes-app
# 选择 "Hello World" worker
# 选择 TypeScript
cd rag-notes-app

装个路由库 Hono(比原生 Workers API 好用):

npm install hono

创建 D1 数据库和 Vectorize 索引:

# 创建 D1 数据库存原始笔记
wrangler d1 create notes-db
# 创建 Vectorize 索引(768 维,配合 bge-base-en-v1.5 模型)
wrangler vectorize create notes-index --dimensions=768 --metric=cosine

然后配置 wrangler.jsonc(或 wrangler.toml):

{
  "name": "rag-notes-app",
  "main": "src/index.ts",
  "compatibility_date": "2024-01-01",
  "node_compat": true,
  // AI 绑定
  "ai": {
    "binding": "AI"
  },
  // D1 数据库绑定
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "notes-db",
      "database_id": "你的数据库ID"  // 从上面创建命令的输出复制
    }
  ],
  // Vectorize 索引绑定
  "vectorize": [
    {
      "binding": "VECTORIZE",
      "index_name": "notes-index"
    }
  ],
  // Workflow 绑定(处理异步向量化任务)
  "workflows": [
    {
      "binding": "RAG_WORKFLOW",
      "name": "rag-workflow",
      "class_name": "RAGWorkflow"
    }
  ]
}

初始化数据库表:

-- schema.sql
CREATE TABLE IF NOT EXISTS notes (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  text TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

运行:

wrangler d1 execute notes-db --file=./schema.sql

实现知识库录入功能

这是整个 RAG 的核心——把用户的笔记转成向量存起来。 创建 src/workflow.ts(Workflow 处理异步任务):

import { WorkflowEntrypoint, WorkflowStep } from 'cloudflare:workers';
type Env = {
  AI: Ai;
  DB: D1Database;
  VECTORIZE: VectorizeIndex;
};
type Params = {
  noteId: number;
  text: string;
};
export class RAGWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const { noteId, text } = event.payload;
    // 步骤1:确认 D1 记录已创建(由主路由完成)
    // 步骤2:生成向量
    const embeddings = await step.do('generate embeddings', async () => {
      const response = await this.env.AI.run(
        '@cf/baai/bge-base-en-v1.5',  // 768维的 Embedding 模型
        { text: [text] }
      );
      return response.data[0];  // 返回向量数组
    });
    // 步骤3:插入 Vectorize
    await step.do('insert vector', async () => {
      await this.env.VECTORIZE.insert([
        {
          id: noteId.toString(),
          values: embeddings,
          metadata: { text }  // 存一份文本方便调试
        }
      ]);
    });
  }
}

主路由 src/index.ts(处理添加笔记):

import { Hono } from 'hono';
import { RAGWorkflow } from './workflow';
type Bindings = {
  AI: Ai;
  DB: D1Database;
  VECTORIZE: VectorizeIndex;
  RAG_WORKFLOW: Workflow;
};
const app = new Hono<{ Bindings: Bindings }>();
// 添加笔记
app.post('/notes', async (c) => {
  const { text } = await c.req.json<{ text: string }>();
  if (!text?.trim()) {
    return c.json({ error: 'Text is required' }, 400);
  }
  // 插入 D1
  const result = await c.env.DB.prepare(
    'INSERT INTO notes (text) VALUES (?) RETURNING id'
  ).bind(text).first<{ id: number }>();
  if (!result) {
    return c.json({ error: 'Failed to create note' }, 500);
  }
  // 触发 Workflow 异步生成向量
  await c.env.RAG_WORKFLOW.create({
    params: { noteId: result.id, text }
  });
  return c.json({
    id: result.id,
    message: 'Note created, vectorization in progress'
  });
});
export default app;
export { RAGWorkflow };

这样一来,用户发 POST 请求添加笔记时:

  1. 文本立即存到 D1
  2. 后台 Workflow 慢慢生成向量、插入 Vectorize
  3. 就算向量化要几秒钟,也不会阻塞用户请求

实现智能问答功能

现在可以存笔记了,接下来做查询。 在 src/index.ts 添加:

// 查询问答
app.get('/', async (c) => {
  const query = c.req.query('q');
  if (!query) {
    return c.json({ error: 'Query parameter "q" is required' }, 400);
  }
  // 第一步:把问题转成向量
  const queryEmbedding = await c.env.AI.run(
    '@cf/baai/bge-base-en-v1.5',
    { text: [query] }
  );
  // 第二步:在 Vectorize 里找最相似的 3 条笔记
  const matches = await c.env.VECTORIZE.query(
    queryEmbedding.data[0],
    { topK: 3, returnMetadata: true }
  );
  if (matches.count === 0) {
    return c.json({ answer: '没有找到相关笔记' });
  }
  // 第三步:从 D1 获取完整文本(如果需要)
  const noteIds = matches.matches.map(m => m.id);
  const notes = await c.env.DB.prepare(
    `SELECT text FROM notes WHERE id IN (${noteIds.map(() => '?').join(',')})`
  ).bind(...noteIds).all();
  // 第四步:构造 Prompt,调用 LLM 生成答案
  const context = notes.results.map((n: any) => n.text).join('\n\n---\n\n');
  const prompt = `以下是相关的笔记内容:
${context}
用户问题:${query}
请基于上述笔记内容回答用户问题。如果笔记中没有相关信息,请说明。`;
  const aiResponse = await c.env.AI.run(
    '@cf/meta/llama-3-8b-instruct',  // 或者用 claude-3-5-sonnet-latest
    {
      messages: [
        { role: 'system', content: '你是一个智能笔记助手' },
        { role: 'user', content: prompt }
      ]
    }
  );
  return c.json({
    answer: aiResponse.response,
    sources: matches.matches.map(m => ({
      id: m.id,
      score: m.score,
      text: m.metadata?.text
    }))
  });
});

测试一下:

# 本地运行
wrangler dev
# 添加笔记
curl -X POST http://localhost:8787/notes \
  -H "Content-Type: application/json" \
  -d '{"text": "Cloudflare Workers AI 支持 Llama 3 和 Claude 模型"}'
curl -X POST http://localhost:8787/notes \
  -H "Content-Type: application/json" \
  -d '{"text": "Vectorize 使用余弦相似度进行向量检索"}'
# 等几秒让 Workflow 完成向量化
# 提问
curl "http://localhost:8787/?q=Workers%20AI%20有哪些模型"

如果一切正常,你会收到基于笔记内容的回答。

删除和更新功能

删除笔记时,记得同时删除 D1 和 Vectorize 的数据:

app.delete('/notes/:id', async (c) => {
  const id = c.req.param('id');
  // 从 D1 删除
  await c.env.DB.prepare('DELETE FROM notes WHERE id = ?').bind(id).run();
  // 从 Vectorize 删除
  await c.env.VECTORIZE.deleteByIds([id]);
  return c.json({ message: 'Note deleted' });
});

更新的话,最简单的方式是先删后加(重新生成向量)。 完整代码可以参考 Cloudflare 官方示例

进阶优化 - 让你的 RAG 更聪明

基础功能跑起来了,但要真正用在实际项目里,还有些细节值得优化。

文本分块策略

现在咱们是把整条笔记当一个单元存的。如果笔记很长(比如一篇技术文档),这样做会有问题:

  1. 检索时整篇文档的相似度可能不高(只有部分段落相关)
  2. Prompt 太长会超出 LLM 的上下文窗口限制 更好的做法是把长文本切成小块(chunk),每块单独生成向量。 简单的分块方法:
function splitText(text: string, chunkSize: number = 500, overlap: number = 50): string[] {
  const chunks: string[] = [];
  let start = 0;
  while (start < text.length) {
    const end = Math.min(start + chunkSize, text.length);
    chunks.push(text.slice(start, end));
    start = end - overlap;  // 重叠一点,避免句子被切断
  }
  return chunks;
}

更智能的方式是按段落或语义分割(可以用 LangChain 的 RecursiveCharacterTextSplitter),不过对大部分场景来说,固定长度+重叠已经够用了。 修改 Workflow,给每个 chunk 分配唯一 ID:

const chunks = splitText(text);
for (let i = 0; i < chunks.length; i++) {
  const chunkId = `${noteId}-${i}`;
  const embeddings = await this.env.AI.run('@cf/baai/bge-base-en-v1.5', {
    text: [chunks[i]]
  });
  await this.env.VECTORIZE.insert([{
    id: chunkId,
    values: embeddings.data[0],
    metadata: { noteId, chunkIndex: i, text: chunks[i] }
  }]);
}

提升检索准确度

调整 topK 和相似度阈值 默认返回 top 3 可能不够,也可能太多。试试调到 5 条,同时过滤掉相似度太低的:

const matches = await c.env.VECTORIZE.query(queryEmbedding.data[0], {
  topK: 5,
  returnMetadata: true
});
// 只保留相似度 > 0.7 的结果
const relevantMatches = matches.matches.filter(m => m.score > 0.7);

相似度分数范围是 0-1(余弦相似度),0.7 以上一般算比较相关。 优化 Prompt 别光把检索到的内容扔给 AI,告诉它怎么用这些信息:

const prompt = `你是一个智能笔记助手。以下是从笔记库中检索到的相关内容(按相关性排序):
${context}
请严格基于上述内容回答用户问题。如果内容不足以回答问题,明确说明"笔记中没有找到相关信息",不要编造答案。
用户问题:${query}`;

关键点:

  • 明确告诉 AI 这是检索到的资料
  • 要求它只基于这些内容回答
  • 允许它承认”不知道” 这样能减少 AI 瞎编的情况。

成本控制和限流

Workers AI 免费层每天有 Neurons 额度限制(具体数值会变,去 Pricing 页面 查最新的)。

监控用量: 在 Cloudflare Dashboard → Workers AI 里能看到每日消耗。不同模型消耗不同,Embedding 模型便宜,LLM 生成贵一些。

降级策略: 如果担心超额,可以:

  1. 限制每个用户的请求频率(用 KV 或 Durable Objects 计数)
  2. 超出额度后改用更小的模型或返回缓存结果
  3. 对于非关键请求,直接返回检索到的原文,不调 LLM
// 简单限流示例
const userKey = c.req.header('X-User-ID') || 'anonymous';
const requestCount = await c.env.KV.get(`rate:${userKey}`) || 0;
if (requestCount > 100) {
  return c.json({ error: 'Rate limit exceeded' }, 429);
}
await c.env.KV.put(`rate:${userKey}`, requestCount + 1, { expirationTtl: 86400 });

切换更强的模型

Llama 3 8B 已经挺不错了,但如果想要更好的理解能力,可以试试 Claude:

// 需要先在 Dashboard 绑定 Anthropic API key
const aiResponse = await c.env.AI.run('claude-3-5-sonnet-latest', {
  messages: [
    { role: 'system', content: '你是一个智能笔记助手' },
    { role: 'user', content: prompt }
  ]
});

Claude 的理解能力和输出质量确实更好,但消耗的 Neurons 也更多。根据实际需求选吧。 我自己的经验是:

  • 简单问答:Llama 3 够用
  • 需要推理、总结:Claude 明显好一些
  • 预算有限:先用 Llama 测试,确定需求后再升级

部署上线和实际应用场景

部署流程

本地测试没问题后,部署超简单:

wrangler deploy

就这一行,Cloudflare 会自动:

  • 打包你的代码
  • 部署到全球边缘节点
  • 生成一个 .workers.dev 域名 你会看到类似这样的输出:
Published rag-notes-app
  https://rag-notes-app.your-account.workers.dev

这就是你的 API 地址了。

绑定自定义域名(可选): 如果你有域名托管在 Cloudflare,可以绑定:

wrangler domains add api.yourdomain.com

或者在 Dashboard → Workers & Pages → 你的 Worker → Settings → Domains 里添加。

环境变量和 Secrets: 如果你用了 Anthropic API key 或其他敏感信息:

wrangler secret put ANTHROPIC_API_KEY
# 输入你的 key

代码里这样用:

const apiKey = c.env.ANTHROPIC_API_KEY;

真实应用场景

这套 RAG 架构能做的事挺多,分享几个实际场景:

1. 企业知识库问答

场景:公司有几百页员工手册、技术文档、FAQ,新员工找资料很麻烦。 做法:

  • 把所有文档上传,按章节分块存入 Vectorize
  • 做个简单的 Web 界面或接入企业微信机器人
  • 员工直接问”报销流程是什么”,系统自动检索相关章节回答 好处:24 小时在线,比翻文档快多了。

2. 智能客服

场景:电商网站有大量商品信息和售后政策,客服重复回答相同问题。 做法:

  • 把常见问题、商品描述、退换货政策存进去
  • 用户咨询时先让 RAG 系统回答
  • 答不上来的再转人工 效果:某个开发者用这套方案,把客服压力减少了 60%+。

3. 个人笔记助手

场景:你用 Notion 或 Obsidian 记了几年笔记,想快速找到某个知识点。 做法:

  • 定期把笔记导出,通过 API 添加到 RAG 系统
  • 需要时直接问”上次看的那个 TypeScript 技巧是啥来着”
  • 系统检索出相关笔记片段 我自己就在用类似的工具,找资料效率真的提升了不少。

4. “Chat with PDF” 工具

场景:用户上传一份 PDF(论文、合同、报告),想快速提取信息。 做法(参考 Rohit Patil 的案例):

  • 用户上传 PDF 到 R2
  • Worker 读取 PDF,提取文本,分块向量化
  • 用户可以问”这份合同的付款条款是什么” 这个场景特别实用,很多法律、咨询行业的人需要。

常见问题排查

问题1:向量维度不匹配

错误:dimension mismatch: expected 768, got 512 原因:Vectorize 索引创建时设的维度(768)和模型输出的维度不一致。 解决:确保索引维度和模型匹配。bge-base-en-v1.5 是 768 维,别用错模型。

问题2:D1 和 Vectorize 数据不一致

现象:查询返回的 note ID 在 D1 里不存在。 原因:可能删除 D1 记录时忘了删 Vectorize,或者 Workflow 失败了。 解决:删除操作要么包在事务里,要么用 Workflow 确保两边都删干净。

问题3:Workflow 超时

错误:workflow execution timeout 原因:向量化大量文本时超过 Workflow 的时间限制。 解决:把大文档拆成多个 Workflow 任务,或者批量处理。

// 分批处理
const batchSize = 10;
for (let i = 0; i < chunks.length; i += batchSize) {
  const batch = chunks.slice(i, i + batchSize);
  await c.env.RAG_WORKFLOW.create({
    params: { noteId, chunks: batch, offset: i }
  });
}

结论

说了这么多,回顾一下咱们做了啥:

  • 弄懂了 RAG 原理:检索增强生成,就是给 AI 开卷考试的权限,先找资料再回答
  • 搭了个能跑的应用:笔记问答系统,从环境准备到代码实现,完整走了一遍
  • 学了优化技巧:文本分块、检索调优、成本控制,这些细节让应用真正可用
  • 看了实际场景:企业知识库、智能客服、个人助手、PDF 聊天,都是可以落地的方向 Cloudflare 这套方案最大的优势是门槛低。不用租 GPU、不用搭数据库、不用操心运维,免费额度对个人项目来说绝对够用。就算是生产环境,付费计划也比自建便宜多了。 接下来你可以:
  1. 立即动手:克隆 官方示例代码wrangler dev 跑起来,5 分钟就能看到效果
  2. 接入真实数据:把你的笔记、文档、FAQ 导进去,看看检索质量如何
  3. 做个前端界面:用 React/Vue 做个简单的聊天界面,或者直接用 Cloudflare Pages 部署
  4. 探索更多可能:试试多模态 RAG(结合图片、表格)、GraphRAG(知识图谱增强)等高级玩法 RAG 是当前 AI 应用最实用的架构之一,掌握它能让你做出很多有意思的东西。试下来,Cloudflare 全家桶确实能解决不少实际问题。 要是碰到问题,可以去 Cloudflare DiscordCommunity 论坛 问,社区挺活跃的。

发布于: 2025年12月1日 · 修改于: 2025年12月4日

相关文章