Skip to content

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

呼び出し可能メソッド

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

呼び出し可能メソッドを使うと、クライアントは WebSocket 経由の RPC(リモートプロシージャコール)でエージェントのメソッドを呼べます。@callable() を付けたメソッドは、ブラウザー、モバイルアプリ、その他のサービスなど、外部クライアントから呼べます。

概要

import { Agent, callable } from "agents";

export class MyAgent extends Agent {
	@callable()
	async greet(name) {
		return `Hello, ${name}!`;
	}
}
import { Agent, callable } from "agents";

export class MyAgent extends Agent {
	@callable()
	async greet(name: string): Promise<string> {
		return `Hello, ${name}!`;
	}
}
// Client
const result = await agent.stub.greet("World");
console.log(result); // "Hello, World!"
// Client
const result = await agent.stub.greet("World");
console.log(result); // "Hello, World!"

仕組み

sequenceDiagram
    participant Client
    participant Agent
    Client->>Agent: agent.stub.greet("World")
    Note right of Agent: Check @callable<br/>Execute method
    Agent-->>Client: "Hello, World!"

@callable() を使う場面

シナリオ 使うもの
ブラウザー / モバイルからエージェントを呼ぶ @callable()
外部サービスからエージェントを呼ぶ @callable()
Worker からエージェントを呼ぶ(同一コードベース) Durable Object RPC(デコレーター不要)
エージェントから別のエージェントを呼ぶ getAgentByName() 経由の Durable Object RPC

@callable() デコレーターは、外部クライアントからの WebSocket ベース RPC 専用です。同じ Worker や別のエージェントから呼ぶときは、標準の Durable Object RPC を直接使います。

基本的な使い方

呼び出し可能メソッドの定義

公開したいメソッドに @callable() デコレーターを付けます。

import { Agent, callable } from "agents";

export class CounterAgent extends Agent {
	initialState = { count: 0, items: [] };

	@callable()
	increment() {
		this.setState({ ...this.state, count: this.state.count + 1 });
		return this.state.count;
	}

	@callable()
	decrement() {
		this.setState({ ...this.state, count: this.state.count - 1 });
		return this.state.count;
	}

	@callable()
	async addItem(item) {
		this.setState({ ...this.state, items: [...this.state.items, item] });
		return this.state.items;
	}

	@callable()
	getStats() {
		return {
			count: this.state.count,
			itemCount: this.state.items.length,
		};
	}
}
import { Agent, callable } from "agents";

export type CounterState = {
	count: number;
	items: string[];
};

export class CounterAgent extends Agent<Env, CounterState> {
	initialState: CounterState = { count: 0, items: [] };

	@callable()
	increment(): number {
		this.setState({ ...this.state, count: this.state.count + 1 });
		return this.state.count;
	}

	@callable()
	decrement(): number {
		this.setState({ ...this.state, count: this.state.count - 1 });
		return this.state.count;
	}

	@callable()
	async addItem(item: string): Promise<string[]> {
		this.setState({ ...this.state, items: [...this.state.items, item] });
		return this.state.items;
	}

	@callable()
	getStats(): { count: number; itemCount: number } {
		return {
			count: this.state.count,
			itemCount: this.state.items.length,
		};
	}
}

クライアントからの呼び出し

クライアントからメソッドを呼ぶ方法は 2 つあります。

agent.stub を使う(推奨)

// Clean, typed syntax
const count = await agent.stub.increment();
const items = await agent.stub.addItem("new item");
const stats = await agent.stub.getStats();
// Clean, typed syntax
const count = await agent.stub.increment();
const items = await agent.stub.addItem("new item");
const stats = await agent.stub.getStats();

agent.call() を使う

// Explicit method name as string
const count = await agent.call("increment");
const items = await agent.call("addItem", ["new item"]);
const stats = await agent.call("getStats");
// Explicit method name as string
const count = await agent.call("increment");
const items = await agent.call("addItem", ["new item"]);
const stats = await agent.call("getStats");

stub プロキシの方が書きやすく、TypeScript のサポートも優れています。

メソッドのシグネチャ

シリアライズ可能な型

引数と戻り値は JSON でシリアライズできる必要があります。

// Valid - primitives and plain objects
class MyAgent extends Agent {
	@callable()
	processData(input) {
		return { result: true };
	}
}

// Valid - arrays
class MyAgent extends Agent {
	@callable()
	processItems(items) {
		return items.map((item) => item.length);
	}
}

