INTEGRITY Dokumentace

Trvalé obnovení

Think vždy obaluje chatová kola do obnovitelných fibery. Pokud se Durable Object vyřadí z paměti uprostřed streamu, Think rekonstruuje veškeré vyrovnávací fragmenty. Částečný výstup uloží a naplánuje pokračování nebo opakování.

Kola WebSocket, podřízený agent chat() tahy, trvalé submitMessages() provedení, automatická pokračování, saveMessages(), a continueLastTurn() jsou zabaleny do runFiber.

Ohraničené obnovení

Přerušení hlídacím mechanismem (watchdog) kvůli zaseknutí streamu (chatStreamStallTimeoutMs) používá stejnou omezenou cestu obnovy. SDK zachová ustálenou částečnou odpověď a naplánuje pokračování. Dočasné zaseknutí se obnoví automaticky. Trvale zaseknutý poskytovatel vyčerpá rozpočet stejnou cestou jako nasazení nebo vyřazení. SDK volá onExhausted, vydá chat:recovery:exhausted, a zobrazuje nakonfigurované terminalMessage.

Nakonfigurujte ohraničenou obnovu nastavením chatRecovery na objekt:

export class MyAgent extends Think {
	chatRecovery = {
		maxAttempts: 6,
		stableTimeoutMs: 10_000,
		terminalMessage: "The assistant was interrupted and could not recover.",
		async onExhausted(ctx) {
			console.warn("Chat recovery exhausted", ctx.incidentId);
		},
	};

	getModel() {
		/* ... */
	}
}
export class MyAgent extends Think<Env> {
	override chatRecovery = {
		maxAttempts: 6,
		stableTimeoutMs: 10_000,
		terminalMessage: "The assistant was interrupted and could not recover.",
		async onExhausted(ctx) {
			console.warn("Chat recovery exhausted", ctx.incidentId);
		},
	};

	getModel() {
		/* ... */
	}
}

Stejné události obnovení jsou dostupné prostřednictvím agents/observability na chat kanálu; opravy přepisu se odesílají na transcript kanál. Více informací najdete v Diagnostické kanály.

onChatRecovery

Přepsat onChatRecovery když potřebujete obnovu specifickou pro poskytovatele, například načtení uloženého výsledku OpenAI Responses místo nového volání modelu:

export class MyAgent extends Think {
	chatRecovery = {
		maxAttempts: 10,
		terminalMessage: "The assistant was interrupted. Please try again.",
	};

	async onChatRecovery(ctx) {
		console.log("Recovering chat turn", ctx.incidentId, ctx.attempt);
		return {}; // persist partial output and continue/retry when possible
	}
}
import type {
	ChatRecoveryContext,
	ChatRecoveryOptions,
} from "@cloudflare/think";

export class MyAgent extends Think<Env> {
	override chatRecovery = {
		maxAttempts: 10,
		terminalMessage: "The assistant was interrupted. Please try again.",
	};

	override async onChatRecovery(
		ctx: ChatRecoveryContext,
	): Promise<ChatRecoveryOptions> {
		console.log("Recovering chat turn", ctx.incidentId, ctx.attempt);
		return {}; // persist partial output and continue/retry when possible
	}
}

ChatRecoveryContext

Pole Typ Popis
incidentId string Stabilní ID pro tento incident obnovy
attempt number Aktuální číslo pokusu pro tento incident, počínaje 1
maxAttempts number Nakonfigurovaný limit počtu pokusů před definitivním vyčerpáním
recoveryKind "retry" | "continue" Zda obnovení zopakuje nezodpovězené uživatelské kolo, nebo bude pokračovat v částečném kole asistenta
streamId string ID streamu přerušeného kola
requestId string ID požadavku přerušeného kola
partialText string Text vygenerovaný před přerušením
partialParts MessagePart[] Části nashromážděné před přerušením
recoveryData unknown | null Data z this.stash() během kola
messages UIMessage[] Aktuální historie konverzace
lastBody Record<string, unknown>? Tělo z přerušeného kola
lastClientTools ClientToolSchema[]? Klientské nástroje z přerušeného kola
createdAt number Milisekundy epochy, kdy kolo začalo

ChatRecoveryOptions

