Skip to content

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

Think

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

@cloudflare/think では、1 つの基底クラスを継承するだけで、状態を持つ AI チャットエージェントを構築できます。返信をストリーミングし、会話を覚え、ツールを呼び出します。getModel() でモデルを渡すと、Think がチャットライフサイクルの残りを接続します。エージェントループ(モデルがツールを呼び、結果を読み、答えが出るまで続ける)、メッセージ永続化、ストリーミング、クライアントツール、ストリーム再開、拡張機能です。すべては Durable Object の SQLite が支えます。

Think は トップレベルエージェントuseAgentChat 経由でブラウザークライアントと WebSocket チャット)と サブエージェント(別のエージェントが chat() で RPC 駆動する子エージェント)の両方として使えます。

クイックスタート

インストール

npm i @cloudflare/think @cloudflare/ai-chat agents ai @cloudflare/shell zod workers-ai-provider

Think は AI SDK v6 と v7 に対応しています。ai@^6@ai-sdk/react@^3 と、ai@^7@ai-sdk/react@^4 と組み合わせます。プロジェクト全体で、AI SDK パッケージのメジャーバージョンを揃えてください。

サーバー

import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";

export class MyAgent extends Think {
	getModel() {
		return createWorkersAI({ binding: this.env.AI })(
			"@cf/moonshotai/kimi-k2.6",
		);
	}
}

export default {
	async fetch(request, env) {
		return (
			(await routeAgentRequest(request, env)) ||
			new Response("Not found", { status: 404 })
		);
	},
};
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";

export class MyAgent extends Think<Env> {
	getModel() {
		return createWorkersAI({ binding: this.env.AI })(
			"@cf/moonshotai/kimi-k2.6",
		);
	}
}

export default {
	async fetch(request: Request, env: Env) {
		return (
			(await routeAgentRequest(request, env)) ||
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

これで完了です。Think が WebSocket チャットプロトコル、メッセージ永続化、エージェントループ、メッセージのサニタイズ、ストリーム再開、クライアントツール対応、ワークスペースファイルツールを処理します。

クライアント

import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "MyAgent" });
	const { messages, sendMessage, status } = useAgentChat({ agent });

	return (
		<div>
			{messages.map((msg) => (
				<div key={msg.id}>
					<strong>{msg.role}:</strong>
					{msg.parts.map((part, i) =>
						part.type === "text" ? <span key={i}>{part.text}</span> : null,
					)}
				</div>
			))}

			<form
				onSubmit={(e) => {
					e.preventDefault();
					const input = e.currentTarget.elements.namedItem("input");
					sendMessage({ text: input.value });
					input.value = "";
				}}
			>
				<input name="input" placeholder="Send a message..." />
				<button type="submit">Send</button>
			</form>
		</div>
	);
}
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "MyAgent" });
	const { messages, sendMessage, status } = useAgentChat({ agent });

	return (
		<div>
			{messages.map((msg) => (
				<div key={msg.id}>
					<strong>{msg.role}:</strong>
					{msg.parts.map((part, i) =>
						part.type === "text" ? <span key={i}>{part.text}</span> : null,
					)}
				</div>
			))}

			<form
				onSubmit={(e) => {
					e.preventDefault();
					const input = e.currentTarget.elements.namedItem(
						"input",
					) as HTMLInputElement;
					sendMessage({ text: input.value });
					input.value = "";
				}}
			>
				<input name="input" placeholder="Send a message..." />
				<button type="submit">Send</button>
			</form>
		</div>
	);
}

設定

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  // Set this to today's date
  "compatibility_date": "2026-09-20",
  "compatibility_flags": [
    "nodejs_compat"
  ],
  "ai": {
    "binding": "AI"
  },
  "durable_objects": {
    "bindings": [
      {
        "class_name": "MyAgent",
        "name": "MyAgent"
      }
    ]
  },
  "migrations": [
    {
      "new_sqlite_classes": [
        "MyAgent"
      ],
      "tag": "v1"
    }
  ]
}
# Set this to today's date
compatibility_date = "2026-09-20"
compatibility_flags = ["nodejs_compat"]

