OpenAICloudflare2026/04/16 13:00

AI Search: the search primitive for your agents

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

openaijamodel: gpt-5-mini-2025-08-07

AI Search:エージェント向け検索プリミティブ

Key Points

  • ハイブリッド検索
  • インスタンス動的作成
  • メタデータブースト

Summary

AI Search(旧 AutoRAG)は、エージェント向けに設計されたプラグアンドプレイの検索プリミティブです。ベクトル検索とBM25キーワード検索を同時に実行して結果を融合するハイブリッド検索、インスタンスごとの組み込みストレージとベクトルインデックス、ランタイムでインスタンスを作成・削除する ai_search_namespaces バインディング、メタデータによるランキングブースト、複数インスタンス横断検索などを提供します。Agents SDK / Workers と組み合わせて、顧客ごとの履歴や共有ドキュメントを即時に検索・保存できる実装が可能です。

Key Points

  • ハイブリッド検索: ベクトル検索とBM25を並列実行して結果を融合(オプションで再ランキング)。
  • 組み込みストレージとインデックス: 新規インスタンスはR2とVectorizeベースのストレージ+ベクトルインデックスを持つ。ファイルはAPI経由でアップロードして自動インデックス化。
  • ランタイム管理: ai_search_namespaces でワーカーから create / delete が可能。エージェントごと・顧客ごと・言語ごとにインスタンスを動的に分けられる。
  • マルチインスタンス検索: 検索時に instance_ids を指定して共有KBと顧客固有KBを同時検索(例: product-knowledgecustomer-<id>)。
  • 即時検索可能にするアップロード: インデックス完了まで待つ instance.items.uploadAndPoll(filename, content) を利用して、保存直後に検索可能にする。
  • 検索設定(実用オプション): index_methodkeyword/vector)、keyword_tokenizerporter/trigram)、keyword_match_modeand/or)、fusion_methodrrf/max)、rerankingreranking_model
  • メタデータブースト: ドキュメントのメタデータ(例: timestamp)を boost_by で優先表示可能。

Practical tips

  • インスタンス作成例: await env.SUPPORT_KB.create({ id: 'customer-123', index_method: { keyword: true, vector: true } })
  • 検索例: await env.SUPPORT_KB.search({ query, ai_search_options: { instance_ids: ['product-knowledge','customer-123'], boost_by: [{ field: 'timestamp', direction: 'desc' }] } })
  • 長期運用: コード用は trigram、自然言語用は porter を使い分け、重要なビジネスルールはメタデータブーストで反映する。

Where it helps

  • サポートエージェントの顧客ごとの解決履歴管理と共有ドキュメントの参照
  • コード検索とトラブルシューティングで「正確な用語」と「意味的類似」を両立させたいケース
  • マルチテナント/多言語でインスタンスを分けてスケールさせたいシステム

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

openaijamodel: gpt-5-mini-2025-08-07

AI Search:エージェント向け検索プリミティブ

AI Search:エージェント向け検索プリミティブ

2026-04-16 • Gabriel Massadas、Miguel Cardoso、Anni Wang • 読了約7分

すべてのエージェントは検索を必要とします。コーディングエージェントはリポジトリ内の何百万ものファイルを検索し、サポートエージェントは顧客チケットや社内ドキュメントを検索します。ユースケースは異なりますが、根本的な問題は同じです:モデルに適切な情報を適切なタイミングで渡すこと。

独自に検索を構築する場合、ベクトルインデックス、ドキュメントを解析・チャンク化するインデクシングパイプライン、データが変化したときにインデックスを最新に保つ仕組みが必要です。キーワード検索も必要なら、別のインデックスとその上の融合ロジックが必要になります。さらに各エージェントごとに検索可能なコンテキストが必要なら、それらをエージェント単位でセットアップしなければなりません。

AI Search(旧 AutoRAG)は、そのためのプラグアンドプレイな検索プリミティブです。インスタンスを動的に作成し、データを与え、Worker、Agents SDK、または Wrangler CLI から検索できます。

主な提供内容:

  • ハイブリッド検索(Hybrid search)

    • セマンティック検索(vector)とキーワード検索(BM25)を同一クエリで有効にできます。ベクトル検索とBM25を並列で実行し、結果を融合します。
    • (このブログの検索は現在 AI Search によって動いています。右上の虫眼鏡アイコンを試してください。)
  • 組み込みのストレージとインデックス

    • 新しいインスタンスには専用のストレージとベクトルインデックスが付属します。ファイルをAPI経由でインスタンスに直接アップロードすればインデックス化されます。R2バケットを事前にセットアップしたり、外部データソースを先に接続したりする必要はありません。
  • ai_search_namespaces バインディング

    • このバインディングを使うと、Worker からランタイムにインスタンスを作成・削除できます。エージェントごと、顧客ごと、言語ごとに再デプロイなしでインスタンスを立ち上げられます。
  • ドキュメントにメタデータを付与してクエリ時にランクをブーストできる機能、複数インスタンスを単一の呼び出しで横断検索する機能。

以下では、実際の利用例を見ていきます。