Pole Typ Popis
persist boolean? Zda uchovat částečnou zprávu asistenta
continue boolean? Zda automaticky pokračovat novým kolem prostřednictvím continueLastTurn()

S persist: true, částečná zpráva se uloží. S continue: true, Think volá continueLastTurn() poté, co agent dosáhne stabilního stavu.

Pro přerušení před zahájením streamu, kdy ctx.streamId === "" a ctx.partialText === "" ale poslední uložená zpráva je stále nezodpovězená zpráva uživatele, Think tento krok automaticky opakuje, pokud continue je false.

onChatRecovery(ctx: ChatRecoveryContext): ChatRecoveryOptions {
	if (!ctx.streamId && !ctx.partialText) {
		console.log("Recovering a pre-stream interruption");
	}
	return {};
}

Použijte ctx.createdAt a přeskočit zastaralá obnovení. Pokud je například přerušený tah starší než několik minut, vraťte { continue: false } aby se dílčí odpověď zachovala bez spuštění staré kontinuace.

Trvalé vedení evidence zůstává aktivní, když automatické pokračování není vhodné. Vraťte { continue: false } k zamezení dalšího volání modelu. Ohledně řízení zrušení, vedlejších efektů a nákladů viz Řiďte automatické pokračování.

Rozpočty a limity obnovení

Přiřaďte chatRecovery objekt pro ladění limitů zotavení a koncového chování. Probíhající tah přežije opakovaná přerušení, dokud zůstává v rámci maxRecoveryWork limit. Následující možnosti určují, kdy se obnovení zastaví:

export class MyAgent extends Think {
	chatRecovery = {
		maxAttempts: 10,
		noProgressTimeoutMs: 5 * 60 * 1000,
		maxRecoveryWork: 1_000,
		terminalMessage: "The assistant was interrupted and could not recover.",
		// Consulted from the second recovery attempt onward. Return false to stop.
		// Called as `config.shouldKeepRecovering(ctx)`, so it is NOT bound to the
		// agent instance — track real token/cost spend in your own store keyed by
		// `ctx.recoveryRootRequestId`.
		async shouldKeepRecovering(ctx) {
			return (await getSpendForTurn(ctx.recoveryRootRequestId)) < MAX_SPEND;
		},
		async onExhausted(ctx) {
			console.warn("Recovery exhausted", ctx.incidentId, ctx.reason);
		},
	};
}
export class MyAgent extends Think<Env> {
	override chatRecovery = {
		maxAttempts: 10,
		noProgressTimeoutMs: 5 * 60 * 1000,
		maxRecoveryWork: 1_000,
		terminalMessage: "The assistant was interrupted and could not recover.",
		// Consulted from the second recovery attempt onward. Return false to stop.
		// Called as `config.shouldKeepRecovering(ctx)`, so it is NOT bound to the
		// agent instance — track real token/cost spend in your own store keyed by
		// `ctx.recoveryRootRequestId`.
		async shouldKeepRecovering(ctx) {
			return (await getSpendForTurn(ctx.recoveryRootRequestId)) < MAX_SPEND;
		},
		async onExhausted(ctx) {
			console.warn("Recovery exhausted", ctx.incidentId, ctx.reason);
		},
	};
}
Pole Výchozí Popis
maxAttempts 10 Limit počtu pokusů. Resetuje se při jakémkoli postupu, takže zachytí těsnou smyčku alarmů bez postupu, nikoli zdravé dlouhé kolo.
stableTimeoutMs 10_000 Jak dlouho pokus čeká, než izolát dosáhne stabilního stavu, než dojde k novému naplánování.
noProgressTimeoutMs 300_000 (5 min) Primární limit pro zaseknuté kolo: maximální doba bez postupu vpřed před uzavřením. Resetuje se při každém pokusu, který přináší postup.
maxRecoveryWork 1,000 Pojistka proti nekontrolované smyčce: maximální počet vytvořených jednotek obsahu/nástrojů, než se ještě probíhající kolo uzavře. Nastavte vyšší hodnotu nebo Infinity pro dlouhé agentní kolo.
maxOomRetries 3 Rozpočet na opakování pro resety limitu paměti Durable Object. Nastavte 0 a zastavit se po prvním resetu limitu paměti.
shouldKeepRecovering , Zásady volajícího se konzultují od druhého pokusu dál. Vraťte false a zastavit obnovení. Místo napojení pro rozpočet tokenů/nákladů (ctx.work je hrubý počet segmentů, nikoli tokenů).
terminalMessage obecná zpráva Zpráva, která se zobrazí uživateli, pokud se od obnovení upustí.
onExhausted , Volá se jednou, když se obnovení vzdá. Prozkoumejte ctx.reason.