[ai]
binding = "AI"

[[durable_objects.bindings]]
class_name = "MyAgent"
name = "MyAgent"

[[migrations]]
new_sqlite_classes = ["MyAgent"]
tag = "v1"

トレーシング

Think は内部で wrapAISDK() を使い、モデルターン、ツール呼び出し、承認ライフサイクルの区間を計測します。AI SDK をラップしたり、アダプターを設定したりする必要はありません。invoke_agentchatexecute_tooltool_approval のスパンを Workers Observability へ送るには、Workers トレースを有効にします。

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "observability": {
    "traces": {
      "enabled": true
    }
  }
}
[observability.traces]
enabled = true

トレースは Cloudflare ダッシュボードの Agents ビューに表示されます。会話とトレースのタイムラインを確認できます。トレーシングをオフにするには、observability.traces.enabledfalse にします。Think のエージェントクラスを変更する必要はありません。

スパン属性、ペイロード制御、トレースのエクスポート、AI SDK v6 または v7 の直接セットアップは トレーシング を参照してください。

Think と AIChatAgent の比較

Think と AIChatAgent はどちらも Agent を継承し、同じ cf_agent_chat_* WebSocket プロトコルを話します。目的は異なります。

AIChatAgent はプロトコルアダプターです。onChatMessage をオーバーライドし、streamText の呼び出し、ツールの接続、メッセージ変換、Response の返却は自分で担当します。AIChatAgent が処理するのは配管です。メッセージ永続化、ストリーミング、中止、再開です。LLM 呼び出しそのものはすべて呼び出し側の責任です。

Think は方針の決まったフレームワークです。判断は枠組み側が行います。getModel() がモデルを返し、getSystemPrompt() または configureSession() がプロンプトを設定し、getTools() がツールを返します。デフォルトの onChatMessage が完全なエージェントループを実行します。パイプライン全体ではなく、個別の部品をオーバーライドします。

観点 AIChatAgent Think
最小のサブクラス 約 15 行(streamText + ツール + システムプロンプト + 応答) 3 行(getModel() のみ)
ストレージ 平坦な SQL テーブル Session: ツリー構造のメッセージ、コンテキストブロック、コンパクション、FTS5
再生成 破壊的(古い応答を削除) 非破壊的な分岐(古い応答を保持)
コンテキスト管理 手動 LLM が読み書きできる永続メモリ付きコンテキストブロック
サブエージェント RPC 組み込みなし StreamCallback 付き chat()
プログラム起点のターン saveMessages() saveMessages()submitMessages()continueLastTurn()
コンパクション maxPersistedMessages(最も古いものを削除) オーバーレイによる非破壊的な要約
検索 なし セッション内およびセッション横断の FTS5 全文検索

AIChatAgent を使う場合

  • LLM 呼び出しを完全に制御したい(RAG、複数モデル、独自ストリーミング)
  • HTTP ミドルウェアやテスト向けに Response 戻り値が欲しい
  • メモリ要件のないシンプルなチャットボットを作る

Think を使う場合

  • 早く出荷したい(すべて接続済みの 3 行サブクラス)
  • 永続メモリが必要(モデルが読み書きできるコンテキストブロック)
  • 長い会話が必要(非破壊的なコンパクション)
  • 会話検索が必要(FTS5)
  • サブエージェントシステムを作る(ストリーミング付きの親子 RPC)
  • 能動的なエージェントが必要(スケジュールタスクや webhook からのプログラム起点ターン)
  • webhook や RPC 呼び出し元向けの、耐久的な非同期サブミッションが必要

ターン API を選ぶ

Think には、ターンを開始または継続する方法がいくつかあります。どれも 1 つの公開エントリポイント runTurn(options) に集まります。古いメソッドは便利なショートカットとして残っています。

runTurn()