// Invalid - non-serializable types
// Functions, Dates, Maps, Sets, etc. cannot be serialized
// Valid - primitives and plain objects
class MyAgent extends Agent {
	@callable()
	processData(input: { name: string; count: number }): { result: boolean } {
		return { result: true };
	}
}

// Valid - arrays
class MyAgent extends Agent {
	@callable()
	processItems(items: string[]): number[] {
		return items.map((item) => item.length);
	}
}

// Invalid - non-serializable types
// Functions, Dates, Maps, Sets, etc. cannot be serialized

非同期メソッド

同期メソッドと非同期メソッドのどちらも使えます。

// Sync method
class MyAgent extends Agent {
	@callable()
	add(a, b) {
		return a + b;
	}
}

// Async method
class MyAgent extends Agent {
	@callable()
	async fetchUser(id) {
		const user = await this.sql`SELECT * FROM users WHERE id = ${id}`;
		return user[0];
	}
}
// Sync method
class MyAgent extends Agent {
	@callable()
	add(a: number, b: number): number {
		return a + b;
	}
}

// Async method
class MyAgent extends Agent {
	@callable()
	async fetchUser(id: string): Promise<User> {
		const user = await this.sql`SELECT * FROM users WHERE id = ${id}`;
		return user[0];
	}
}

戻り値のないメソッド

値を返さないメソッドです。

class MyAgent extends Agent {
	@callable()
	async logEvent(event) {
		await this.sql`INSERT INTO events (name) VALUES (${event})`;
	}
}
class MyAgent extends Agent {
	@callable()
	async logEvent(event: string): Promise<void> {
		await this.sql`INSERT INTO events (name) VALUES (${event})`;
	}
}

クライアント側では、メソッド完了時に解決する Promise が返ります。

await agent.stub.logEvent("user-clicked");
// Resolves when the server confirms execution
await agent.stub.logEvent("user-clicked");
// Resolves when the server confirms execution

ストリーミング応答

AI のテキスト生成のように、時間をかけてデータを出すメソッドには、ストリーミングを使います。

ストリーミングメソッドの定義

import { Agent, callable } from "agents";

export class AIAgent extends Agent {
	@callable({ streaming: true })
	async generateText(stream, prompt) {
		// First parameter is always StreamingResponse for streaming methods

		for await (const chunk of this.llm.stream(prompt)) {
			stream.send(chunk); // Send each chunk to the client
		}

		stream.end(); // Signal completion
	}

	@callable({ streaming: true })
	async streamNumbers(stream, count) {
		for (let i = 0; i < count; i++) {
			stream.send(i);
			await new Promise((resolve) => setTimeout(resolve, 100));
		}
		stream.end(count); // Optional final value
	}
}
import { Agent, callable, type StreamingResponse } from "agents";

export class AIAgent extends Agent {
	@callable({ streaming: true })
	async generateText(stream: StreamingResponse, prompt: string) {
		// First parameter is always StreamingResponse for streaming methods

		for await (const chunk of this.llm.stream(prompt)) {
			stream.send(chunk); // Send each chunk to the client
		}

		stream.end(); // Signal completion
	}

	@callable({ streaming: true })
	async streamNumbers(stream: StreamingResponse, count: number) {
		for (let i = 0; i < count; i++) {
			stream.send(i);
			await new Promise((resolve) => setTimeout(resolve, 100));
		}
		stream.end(count); // Optional final value
	}
}

クライアントでのストリーム受信

// Preferred format (supports timeout and other options)
await agent.call("generateText", [prompt], {
	stream: {
		onChunk: (chunk) => {
			// Called for each chunk
			appendToOutput(chunk);
		},
		onDone: (finalValue) => {
			// Called when stream ends
			console.log("Stream complete", finalValue);
		},
		onError: (error) => {
			// Called if an error occurs
			console.error("Stream error:", error);
		},
	},
});

// Legacy format (still supported for backward compatibility)
await agent.call("generateText", [prompt], {
	onChunk: (chunk) => appendToOutput(chunk),
	onDone: (finalValue) => console.log("Done", finalValue),
	onError: (error) => console.error("Error:", error),
});
// Preferred format (supports timeout and other options)
await agent.call("generateText", [prompt], {
	stream: {
		onChunk: (chunk) => {
			// Called for each chunk
			appendToOutput(chunk);
		},
		onDone: (finalValue) => {
			// Called when stream ends
			console.log("Stream complete", finalValue);
		},
		onError: (error) => {
			// Called if an error occurs
			console.error("Stream error:", error);
		},
	},
});

