Skip to content

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

ライフサイクルフック

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

Think は streamText の呼び出しを担当し、チャットターンの各段階にフックを提供します。フックは入口に関係なく、すべてのターンで発火します。WebSocket チャット、サブエージェントの chat()saveMessages()、耐久性のある submitMessages() 実行、continueLastTurn()、ツール結果後の自動継続が含まれます。

フック一覧

フック 発火タイミング 戻り値 非同期
configureSession(session) onStart 中に一度だけ Session はい
beforeTurn(ctx) streamText の前 TurnConfig または void はい
beforeStep(ctx) 各モデルステップの前 StepConfig または void はい
beforeToolCall(ctx) サーバー側ツールの実行前 ToolCallDecision または void はい
afterToolCall(ctx) ツールの結果が分かったあと void はい
onStepFinish(ctx) 各ステップ完了後 void はい
onChunk(ctx) ストリーミングチャンクごと void はい
onChatResponse(result) ターン完了後、メッセージ永続化のあと void はい
onChatError(error, ctx?) ターン中のエラー時 伝播するエラー いいえ
classifyChatError(error, ctx?) ターンエラー時で、contextOverflow.reactive が有効なとき ChatErrorClassification または void いいえ

実行順

ツール呼び出しが 2 回あるターンの場合:

flowchart TD
    cfg["configureSession() — 起動時に一度だけ。ターンごとではない"] --> bt["beforeTurn() — コンテキストを確認し、model / tools / prompt を上書き"]
    bt --> bs

    subgraph loop ["streamText(ステップごとに繰り返し)"]
        bs["beforeStep()"] --> chunk["onChunk() — ストリーミングチャンクごと"]
        chunk --> btc["beforeToolCall()"]
        btc --> exec["ツールを実行"]
        exec --> atc["afterToolCall()"]
        atc --> sf["onStepFinish()"]
        sf -->|"さらにステップがある"| bs
    end

    sf -->|"ターン完了"| ocr["onChatResponse() — メッセージを永続化し、ターンロックを解除"]

beforeTurn

streamText の前に呼ばれます。組み立て済みのコンテキスト(システムプロンプト、変換済みメッセージ、マージ済みツール、モデル)を受け取ります。一部を上書きするには TurnConfig を返し、デフォルトを使う場合は void を返します。

beforeTurn(ctx: TurnContext): TurnConfig | void | Promise<TurnConfig | void>

TurnContext

フィールド 説明
system string 組み立て済みのシステムプロンプト(コンテキストブロックまたは getSystemPrompt() から)
messages ModelMessage[] 組み立て済みのモデルメッセージ(切り詰め、剪定済み)
tools ToolSet マージ済みツールセット(workspace + getTools + session + extensions + MCP + client)
model LanguageModel getModel() のモデル
continuation boolean 継続ターンかどうか(ツール結果後の自動継続)
body Record<string, unknown> クライアントリクエストのカスタム body フィールド

TurnConfig

すべてのフィールドは任意です。変更したい項目だけ返します。

フィールド 説明
model LanguageModel このターンのモデルを上書きします
system string システムプロンプトを上書きします
messages ModelMessage[] 組み立て済みメッセージを上書きします
tools ToolSet 追加でマージするツール(加算)
activeTools string[] モデルが呼べるツールを制限します
toolChoice ToolChoice 特定のツール呼び出しを強制します
maxSteps number このターンの maxSteps を上書きします
sendReasoning boolean このターンの推論チャンクを送信します
chatStreamStallTimeoutMs number このターンのストリーム停滞ウォッチドッグを上書きします(0 で無効)。ターン後に自動リセットします。遅いツールがあるターン向けです。耐久リカバリー を参照してください
output Output このターンで構造化出力を要求します
providerOptions Record<string, unknown> プロバイダー固有のオプション
experimental_telemetry object このターンの AI SDK テレメトリ設定
experimental_transform StreamTextTransform | StreamTextTransform[] このターンの AI SDK ストリーム変換です。ストリーム部品の検査や書き換え(例: ツール結果から source 部品を出す)に使います。順に適用されます

継続ターンでは安いモデルに切り替えます。

beforeTurn(ctx: TurnContext) {
	if (ctx.continuation) {
		return { model: this.cheapModel };
	}
}

モデルが呼べるツールを制限します。

beforeTurn(ctx: TurnContext) {
	return { activeTools: ["read", "write", "getWeather"] };
}

クライアントの body からターンごとのコンテキストを追加します。

beforeTurn(ctx: TurnContext) {
	if (ctx.body?.selectedFile) {
		return {
			system: ctx.system + `\n\nUser is editing: ${ctx.body.selectedFile}`,
		};
	}
}

内部の継続ターンでは推論を隠します。

beforeTurn(ctx: TurnContext) {
	if (ctx.continuation) {
		return { sendReasoning: false };
	}
}

ターンで構造化出力を強制します。

import { Output } from "ai";
import { z } from "zod";