runTurn() は、統一されたターン受付 API です。1 つのメソッド、3 つのモードで、options.mode で選びます。

モード 使うとき 戻り値 ショートカット先
"wait"(デフォルト) 呼び出し側が、モデル応答の完了までブロックできる Promise<TurnResult> saveMessages()
"submit" 素早い耐久的な受付と、あとからの状態確認が必要 Promise<SubmitMessagesResult> submitMessages()
"stream" 応答をコールバックへストリーミングしたい(RPC) Promise<void> chat()

input は文字列、UIMessage、メッセージ配列、または waitstream モードでは受付時に評価される関数 (current) => UIMessage[] を受け付けます(submit は関数入力を受け付けません)。

export class Assistant extends Think {
	async examples(inboundEventId) {
		// wait — block for the result
		const result = await this.runTurn({ input: "Summarize the latest thread" });
		if (result.status === "completed") {
			// result.message is the assistant message; result.continuation is false
		}

		// submit — durable acceptance, check status later
		const submission = await this.runTurn({
			mode: "submit",
			input: "Process this webhook",
			idempotencyKey: inboundEventId, // dedupe; safe to retry
		});
		// submission.accepted is true on first accept; submission.status is "pending"

		// stream — drive a callback (the same surface as chat())
		await this.runTurn({
			mode: "stream",
			input: "Stream me",
			callback: {
				onStart({ requestId }) {},
				onEvent(json) {}, // UIMessageChunk JSON
				onDone() {},
				onError(error) {},
			},
		});

		// continuation — continue the last assistant turn instead of sending input
		await this.runTurn({ continuation: true });
	}
}
export class Assistant extends Think<Env> {
	async examples(inboundEventId: string) {
		// wait — block for the result
		const result = await this.runTurn({ input: "Summarize the latest thread" });
		if (result.status === "completed") {
			// result.message is the assistant message; result.continuation is false
		}

		// submit — durable acceptance, check status later
		const submission = await this.runTurn({
			mode: "submit",
			input: "Process this webhook",
			idempotencyKey: inboundEventId, // dedupe; safe to retry
		});
		// submission.accepted is true on first accept; submission.status is "pending"

		// stream — drive a callback (the same surface as chat())
		await this.runTurn({
			mode: "stream",
			input: "Stream me",
			callback: {
				onStart({ requestId }) {},
				onEvent(json) {}, // UIMessageChunk JSON
				onDone() {},
				onError(error) {},
			},
		});

		// continuation — continue the last assistant turn instead of sending input
		await this.runTurn({ continuation: true });
	}
}

主な動作:

  • ブロッキングモードは入れ子にできません。 稼働中のターンの 内側(例: ツールの execute)から wait / stream / continuation(または同等のショートカット)を呼ぶと例外になります。ターンキューがデッドロックするためです。ターン内からは runTurn({ mode: "submit" })(耐久的で、現在のターンがキューを空けたあとに実行)か、addMessages()(トランスクリプトのみ、推論なし)を使います。
  • submit はべき等です。 submissionIdidempotencyKey を渡します。既知のキーを再提出すると、2 つ目のターンは始まらず、既存レコードが accepted: false で返ります。プログラム起点のサブミッション を参照してください。
  • 復旧に安全です。 waitstream、排出済みの submit 経路は、復旧ファイバー内で推論を実行します。中断したターンは退避後に再開します。

runTurn は、オプションと結果の型と一緒にエクスポートされます。RunTurnOptionsRunTurnWaitRunTurnSubmitRunTurnStreamTurnInputMessagesTurnResult です。

ショートカットを選ぶ

次の表は、各シナリオを最も直接的な呼び出しに対応づけます。各ショートカットのシグネチャは変わりません。狭い面だけ欲しいときはショートカットを、1 つのメンタルモデルで揃えたいときは runTurn() を使います。

