如果你用电脑端浏览器访问我的博客,就会注意到右下角多了一个悬浮的对话球——这就是我新上线的博客 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.jsonc 或 wrangler.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 的形式“复活”。
当时我的答案是不介意,但如果是自己亲手制作,也许会有点不同,毕竟我不是什么名人,如果真正到了生命的自然终结,又有多少人会怀念我?但如果能给他们带来一些慰藉,这一切就有了意义。