跳转到内容
搜索文档

将 OpenAPI 服务与 Code Mode 结合使用

最后更新 查看 MarkdownAgent 设置

使用 OpenApiConnector 在持久 Code Mode 运行时内暴露 OpenAPI 服务。连接器为 OpenAPI 文档中的每个操作推导一个沙箱方法。

模型可用 codemode.search() 发现方法,用 codemode.describe() 请求聚焦的输入类型。完整 OpenAPI 文档无需进入模型上下文。

本页介绍 Agent 消费 OpenAPI 服务。要通过 searchexecute 向外部 MCP client 发布 OpenAPI 服务,请参阅构建 search and execute MCP server

前置条件

项目需配置 持久 Code Mode 运行时。运行时设置提供本指南使用的 Worker Loader 绑定与 CodemodeRuntime 导出。

创建 OpenAPI connector

  1. 将 OpenAPI 文档加入项目。为每个 operation 提供唯一 operationId,以产生 stable sandbox method 名:

    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. 创建 connector。实现 spec() 返回文档,request() 发起已认证的 host-side 请求:

    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;
    	}
    }

    凭证保留在 host Worker。Model 代码收到 connector method 及其结果,而非 ORDERS_API_TOKEN

    tool() hook 装饰 derived operation。本例在 create_order 执行前要求 approval。也可用该 hook 添加 replay 或 rollback 行为。

  3. 导入 connector 并加入 runtime:

    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. 让 model 发现 operation 并调用生成的 connector method:

    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 以保持方法名稳定并避免冲突。

每个生成方法接受一个对象:

  • Path、query、header 参数为 top-level 字段。
  • JSON 请求体在 body 下。
  • 必填 OpenAPI 参数成为必填 TypeScript 字段。
  • Input schema 中的 local $ref 在生成类型前 resolve。

连接器替换路径参数并向 request() 传递规范化的 { path, method, params, body, headers }

当前 connector 推导 input 类型但不从 OpenAPI response schema 推导 response 类型。因此生成 method 返回 unknown,除非应用通过其他 connector 实现提供更具体声明。

请求逃生舱

每个 OpenAPI connector 还暴露 low-level request() sandbox method。OpenAPI 文档未描述 model 所需 operation 时使用:

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

有派生操作方法时优先使用。它们提供可发现的描述与生成的输入类型。

exposeSpec() 默认返回 false。仅当模型代码需要原始 OpenAPI 文档时重写返回 true。大文档可能产生大结果与持久日志条目。

这篇文档对您有帮助吗?