用途 API
ブラウザーユーザーがチャットメッセージを送る WebSocket チャットプロトコル上の useAgentChat
サーバーコードがモデル応答を待てる saveMessages()
サーバーコードが素早い耐久的な受付と、あとからの状態確認を必要とする submitMessages()
繰り返しのプロンプト駆動ターンやハンドラーを作る getScheduledTasks()
親コードが特定の子へ直接ストリーミング RPC する subAgent(...).chat()
親が保持する子エージェントへ作業を委譲する agentTool() または runAgentTool()
ターンをアプリ所有のべき等な副作用で囲む startFiber()
複数ステップの耐久オーケストレーションを調整する Workflows
モデルターンを始めずにコンテキストやメッセージを追加する addMessages()
高度なサブクラスや復旧コードがアシスタントターンを継続する continueLastTurn()

呼び出し側がトリガーを所有し、ターン完了を待てる場合は saveMessages() を使います。タイムアウトの曖昧さで再試行が危険になる場合は submitMessages() を使います。

ターンなしでメッセージを追加する

モデルターンを始めずにトランスクリプトへ書き込むには addMessages() を使います。過去の履歴の取り込みや、次のターンが見るべき背景コンテキストの注入に使います。

export class Assistant extends Think {
	async importContext() {
		await this.addMessages([
			{
				id: crypto.randomUUID(),
				role: "user",
				parts: [{ type: "text", text: "Imported context" }],
			},
		]);
	}
}
export class Assistant extends Think<Env> {
	async importContext() {
		await this.addMessages([
			{
				id: crypto.randomUUID(),
				role: "user",
				parts: [{ type: "text", text: "Imported context" }],
			},
		]);
	}
}

addMessages() は Session ツリーへ追加(または upsert)します。

  • 推論は実行せず、ターンキューにも入りません。ツールの execute 内から呼んでもデッドロックしません。
  • 配列の要素は直線的に追加されます(それぞれ直前の要素の下に付きます)。取り込んだ履歴は 1 本のパスのままです。デフォルトでは最初のメッセージが、最新のコミット済みリーフに付きます。別の場所へ付けるには parentId を渡し、ルートメッセージにするには null を渡します。
  • 追加は メッセージ id でべき等 です。既存メッセージをその場で更新するには { mode: "upsert" } を渡します。

推奨パターンは「コンテキストを追加してからターンを実行」です。addMessages() のあと runTurn() を呼びます。

転送、キャンセル、再生ポリシーを自分のコードが持つ、低レベルの親子ストリーミングには chat() を使います。親モデルや workflow が子エージェントへ委譲し、保持された子の実行、イベント再生、中止の橋渡し、UI ドリルインが欲しい場合は ツールとしての Agents を使います。

ターン周辺のアプリケーションジョブが耐久単位である場合(webhook を一度だけ受け付ける、シリアライズされたチャネルやスレッド先を復元する、見える返信を投稿する、アプリレベルの復旧ポリシーを記録する)は、Think の外で startFiber() を使います。Think のサブミッションは会話受付とターンの直列化を担当し、managed fiber は外部ジョブの受付、べき等な副作用、アプリケーション復旧を担当します。

このセクションの内容

はじめに

Think エージェントを手順どおりに構築します。

設定

設定の上書き、動的設定、Session 連携。

ツール

ワークスペースツール、コード実行、ブラウザーツール、拡張機能。

アクション

べき等性、承認、認可、返信添付を備えたサーバーアクション。

チャネル

チャネルごとのポリシー、チャネル選択、帯域外通知。

Workflows

Cloudflare Workflows 内の、耐久的なモデル駆動の推論ステップ。

耐久復旧

チャット復旧、ストール監視、安定検出。

Agent Skills

getSkills() によるオンデマンドの手順、リソース、スクリプト。

謝辞

Think の設計は Pi に着想を得ています。

Assistant の例

サブエージェントルーティング、共有ワークスペース、MCP、チャット復旧、GitHub OAuth を備えた、複数セッションの Think アシスタントを確認します。

関連

役に立ちましたか?