AI Search:为你的 agent 而生的搜索原语
原文:AI Search: the search primitive for your agents Source: https://blog.cloudflare.com/ai-search-agent-primitive/
2026-04-16
每个 agent 都需要搜索:编码 agent 在仓库中搜索数百万个文件,或者支持 agent 搜索客户工单和内部文档。用例不同,但底层问题是一样的:在合适的时间将正确的信息送到模型面前。
如果你自己构建搜索,你需要一个向量索引、一个解析并切分文档的索引管线,以及一个在数据变化时让索引保持最新的东西。如果你还需要关键词搜索,那是一个独立的索引,加上在它之上的融合逻辑。如果你的每个 agent 都需要自己的可搜索上下文,你就要为每个 agent 设置所有这些。
AI Search(原 AutoRAG)就是你需要的即插即用的搜索原语。你可以动态创建实例,把数据交给它,然后搜索——从 Worker、Agents SDK 或 Wrangler CLI。下面是我们正在发布的内容:
-
混合搜索。在同一个查询中同时启用语义匹配和关键词匹配。向量搜索和 BM25 并行运行,结果融合在一起。(我们博客的搜索现在由 AI Search 提供支持。请试试右上角的放大镜图标。)
-
内置存储和索引。 新实例自带其存储和向量索引。通过 API 直接将文件上传到一个实例,它们就被索引了。无需设置 R2 bucket,无需先连接外部数据源。新的
ai_search_namespacesbinding 让你可以从你的 Worker 在运行时创建和删除实例,所以你可以为每个 agent、每个客户或每种语言启动一个,无需重新部署。
你现在还可以给文档附加 metadata,在查询时用它来提升排名,以及在单次调用中跨多个实例查询。
现在,让我们看看这在实践中意味着什么。
实战:客户支持 Agent
我们走过一个 support agent 的例子,它搜索两种知识:共享的产品文档,以及像过去解决方案那样的 per-customer 历史。产品文档太大,无法装入上下文窗口,而每个客户的历史随每次解决的 issue 增长,所以 agent 需要检索来找到相关内容。
下面是用 AI Search 和 Agents SDK 实现的样子。从脚手架一个项目开始:
npm create cloudflare@latest -- --template cloudflare/agents-starter
首先,把 AI Search namespace 绑定到你的 Worker:
// wrangler.jsonc
{
"ai_search_namespaces": [
{ "binding": "SUPPORT_KB", "namespace": "support" }
],
"ai": { "binding": "AI" },
"durable_objects": {
"bindings": [
{ "name": "SupportAgent", "class_name": "SupportAgent" }
]
}
}
假设你的共享产品文档放在一个名为 product-doc 的 R2 bucket 中。你可以在 Cloudflare Dashboard 上,在 support namespace 中,以该 bucket 为后端创建一次性的 AI Search 实例(命名为 product-knowledge):
那是你的共享知识库,每个 agent 都可以引用的文档。
当一个客户带着新问题回来时,知道之前已经尝试过什么可以为大家节省时间。你可以通过为每个客户创建一个 AI Search 实例来追踪它。每次解决一个问题后,agent 会保存一份关于哪里出了问题以及如何修复的总结。随着时间推移,这构建了一份过去解决方案的可搜索日志。你可以使用 namespace binding 动态创建实例:
// create a per-customer instance when they first show up
await env.SUPPORT_KB.create({
id: `customer-${customerId}`,
index_method:{ keyword: true, vector: true }
});
每个实例得到自己的内置存储和向量索引——由 R2 和 Vectorize 提供支持。实例从空开始,随时间累积上下文。下次客户回来时,所有这些都是可搜索的。
下面是几个客户之后这个 namespace 的样子:
namespace: "support"
├── product-knowledge (R2 as source, shared across all agents)
├── customer-abc123 (managed storage, per-customer)
├── customer-def456 (managed storage, per-customer)
└── customer-ghi789 (managed storage, per-customer)
现在是 agent 本身。它扩展了 Agents SDK 的 AIChatAgent,定义了两个工具。我们使用 Kimi K2.5 作为 LLM,通过 Workers AI。模型根据对话决定何时调用工具:
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) {
// the client passes customerId in the request body
// via the Agent SDK's sendMessage({ body: { customerId } })
const customerId = options?.body?.customerId;
// create a per-customer instance when they first show up.
// each instance gets its own storage and vector index.
if (customerId) {
try {
await this.env.SUPPORT_KB.create({
id: `customer-${customerId}`,
index_method: { keyword: true, vector: true }
});
} catch {
// instance already exists
}
}
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/moonshotai/kimi-k2.5"),
system: `You are a support agent. Use search_knowledge_base
to find relevant docs before answering. Search results
include both product docs and this customer's past
resolutions — use them to avoid repeating failed fixes
and to recognize recurring issues. When the issue is
resolved, call save_resolution before responding.`,
// this.messages is the full conversation history, automatically
// persisted by AIChatAgent across reconnects
messages: await convertToModelMessages(this.messages),
tools: {
// tool 1: search across shared product docs AND this
// customer's past resolutions in a single call
search_knowledge_base: tool({
description: "Search product docs and customer history",
inputSchema: z.object({
query: z.string().describe("The search query"),
}),
execute: async ({ query }) => {
// always search product docs;
// include customer history if available
const instances = ["product-knowledge"];
if (customerId) {
instances.push(`customer-${customerId}`);
}
return await this.env.SUPPORT_KB.search({
query: query,
ai_search_options: {
// surface recent docs over older ones
boost_by: [
{ field: "timestamp", direction: "desc" }
],
// search across both instances at once
instance_ids: instances
}
});
}
}),
// tool 2: after resolving an issue, the agent saves a
// summary so future agents have full context
save_resolution: tool({
description:
"Save a resolution summary after solving a customer's issue",
inputSchema: z.object({
filename: z.string().describe(
"Short descriptive filename, e.g. 'billing-fix.md'"
),
content: z.string().describe(
"What the problem was, what caused it, and how it was resolved"
),
}),
execute: async ({ filename, content }) => {
if (!customerId) return { error: "No customer ID" };
const instance = this.env.SUPPORT_KB.get(
`customer-${customerId}`
);
// uploadAndPoll waits until indexing is complete,
// so the resolution is searchable before the next query
const item = await instance.items.uploadAndPoll(
filename, content
);
return { saved: true, filename, status: item.status };
}
}),
},
// cap agentic tool-use loops at 10 steps
stopWhen: stepCountIs(10),
abortSignal: options?.abortSignal,
});
return result.toUIMessageStreamResponse();
}
}
// route requests to the SupportAgent durable object
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 Search 如何找到你要找的东西
在底层,AI Search 运行一个多步骤的检索管线,其中每一步都是可配置的。
Hybrid Search:理解意图并匹配术语的搜索
到目前为止,AI Search 只提供向量搜索。向量搜索擅长理解意图,但可能丢失具体细节。在查询 “ERR_CONNECTION_REFUSED timeout” 中,embedding 捕获了连接故障的宽泛概念。但用户不是在找一般的网络文档。他们要找的是提到 “ERR_CONNECTION_REFUSED” 的具体那篇文档。向量搜索可能返回关于故障排除的结果,而不必浮出包含该确切错误字符串的页面。
关键词搜索填补了这个空白。AI Search 现在支持 BM25,这是最广泛使用的检索打分函数之一。BM25 根据查询词在文档中出现的频率、这些词在整个语料库中的稀有程度,以及文档的长度来给文档打分。它奖励特定术语的匹配,惩罚常见填充词,并按文档长度归一化。当你搜索 “ERR_CONNECTION_REFUSED timeout” 时,BM25 找到实际包含 “ERR_CONNECTION_REFUSED” 这个术语的文档。然而,BM25 可能会错过一篇关于“网络连接故障排除“的页面,即使它描述的是同一问题。这是向量搜索的强项,也是为什么你需要两者。
当你启用 hybrid search 时,它并行运行向量和 BM25,融合结果,可选地重排:
让我们看看 BM25 的新配置,以及它们如何组合在一起。
-
Tokenizer 控制你的文档在索引时如何被切分成可匹配的术语。Porter stemmer(选项:
porter)对单词进行词干提取,这样 “running” 匹配 “run”。Trigram(选项:trigram)匹配字符子串,这样 “conf” 匹配 “configuration”。你可以用 porter 处理像文档这样的自然语言内容,用 trigram 处理代码,因为代码中部分匹配很重要。 -
Keyword match mode 控制查询时哪些文档作为 BM25 打分的候选。
AND要求所有查询词出现在一个文档中,OR 包含至少有一个匹配的任何文档。 -
Fusion 控制查询时向量和关键词结果如何被组合成最终的结果列表。Reciprocal rank fusion(选项:
rrf)按排名位置而非分数合并,这样避免比较两个不兼容的打分尺度,而 max fusion(选项:max)取较高分数。 -
(可选)Reranking 添加一个 cross-encoder 通道,通过一起评估查询和文档作为一对来重新打分。它有助于捕获结果有正确术语但没有回答问题的情况。
每个选项在省略时都有合理的默认值。每次创建一个新实例时,你都可以灵活地配置重要的事情:
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"
});
Boost relevance:浮出重要的内容
检索给你相关的结果,但仅有相关性并不总是够的。例如,在新闻搜索中,上周的一篇文章和三年前的一篇文章在语义上可能都与“选举结果“相关,但大多数用户可能想要最近的那个。Boosting 让你可以在检索之上叠加业务逻辑,根据文档元数据微调排名。
你可以基于 timestamp(每个 item 都内置)或你定义的任何自定义元数据字段进行 boost。
// boost high priority docs
const results = await instance.search({
query: "deployment guide",
ai_search_options: {
boost_by: [
{ field: "timestamp", direction: "desc" }
]
}
});
Cross-instance search:跨边界查询
在 support agent 例子中,产品文档和客户解决方案历史按设计存放在独立的实例中。但当 agent 回答问题时,它需要同时来自两个地方的上下文。没有 cross-instance search,你需要做两次单独的调用并自己合并结果。
Namespace binding 暴露了一个 search() 方法为你处理这件事。传入一个实例名数组,得到一个排名好的列表:
const results = await env.SUPPORT_KB.search({
query: "billing error",
ai_search_options: {
instance_ids: ["product-knowledge", "customer-abc123"]
}
});
结果在不同实例间合并和排名。Agent 不需要知道或关心共享文档和客户解决方案历史存放在不同的地方。
AI Search 实例如何工作
到此,我们覆盖了 AI Search 如何找到正确的结果。现在让我们看看你如何创建和管理你的搜索实例。
如果你在这次发布之前用过 AI Search,你知道这个流程:创建一个 R2 bucket,把它链接到一个 AI Search 实例,AI Search 为你生成一个 service API token,你管理在你账户上配给的 Vectorize 索引。上传一个对象需要你写入 R2,然后等一个同步任务运行才能让对象被索引。
现在创建的新实例工作方式不同。当你调用 create() 时,实例自带其存储和向量索引。你可以上传一个文件,文件被立即送去索引,你可以通过一个 uploadAndpoll() API 轮询索引状态。一旦完成,你就可以立即搜索这个实例,没有外部依赖需要打通。
const instance = env.AI_SEARCH.get("my-instance");
// upload and wait for indexing to complete
const item = await instance.items.uploadAndPoll("faq.md", content, {
metadata: { category: "onboarding" }
});
console.log(item.status); // "completed"
// immediately search after indexing is completed
const results = await instance.search({
// alternative way to pass in users' query other than using parameter query
messages: [{ role: "user", content: "onboarding guide" }],
});
每个实例还可以连接到一个外部数据源(R2 bucket 或网站),并按同步计划运行。它可以与提供的内置存储并存。在 support agent 例子中,product-knowledge 由一个 R2 bucket 支持用于共享文档,而每个客户的实例使用内置存储用于即时上传的上下文。
Namespaces:在运行时创建搜索实例
ai_search_namespaces 是一个新的 binding,你可以利用它在运行时动态创建搜索实例。它取代了之前的 env.AI.autorag() API,后者通过 AI binding 访问 AI Search。旧的 binding 将通过 Workers compatibility dates 继续工作。
// wrangler.jsonc
{
"ai_search_namespaces": [
{ "binding": "AI_SEARCH", "namespace": "example" },
]
}
Namespace binding 在 namespace 层提供 create()、delete()、list() 和 search() 等 API。如果你正在动态创建实例(例如,per agent、per customer、per tenant),这就是要使用的 binding。
// create an instance
const instance = await env.AI_SEARCH.create({
id: "my-instance"
});
// delete an instance and all its indexed data
await env.AI_SEARCH.delete("old-instance");
新实例的定价
今天起创建的新实例将自动获得内置存储和向量索引。
这些实例在 AI Search 处于 open beta 期间使用免费,有以下限制。当使用网站作为数据源时,使用 Browser Run(原 Browser Rendering) 进行的网站爬取现在也是一项内置服务,意味着你不会因此被单独计费。Beta 之后,目标是为 AI Search 作为单一服务提供统一定价,而不是为每个底层组件单独计费。Workers AI 和 AI Gateway 用量将继续单独计费。
我们将至少提前 30 天通知,并在任何计费开始之前传达定价细节。
|
限制 |
Workers Free |
Workers Paid |
|---|---|---|
|
每账户的 AI Search 实例数 |
100 |
5,000 |
|
每实例的文件数 |
100,000 |
1M,或 hybrid search 时 500K |
|
最大文件大小 |
4MB |
4MB |
|
每月查询次数 |
20,000 |
无限 |
|
每天最大爬取页数 |
500 |
无限 |
已存在的实例怎么办?
如果你在这次发布之前创建了实例,它们继续像今天一样工作。你的 R2 bucket、Vectorize 索引和 Browser Run 用量留在你的账户上,按以前的方式计费。我们将很快分享已存在实例的迁移细节。
今天就开始
搜索是 agent 能做的最基本的事情之一。有了 AI Search,你不必构建实现它的基础设施。创建一个实例,把数据交给它,让你的 agent 搜索它。
通过运行这个命令创建你的第一个实例,今天就开始:
npx wrangler ai-search create my-search
查看文档,在 Cloudflare Developer Discord 上告诉我们你正在构建什么。