ctx.reason v hooku exhausted nabývá jedné z těchto hodnot: no_progress_timeout (zaseknuté), max_attempts_exceeded (smyčka alarmu bez postupu), work_budget_exceeded (neřízené), recovery_aborted (vaše shouldKeepRecovering vrácen false), out_of_memory (rozpočet opakování při limitu paměti), nebo stable_timeout (extrémní fluktuace). Viz Obnovení streamu pro úplnou sdílenou referenci. Think a @cloudflare/ai-chat používají stejnou konfiguraci obnovy.

Oprava přerušených volání nástrojů

Když je kolo přerušeno uprostřed provádění, přepis může obsahovat volání nástroje bez ustáleného výsledku. Před dalším voláním poskytovatele Think každé takové volání opraví, aby ho model tiše znovu nespustil a poskytovatel přepis neodmítl s AI_MissingToolResultsError. Výchozí chování převede přerušené volání na chybový výsledek nástroje, takže záznam zůstane zachován a konverze pro něj bude mít stále k dispozici výsledek nástroje.

Přepsat repairInterruptedToolPart k přizpůsobení opraveného tvaru. Nejčastějším případem je nástroj řešený na straně klienta, například ask_user otázku, která nemá žádný server execute a běžně na ni odpovídá až další zpráva uživatele. Převedením na obyčejnou textovou část model tuto otázku vnímá jako běžnou konverzaci, nikoli jako chybu nástroje, a zachovává otázku doslovně i po compaction:

export class MyAgent extends Think {
	repairInterruptedToolPart(part) {
		const record = part;
		if (record.type === "tool-ask_user") {
			const input = record.input;
			if (input?.prompt) {
				return { type: "text", text: input.prompt };
			}
		}
		return super.repairInterruptedToolPart(part);
	}
}
import type { UIMessage } from "ai";

export class MyAgent extends Think<Env> {
	protected override repairInterruptedToolPart(
		part: UIMessage["parts"][number],
	): UIMessage["parts"][number] {
		const record = part as Record<string, unknown>;
		if (record.type === "tool-ask_user") {
			const input = record.input as { prompt?: string } | undefined;
			if (input?.prompt) {
				return { type: "text", text: input.prompt };
			}
		}
		return super.repairInterruptedToolPart(part);
	}
}

Toto probíhá při opravě transkriptu, ještě před tím, než se opravený transkript uloží a odešle modelu, takže konverze ovlivňuje aktuální kolo, ne jen to následující. Ten input je již normalizován na platný objekt. Vrácená část nástroje musí obsahovat vyřešený výsledek (output-available, output-error, nebo output-denied); v pořádku je i vrácení jiné než nástrojové části, například textu.

Obnovení po přetečení kontextového okna

Komprimace se kontroluje mezi koly, compactAfter() se spustí po každém appendMessage(). Jediné dlouhé kolo nabité nástroji ale postupně zvětšuje prompt uvnitř jednoho streamText smyčku a může překročit kontextové okno modelu uprostřed kola, ještě před další kontrolou před kolem. Poskytovatel poté požadavek odmítne ("prompt is too long", context_length_exceeded), a kolo by jinak skončilo trvalou chybou.

Think se z toho obnovuje pomocí dvou volitelných vrstev nezávislých na poskytovateli, obě konfigurovatelné prostřednictvím contextOverflow vlastnost. Obě jsou ve výchozím stavu vypnuté, takže stávající chování zůstává beze změny. Obě využívají funkci komprimace vaší relace, proto vyžadují configureSession() hodnotou onCompaction() nakonfigurováno. Obojí vyžaduje classifyChatError k tomu, aby Think poznal, které chyby jsou přetečení: Think ve svém jádru neobsahuje žádné porovnávání specifické pro poskytovatele.

