INTEGRITY Документация

Хуки жизненного цикла

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 нет

Порядок выполнения

Для хода с двумя вызовами инструментов:

flowchart TD
    cfg["configureSession() — once at startup, not per-turn"] --> bt["beforeTurn() — inspect context, override model/tools/prompt"]
    bt --> bs

    subgraph loop ["streamText (repeats per step)"]
        bs["beforeStep()"] --> chunk["onChunk() — per streaming chunk"]
        chunk --> btc["beforeToolCall()"]
        btc --> exec["tool executes"]
        exec --> atc["afterToolCall()"]
        atc --> sf["onStepFinish()"]
        sf -->|"more steps"| bs
    end

    sf -->|"turn complete"| ocr["onChatResponse() — message persisted, turn lock released"]

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> Пользовательские поля тела из клиентского запроса

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

Добавьте контекст для каждого хода из тела клиентского запроса:

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 есть prepareStep не принимает output переопределение, поэтому beforeStep не может переключать структурированный вывод в рамках одного шага.

beforeStep

Вызывается перед каждым шагом AI SDK в агентном цикле. Think перенаправляет этот хук в streamText как prepareStep, чтобы он получал полный контекст prepare-step из AI SDK и мог возвращать переопределения для каждого шага. Используйте 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 выполняется. Если вам нужен настоящий предварительный streaming от этого инструмента, избегайте его перехвата с помощью 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 Присутствует, когда success это true
error unknown Присутствует, когда success это false

Для заблокированных и заменённых вызовов инструментов success это true потому что модель получает корректный результат инструмента. Только исключения, возникшие при исходном выполнении инструмента, проявляются как success: false.

onStepFinish

Вызывается после завершения каждого шага в агентном цикле. StepContext это событие step-finish из AI SDK, поэтому оно включает полную запись шага: сгенерированный текст, рассуждения, файлы, источники, типизированные вызовы инструментов и их результаты, использование, предупреждения, метаданные запроса и ответа, а также метаданные провайдера.

onStepFinish(ctx: StepContext) {
	console.log(
		`Step ${ctx.stepNumber} (${ctx.finishReason}): ` +
			`${ctx.usage.inputTokens}in/${ctx.usage.outputTokens}out`,
	);
}
Поле Описание
stepNumber Индекс шага, отсчитываемый от нуля
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 Установите значение "context_overflow" в терминальном onChatError когда переполнение контекста не удалось восстановить (см. classifyChatError); undefined иначе

Think также генерирует chat:request:failed на agents:chat канал наблюдаемости с той же информацией об этапе и сохранении.

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 мог реагировать, не встраивая специфичные для провайдера строки во фреймворк: то же разделение, что и у tokenCounter вы передаете в compactAfter(). Приложение владеет этим сопоставлением, поскольку знает, с каким провайдером и моделью оно взаимодействует.

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

ChatErrorClassification это "context_overflow" | "rate_limit" | "transient" | "fatal" | "unknown". Сегодня этот хук отвечает только за восстановление при переполнении контекста. Think вызывает его, когда в ходе возникает ошибка и contextOverflow.reactive включён. Если reactive отключён, он не вызывается.

Возврат "context_overflow" запускает защитный механизм compact-and-retry (см. Восстановление после переполнения контекстного окна). Если восстановление не может спасти ход, эта классификация проявляется в финальном onChatError вызов через ChatErrorContext.classification.

Остальные категории зарезервированы на будущее. Если сегодня вернуть одну из них, это не даст эффекта и не будет передано в onChatError. Возврат void (значение по умолчанию) сохраняет существующее поведение при завершении.

Аргументом может быть Error, AI SDK APICallError (с statusCode/responseBody), либо, для ошибок поставщика внутри потока, которые проявляются как часть потока с ошибкой, а не как исключение, строку с сообщением об ошибке. Сужайте тип соответствующим образом. Ошибки поставщика из-за переполнения контекста приходят как части потока с ошибкой, поэтому этот хук получает их в виде строки, а не как выброшенное исключение.

Вторым аргументом является 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);
	}
}