// Legacy format (still supported for backward compatibility)
await agent.call("generateText", [prompt], {
	onChunk: (chunk) => appendToOutput(chunk),
	onDone: (finalValue) => console.log("Done", finalValue),
	onError: (error) => console.error("Error:", error),
});

StreamingResponse API

メソッド 説明
send(chunk) チャンクをクライアントへ送ります
end(finalChunk?) ストリームを終了します。最終値を付けることもできます
error(message) エラーをクライアントへ送り、ストリームを閉じます
class MyAgent extends Agent {
	@callable({ streaming: true })
	async processWithProgress(stream, items) {
		for (let i = 0; i < items.length; i++) {
			await this.process(items[i]);
			stream.send({ progress: (i + 1) / items.length, item: items[i] });
		}
		stream.end({ completed: true, total: items.length });
	}
}
class MyAgent extends Agent {
	@callable({ streaming: true })
	async processWithProgress(stream: StreamingResponse, items: string[]) {
		for (let i = 0; i < items.length; i++) {
			await this.process(items[i]);
			stream.send({ progress: (i + 1) / items.length, item: items[i] });
		}
		stream.end({ completed: true, total: items.length });
	}
}

TypeScript との統合

型付きのクライアント呼び出し

エージェントクラスを型パラメーターに渡すと、型安全性が得られます。

import { useAgent } from "agents/react";

function App() {
	const agent = useAgent({
		agent: "MyAgent",
		name: "default",
	});

	async function handleGreet() {
		// TypeScript knows the method signature
		const result = await agent.stub.greet("World");
		// ^? string
	}

	// TypeScript catches errors
	// await agent.stub.greet(123); // Error: Argument of type 'number' is not assignable
	// await agent.stub.nonExistent(); // Error: Property 'nonExistent' does not exist
}
import { useAgent } from "agents/react";
import type { MyAgent } from "./server";

function App() {
	const agent = useAgent<MyAgent>({
		agent: "MyAgent",
		name: "default",
	});

	async function handleGreet() {
		// TypeScript knows the method signature
		const result = await agent.stub.greet("World");
		// ^? string
	}

	// TypeScript catches errors
	// await agent.stub.greet(123); // Error: Argument of type 'number' is not assignable
	// await agent.stub.nonExistent(); // Error: Property 'nonExistent' does not exist
}

呼び出し可能ではないメソッドの除外

@callable() を付けていないメソッドは、型から除外できます。

class MyAgent extends Agent {
	@callable()
	publicMethod() {
		return "public";
	}

	// Not callable from clients
	internalMethod() {
		// internal logic
	}
}

// Exclude internal methods from the client type
const agent = useAgent({
	agent: "MyAgent",
});

agent.stub.publicMethod(); // Works
// agent.stub.internalMethod(); // TypeScript error
class MyAgent extends Agent {
	@callable()
	publicMethod(): string {
		return "public";
	}

	// Not callable from clients
	internalMethod(): void {
		// internal logic
	}
}

// Exclude internal methods from the client type
const agent = useAgent<Omit<MyAgent, "internalMethod">>({
	agent: "MyAgent",
});

agent.stub.publicMethod(); // Works
// agent.stub.internalMethod(); // TypeScript error

エラー処理

呼び出し可能メソッドでのエラー送出

呼び出し可能メソッドで投げたエラーは、クライアントへ伝わります。

class MyAgent extends Agent {
	@callable()
	async riskyOperation(data) {
		if (!isValid(data)) {
			throw new Error("Invalid data format");
		}

		try {
			await this.processData(data);
		} catch (e) {
			throw new Error("Processing failed: " + e.message);
		}
	}
}
class MyAgent extends Agent {
	@callable()
	async riskyOperation(data: unknown): Promise<void> {
		if (!isValid(data)) {
			throw new Error("Invalid data format");
		}

		try {
			await this.processData(data);
		} catch (e) {
			throw new Error("Processing failed: " + e.message);
		}
	}
}

クライアント側のエラー処理

try {
	const result = await agent.stub.riskyOperation(data);
} catch (error) {
	// Error thrown by the agent method
	console.error("RPC failed:", error.message);
}
try {
	const result = await agent.stub.riskyOperation(data);
} catch (error) {
	// Error thrown by the agent method
	console.error("RPC failed:", error.message);
}

ストリーミングのエラー処理

ストリーミングメソッドでは、onError コールバックを使います。