1. Reaktivní záložní mechanismus: contextOverflow.reactive. Pokud tah selže s chybou, kterou klasifikujete jako "context_overflow", Think zahodí zkrácenou částečnou odpověď, spustí session.compact(), a znovu spustí kolo od zkomprimované historie. Částečná odpověď se neukládá: kolo se spouští znovu od začátku, takže ponechání useknuté zprávy asistenta by ji vedle obnovené odpovědi ponechalo osiřelou. Je omezeno pomocí contextOverflow.maxRetries (výchozí 1); pokud komprimace nedokáže historii zkrátit nebo je rozpočet vyčerpán, přetečení se projeví jako trvalá chyba prostřednictvím onChatError hodnotou classification: "context_overflow" : nikdy se nezacyklí ani neskončí potichu.

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

export class MyAgent extends Think {
	contextOverflow = { reactive: true };

	// The bundled classifier covers the common providers (Anthropic, OpenAI,
	// Google, Bedrock, …). Assign it directly, or write your own.
	classifyChatError = defaultContextOverflowClassifier;
}
import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think<Env> {
	override contextOverflow = { reactive: true };

	// The bundled classifier covers the common providers (Anthropic, OpenAI,
	// Google, Bedrock, …). Assign it directly, or write your own.
	override classifyChatError = defaultContextOverflowClassifier;
}

2. Proaktivní guard: contextOverflow.proactive. Předchází chybě poskytovatele dříve, než nastane. Před každým krokem Think přečte modelem nahlášený usage.inputTokens (nezávislé na poskytovateli) a pokud překročí maxInputTokens * (headroom ?? 0.9), provede komprimaci na místě a nově zkomprimovanou historii předá do nadcházejícího kroku. Pokud poskytovatel vynechá inputTokens, jako záložní řešení použije usage.totalTokens (bezpečný, mírně nadhodnocený odhad: komprimuje raději o něco dříve, než aby minul práh). Komprimuje nejvýše proactive.maxCompactions krát za tah (výchozí 1), nezávisle na reaktivním maxRetries rozpočet, takže historie, kterou nelze zkrátit, se nekomprimuje při každém kroku.

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

export class MyAgent extends Think {
	contextOverflow = {
		reactive: true,
		// Compact mid-turn once a step approaches 90% of a 200K window.
		proactive: { maxInputTokens: 200_000 },
	};

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

export class MyAgent extends Think<Env> {
	override contextOverflow = {
		reactive: true,
		// Compact mid-turn once a step approaches 90% of a 200K window.
		proactive: { maxInputTokens: 200_000 },
	};

	override classifyChatError = defaultContextOverflowClassifier;
}

Použijte obě vrstvy samostatně, nebo obě dohromady: proaktivní guard zabrání většině přetečení a reaktivní záložní mechanismus zachytí vše, co jím ještě prokluzne (například kolo, které už od začátku přesahuje rozpočet, nebo jediný výsledek nástroje tak velký, že mu komprimace nepomůže: v takovém případě se kolo čistě ukončí). Obě vrstvy platí pro každou vstupní cestu kola (WebSocket, podřízeného agenta chat(), a programové saveMessages() / submitMessages()), a oba vydávají chat:context:compacted událost diagnostického kanálu.

Spustitelnou ukázku proti reálnému modelu Workers AI najdete v context-overflow-recovery příklad ↗.

Detekce stability

Think poskytuje metody, kterými lze ověřit, zda je agent ve stabilním stavu: žádné nevyřízené výsledky nástrojů, žádná nevyřízená schválení, žádná aktivní kola.

hasPendingInteraction

Vrátí true pokud některá zpráva asistenta obsahuje nevyřízená volání nástrojů (nástroje bez výsledků nebo s čekajícím schválením).

protected hasPendingInteraction(): boolean

waitUntilStable

Vrací promise, který se vyřeší na true když agent dosáhne stabilního stavu, nebo false pokud je časový limit překročen.

const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
	await this.saveMessages([
		{
			id: crypto.randomUUID(),
			role: "user",
			parts: [{ type: "text", text: "Now that you are done, summarize." }],
		},
	]);
}
const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
	await this.saveMessages([
		{
			id: crypto.randomUUID(),
			role: "user",
			parts: [{ type: "text", text: "Now that you are done, summarize." }],
		},
	]);
}