const ResultSchema = z.object({ severity: z.enum(["low", "high"]) });

beforeTurn(ctx: TurnContext) {
	if (ctx.body?.mode === "structured-answer") {
		return {
			output: Output.object({ schema: ResultSchema }),
			activeTools: [],
		};
	}
}

output はターン単位の設定だけです。AI SDK の prepareStepoutput の上書きを受け付けないため、beforeStep で 1 ステップだけ構造化出力を切り替えることはできません。

beforeStep

エージェントループ内の各 AI SDK ステップの前に呼ばれます。Think はこのフックを streamTextprepareStep として転送するため、AI SDK の prepare-step コンテキスト全体を受け取り、ステップ単位の上書きを返せます。ターン全体の組み立ては beforeTurn、ステップ番号や直前のステップ結果に依存する判断は beforeStep を使います。

beforeStep(ctx: PrepareStepContext): StepConfig | void {
	if (ctx.stepNumber > 0) {
		return { activeTools: [] };
	}
}

beforeToolCall

サーバー側ツールの execute 関数が走る前に呼ばれます。Think は各サーバー側ツールをラップし、モデルがツール結果を受け取る前に、呼び出しの許可、変更、ブロック、差し替えができます。

beforeToolCall(ctx: ToolCallContext): ToolCallDecision | void {
	if (ctx.toolName === "delete" && this.isReadOnlyMode) {
		return { action: "block", reason: "delete is disabled in read-only mode" };
	}

	if (ctx.toolName === "weather") {
		const cached = this.weatherCache.get(JSON.stringify(ctx.input));
		if (cached) return { action: "substitute", output: cached };
	}
}
フィールド 説明
toolName string 呼び出されるツール名
input unknown モデルが渡した入力
toolCallId string このツール呼び出しの ID
messages ModelMessage[] ツール実行時点で見えるメッセージ
abortSignal AbortSignal | undefined ターンがキャンセルされたときに中断するシグナル

実行を制御するには ToolCallDecision を返します。

判定 動作
void または { action: "allow" } 元の入力で元のツールを実行します
{ action: "allow", input } 変更した入力で元のツールを実行します
{ action: "block", reason } 元のツールをスキップし、reason をツール結果として返します
{ action: "substitute", output } 元のツールをスキップし、output をツール結果として返します

ラップしたツールが予備結果として AsyncIterable を返す場合、Think は beforeToolCall のあと、イテラブルを最後に yield された値へ畳みます。そのツールから本当の予備ストリーミングが必要な場合は、beforeToolCall で横取りしないでください。

afterToolCall

ツールの結果が分かったあとに呼ばれます。実際の実行、ブロックした呼び出し、差し替えた呼び出し、スローされたツールエラーを含みます。

afterToolCall(ctx: ToolCallResultContext) {
	if (!ctx.success) return;

	this.env.ANALYTICS.writeDataPoint({
		blobs: [ctx.toolName],
		doubles: [JSON.stringify(ctx.output).length],
	});
}
フィールド 説明
toolName string 呼び出されたツール名
input unknown モデルが渡した入力
toolCallId string このツール呼び出しの ID
messages ModelMessage[] ツール実行時点で見えるメッセージ
durationMs number ツール実行時間(ミリ秒)
success boolean モデルが成功したツール結果を受け取ったかどうか
output unknown successtrue のときに存在します
error unknown successfalse のときに存在します

ブロックと差し替えのツール呼び出しでは、モデルが有効なツール結果を受け取るため successtrue です。元のツール実行からスローされたエラーだけが success: false になります。

onStepFinish

エージェントループ内の各ステップ完了後に呼ばれます。StepContext は AI SDK の step-finish イベントなので、ステップ全体の記録を含みます。生成テキスト、推論、ファイル、ソース、型付きツール呼び出しと結果、使用量、警告、リクエストとレスポンスのメタデータ、プロバイダーメタデータです。

onStepFinish(ctx: StepContext) {
	console.log(
		`Step ${ctx.stepNumber} (${ctx.finishReason}): ` +
			`${ctx.usage.inputTokens}in/${ctx.usage.outputTokens}out`,
	);
}
フィールド 説明
stepNumber ステップの 0 始まりインデックス
text このステップで生成されたテキスト
reasoning モデルが出した推論部品
files ステップ中に生成されたファイル
sources モデルが使った引用またはソース
toolCalls このステップで行われた型付きツール呼び出し
toolResults このステップで受け取った型付きツール結果
finishReason ステップが終了した理由
usage トークン使用量(キャッシュと推論トークンを含む)
providerMetadata プロバイダー固有のメタデータ

onChunk

各ストリーミングチャンクで呼ばれます。高頻度で、トークンごとに発火します。ストリーミング分析、進捗表示、トークン計測に使います。観測専用です。

onChatResponse

チャットターンがアシスタントメッセージを生成し、永続化したあとに呼ばれます。このフックの前にターンロックは解除されるため、内部から saveMessages や他のメソッドを呼んでも問題ありません。

