Cloudflare AI搜索:AI代理的统一检索原语
Cloudflare AI··作者 Anni Wang
关键信息
AI搜索支持向量和BM25(关键词)搜索并行执行,并在查询时融合结果;支持为文档附加元数据以提升排名,并可通过`ai_search_namespaces`绑定一次性跨多个实例查询。
资讯摘要
Cloudflare的AI搜索是一种新工具,旨在帮助AI代理高效地从结构化和非结构化数据中检索相关信息。它将语义搜索(使用向量嵌入)和关键词搜索(通过BM25)结合成一个单一的混合查询引擎。每个实例都自带存储和索引,无需设置外部数据库如R2或Vectorize。
开发者可以通过API或CLI动态创建每代理或每客户的实例,非常适合客户支持代理等场景,它们既需要访问共享的产品文档,也需要访问单个客户的过往记录。该系统还支持元数据过滤和排名增强,实现对响应内容的细粒度控制。

资讯正文
每个代理都需要搜索功能:编码代理需要在代码库中搜索数百万个文件,支持代理则需要查找客户工单和内部文档。虽然应用场景不同,但核心问题是一样的:在正确的时间将正确的信息传递给模型。
如果你自己搭建搜索系统,你需要一个向量索引、一个解析和切分文档的索引管道,以及一种机制来在数据更新时保持索引同步。如果你还需要关键词搜索,那还需要另一个独立的索引和融合逻辑。如果每个代理都需要自己的可搜索上下文,那你得为每个代理单独配置这一切。
AI Search(原名 AutoRAG)就是你所需要的即插即用的搜索基础组件。你可以动态创建实例,输入你的数据并进行搜索——无论是从 Worker、Agents SDK 还是 Wrangler CLI 中调用。我们正在推出以下功能:
混合搜索。在同一查询中同时启用语义匹配和关键词匹配。向量搜索与 BM25 并行运行,并将结果融合。我们博客上的搜索现在就由 AI Search 驱动。你可以尝试右上角的放大镜图。
内置存储和索引。新实例自带存储空间和向量索引。你可以通过 API 直接上传文件到实例,它们会自动被索引。无需设置 R2 存储桶,也不用提前连接外部数据源。新的 ai_search_namespaces 绑定让你能在 Worker 中运行时创建和删除实例,因此你可以为每个代理、每个客户或每种语言单独创建一个实例,而无需重新部署。
你现在还可以为文档附加元数据,并在查询时利用这些元数据提升排名,还能在一次调用中跨多个实例进行查询。
接下来,我们看看这在实际场景中意味着什么。
实战示例:客户服务代理
我们来走一遍一个客户服务代理的实现过程,它需要查找两类知识:共享的产品文档和每个客户的专属历史记录,比如过去的解决方案。产品文档太大,无法放入上下文窗口;而每位客户的记录随着每次问题解决不断增长,因此代理必须通过检索找到相关内容。
这就是使用 AI Search 和 Agents SDK 的实现方式。首先,初始化一个项目:
npm create cloudflare@latest -- --template cloudflare/agents-starter
然后,在你的 Worker 中绑定一个 AI Search 命名空间:
// wrangler.jsonc
{
"ai_search_namespaces": [
{ "binding": "SUPPORT_KB", "namespace": "support" }
"ai": { "binding": "AI" },
"durable_objects": {
"bindings": [
{ "name": "SupportAgent", "class_name": "SupportAgent" }
}
}
假设你的共享产品文档存放在名为 product-doc 的 R2 存储桶中。你可以在 Cloudflare 控制面板的 support 命名空间内创建一个一次性 AI Search 实例(命名为 product-knowledge),并将其后端指向该存储桶:
这就是你的共享知识库,所有代理都可以引用这些文档。
当客户带着新问题回来时,了解之前尝试过什么可以节省所有人的时间。你可以通过为每个客户创建一个AI Search实例来追踪这一点。每次问题解决后,代理会保存一个关于问题原因和修复方式的摘要。随着时间推移,这会形成一份可搜索的历史解决方案日志。你可以使用命名空间绑定动态创建实例:
// 在客户首次出现时为其创建一个实例
await env.SUPPORT_KB.create({
id: `customer-${customerId}`,
index_method:{ keyword: true, vector: true }
});
每个实例都有自己的内置存储和向量索引——由R2和Vectorize提供支持。实例初始为空,并随时间积累上下文。下次客户回来时,所有内容都是可搜索的。
以下是几个客户之后命名空间的样子:
namespace: "support"
├── product-knowledge (以R2为源,所有代理共享)
├── customer-abc123 (管理存储,按客户独立)
├── customer-def456 (管理存储,按客户独立)
└── customer-ghi789 (管理存储,按客户独立)
现在来看代理本身。它扩展了Agents SDK中的AIChatAgent,并定义了两个工具。我们通过Workers AI使用Kimi K2.5作为大语言模型。模型根据对话内容决定何时调用这些工具:
import { AIChatAgent, type OnChatMessageOptions } from "@cloudflare/ai-chat";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { routeAgentRequest } from "agents";
import { z } from "zod";
export class SupportAgent extends AIChatAgent<Env> {
async onChatMessage(_onFinish: unknown, options?: OnChatMessageOptions) {
// 客户端通过Agent SDK的sendMessage({ body: { customerId } })将customerId传入请求体
const customerId = options?.body?.customerId;
// 在客户首次出现时创建一个实例
// 每个实例都有自己的存储和向量索引
if (customerId) {
try {
await this.env.SUPPORT_KB.create({
id: `customer-${customerId}`,
index_method: { keyword: true, vector: true }
});
} catch {
// 实例已存在
}
}
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/moonshotai/kimi-k2.5"),
system: `你是一名客服代理。在回答前请使用search_knowledge_base查找相关文档。搜索结果包括产品文档和该客户的过往解决方案——利用它们避免重复失败的修复方案并识别反复出现的问题。当问题解决时,请在回复前调用save_resolution。`,
// this.messages是完整的对话历史,AIChatAgent会在重连时自动持久化
messages: await convertToModelMessages(this.messages),
tools: {
// 工具1:单次调用中同时搜索共享的产品文档和该客户的过往解决方案
search_knowledge_base: tool({
description: "搜索产品文档和客户历史",
inputSchema: z.object({
query: z.string().describe("搜索查询")
}),
execute: async ({ query }) => {
// 始终搜索产品文档;
AI搜索:代理的搜索原语
// 如果可用,包含客户历史记录
const instances = ["product-knowledge"];
if (customerId) {
instances.push(`customer-${customerId}`);
}
return await this.env.SUPPORT_KB.search({
query: query,
ai_search_options: {
// 优先展示较新的文档而非较旧的
boost_by: [
{ field: "timestamp", direction: "desc" }
// 同时在两个实例中进行搜索
instance_ids: instances
});
},
// 工具2:问题解决后,代理会保存摘要,以便未来的代理拥有完整上下文
save_resolution: tool({
description:
"在解决客户问题后保存解决摘要",
inputSchema: z.object({
filename: z.string().describe(
"简短描述性的文件名,例如 'billing-fix.md'"
content: z.string().describe(
"问题是什么、原因是什么以及如何解决的"
}),
execute: async ({ filename, content }) => {
if (!customerId) return { error: "没有客户ID" };
const instance = this.env.SUPPORT_KB.get(
`customer-${customerId}`
);
// uploadAndPoll 会等待索引完成,确保解决摘要在下一次查询前即可被搜索到
const item = await instance.items.uploadAndPoll(
filename, content
);
return { saved: true, filename, status: item.status };
}
}),
},
// 将代理工具使用循环限制在10步内
stopWhen: stepCountIs(10),
abortSignal: options?.abortSignal,
});
return result.toUIMessageStreamResponse();
}
}
// 将请求路由到 SupportAgent 持久对象
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
}
} satisfies ExportedHandler<Env>;
有了这个设置,模型可以决定何时搜索、何时保存。当它搜索时,会同时查询 product-knowledge 和该客户的过往解决方案。当问题解决后,它会保存一份摘要,这份摘要将在未来对话中立即可被搜索。
AI搜索如何找到你想要的内容
在底层,AI搜索运行一个可配置的多步骤检索管道,每一步都可以自定义。
混合搜索:理解意图并匹配关键词的搜索方式
直到现在,AI搜索只提供向量搜索功能。向量搜索擅长理解意图,但可能丢失细节。例如,在查询 "ERR_CONNECTION_REFUSED timeout" 中,嵌入向量捕捉的是连接失败这一广泛概念,但用户并不是在寻找通用网络文档,而是想找到明确提到 "ERR_CONNECTION_REFUSED" 的具体文档。向量搜索可能会返回关于故障排除的文档,却从未展示包含该确切错误字符串的页面。
关键词搜索填补了这一空白。AI Search 现在支持 BM25,这是最广泛使用的检索评分函数之一。BM25 根据查询词在文档中出现的频率、这些词在整个语料库中的稀有程度以及文档长度来对文档进行评分。它奖励特定术语的匹配,惩罚常见填充词,并对文档长度进行归一化处理。当你搜索 'ERR_CONNECTION_REFUSED timeout' 时,BM25 会找到实际包含 'ERR_CONNECTION_REFUSED' 这个词的文档。然而,BM25 可能会遗漏一篇关于“网络连接故障排除”的文章,尽管该文章可能描述的是同样的问题。这正是向量搜索的优势所在,因此你需要两者结合使用。
启用混合搜索后,系统会并行运行向量搜索和 BM25 搜索,融合结果,并可选地重新排序:
让我们看看 BM25 的新配置选项,以及它们如何协同工作。
分词器(Tokenizer)控制文档在索引时如何被拆分为可匹配的词项。Porter 词干提取器(选项:porter)会对单词进行词干化处理,使得 "running" 能匹配到 "run";三元语法(option:trigram)则匹配字符子串,例如 "conf" 能匹配到 "configuration"。你可以为自然语言内容(如文档)使用 porter,为代码类内容使用 trigram,因为后者更看重部分匹配。
关键词匹配模式(Keyword match mode)控制哪些文档会在查询时成为 BM25 评分的候选对象。
AND 要求所有查询词都出现在文档中,OR 则只要有一个匹配即可。
融合方式(Fusion)控制向量搜索与关键词搜索结果如何合并成最终的结果列表。
互逆排名融合(option:rrf)按排名位置合并而非分数,避免比较两种不兼容的评分体系;而最大值融合(option:max)则选取两个分数中更高的那个。
(可选)重排序(Reranking)添加了一个交叉编码器步骤,通过将查询和文档作为一个整体对进行评估来重新评分结果。这有助于捕捉那些虽然含有正确词汇但并未回答问题的情况。
每个选项都有合理的默认值,如果未指定。你可以在创建新实例时灵活配置所需的内容:
const instance = await env.AI_SEARCH.create({
id: "my-instance",
index_method: { keyword: true, vector: true },
indexing_options: {
keyword_tokenizer: "porter"
},
retrieval_options: {
keyword_match_mode: "or"
},
fusion_method: "rrf",
reranking: true,
reranking_model: "@cf/baai/bge-reranker-base"
});
提升相关性:突出重要信息
检索功能可以带来相关结果,但仅靠相关性并不总是足够。例如,在新闻搜索中,一篇上周的文章和一篇三年前的文章可能都与 "选举结果" 语义相关,但大多数用户可能更希望看到近期的文章。提升机制允许你在检索基础上叠加业务逻辑,通过文档元数据调整排名。
你可以基于时间戳(每条记录内置)或任何自定义元数据字段进行提升。
// 提升高优先级文档
const results = await instance.search({
query: "deployment guide",
ai_search_options: {
boost_by: [
{ field: "timestamp", direction: "desc" }
}
});
跨实例搜索:突破边界进行查询
在支持代理的例子中,产品文档和客户解决历史记录按设计分别存储在不同的实例中。但当代理回答问题时,它需要同时从这两个地方获取上下文。如果没有跨实例搜索功能,你就得进行两次独立的调用,然后自己合并结果。
命名空间绑定提供了一个 search() 方法来帮你处理这个问题。传入一个实例名称数组,就能得到一个统一排序的结果列表:
const results = await env.SUPPORT_KB.search({
query: "billing error",
ai_search_options: {
instance_ids: ["product-knowledge", "customer-abc123"]
}
});
结果会跨实例合并并排序。代理不需要知道或关心共享文档和客户解决历史记录分别存储在不同位置。
AI Search 实例如何工作
到目前为止,我们已经介绍了 AI Search 如何找到正确的结果。现在来看看你如何创建和管理自己的搜索实例。
如果你之前使用过 AI Search,那么你知道之前的设置流程:创建一个 R2 存储桶,将其链接到一个 AI Search 实例,AI Search 会为你生成一个服务 API 令牌,并且你需要管理在账户上分配的 Vectorize 索引。上传对象时,你需要写入 R2,然后等待同步任务完成才能让对象被索引。
现在新建的实例运作方式不同了。当你调用 create() 时,实例自带存储和向量索引。你可以上传文件,文件会被立即发送去索引,而且你可以通过一个 uploadAndPoll() API 轮询索引状态。一旦完成,你就可以立即在这个实例中进行搜索,且无需再配置任何外部依赖。
const instance = env.AI_SEARCH.get("my-instance");
// 上传并等待索引完成
const item = await instance.items.uploadAndPoll("faq.md", content, {
metadata: { category: "onboarding" }
});
console.log(item.status); // "completed"
// 索引完成后立即搜索
const results = await instance.search({
// 除了使用 query 参数外,也可以用 messages 来传递用户查询
messages: [{ role: "user", content: "onboarding guide" }],
});
每个实例还可以连接一个外部数据源(R2 存储桶或网站),并按照同步计划运行。它可以与内置存储共存。在支持代理的例子中,product-knowledge 实例由一个 R2 存储桶支撑,用于存放共享文档;而每个客户的实例则使用内置存储来实时上传上下文。
命名空间:在运行时创建搜索实例
ai_search_namespaces 是一个你可以用来在运行时动态创建搜索实例的新绑定。它取代了旧版的 env.AI.autorag() API,后者通过绑定访问 AI Search。旧版绑定将继续使用 Workers 兼容性日期继续工作。
// wrangler.jsonc
{
"ai_search_namespaces": [
{ "binding": "AI_SEARCH", "namespace": "example" },
}
命名空间绑定让你可以在命名空间级别使用 create()、delete()、list() 和 search() 等 API。如果你正在动态创建实例(例如按代理、客户或租户),这就是你应该使用的绑定。
// 创建一个实例
const instance = await env.AI_SEARCH.create({
id: "my-instance"
});
// 删除实例及其所有索引数据
await env.AI_SEARCH.delete("old-instance");
新实例的定价
从今天起创建的新实例将自动获得内置存储和向量索引。
在 AI Search 处于公开测试阶段时,这些实例免费使用,具体限制如下。当以网站作为数据源时,浏览器运行(原浏览器渲染)功能也已作为内置服务提供,这意味着您无需为它单独付费。测试结束后,我们的目标是将 AI Search 作为一个统一的服务进行定价,而不是分别对每个底层组件计费。Workers AI 和 AI Gateway 的使用将继续单独计费。
我们将在开始收费前至少提前30天通知,并公布详细的定价信息。
限制条件
Workers 免费版
Workers 付费版
每个账户的 AI Search 实例数量
100
5,000
每个实例的文件数量
100,000
100万或混合搜索50万
单个文件最大大小
4MB
4MB
每月查询次数
20,000
无限
每日最多爬取页面数
500
无限
现有实例的情况如何?
如果您在本次发布之前创建了实例,它们将继续像现在一样正常工作。您的 R2 存储桶、Vectorize 索引以及浏览器运行的使用情况仍保留在您的账户中,并按原有方式计费。我们很快会分享现有实例的迁移详情。
立即开始使用
搜索是代理最基础的能力之一。借助 AI Search,您无需自行搭建基础设施即可实现这一功能。只需创建一个实例,导入您的数据,然后让代理进行搜索即可。
立即开始,通过以下命令创建您的第一个实例:
npx wrangler ai-search create my-search
查看文档并前往 Cloudflare 开发者 Discord 社区告诉我们您正在构建什么。
来源与参考
收录于 2026-04-17