Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Code Mode で OpenAPI サービスを使う

最終更新 Markdown で表示Agent セットアップ

OpenApiConnector を使うと、耐久性のある Code Mode ランタイム内で OpenAPI サービスを公開できます。コネクターは、OpenAPI ドキュメントの各オペレーションからサンドボックスメソッドを 1 つ導出します。

モデルは codemode.search() でメソッドを発見し、codemode.describe() で絞った入力型を取得できます。OpenAPI ドキュメント全体をモデルのコンテキストに入れる必要はありません。

このページは、エージェントが OpenAPI サービスを利用する場合です。searchexecute 経由で外部 MCP クライアントへ OpenAPI サービスを公開するには、search と execute の MCP サーバーを構築する を参照してください。

前提条件

耐久性のある Code Mode ランタイム を設定したプロジェクトが必要です。ランタイムのセットアップで、このガイドが使う Worker Loader バインディングと CodemodeRuntime のエクスポートが用意されます。

OpenAPI コネクターを作成する

  1. プロジェクトに OpenAPI ドキュメントを追加します。各オペレーションに一意の operationId を付け、安定したサンドボックスメソッド名にします。

    src/orders-openapi.jsjs
    export const ordersOpenApiSpec = {
    	openapi: "3.1.0",
    	info: { title: "Orders API", version: "1.0.0" },
    	paths: {
    		"/orders/{orderId}": {
    			get: {
    				operationId: "get_order",
    				summary: "Get an order by ID.",
    				parameters: [
    					{
    						name: "orderId",
    						in: "path",
    						required: true,
    						schema: { type: "string" },
    					},
    				],
    			},
    		},
    		"/orders": {
    			post: {
    				operationId: "create_order",
    				summary: "Create an order.",
    				requestBody: {
    					required: true,
    					content: {
    						"application/json": {
    							schema: {
    								type: "object",
    								properties: {
    									productId: { type: "string" },
    									quantity: { type: "integer" },
    								},
    								required: ["productId", "quantity"],
    							},
    						},
    					},
    				},
    			},
    		},
    	},
    };
    src/orders-openapi.tsts
    export const ordersOpenApiSpec = {
    	openapi: "3.1.0",
    	info: { title: "Orders API", version: "1.0.0" },
    	paths: {
    		"/orders/{orderId}": {
    			get: {
    				operationId: "get_order",
    				summary: "Get an order by ID.",
    				parameters: [
    					{
    						name: "orderId",
    						in: "path",
    						required: true,
    						schema: { type: "string" },
    					},
    				],
    			},
    		},
    		"/orders": {
    			post: {
    				operationId: "create_order",
    				summary: "Create an order.",
    				requestBody: {
    					required: true,
    					content: {
    						"application/json": {
    							schema: {
    								type: "object",
    								properties: {
    									productId: { type: "string" },
    									quantity: { type: "integer" },
    								},
    								required: ["productId", "quantity"],
    							},
    						},
    					},
    				},
    			},
    		},
    	},
    } as const;
  2. コネクターを作成します。ドキュメントを返す spec() と、ホスト側で認証付きリクエストを行う request() を実装します。

    src/orders-connector.jsjs
    import { OpenApiConnector } from "@cloudflare/codemode";
    import { ordersOpenApiSpec } from "./orders-openapi";
    
    const API_ORIGIN = "https://api.example.com";
    
    export class OrdersConnector extends OpenApiConnector {
    	name() {
    		return "orders";
    	}
    
    	instructions() {
    		return "Use for reading and creating orders.";
    	}
    
    	spec() {
    		return ordersOpenApiSpec;
    	}
    
    	async request(options) {
    		if (!options.path.startsWith("/")) {
    			throw new Error("Orders API path must start with a slash");
    		}
    
    		const url = new URL(options.path, API_ORIGIN);
    		for (const [key, value] of Object.entries(options.params ?? {})) {
    			if (value !== undefined) {
    				url.searchParams.set(key, String(value));
    			}
    		}
    
    		const response = await fetch(url, {
    			method: options.method ?? "GET",
    			headers: {
    				...(options.body !== undefined
    					? { "Content-Type": "application/json" }
    					: {}),
    				...options.headers,
    				Authorization: `Bearer ${this.env.ORDERS_API_TOKEN}`,
    			},
    			body:
    				options.body === undefined ? undefined : JSON.stringify(options.body),
    		});
    
    		if (!response.ok) {
    			throw new Error(`Orders API request failed: ${response.status}`);
    		}
    		if (response.status === 204) return null;
    		return response.json();
    	}
    
    	tool(name, tool) {
    		if (name === "create_order") {
    			return { ...tool, requiresApproval: true };
    		}
    		return tool;
    	}
    }
    src/orders-connector.tsts
    import {
    	OpenApiConnector,
    	type ConnectorTool,
    	type OpenApiRequestOptions,
    } from "@cloudflare/codemode";
    import { ordersOpenApiSpec } from "./orders-openapi";
    
    const API_ORIGIN = "https://api.example.com";
    
    export class OrdersConnector extends OpenApiConnector<Env> {
    	override name() {
    		return "orders";
    	}
    
    	protected override instructions() {
    		return "Use for reading and creating orders.";
    	}
    
    	protected override spec() {
    		return ordersOpenApiSpec;
    	}
    
    	protected override async request(options: OpenApiRequestOptions) {
    		if (!options.path.startsWith("/")) {
    			throw new Error("Orders API path must start with a slash");
    		}
    
    		const url = new URL(options.path, API_ORIGIN);
    		for (const [key, value] of Object.entries(options.params ?? {})) {
    			if (value !== undefined) {
    				url.searchParams.set(key, String(value));
    			}
    		}
    
    		const response = await fetch(url, {
    			method: options.method ?? "GET",
    			headers: {
    				...(options.body !== undefined
    					? { "Content-Type": "application/json" }
    					: {}),
    				...options.headers,
    				Authorization: `Bearer ${this.env.ORDERS_API_TOKEN}`,
    			},
    			body:
    				options.body === undefined
    					? undefined
    					: JSON.stringify(options.body),
    		});
    
    		if (!response.ok) {
    			throw new Error(`Orders API request failed: ${response.status}`);
    		}
    		if (response.status === 204) return null;
    		return response.json();
    	}
    
    	protected override tool(name: string, tool: ConnectorTool): ConnectorTool {
    		if (name === "create_order") {
    			return { ...tool, requiresApproval: true };
    		}
    		return tool;
    	}
    }

    認証情報はホスト Worker に残ります。モデルが書いたコードが受け取るのはコネクターメソッドとその結果であり、ORDERS_API_TOKEN ではありません。

    tool() フックは導出したオペレーションを装飾します。この例では、create_order の実行前に承認を求めます。フックでリプレイやロールバックの振る舞いを追加することもできます。

  3. コネクターをインポートし、ランタイムに追加します。

    src/server.jsjs
    import { AIChatAgent } from "@cloudflare/ai-chat";
    import {
    	createCodemodeRuntime,
    	DynamicWorkerExecutor,
    } from "@cloudflare/codemode";
    import { OrdersConnector } from "./orders-connector";
    
    export class Chat extends AIChatAgent {
    	#runtime() {
    		return createCodemodeRuntime({
    			ctx: this.ctx,
    			executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }),
    			connectors: [new OrdersConnector(this.ctx, this.env)],
    		});
    	}
    
    	async onChatMessage() {
    		const tools = { codemode: this.#runtime().tool() };
    		// Pass tools to your model call.
    	}
    }
    src/server.tsts
    import { AIChatAgent } from "@cloudflare/ai-chat";
    import {
    	createCodemodeRuntime,
    	DynamicWorkerExecutor,
    } from "@cloudflare/codemode";
    import { OrdersConnector } from "./orders-connector";
    
    export class Chat extends AIChatAgent<Env> {
    	#runtime() {
    		return createCodemodeRuntime({
    			ctx: this.ctx,
    			executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }),
    			connectors: [new OrdersConnector(this.ctx, this.env)],
    		});
    	}
    
    	async onChatMessage() {
    		const tools = { codemode: this.#runtime().tool() };
    		// Pass tools to your model call.
    	}
    }
  4. モデルにオペレーションを発見させ、生成されたコネクターメソッドを呼び出させます。

    async () => {
    	const matches = await codemode.search("get an order by ID");
    	const docs = await codemode.describe(matches.results[0].path);
    
    	const order = await orders.get_order({ orderId: "order-123" });
    	return { docs, order };
    };