await agent.call("streamData", [input], {
	stream: {
		onChunk: (chunk) => handleChunk(chunk),
		onError: (errorMessage) => {
			console.error("Stream error:", errorMessage);
			showErrorUI(errorMessage);
		},
		onDone: (result) => handleComplete(result),
	},
});
await agent.call("streamData", [input], {
	stream: {
		onChunk: (chunk) => handleChunk(chunk),
		onError: (errorMessage) => {
			console.error("Stream error:", errorMessage);
			showErrorUI(errorMessage);
		},
		onDone: (result) => handleComplete(result),
	},
});

サーバー側では、stream.error() でストリーム途中にエラーを送れます。

class MyAgent extends Agent {
	@callable({ streaming: true })
	async processItems(stream, items) {
		for (const item of items) {
			try {
				const result = await this.process(item);
				stream.send(result);
			} catch (e) {
				stream.error(`Failed to process ${item}: ${e.message}`);
				return; // Stream is now closed
			}
		}
		stream.end();
	}
}
class MyAgent extends Agent {
	@callable({ streaming: true })
	async processItems(stream: StreamingResponse, items: string[]) {
		for (const item of items) {
			try {
				const result = await this.process(item);
				stream.send(result);
			} catch (e) {
				stream.error(`Failed to process ${item}: ${e.message}`);
				return; // Stream is now closed
			}
		}
		stream.end();
	}
}

接続エラー

RPC 呼び出しの待ち中に WebSocket 接続が閉じると、呼び出しは "Connection closed" エラーで自動的に拒否されます。

try {
	const result = await agent.call("longRunningMethod", []);
} catch (error) {
	if (error.message === "Connection closed") {
		// Handle disconnection
		console.log("Lost connection to agent");
	}
}
try {
	const result = await agent.call("longRunningMethod", []);
} catch (error) {
	if (error.message === "Connection closed") {
		// Handle disconnection
		console.log("Lost connection to agent");
	}
}

再接続後の再試行

切断後、クライアントは自動で再接続します。再接続後に失敗した呼び出しを再試行するには、再試行の前に agent.ready を待ちます。

async function callWithRetry(agent, method, args = []) {
	try {
		return await agent.call(method, args);
	} catch (error) {
		if (error.message === "Connection closed") {
			await agent.ready; // Wait for reconnection
			return await agent.call(method, args); // Retry once
		}
		throw error;
	}
}

// Usage
const result = await callWithRetry(agent, "processData", [data]);
async function callWithRetry<T>(
	agent: AgentClient,
	method: string,
	args: unknown[] = [],
): Promise<T> {
	try {
		return await agent.call(method, args);
	} catch (error) {
		if (error.message === "Connection closed") {
			await agent.ready; // Wait for reconnection
			return await agent.call(method, args); // Retry once
		}
		throw error;
	}
}

// Usage
const result = await callWithRetry(agent, "processData", [data]);

@callable を使わない場面

Worker からエージェントへの呼び出し

同じ Worker からエージェントを呼ぶとき(fetch ハンドラーなど)は、Durable Object RPC を直接使います。

import { getAgentByName } from "agents";

export default {
	async fetch(request, env) {
		// Get the agent stub
		const agent = await getAgentByName(env.MyAgent, "instance-name");

		// Call methods directly - no @callable needed
		const result = await agent.processData(data);

		return Response.json(result);
	},
};
import { getAgentByName } from "agents";

export default {
	async fetch(request: Request, env: Env) {
		// Get the agent stub
		const agent = await getAgentByName(env.MyAgent, "instance-name");

		// Call methods directly - no @callable needed
		const result = await agent.processData(data);

		return Response.json(result);
	},
} satisfies ExportedHandler<Env>;

エージェント間の呼び出し

あるエージェントから別のエージェントを呼ぶときです。

class OrchestratorAgent extends Agent {
	async delegateWork(taskId) {
		// Get another agent
		const worker = await getAgentByName(this.env.WorkerAgent, taskId);

		// Call its methods directly
		const result = await worker.doWork();

		return result;
	}
}
class OrchestratorAgent extends Agent {
	async delegateWork(taskId: string) {
		// Get another agent
		const worker = await getAgentByName(this.env.WorkerAgent, taskId);

		// Call its methods directly
		const result = await worker.doWork();

		return result;
	}
}

なぜ使い分けるか

RPC の種類 転送経路 用途
@callable WebSocket 外部クライアント(ブラウザー、アプリ)
Durable Object RPC 内部 Worker からエージェント、エージェント間

内部呼び出しでは Durable Object RPC の方が効率的です。WebSocket のシリアライズを通らないためです。@callable デコレーターは、外部クライアント向けの WebSocket RPC 処理を追加します。

API リファレンス

