本指南将指导您设置和部署您的第一个 Cloudflare AI 应用程序。您将使用 Workers AI、Vectorize、D1 和 Cloudflare Workers 等工具构建一个功能齐全的 AI 驱动的应用程序。
在本教程结束时,您将构建一个 AI 工具,允许您存储信息并使用大型语言模型进行查询。这种模式被称为检索增强生成(RAG),是您可以结合 Cloudflare AI 工具包的多个方面构建的一个有用的项目。您无需具备使用 AI 工具的经验即可构建此应用程序。
- Sign up for a Cloudflare account ↗.
- Install
Node.js
↗.
Node.js version manager
Use a Node version manager like Volta ↗ or nvm ↗ to avoid permission issues and change Node.js versions. Wrangler, discussed later in this guide, requires a Node version of 16.17.0
or later.
您还需要访问 Vectorize。在本教程中,我们将展示如何选择性地与 Anthropic Claude ↗ 集成。您需要一个 Anthropic API 密钥 ↗ 才能这样做。
C3 (create-cloudflare-cli
) 是一个命令行工具,旨在帮助您尽快设置和部署 Workers 到 Cloudflare。
打开一个终端窗口并运行 C3 来创建您的 Worker 项目:
npm create cloudflare@latest -- rag-ai-tutorial
yarn create cloudflare rag-ai-tutorial
pnpm create cloudflare@latest rag-ai-tutorial
For setup, select the following options:
- For What would you like to start with?, choose
Hello World example
. - For Which template would you like to use?, choose
Worker only
. - For Which language do you want to use?, choose
JavaScript
. - For Do you want to use git for version control?, choose
Yes
. - For Do you want to deploy your application?, choose
No
(we will be making some changes before deploying).
在您的项目目录中,C3 生成了几个文件。
C3 创建了哪些文件?
wrangler.jsonc
: 您的 Wrangler 配置文件。worker.js
(在/src
中): 一个用 ES 模块 语法编写的最小化'Hello World!'
Worker。package.json
: 一个最小化的 Node 依赖项配置文件。package-lock.json
: 请参阅npm
关于package-lock.json
的文档 ↗。node_modules
: 请参阅npm
关于node_modules
的文档 ↗。
现在,移动到您新创建的目录中:
cd rag-ai-tutorial
Workers 命令行界面 Wrangler 允许您 创建、测试 和 部署 您的 Workers 项目。C3 将默认在项目中安装 Wrangler。
创建您的第一个 Worker 后,在项目目录中运行 wrangler dev
命令以启动本地服务器来开发您的 Worker。这将允许您在开发过程中本地测试您的 Worker。
npx wrangler dev --remote
您现在可以访问 http://localhost:8787 ↗ 来查看您的 Worker 正在运行。您对代码的任何更改都将触发重新构建,重新加载页面将显示您的 Worker 的最新输出。
要开始使用 Cloudflare 的 AI 产品,您可以将 ai
块添加到 Wrangler 配置文件 中。这将在您的代码中设置一个到 Cloudflare AI 模型的绑定,您可以使用它与平台上的可用 AI 模型进行交互。
此示例使用了 @cf/meta/llama-3-8b-instruct
模型,该模型可以生成文本。
{ "ai": { "binding": "AI" }}
[ai]binding = "AI"
现在,找到 src/index.js
文件。在 fetch
处理程序中,您可以查询 AI
绑定:
export default { async fetch(request, env, ctx) { const answer = await env.AI.run("@cf/meta/llama-3-8b-instruct", { messages: [{ role: "user", content: `9 的平方根是多少?` }], });
return new Response(JSON.stringify(answer)); },};
通过 AI
绑定查询 LLM,我们可以直接在代码中与 Cloudflare AI 的大型语言模型进行交互。在此示例中,我们使用的是 @cf/meta/llama-3-8b-instruct
模型,该模型可以生成文本。
您可以使用 wrangler
部署您的 Worker:
npx wrangler deploy
向您的 Worker 发出请求现在将从 LLM 生成文本响应,并将其作为 JSON 对象返回。
curl https://example.username.workers.dev
{"response":"答案:9的平方根是3。"}
嵌入允许您向 Cloudflare AI 项目中使用的语言模型添加附加功能。这是通过 Vectorize(Cloudflare 的向量数据库)完成的。
要开始使用 Vectorize,请使用 wrangler
创建一个新的嵌入索引。此索引将存储具有 768 个维度的向量,并将使用余弦相似度来确定哪些向量彼此最相似:
npx wrangler vectorize create vector-index --dimensions=768 --metric=cosine
然后,将新 Vectorize 索引的配置详细信息添加到 Wrangler 配置文件中:
{ "vectorize": [ { "binding": "VECTOR_INDEX", "index_name": "vector-index" } ]}
# ... existing wrangler configuration
[[vectorize]]binding = "VECTOR_INDEX"index_name = "vector-index"
向量索引允许您存储维度集合,维度是用于表示数据的浮点数。当您要查询向量数据库时,您也可以将查询转换为维度。Vectorize 旨在高效地确定哪些存储的向量与您的查询最相似。
要实现搜索功能,您必须设置一个 Cloudflare 的 D1 数据库。在 D1 中,您可以存储应用程序的数据。然后,您将此数据更改为向量格式。当有人搜索并与向量匹配时,您可以向他们显示匹配的数据。
使用 wrangler
创建一个新的 D1 数据库:
npx wrangler d1 create database
然后,将上一个命令输出的配置详细信息粘贴到 Wrangler 配置文件 中:
{ "d1_databases": [ { "binding": "DB", "database_name": "database", "database_id": "abc-def-geh" } ]}
# ... existing wrangler configuration
[[d1_databases]]binding = "DB" # 在您的 Worker 的 env.DB 中可用database_name = "database"database_id = "abc-def-geh" # 将此替换为真实的 database_id (UUID)
在此应用程序中,我们将在 D1 中创建一个 notes
表,这将允许我们存储笔记并稍后在 Vectorize 中检索它们。要创建此表,请使用 wrangler d1 execute
运行一个 SQL 命令:
npx wrangler d1 execute database --remote --command "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL)"
现在,我们可以使用 wrangler d1 execute
向我们的数据库中添加一个新笔记:
npx wrangler d1 execute database --remote --command "INSERT INTO notes (text) VALUES ('最好的披萨配料是意大利辣香肠')"
在我们开始创建笔记之前,我们将引入一个 Cloudflare 工作流。这将允许我们定义一个持久的工作流,可以安全、稳健地执行 RAG 过程的所有步骤。
首先,将一个新的 [[workflows]]
块添加到您的 Wrangler 配置文件 中:
{ "workflows": [ { "name": "rag", "binding": "RAG_WORKFLOW", "class_name": "RAGWorkflow" } ]}
# ... existing wrangler configuration
[[workflows]]name = "rag"binding = "RAG_WORKFLOW"class_name = "RAGWorkflow"
在 src/index.js
中,添加一个名为 RAGWorkflow
的新类,它扩展了 WorkflowEntrypoint
:
import { WorkflowEntrypoint } from "cloudflare:workers";
export class RAGWorkflow extends WorkflowEntrypoint { async run(event, step) { await step.do("example step", async () => { console.log("Hello World!"); }); }}
此类将定义一个工作流步骤,该步骤将在控制台中记录“Hello World!”。您可以根据需要向工作流中添加任意数量的步骤。
就其本身而言,此工作流不会执行任何操作。要执行工作流,我们将调用 RAG_WORKFLOW
绑定,并传入工作流正常完成所需的任何参数。以下是我们如何调用工作流的示例:
env.RAG_WORKFLOW.create({ params: { text } });
为了扩展您的 Workers 函数以处理多个路由,我们将添加 hono
,这是一个用于 Workers 的路由库。这将允许我们为向数据库中添加笔记创建一个新路由。使用 npm
安装 hono
:
npm i hono
yarn add hono
pnpm add hono
然后,将 hono
导入您的 src/index.js
文件中。您还应该更新 fetch
处理程序以使用 hono
:
import { Hono } from "hono";const app = new Hono();
app.get("/", async (c) => { const answer = await c.env.AI.run("@cf/meta/llama-3-8b-instruct", { messages: [{ role: "user", content: `9 的平方根是多少?` }], });
return c.json(answer);});
export default app;
这将在根路径 /
处建立一个路由,其功能与先前版本的应用程序相同。
现在,我们可以更新工作流以开始将笔记添加到数据库中,并生成它们的相关嵌入。
此示例使用了 @cf/baai/bge-base-en-v1.5
模型,该模型可用于创建嵌入。嵌入存储在 Vectorize 中,这是 Cloudflare 的向量数据库。用户查询也会转换为嵌入,以便在 Vectorize 中进行搜索。
import { WorkflowEntrypoint } from "cloudflare:workers";
export class RAGWorkflow extends WorkflowEntrypoint { async run(event, step) { const env = this.env; const { text } = event.payload;
const record = await step.do(`create database record`, async () => { const query = "INSERT INTO notes (text) VALUES (?) RETURNING *";
const { results } = await env.DB.prepare(query).bind(text).run();
const record = results[0]; if (!record) throw new Error("Failed to create note"); return record; });
const embedding = await step.do(`generate embedding`, async () => { const embeddings = await env.AI.run("@cf/baai/bge-base-en-v1.5", { text: text, }); const values = embeddings.data[0]; if (!values) throw new Error("Failed to generate vector embedding"); return values; });
await step.do(`insert vector`, async () => { return env.VECTOR_INDEX.upsert([ { id: record.id.toString(), values: embedding, }, ]); }); }}
工作流执行以下操作:
- 接受一个
text
参数。 - 在 D1 的
notes
表中插入一个新行,并检索新行的id
。 - 使用 LLM 绑定的
embeddings
模型将text
转换为向量。 - 将
id
和vectors
上传到 Vectorize 中的vector-index
索引。
通过这样做,您将创建一个新的向量表示形式的笔记,可以用于稍后检索该笔记。
要完成代码,我们将添加一个路由,允许用户向数据库提交笔记。此路由将解析 JSON 请求正文,获取 note
参数,并创建一个新的工作流实例,传递参数:
app.post("/notes", async (c) => { const { text } = await c.req.json(); if (!text) return c.text("Missing text", 400); await c.env.RAG_WORKFLOW.create({ params: { text } }); return c.text("Created note", 201);});
要完成您的代码,您可以更新根路径(/
)以查询 Vectorize。您将把查询转换为向量,然后使用 vector-index
索引来查找最相似的向量。
topK
参数限制了函数返回的向量数量。例如,提供 topK
为 1 将仅返回基于查询的 最相似 向量。将 topK
设置为 5 将返回 5 个最相似的向量。
给定一组相似的向量,您可以检索与存储在这些向量旁边的记录 ID 匹配的笔记。在这种情况下,我们只检索一个笔记 - 但您可以根据需要自定义此设置。
您可以将这些笔记的文本插入 LLM 绑定的提示中。这是检索增强生成(RAG)的基础:在 LLM 的提示中提供来自数据外部的附加上下文,以增强 LLM 生成的文本。
我们将更新提示以包含上下文,并要求 LLM 在回应时使用上下文:
import { Hono } from "hono";const app = new Hono();
// Existing post route...// app.post('/notes', async (c) => { ... })
app.get("/", async (c) => { const question = c.req.query("text") || "9 的平方根是多少?";
const embeddings = await c.env.AI.run("@cf/baai/bge-base-en-v1.5", { text: question, }); const vectors = embeddings.data[0];
const vectorQuery = await c.env.VECTOR_INDEX.query(vectors, { topK: 1 }); let vecId; if ( vectorQuery.matches && vectorQuery.matches.length > 0 && vectorQuery.matches[0] ) { vecId = vectorQuery.matches[0].id; } else { console.log("No matching vector found or vectorQuery.matches is empty"); }
let notes = []; if (vecId) { const query = `SELECT * FROM notes WHERE id = ?`; const { results } = await c.env.DB.prepare(query).bind(vecId).all(); if (results) notes = results.map((vec) => vec.text); }
const contextMessage = notes.length ? `Context:\n${notes.map((note) => `- ${note}`).join("\n")}` : "";
const systemPrompt = `When answering the question or responding, use the context provided, if it is provided and relevant.`;
const { response: answer } = await c.env.AI.run( "@cf/meta/llama-3-8b-instruct", { messages: [ ...(notes.length ? [{ role: "system", content: contextMessage }] : []), { role: "system", content: systemPrompt }, { role: "user", content: question }, ], }, );
return c.text(answer);});
app.onError((err, c) => { return c.text(err);});
export default app;
如果您正在处理较大的文档,您有选择使用 Anthropic 的 Claude 模型 ↗,这些模型具有大型上下文窗口,非常适合 RAG 工作流。
要开始,安装 @anthropic-ai/sdk
包:
npm i @anthropic-ai/sdk
yarn add @anthropic-ai/sdk
pnpm add @anthropic-ai/sdk
在 src/index.js
中,您可以更新 GET /
路由以检查 ANTHROPIC_API_KEY
环境变量。如果设置了该变量,我们可以使用 Anthropic SDK 生成文本。如果没有设置,我们将回退到现有的 Workers AI 代码:
import Anthropic from '@anthropic-ai/sdk';
app.get('/', async (c) => { // ... Existing code const systemPrompt = `When answering the question or responding, use the context provided, if it is provided and relevant.`
let modelUsed: string = "" let response = null
if (c.env.ANTHROPIC_API_KEY) { const anthropic = new Anthropic({ apiKey: c.env.ANTHROPIC_API_KEY })
const model = "claude-3-5-sonnet-latest" modelUsed = model
const message = await anthropic.messages.create({ max_tokens: 1024, model, messages: [ { role: 'user', content: question } ], system: [systemPrompt, notes ? contextMessage : ''].join(" ") })
response = { response: message.content.map(content => content.text).join("\n") } } else { const model = "@cf/meta/llama-3.1-8b-instruct" modelUsed = model
response = await c.env.AI.run( model, { messages: [ ...(notes.length ? [{ role: 'system', content: contextMessage }] : []), { role: 'system', content: systemPrompt }, { role: 'user', content: question } ] } ) }
if (response) { c.header('x-model-used', modelUsed) return c.text(response.response) } else { return c.text("We were unable to generate output", 500) }})
最后,您需要在 Workers 应用程序中设置 ANTHROPIC_API_KEY
环境变量。您可以使用 wrangler secret put
来实现:
$ npx wrangler secret put ANTHROPIC_API_KEY
如果您不再需要笔记,可以从数据库中删除它。每次删除笔记时,您还需要从 Vectorize 中删除相应的向量。您可以通过在 src/index.js
文件中构建 DELETE /notes/:id
路由来实现这一点:
app.delete("/notes/:id", async (c) => { const { id } = c.req.param();
const query = `DELETE FROM notes WHERE id = ?`; await c.env.DB.prepare(query).bind(id).run();
await c.env.VECTOR_INDEX.deleteByIds([id]);
return c.status(204);});
对于较大的文本块,建议将文本分割成较小的块。这允许 LLM 更有效地收集相关上下文,而无需检索大块文本。
为了实现这一点,我们将向项目中添加一个新的 NPM 包,@langchain/textsplitters
:
npm i @langchain/textsplitters
yarn add @langchain/textsplitters
pnpm add @langchain/textsplitters
此包提供的 RecursiveCharacterTextSplitter
类将文本分割成较小的块。它可以根据需要进行自定义,但默认配置在大多数情况下都有效:
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
const text = "Some long piece of text...";
const splitter = new RecursiveCharacterTextSplitter({ // These can be customized to change the chunking size // chunkSize: 1000, // chunkOverlap: 200,});
const output = await splitter.createDocuments([text]);console.log(output); // [{ pageContent: 'Some long piece of text...' }]
要使用此分割器,我们将更新工作流以将文本分割成较小的块。然后,我们将遍历这些块,并为每个文本块运行工作流的其余部分:
export class RAGWorkflow extends WorkflowEntrypoint { async run(event, step) { const env = this.env; const { text } = event.payload; let texts = await step.do("split text", async () => { const splitter = new RecursiveCharacterTextSplitter(); const output = await splitter.createDocuments([text]); return output.map((doc) => doc.pageContent); });
console.log( "RecursiveCharacterTextSplitter generated ${texts.length} chunks", );
for (const index in texts) { const text = texts[index]; const record = await step.do( `create database record: ${index}/${texts.length}`, async () => { const query = "INSERT INTO notes (text) VALUES (?) RETURNING *";
const { results } = await env.DB.prepare(query).bind(text).run();
const record = results[0]; if (!record) throw new Error("Failed to create note"); return record; }, );
const embedding = await step.do( `generate embedding: ${index}/${texts.length}`, async () => { const embeddings = await env.AI.run("@cf/baai/bge-base-en-v1.5", { text: text, }); const values = embeddings.data[0]; if (!values) throw new Error("Failed to generate vector embedding"); return values; }, );
await step.do(`insert vector: ${index}/${texts.length}`, async () => { return env.VECTOR_INDEX.upsert([ { id: record.id.toString(), values: embedding, }, ]); }); } }}
现在,当向 /notes
端点提交大块文本时,它们将被分割成较小的块,并且每个块将由工作流处理。
如果您在第 1 步中没有部署您的 Worker,请使用 Wrangler 将您的 Worker 部署到 *.workers.dev
子域、自定义域(如果您已配置),或者如果您没有配置任何子域或域,Wrangler 将在发布过程中提示您设置一个。
npx wrangler deploy
在 <YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev
预览您的 Worker。
完整版本的此代码库可在 GitHub 上找到。它包括一个前端 UI 用于查询、添加和删除笔记,以及一个后端 API 用于与数据库和向量索引进行交互。您可以在这里找到它:github.com/kristianfreeman/cloudflare-retrieval-augmented-generation-example ↗。
要做更多:
- @2025 Cloudflare Ubitools
- Cf Repo