導出メソッドの動作

OpenApiConnector は、サニタイズした operationId をメソッド名に使います。オペレーションに operationId がない場合は、HTTP メソッドとパスから名前を導出します。メソッド名を安定させ、衝突を避けるために、一意のオペレーション ID を定義します。

生成される各メソッドは、オブジェクトを 1 つ受け取ります。

  • パス、クエリ、ヘッダーのパラメーターはトップレベルのフィールドになります。
  • JSON リクエストボディは body の下に置かれます。
  • 必須の OpenAPI パラメーターは、必須の TypeScript フィールドになります。
  • 入力スキーマ内のローカル $ref は、型生成前に解決されます。

コネクターはパスパラメーターを代入し、正規化した { path, method, params, body, headers } オブジェクトを request() へ渡します。

現在のコネクターは入力型を導出しますが、OpenAPI のレスポンススキーマからレスポンス型は導出しません。そのため、生成メソッドの戻り値は unknown です。別のコネクター実装で、より具体的な宣言を付ける場合を除きます。

リクエストのエスケープハッチ

すべての OpenAPI コネクターは、低レベルの request() サンドボックスメソッドも公開します。OpenAPI ドキュメントに、モデルが必要とするオペレーションが無いときに使います。

const result = await orders.request({
	path: "/orders",
	method: "GET",
	params: { status: "processing" },
});

使えるときは、導出されたオペレーションメソッドを優先します。発見しやすい説明と、生成された入力型が付きます。

exposeSpec() のデフォルト戻り値は false です。モデルが書いたコードが生の OpenAPI ドキュメントにアクセスする必要があるときだけ、true を返すようオーバーライドします。大きなドキュメントは、大きな結果と耐久ログエントリを生みます。

役に立ちましたか?