用 Cloudflare Workers 免费给博客增加一个带有知识库的 AI 助手

English

如果你用电脑端浏览器访问我的博客,就会注意到右下角多了一个悬浮的对话球——这就是我新上线的博客 AI 助手,欢迎你跟他对话测试一下它是否足够聪明。

它不仅能跟你像正常大模型那样聊天,更能调用我的博客知识库,以我的口吻为你推荐文章、解答技术问题。

今天就来复盘一下,如何利用 Cloudflare Workers AI 以及 Vectorize 快速实现一个有知识库的 AI 助手。

第一步:让 AI 先跑起来

要想给静态博客加 AI,调用付费的 API 成本比较高,服务端本地运行大模型更不现实。但我发现 Cloudflare Workers AI 提供每天 10000 点的免费额度,足够个人博客使用了,提供的模型也比较丰富。

在对话模型的选择上,我选择了 @cf/qwen/qwen3-30b-a3b-fp8,这个模型速度快、消耗少、足够用于日常对话。还有一些模型也不错,比如@cf/meta/llama-3.1-8b-instruct其他模型参考官方文档

只需要短短几步,一个支持流式输出的云端 AI 后端就能跑起来:

创建 Worker 项目

首先安装 nodejs, 推荐用 nvm 安装,这样可以方便地管理多版本 node

然后创建项目

npm create cloudflare@latest -- hello-ai

绑定 AI

编辑项目中的 wrangler.jsonc,或者是 wrangler.toml

{
  "ai": {
    "binding": "AI"
  }
}

或者

[ai]
binding = "AI"

这样就给 Workers 绑定了 AI 能力

运行一个简单的 AI 对话

修改 src/index.ts 代码,快速运行一个简单的对话任务

// src/index.ts
export interface Env {
  AI: Ai;
}

export default {
  async fetch(request, env): Promise<Response> {

    const messages = [
      { role: "system", content: "You are a friendly assistant" },
      {
        role: "user",
        content: "What is the origin of the phrase Hello, World",
      },
    ];

    const stream = await env.AI.run("@cf/qwen/qwen3-30b-a3b-fp8", {
      messages,
      stream: true,
    });

    return new Response(stream, {
      headers: { "content-type": "text/event-stream" },
    });
  },
} satisfies ExportedHandler<Env>;

测试和部署

你可以通过 npx wrangler dev 命令快速本地测试,使用 curl 测试对话

npx wrangler dev

测试通过后就可以部署了,首先登录你的账号

npx wrangler login

然后执行 deploy命令进行部署

npx wrangler deploy

部署后这个服务就运行在以下网址,你可以修改 wrangler.jsonc 里面的 name 字段修改 dev 域名,也可以直接绑定自己的域名

https://hello-ai.<YOUR_SUBDOMAIN>.workers.dev

第二步:增加 RAG 检索能力

为了让 AI 了解你的更多信息,以你的口吻解答访客关于你博客内容的问题,我们需要引入知识库。

Cloudflare 提供了向量数据库 Vectorize

创建向量索引

在投喂数据之前,你需要先创建一个向量索引。这里我们使用 bge-m3 模型,它的向量维度是 1024,相似度算法选择 cosine

# 创建名为 blog-index 的向量索引
npx wrangler vectorize create blog-index --dimensions=1024 --metric=cosine

创建好之后,记得在 wrangler.jsoncwrangler.toml 中进行绑定:

{
  "vectorize": [
    {
      "binding": "VECTORIZE",
      "index_name": "blog-index"
    }
  ]
}

投喂知识库

我编写了一段 Node 脚本,把博客 content/post 里的技术文章全部扫描一遍,按照 450 个字符分割,去除没有必要的链接、图片等信息。

最后,脚本将这些碎片通过 HTTP 发送给我们刚刚创建的 Cloudflare Worker,由 Worker 后端调用 @cf/baai/bge-m3 将其转化成向量矩阵并存入 Vectorize 数据库:

// Worker 内部的投喂入库逻辑
const { data } = await env.AI.run("@cf/baai/bge-m3", { text: [doc.text] }) as any;

await env.VECTORIZE.upsert([{
    id: doc.id,
    values: data[0],
    metadata: { text: doc.text, ...doc.metadata }
}]);

检索知识库

当访客提问时,先用 bge-m3 把提问也转换成向量,并去 Vectorize 寻找最接近的 6 个片段作为上下文。

const { data } = await env.AI.run("@cf/baai/bge-m3", { text: [lastUserMessage] }) as any;
const matches = await env.VECTORIZE.query(data[0], { topK: 6, returnMetadata: true });

// 双重过滤:下限 0.50,且 top-1 必须 ≥ 0.60 才启用 RAG 上下文
const topScore = matches.matches[0]?.score || 0;
const relevantMatches = topScore >= 0.60
    ? matches.matches.filter(m => m.score > 0.50)
    : [];

let context = "";
const sources: any[] = [];

if (relevantMatches.length > 0) {
    const uniqueUrls = new Set();
    context = relevantMatches.map(m => {
        const title = m.metadata?.title as string;
        const link = m.metadata?.url as string;
        const text = (m.metadata?.text as string) || "";
        
        if (!uniqueUrls.has(link)) {
            sources.push({ title, url: link });
            uniqueUrls.add(link);
        }
        
        return `### 《${title}\nURL: ${link}\n内容片段: ${text.slice(0, 600)}...`;
    }).join("\n\n");
} 

提示词优化

最后一步,加上决定性格的 System Prompt:

“你不是助理,你是文章作者 Liu 的数字分身。这些上下文不是搜索到的文档,而是你脑海中真实的个人经历”

数字分身也许不远了

不过由于目前的流程还是比较简陋,比如大模型只有 30b,使用的 bge-m3 也是比较小的知识库模型,也没有用到 rerank 重排,所以它只是了解了我的一部分信息的高级客服,离真正的数字分身还差的远。

但如果能投喂更多的数据,使用更强大的模型,使用更智能的检索信息的方式,再加上我的形象和声音,也许就真的可以成为一个以假乱真的 “数字分身” 了。

到那时候,也许我也会思考一个更深邃的问题,那就是我是否有足够的心理准备,提前创建自己的数字生命。这让我想起曾经回答过的一个知乎问题:大概是问是否介意死后被亲友以 AI 的形式“复活”。

当时我的答案是不介意,但如果是自己亲手制作,也许会有点不同,毕竟我不是什么名人,如果真正到了生命的自然终结,又有多少人会怀念我?但如果能给他们带来一些慰藉,这一切就有了意义。

加载中...
📊 加载中...
感谢Jimmy | 隐私政策 | 赞赏支持
Liu 的 AI 助手