アシスタントメッセージを永続化するすべてのターン経路で発火します。WebSocket、サブエージェント RPC、saveMessages、自動継続です。アシスタント部品を出す前にターンが失敗した場合は、代わりに onChatError がエラーを処理します。

onChatResponse(result: ChatResponseResult) {
	if (result.status === "completed") {
		console.log(`Turn ${result.requestId}: ${result.message.parts.length} parts`);
	}
}
フィールド 説明
message UIMessage 永続化されたアシスタントメッセージ
requestId string このターンの一意な ID
continuation boolean 継続ターンだったかどうか
status "completed" | "error" | "aborted" ターンの終了方法
error string? エラーメッセージ(status"error" のとき)

onChatError

チャットターン中にエラーが起きたときに呼ばれます。伝播するエラーを返すか、別のエラーを返します。任意のコンテキストは、失敗が起きた場所と、ユーザーメッセージがすでに永続化されていたかを示します。部分的なアシスタントメッセージがある場合は、このフックの前に永続化されます。

onChatError(error: unknown, ctx?: ChatErrorContext): unknown

ChatErrorContext には次が含まれます。

フィールド 説明
requestId string | undefined 取得できる場合のチャットリクエスト ID
stage "parse" | "persist" | "turn" | "stream" | "recovery" | "transcript" 失敗した段階
messagesPersisted boolean 受信したユーザーメッセージがすでに保存されていたか
classification ChatErrorClassification | undefined コンテキスト超過を回復できなかったときの終端 onChatError"context_overflow" になります(classifyChatError を参照)。それ以外は undefined です

Think は同じ段階と永続化情報を、agents:chat 可観測性チャネルの chat:request:failed としても出します。

onChatError(error: unknown, ctx?: ChatErrorContext) {
	console.error("Chat turn failed:", ctx?.stage, error);
	if (ctx?.classification === "context_overflow") {
		return new Error("This conversation is too long to continue. Please start a new one.");
	}
	return new Error("Something went wrong. Please try again.");
}

classifyChatError

ターン中にエラーが起きたとき、onChatError の前に呼ばれます。生のプロバイダーエラーを、プロバイダー非依存の分類へ写します。Think がフレームワーク内にプロバイダー固有の文字列を埋め込まずに反応できるようにするためです。compactAfter() に渡す tokenCounter と同じ役割分担です。どのプロバイダーとモデルを使うかはアプリが知っているため、写像はアプリが持ちます。

classifyChatError(error: unknown, ctx?: ChatErrorContext): ChatErrorClassification | void

ChatErrorClassification"context_overflow" | "rate_limit" | "transient" | "fatal" | "unknown" です。現在このフックが駆動するのはコンテキスト超過の回復だけです。Think はターンがエラーになり、contextOverflow.reactive が有効なときに呼びます。reactive がオフなら呼ばれません。

"context_overflow" を返すと、圧縮して再試行するバックストップが走ります(コンテキストウィンドウ超過の回復 を参照)。回復でターンを救えなかった場合、その分類は終端の onChatError 呼び出しで ChatErrorContext.classification として表面化します。

他の分類は将来用に予約されています。今返しても何も起きず、onChatError へ転送されません。void を返す(デフォルト)と、既存の終端動作のままです。

引数は Error、AI SDK の APICallErrorstatusCode / responseBody 付き)、またはスローではなくストリームエラー部品として表面化するストリーム内プロバイダーエラーの場合は、エラーメッセージ文字列です。型を絞り込んでください。プロバイダーのコンテキスト超過エラーはストリーム内エラー部品として届くため、このフックはスローされた例外ではなく文字列で受け取ります。

第 2 引数は ChatErrorContext です。超過回復中は { stage: "stream", requestId } なので、分類器は進行中のターンとエラーを対応付けできます。たとえば cancelChat(requestId) を呼んで回復を打ち切る、などです。

よくある場合は、同梱の defaultContextOverflowClassifier を割り当てます。Anthropic、OpenAI、Google、Bedrock などのコンテキスト超過エラーに一致します。

import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think {
	classifyChatError = defaultContextOverflowClassifier;
}
import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think<Env> {
	override classifyChatError = defaultContextOverflowClassifier;
}

独自に書くこともできます。同梱の分類器へ委譲しても構いません。

import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think {
	classifyChatError(error) {
		if (error instanceof Error && /rate.?limit/i.test(error.message)) {
			return "rate_limit";
		}
		return defaultContextOverflowClassifier(error);
	}
}
import type { ChatErrorClassification } from "@cloudflare/think";
import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think<Env> {
	override classifyChatError(error: unknown): ChatErrorClassification | void {
		if (error instanceof Error && /rate.?limit/i.test(error.message)) {
			return "rate_limit";
		}
		return defaultContextOverflowClassifier(error);
	}
}

役に立ちましたか?