実践:カスタマーサポートエージェント

共通の製品ドキュメントと、顧客固有の履歴(過去の解決履歴)という2種類の知識を検索するサポートエージェントを例に説明します。製品ドキュメントはコンテキストウィンドウに収まりきらないことが多く、顧客ごとの履歴は解決ごとに増えていくため、関連する情報を取り出すために検索(retrieval)が必要です。

以下は AI Search と Agents SDK を使った構成例です。

まずプロジェクトをスキャフォールドします:

npm create cloudflare@latest -- --template cloudflare/agents-starter

最初に、Worker に AI Search ネームスペースをバインドします(wrangler.jsonc の例):

// wrangler.jsonc
{
  "ai_search_namespaces": [
    {
      "binding": "SUPPORT_KB",
      "namespace": "support"
    }
  ],
  "ai": {
    "binding": "AI"
  },
  "durable_objects": {
    "bindings": [
      {
        "name": "SupportAgent",
        "class_name": "SupportAgent"
      }
    ]
  }
}

たとえば、共通の製品ドキュメントが R2 バケット product-doc にあるとします。Cloudflare Dashboard からサポート用ネームスペース内に、そのバケットをソースにした一時的(one-off)AI Search インスタンス product-knowledge を作成できます。

それが共有ナレッジベースで、すべてのエージェントが参照できるドキュメント群になります。顧客が再度問題を報告した際に、既に試したことを把握しておくと時間が節約できます。

顧客ごとの履歴は、顧客毎に AI Search インスタンスを作ることで追跡できます。各解決後に、エージェントは何が起こり、どのように直したかの要約を保存します。これにより過去の解決履歴の検索可能なログが蓄積されます。

ネームスペースバインディングを使ってインスタンスを動的に作成できます:

// 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: "support"
    • product-knowledge (R2 をソースに、全エージェントで共有)
    • customer-abc123 (managed storage, per-customer)
    • customer-def456 (managed storage, per-customer)
    • customer-ghi789 (managed storage, per-customer)

エージェント本体

エージェントは Agents SDK の AIChatAgent を拡張し、2つのツールを定義します。LLM には 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) {
    // 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 は多段階の検索パイプラインを実行し、各ステップは設定可能です。

ハイブリッド検索:意図を理解しつつ用語も一致させる検索

これまでは AI Search はベクトル検索だけを提供していました。ベクトル検索は意図の理解に優れますが、詳細(特定の用語)を取りこぼすことがあります。例えばクエリ "ERR_CONNECTION_REFUSED timeout" では、埋め込みは接続失敗という概念を捉えますが、ユーザーは "ERR_CONNECTION_REFUSED" を含む特定のドキュメントを探しています。ベクトル検索は一般的なトラブルシューティングの結果を返すかもしれませんが、エラー文字列そのものを含むページを表面化しない可能性があります。

キーワード検索(BM25)はこのギャップを埋めます。BM25 はクエリ用語の出現頻度、コーパス全体での希少性、ドキュメント長を元にスコアリングします。特定の用語一致を重視し、一般的な助詞などを減点し、文書長で正規化します。クエリ "ERR_CONNECTION_REFUSED timeout" なら、BM25 は実際に "ERR_CONNECTION_REFUSED" を含むドキュメントを見つけます。とはいえ BM25 は「ネットワーク接続のトラブルシューティング」的なページを見逃すかもしれません。そこをベクトル検索が補完するため、両方が必要になります。

ハイブリッド検索を有効にするとベクトルとBM25を並列で実行し、結果を融合して(必要に応じて再ランク付けも行い)返します。

以下は BM25 の新しい設定とそれらがどのように組み合わさるかの概要です。

  • tokenizer(トークナイザー)

    • ドキュメントをインデックス時にマッチ可能な用語へ分割する方法を制御します。
    • porter(Porter stemmer):"running" が "run" にステミングされるなど、自然言語コンテンツ向け。
    • trigram:文字列の部分一致を扱うので、コード内で部分一致が重要な場合に有効("conf" が "configuration" にマッチするなど)。
  • keyword match mode(キーワード一致モード)

    • クエリ時に BM25 の候補となるドキュメントの選び方を制御します。
    • AND は全てのクエリ用語が出現するドキュメントのみ、OR は少なくとも1つの用語があるものを含めます。
  • fusion(融合)

    • クエリ時にベクトルとキーワードの結果を最終的な結果リストにどのように統合するかを制御します。
    • rrf(reciprocal rank fusion):スコアではなく順位位置でマージするため、互換性のないスコア尺度を比較する問題を避けます。
    • 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"
});

関連度のブースト:重要なものを表面化する

検索は関連性の高い結果を返しますが、関連性だけでは十分でないことがあります。たとえばニュース検索では「選挙結果」に関する記事が1週間前のものと3年前のものとで両方関連していても、ほとんどのユーザーはより最近の記事を欲しがるでしょう。ブースト(boosting)はドキュメントのメタデータに基づいてランキングを押し上げることで、ビジネスロジックを検索結果に重ねることを可能にします。