@callable(metadata?) デコレーター

外部クライアントから呼び出せるメソッドとしてマークします。

import { callable } from "agents";

class MyAgent extends Agent {
	@callable()
	method() {}

	@callable({ streaming: true })
	streamingMethod(stream) {}

	@callable({ description: "Fetches user data" })
	getUser(id) {}
}
import { callable } from "agents";

class MyAgent extends Agent {
	@callable()
	method(): void {}

	@callable({ streaming: true })
	streamingMethod(stream: StreamingResponse): void {}

	@callable({ description: "Fetches user data" })
	getUser(id: string): User {}
}

CallableMetadata 型

type CallableMetadata = {
	/** Optional description of what the method does */
	description?: string;
	/** Whether the method supports streaming responses */
	streaming?: boolean;
};

StreamingResponse クラス

ストリーミングの呼び出し可能メソッドで、クライアントへデータを送るときに使います。

import {} from "agents";

class MyAgent extends Agent {
	@callable({ streaming: true })
	async streamData(stream, input) {
		stream.send("chunk 1");
		stream.send("chunk 2");
		stream.end("final");
	}
}
import { type StreamingResponse } from "agents";

class MyAgent extends Agent {
	@callable({ streaming: true })
	async streamData(stream: StreamingResponse, input: string) {
		stream.send("chunk 1");
		stream.send("chunk 2");
		stream.end("final");
	}
}
メソッド シグネチャ 説明
send (chunk: unknown) => void チャンクをクライアントへ送ります
end (finalChunk?: unknown) => void ストリームを終了します
error (message: string) => void エラーを送り、ストリームを閉じます

クライアントのメソッド

メソッド シグネチャ 説明
agent.call (method, args?, options?) => Promise 名前を指定してメソッドを呼びます
agent.stub Proxy 型付きのメソッド呼び出し
// Using call()
await agent.call("methodName", [arg1, arg2]);
await agent.call("streamMethod", [arg], {
	stream: { onChunk, onDone, onError },
});

// With timeout (rejects if call does not complete in time)
await agent.call("slowMethod", [], { timeout: 5000 });

// Using stub
await agent.stub.methodName(arg1, arg2);
// Using call()
await agent.call("methodName", [arg1, arg2]);
await agent.call("streamMethod", [arg], {
	stream: { onChunk, onDone, onError },
});

// With timeout (rejects if call does not complete in time)
await agent.call("slowMethod", [], { timeout: 5000 });

// Using stub
await agent.stub.methodName(arg1, arg2);

CallOptions 型

type CallOptions = {
	/** Timeout in milliseconds. Rejects if call does not complete in time. */
	timeout?: number;
	/** Streaming options */
	stream?: {
		onChunk?: (chunk: unknown) => void;
		onDone?: (finalChunk: unknown) => void;
		onError?: (error: string) => void;
	};
};

getCallableMethods() メソッド

エージェント上の呼び出し可能メソッドとメタデータのマップを返します。メソッド一覧の確認や、ドキュメントの自動生成に使えます。

const methods = agent.getCallableMethods();
// Map<string, CallableMetadata>

for (const [name, meta] of methods) {
	console.log(`${name}: ${meta.description || "(no description)"}`);
	if (meta.streaming) console.log("  (streaming)");
}
const methods = agent.getCallableMethods();
// Map<string, CallableMetadata>

for (const [name, meta] of methods) {
	console.log(`${name}: ${meta.description || "(no description)"}`);
	if (meta.streaming) console.log("  (streaming)");
}

トラブルシューティング

SyntaxError: Invalid or unexpected token

@callable() 使用時に開発サーバーが SyntaxError: Invalid or unexpected token で失敗する場合、次の 2 つが必要です。

1. agents/vite プラグインを追加します — Vite 8 はトランスパイルに Oxc を使います。Oxc はまだ TC39 デコレーターに対応していません。プラグインが必要な変換を追加します。

vite.config.tsts
import agents from "agents/vite";

export default defineConfig({
	plugins: [agents(), react(), cloudflare()],
});

2. agents/tsconfig を継承します"target": "ES2021" と、その他の推奨コンパイラーオプションが設定されます。

tsconfig.jsonjson
{
	"extends": "agents/tsconfig"
}

共有設定を継承できない場合は、tsconfig.json"target": "ES2021" を手動で設定します。

次のステップ

Agents API

Agents SDK の API リファレンスです。

WebSockets

クライアントとのリアルタイム双方向通信です。

状態管理

エージェントとクライアント間で状態を同期します。

役に立ちましたか?