← Vytvářejte agenty na Cloudflare / agents / communication-channels / chat
Chat agenti
Vytvářejte chatovací rozhraní poháněná AI pomocí AIChatAgent a useAgentChat. Zprávy se automaticky ukládají do SQLite, streamy se po odpojení obnoví a volání nástrojů fungují napříč serverem i klientem.
Přehled
@cloudflare/ai-chat balíček poskytuje dvě hlavní API:
| Export | Import | Účel |
|---|---|---|
AIChatAgent |
@cloudflare/ai-chat |
Třída agenta na straně serveru s perzistencí zpráv a streamováním |
useAgentChat |
@cloudflare/ai-chat/react |
React hook pro tvorbu chatovacích uživatelských rozhraní |
Pokročilé pomocné funkce jsou také k dispozici z @cloudflare/ai-chat/react, @cloudflare/ai-chat/types, a agents/chat; viz Exporty pro úplné rozhraní balíčku.
Postaveno na AI SDK ↗ a Cloudflare Durable Objects získáte:
- Automatická perzistence zpráv : Konverzace uložené v SQLite přežijí restart
- Obnovitelné streamování : odpojení klienti pokračují uprostřed streamu bez ztráty dat
- Synchronizace v reálném čase : zprávy odesílané všem připojeným klientům přes WebSocket
- Podpora nástrojů : vzory nástrojů na straně serveru, na straně klienta a s člověkem ve smyčce
- Datové části : Připojte k textu ve zprávách typovaný JSON (citace, průběh, využití)
- Ochrana velikosti řádku : Automatická komprimace, když se zprávy blíží limitům SQLite
Rychlý start
Instalovat
npm i @cloudflare/ai-chat agents ai @ai-sdk/react workers-ai-providerServer
import { AIChatAgent } from "@cloudflare/ai-chat";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages } from "ai";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
// Use any provider such as workers-ai-provider, openai, anthropic, google, etc.
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
});
return result.toUIMessageStreamResponse();
}
}import { AIChatAgent } from "@cloudflare/ai-chat";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages } from "ai";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
// Use any provider such as workers-ai-provider, openai, anthropic, google, etc.
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
});
return result.toUIMessageStreamResponse();
}
}Klient
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "ChatAgent" });
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="Type a message..." />
<button type="submit" disabled={status !== "ready"}>
Send
</button>
</form>
</div>
);
}import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "ChatAgent" });
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="Type a message..." />
<button type="submit" disabled={status !== "ready"}>
Send
</button>
</form>
</div>
);
}Konfigurace Wrangleru
// wrangler.jsonc
{
"ai": { "binding": "AI" },
"durable_objects": {
"bindings": [{ "name": "ChatAgent", "class_name": "ChatAgent" }],
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["ChatAgent"] }],
} new_sqlite_classes migrace je nutná: AIChatAgent používá SQLite pro perzistenci zpráv a ukládání chunků streamu do vyrovnávací paměti.
Jak to funguje
sequenceDiagram
participant Client as Client (useAgentChat)
participant Agent as AIChatAgent
participant DB as SQLite
Client->>Agent: CF_AGENT_USE_CHAT_REQUEST (WebSocket)
Agent->>DB: Persist messages
Agent->>Agent: onChatMessage()
loop Streaming response
Agent-->>Client: CF_AGENT_USE_CHAT_RESPONSE (chunks)
Agent->>DB: Buffer chunks
end
Agent->>DB: Persist final message
Agent-->>Client: CF_AGENT_CHAT_MESSAGES (broadcast to all clients)
- Klient odešle zprávu přes WebSocket
AIChatAgentukládá zprávy do SQLite a volá vášonChatMessagemetoda- Vaše metoda vrací streamovanou
Response(obvykle zstreamText) - Chunky se v reálném čase streamují zpět přes WebSocket
- Když se stream dokončí, závěrečná zpráva se uloží a odešle všem připojením
Server API
AIChatAgent
Rozšiřuje Agent z agents balíček. Spravuje stav konverzace, perzistenci a streamování.
import { AIChatAgent } from "@cloudflare/ai-chat";
export class ChatAgent extends AIChatAgent {
// Access current messages
// this.messages: UIMessage[]
// Limit stored messages (optional)
maxPersistedMessages = 200;
async onChatMessage(onFinish, options) {
// onFinish: callback for streamText (cleanup is automatic)
// options.abortSignal: cancel signal
// options.body: custom data from client
// options.continuation: true for continuation turns
// Return a Response (streaming or plain text)
}
}import { AIChatAgent } from "@cloudflare/ai-chat";
export class ChatAgent extends AIChatAgent {
// Access current messages
// this.messages: UIMessage[]
// Limit stored messages (optional)
maxPersistedMessages = 200;
async onChatMessage(onFinish, options?) {
// onFinish: callback for streamText (cleanup is automatic)
// options.abortSignal: cancel signal
// options.body: custom data from client
// options.continuation: true for continuation turns
// Return a Response (streaming or plain text)
}
}onChatMessage
Toto je hlavní metoda, kterou přepisujete. Přijímá kontext konverzace a měla by vracet Response.
Streamovaná odpověď (nejběžnější):
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You are a helpful assistant.",
messages: await convertToModelMessages(this.messages),
});
return result.toUIMessageStreamResponse();
}
}export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You are a helpful assistant.",
messages: await convertToModelMessages(this.messages),
});
return result.toUIMessageStreamResponse();
}
}Odpověď v prostém textu:
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
return new Response("Hello! I am a simple agent.", {
headers: { "Content-Type": "text/plain" },
});
}
}Přístup k vlastním datům těla a ID požadavku:
export class ChatAgent extends AIChatAgent {
async onChatMessage(_onFinish, options) {
const { timezone, userId } = options?.body ?? {};
// Use these values in your LLM call or business logic
// options.requestId — unique identifier for this chat request,
// useful for logging and correlating events
console.log("Request ID:", options?.requestId);
if (options?.continuation) {
// This turn continues a previous assistant message after a tool result,
// continueLastTurn(), or recovery.
}
}
}options.continuation je true pro automatická pokračování po výsledcích nástrojů nebo schváleních, volání continueLastTurn(), a obnovená kola. Použijte ji k výběru jiného modelu, úpravě systémového promptu nebo přeskočení nákladného sestavování kontextu u navazujících kol.
this.messages
Aktuální historie konverzace, načtená ze SQLite. Jde o pole UIMessage objekty z AI SDK. Zprávy se po každé interakci automaticky ukládají.
maxPersistedMessages
Omezí počet zpráv uložených v SQLite. Když je limit překročen, nejstarší zprávy se smažou. Toto řízení se týká pouze úložiště, neovlivňuje to, co se posílá LLM.
export class ChatAgent extends AIChatAgent {
maxPersistedMessages = 200;
}export class ChatAgent extends AIChatAgent {
maxPersistedMessages = 200;
}Chcete-li ovlivnit, co se odešle modelu, použijte pruneMessages():
import { streamText, convertToModelMessages, pruneMessages } from "ai";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: pruneMessages({
messages: await convertToModelMessages(this.messages),
reasoning: "before-last-message",
toolCalls: "before-last-2-messages",
}),
});
return result.toUIMessageStreamResponse();
}
}import { streamText, convertToModelMessages, pruneMessages } from "ai";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: pruneMessages({
messages: await convertToModelMessages(this.messages),
reasoning: "before-last-message",
toolCalls: "before-last-2-messages",
}),
});
return result.toUIMessageStreamResponse();
}
}waitForMcpConnections
Ovládá, zda AIChatAgent počká, až se ustálí připojení serverů MCP, a teprve poté zavolá onChatMessage. Tím se zajistí this.mcp.getAITools() vrátí úplnou sadu nástrojů, zejména po hibernaci Durable Object, kdy se připojení obnovují na pozadí.
| Hodnota | Chování |
|---|---|
{ timeout: 10_000 } |
Čekejte až 10 sekund (výchozí) |
{ timeout: N } |
Čekejte až N milisekund |
true |
Čekejte neomezeně dlouho, dokud nejsou všechna připojení připravena |
false |
Nečekat (staré chování před verzí 0.2.0) |
export class ChatAgent extends AIChatAgent {
// Default — waits up to 10 seconds
// waitForMcpConnections = { timeout: 10_000 };
// Wait forever
waitForMcpConnections = true;
// Disable waiting
waitForMcpConnections = false;
}export class ChatAgent extends AIChatAgent {
// Default — waits up to 10 seconds
// waitForMcpConnections = { timeout: 10_000 };
// Wait forever
waitForMcpConnections = true;
// Disable waiting
waitForMcpConnections = false;
}Pro kontrolu na nižší úrovni volejte this.mcp.waitForConnections() přímo uvnitř vašeho onChatMessage místo toho.
messageConcurrency
Řídí, jak se chovají překrývající se uživatelská odeslání, pokud je kolo chatu již aktivní nebo zařazené ve frontě.
export class ChatAgent extends AIChatAgent {
messageConcurrency = "queue";
}export class ChatAgent extends AIChatAgent {
messageConcurrency = "queue";
}| Strategie | Chování |
|---|---|
"queue" (výchozí) |
Zařadí každé podání do fronty a zpracuje je v pořadí |
"latest" |
Ponechte pouze nejnovější překrývající se odeslání; nahrazená odeslání si i nadále uchovávají své uživatelské zprávy, ale nezahajují kolo modelu |
"merge" |
Zařadí překrývající se podání do fronty a poté sloučí jejich koncové uživatelské zprávy do jednoho společného kola ještě před spuštěním posledního zařazeného kola |
"drop" |
Překrývající se odeslání zcela ignorujte. Zprávy se neukládají. |
{ strategy: "debounce", debounceMs?: number } |
Poslední hodnota na konci intervalu s tichým oknem (výchozí 750 ms) |
Toto nastavení se týká pouze sendMessage() odeslání. Regenerace, pokračování nástrojů, schválení, vymazání a programové saveMessages() volání si zachovávají své dosavadní serializované chování.
persistMessages a saveMessages
persistMessages uchovává zprávy v SQLite a odesílá aktualizaci všem připojeným klientům, ale ne spustí tah modelu. Použijte ho, pokud chcete do konverzace vkládat zprávy, aniž byste spouštěli novou odpověď.
saveMessages ukládá zprávy a spouští onChatMessage() pro novou odpověď. Před spuštěním počká na dokončení každého probíhajícího chatového kola, takže naplánované nebo programové zprávy se nikdy nepřekrývají s právě probíhajícím streamem.
// Store messages without triggering a response
await this.persistMessages(messages);
// Store messages AND trigger onChatMessage
const { requestId, status } = await this.saveMessages(messages);// Store messages without triggering a response
await this.persistMessages(messages);
// Store messages AND trigger onChatMessage
const { requestId, status } = await this.saveMessages(messages);saveMessages přijímá buď pole zpráv, nebo funkci, která odvodí další seznam zpráv z naposledy uložené this.messages. Když se ve frontě nahromadí více volání, použijte formu funkce, abyste se vyhnuli zastaralému výchozímu stavu:
await this.saveMessages((messages) => [
...messages,
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Summarize the latest data" }],
createdAt: new Date(),
},
]);await this.saveMessages((messages) => [
...messages,
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Summarize the latest data" }],
createdAt: new Date(),
},
]);saveMessages vrací { requestId, status, error? } kde status je "completed" pokud kolo proběhlo, "error" pokud stream nahlásil chybu, "skipped" pokud byl chat vymazán ještě předtím, než začal, nebo "aborted" pokud externí AbortSignal ho zrušil ještě před dokončením. Když status je "error", error obsahuje chybovou zprávu streamu, je-li k dispozici.
Předejte options.signal pro zrušení programového tahu mimo chatovacího agenta. To se hodí, když potřebuje volání nadřazeného nástroje zrušit tah podřízeného agenta, aniž by znalo interně vygenerované ID požadavku:
const controller = new AbortController();
const result = await this.saveMessages(
(messages) => [...messages, syntheticUserMessage],
{ signal: controller.signal },
);
if (result.status === "aborted") {
// Partial chunks already streamed are persisted.
}const controller = new AbortController();
const result = await this.saveMessages(
(messages) => [...messages, syntheticUserMessage],
{ signal: controller.signal },
);
if (result.status === "aborted") {
// Partial chunks already streamed are persisted.
}continueLastTurn() přijímá stejné options.signal argument. AbortSignal objekty nemohou překročit hranice RPC Durable Object, proto vytvořte controller uvnitř Durable Object, který volá saveMessages() nebo continueLastTurn(). Signál existuje pouze v paměti. Pokud Durable Object zahibernuje uprostřed kola, obnovené kolo poběží bez původního signálu. Má-li zrušení přežít restart, uložte záměr zrušení trvale.
onChatResponse
Volá se poté, co kolo chatu vytvoří a uloží zprávu asistenta. Zámek kola se uvolní ještě před spuštěním tohoto hooku, takže je bezpečné zavolat saveMessages zevnitř. Spouští se pro cesty kola, které ukládají zprávu asistenta: požadavky chatu přes WebSocket, saveMessages, a automatické pokračování. Pokud kolo selže dříve, než vytvoří jakoukoli část odpovědi asistenta, chyba se místo toho projeví prostřednictvím původního požadavku.
export class ChatAgent extends AIChatAgent {
async onChatResponse(result) {
if (result.status === "completed") {
console.log("Turn completed:", result.requestId);
}
if (result.status === "error") {
console.error("Turn failed:", result.error);
}
}
}import type { ChatResponseResult } from "@cloudflare/ai-chat";
export class ChatAgent extends AIChatAgent {
protected async onChatResponse(result: ChatResponseResult) {
if (result.status === "completed") {
console.log("Turn completed:", result.requestId);
}
if (result.status === "error") {
console.error("Turn failed:", result.error);
}
}
} ChatResponseResult obsahuje:
| Pole | Typ | Popis |
|---|---|---|
message |
UIMessage |
Finalizovaná zpráva asistenta z tohoto kola |
requestId |
string |
ID požadavku spojené s tímto kolem |
continuation |
boolean |
Zda toto kolo navazovalo na předchozí kolo asistenta |
status |
"completed" | "error" | "aborted" |
Jak kolo skončilo |
error |
string | undefined |
Chybová zpráva, když status je "error" |
sanitizeMessageForPersistence
Přepište tuto metodu, chcete-li na zprávy použít vlastní transformace před jejich uložením do úložiště. Tento hook se spouští po vestavěné čištění dat (odstranění metadat OpenAI, oříznutí payloadu nástrojů spouštěných poskytovatelem Anthropic, filtrování prázdných reasoning částí).
export class ChatAgent extends AIChatAgent {
sanitizeMessageForPersistence(message) {
return {
...message,
parts: message.parts.map((part) => {
if (
"output" in part &&
typeof part.output === "string" &&
part.output.length > 1000
) {
return { ...part, output: "[redacted]" };
}
return part;
}),
};
}
}export class ChatAgent extends AIChatAgent {
protected sanitizeMessageForPersistence(message: UIMessage): UIMessage {
return {
...message,
parts: message.parts.map((part) => {
if (
"output" in part &&
typeof part.output === "string" &&
part.output.length > 1000
) {
return { ...part, output: "[redacted]" };
}
return part;
}),
};
}
}Pomocníci pro životní cyklus kola
Tyto metody pomáhají koordinovat programová kola a čekat na nevyřízené interakce.
hasPendingInteraction()
Vrátí true když zpráva asistenta čeká na výsledek klientského nástroje nebo na schválení.
if (this.hasPendingInteraction()) {
console.log("Waiting for user to approve or provide tool output");
}if (this.hasPendingInteraction()) {
console.log("Waiting for user to approve or provide tool output");
}waitUntilStable()
Čeká, dokud není konverzace zcela stabilní: žádný aktivní stream, žádné čekající interakce client-tool a žádná fronta pokračovacích kol. Vrací true jakmile je stabilní, nebo false pokud časový limit vyprší dříve, než se nevyřízená interakce dokončí.
const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
console.log("All turns complete, safe to proceed");
}const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
console.log("All turns complete, safe to proceed");
}Toto je obzvlášť užitečné s saveMessages pro toky řízené serverem:
await this.saveMessages((messages) => [...messages, syntheticUserMessage]);
await this.waitUntilStable({ timeout: 60_000 });
// The assistant has finished respondingawait this.saveMessages((messages) => [...messages, syntheticUserMessage]);
await this.waitUntilStable({ timeout: 60_000 });
// The assistant has finished respondingresetTurnState()
Přeruší aktivní kolo a znehodnotí pokračování čekající ve frontě. Vestavěný CF_AGENT_CHAT_CLEAR handler toto volá automaticky, ale v případě potřeby to můžete zavolat i ručně.
Hooky životního cyklu
Přepsat onConnect a onClose pro přidání vlastní logiky. Obnovení streamu a synchronizaci zpráv za vás zajišťuje systém:
export class ChatAgent extends AIChatAgent {
async onConnect(connection, ctx) {
// Your custom logic (e.g., logging, auth checks)
console.log("Client connected:", connection.id);
// Stream resumption and message sync are handled automatically
}
async onClose(connection, code, reason, wasClean) {
console.log("Client disconnected:", connection.id);
// Connection cleanup is handled automatically
}
}export class ChatAgent extends AIChatAgent {
async onConnect(connection, ctx) {
// Your custom logic (e.g., logging, auth checks)
console.log("Client connected:", connection.id);
// Stream resumption and message sync are handled automatically
}
async onClose(connection, code, reason, wasClean) {
console.log("Client disconnected:", connection.id);
// Connection cleanup is handled automatically
}
} destroy() metoda zruší veškeré čekající požadavky chatu a vyčistí stav streamu. Volá se automaticky, když je Durable Object odstraněn z paměti, ale v případě potřeby ji můžete zavolat i ručně.
Zrušení požadavku
Když uživatel v uživatelském rozhraní chatu klikne na "stop", klient odešle CF_AGENT_CHAT_REQUEST_CANCEL zprávu. Server to předá do abortSignal v options:
export class ChatAgent extends AIChatAgent {
async onChatMessage(_onFinish, options) {
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
abortSignal: options?.abortSignal, // Pass through for cancellation
});
return result.toUIMessageStreamResponse();
}
}export class ChatAgent extends AIChatAgent {
async onChatMessage(_onFinish, options) {
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
abortSignal: options?.abortSignal, // Pass through for cancellation
});
return result.toUIMessageStreamResponse();
}
}Podtřídy mohou také rušit kola zevnitř Durable Object:
protected abortRequest(requestId: string, reason?: unknown): void
protected abortAllRequests(): voidPoužijte abortRequest() když znáte ID požadavku. Použijte abortAllRequests() pro jednoúčelové pomocné funkce, které mají zrušit právě probíhající kolo, ať je jakékoli. Upřednostněte SaveMessagesOptions.signal pro programová kola, pokud můžete signál předat přímo v místě volání.
Obnovení streamu
Automatické obnovení streamu (resume možnost na useAgentChat) je obnovení po opětovném připojení klienta : obnoví aktivní stream, když se klient odpojí a znovu připojí. Nezahrnuje vyřazení Durable Object z paměti. Pokud je proces Workeru nebo Durable Object vyřazen z paměti během probíhajícího volání modelu, samotný stream zanikne. Tento případ řeší trvalé obnovení chatu.
Vyřazení Durable Object z paměti uprostřed streamu trvale přeruší spojení s LLM. Trvalé obnovení obaluje každé AIChatAgent a Think kolo chatu v runFiber(). Fiber poskytuje automatické keepAlive během streamování a hook pro obnovení při restartu.
Řádek fiberu přežije v SQLite i po vyřazení z paměti. Při další aktivaci framework rozpozná přerušený fiber. Sestaví částečnou odpověď z bufferovaných částí streamu a zavolá onChatRecovery.
Trvalé obnovení je vždy zapnuté. Použijte chatRecovery pouze pro ladění rozpočtů na zotavení a koncového chování:
export class ChatAgent extends AIChatAgent {
chatRecovery = {
maxAttempts: 10,
stableTimeoutMs: 10_000,
terminalMessage: "The assistant was interrupted and could not recover.",
// Primary stuck-turn bound. Resets on every progress-bearing attempt, so a
// turn that keeps producing content survives unbounded interruption.
noProgressTimeoutMs: 5 * 60 * 1000,
// Runaway-loop guard. Defaults to 1,000. Set a higher value for a long
// agentic turn, or Infinity to remove the cap.
maxRecoveryWork: 200,
// Caller policy consulted from the second recovery attempt onward. Return
// false to stop recovery. This is where you enforce a token/cost budget.
// Note: this is called as `config.shouldKeepRecovering(ctx)`, so it is not
// bound to the agent instance — track spend in your own store keyed by the
// incident.
async shouldKeepRecovering(ctx) {
return (await getSpendForTurn(ctx.recoveryRootRequestId)) < MAX_SPEND;
},
async onExhausted(ctx) {
console.warn("Chat recovery exhausted", ctx.incidentId, ctx.reason);
},
};
}export class ChatAgent extends AIChatAgent {
override chatRecovery = {
maxAttempts: 10,
stableTimeoutMs: 10_000,
terminalMessage: "The assistant was interrupted and could not recover.",
// Primary stuck-turn bound. Resets on every progress-bearing attempt, so a
// turn that keeps producing content survives unbounded interruption.
noProgressTimeoutMs: 5 * 60 * 1000,
// Runaway-loop guard. Defaults to 1,000. Set a higher value for a long
// agentic turn, or Infinity to remove the cap.
maxRecoveryWork: 200,
// Caller policy consulted from the second recovery attempt onward. Return
// false to stop recovery. This is where you enforce a token/cost budget.
// Note: this is called as `config.shouldKeepRecovering(ctx)`, so it is not
// bound to the agent instance — track spend in your own store keyed by the
// incident.
async shouldKeepRecovering(ctx) {
return (await getSpendForTurn(ctx.recoveryRootRequestId)) < MAX_SPEND;
},
async onExhausted(ctx) {
console.warn("Chat recovery exhausted", ctx.incidentId, ctx.reason);
},
};
} chatRecovery objekt přijímá následující konfigurační možnosti:
| Pole | Výchozí | Popis |
|---|---|---|
maxAttempts |
10 |
Limit počtu pokusů před trvalým vyčerpáním. 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 o obnovení čeká, než izolát dosáhne stabilního stavu, než dojde k novému naplánování. |
terminalMessage |
obecná zpráva | Zpráva zobrazená uživateli, když se od obnovení upustí. |
noProgressTimeoutMs |
300_000 (5 min) |
Primární limit pro zaseknuté kolo: jak dlouho může incident zůstat bez postupu vpřed, než je uzavřen (no_progress_timeout). Resetuje se při každém pokusu, který přináší postup, takže tah, který stále generuje obsah, přežije i neomezené přerušení. |
maxRecoveryWork |
1,000 |
Pojistka proti nekontrolované smyčce. Maximální počet vytvořených jednotek obsahu/nástrojů od začátku incidentu, 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 o obnovení dál. Vraťte false a zastavit obnovení. Použijte ho k vynucení rozpočtu tokenů nebo nákladů. ctx.work je hrubý počet segmentů, nikoli tokenů, proto si skutečné výdaje sledujte sami. |
onExhausted |
, | Volá se jednou, když se obnovení vzdá, ještě před doručením koncové zprávy. Prozkoumejte ctx.reason pro důvod. |
ChatRecoveryProgressContext ( ctx předané do shouldKeepRecovering) obsahuje následující pole:
| Pole | Typ | Popis |
|---|---|---|
incidentId |
string |
Stabilní ID pro tento incident obnovy. |
requestId |
string |
ID požadavku pro aktuální pokračování (mění se s každým zřetězeným pokračováním). |
recoveryRootRequestId |
string |
Stabilní ID pro celý řetězec pokračování: správný klíč pro sledování rozpočtu podle jednotlivých incidentů. |
attempt |
number |
Číslo pokusu pro tento incident (2 nebo vyšší, pokud tento hook běží). |
maxAttempts |
number |
Nakonfigurovaný limit počtu pokusů. |
recoveryKind |
"retry" | "continue" |
Zda obnovení opakuje nezodpovězené uživatelské kolo, nebo pokračuje v částečném kole asistenta. |
work |
number |
Hrubý, monotónní počet segmentů obsahu nebo nástrojů vytvořených od otevření incidentu (nikoli tokenů). |
ageMs |
number |
Milisekundy reálného času od prvního přerušení incidentu. |
Postupující kolo přežije opakovaná přerušení, dokud zůstává v rámci maxRecoveryWork limit. Obnovení se uzavře jedním z těchto ctx.reason hodnoty:
no_progress_timeout: žádný postup vpřed v rámci okna bez postupu (zaseknuté kolo).max_attempts_exceeded: limit počtu pokusů se vyčerpal ve smyčce alarmů bez postupu.work_budget_exceeded: kolo dál produkovalo obsah, ale překročilomaxRecoveryWork(neřízená smyčka).recovery_aborted: vášshouldKeepRecoveringhook vrátilfalse.out_of_memory: obnovení překročilo rozpočet opakování při limitu paměti.stable_timeout: pokusy o obnovení opakovaně vypršely při čekání na stabilní stav, dokud se nevyčerpal rozpočet (extrémní fluktuace).
Kola čekající na člověka nejsou uzavřená
Kolo se může pozastavit na interakci s klientem, kterou nedokáže vyřešit samo: volání nástroje na straně klienta (nástroj bez serverové execute, jehož výsledek klient přehraje), nebo approval-requested část. Takové kolo čeká na člověka, není zaseknuté.
Dokud interakce čeká na vyřízení, je kolo vyňato z každého rozpočtu na obnovení. Okno bez postupu, limit počtu pokusů, maxRecoveryWork, a shouldKeepRecovering jsou všechny pozastaveny. Uživatel, kterému zabere několik minut odpovědět na dotaz přerušený nasazením, nikdy nespustí zapečetění. Zotavení dané kolo raději pozastaví, než aby jej nechalo selhat, a jakmile uživatel nakonec schválí akci nebo dorazí výsledek nástroje, kolo pokračuje běžnou cestou pokračování.
Tato výjimka platí pouze na straně klienta. Serverový nástroj, jehož execute() byl zabit uprostřed běhu, je to skutečný osiřelý záznam, takže není z opravy vyňat a místo toho se zotaví přes opravu přepisu.
Sledujte vyčerpání terminálů prostřednictvím observability:
import { subscribe } from "agents/observability";
const unsubscribe = subscribe("chat", (event) => {
if (event.type === "chat:recovery:exhausted") {
console.error("Chat recovery exhausted", event.payload);
}
});import { subscribe } from "agents/observability";
const unsubscribe = subscribe("chat", (event) => {
if (event.type === "chat:recovery:exhausted") {
console.error("Chat recovery exhausted", event.payload);
}
});onChatRecovery
Přepište pro implementaci recovery specifického pro poskytovatele. Výchozí chování uloží částečnou odpověď a naplánuje pokračování přes continueLastTurn().
export class ChatAgent extends AIChatAgent {
async onChatRecovery(ctx) {
console.log(`Recovered ${ctx.partialText.length} chars of partial text`);
// Default: persist partial + schedule continuation
return {};
}
}import type {
ChatRecoveryContext,
ChatRecoveryOptions,
} from "@cloudflare/ai-chat";
export class ChatAgent extends AIChatAgent {
override async onChatRecovery(
ctx: ChatRecoveryContext,
): Promise<ChatRecoveryOptions> {
console.log(`Recovered ${ctx.partialText.length} chars of partial text`);
// Default: persist partial + schedule continuation
return {};
}
}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 přerušeného streamu |
requestId |
string |
ID původního chatového požadavku |
partialText |
string |
Text vygenerovaný před vyřazením z paměti |
partialParts |
MessagePart[] |
Části zprávy (text, reasoning, volání nástrojů) vygenerované před vyřazením |
recoveryData |
unknown | null |
Data z this.stash() : zcela pod kontrolou uživatele |
messages |
ChatMessage[] |
Úplná historie konverzace |
lastBody |
Record<string, unknown> | undefined |
Původní tělo požadavku |
lastClientTools |
ClientToolSchema[] | undefined |
Schémata klientských nástrojů z původního požadavku |
createdAt |
number |
Milisekundy epochy, kdy začalo přerušené kolo |
ChatRecoveryOptions:
| Pole | Výchozí | Popis |
|---|---|---|
persist |
true |
Uložit částečnou odpověď jako zprávu asistenta |
continue |
true |
Naplánujte pokračování prostřednictvím continueLastTurn() |
Běžné návratové hodnoty:
{}: uloží částečný výsledek a automaticky pokračuje (výchozí, funguje u poskytovatelů podporujících assistant prefill){ continue: false }: uloží částečný výsledek, ale nepokračuje automaticky (pokračování zpracujte sami){ persist: false, continue: false }: neukládejte nevyřízený zbytek a vše zpracujte sami (například načtěte dokončenou odpověď od poskytovatele)
Dokončená práce se nikdy neztratí: persist: false pouze potlačuje uložení částečného výsledku, který nemá co ztratit, protože nic není potvrzené. Částečný výsledek, který již obsahuje potvrzené výsledky nástrojů (dokončenou, často neidempotentní práci), se uloží vždy, takže aplikace nemůže omylem zahodit dokončená volání nástrojů, a nikdy nepotřebuje { persist: true } jen pro jistotu.
Když k obnovení dojde dříve, než se zapíší jakékoli části streamu, neexistuje žádná částečná zpráva asistenta, na kterou by šlo navázat. Pokud je poslední uložená zpráva stále nezodpovězenou zprávou uživatele z přerušeného kola, framework toto kolo automaticky zopakuje, pokud continue je false.
Použijte ctx.createdAt a přeskočit zastaralá obnovení:
override async onChatRecovery(
ctx: ChatRecoveryContext,
): Promise<ChatRecoveryOptions> {
if (Date.now() - ctx.createdAt > 2 * 60 * 1000) {
return { continue: false };
}
return {};
}Řiďte automatické pokračování
Trvalé vedení evidence zůstává aktivní, když automatické pokračování není vhodné.
- Vraťte
{ continue: false }když je jiné volání modelu nebezpečné. - Uložte záměr zrušení a přečtěte si jej v
onChatRecovery(). - Zaznamenávejte klíče idempotence před externími vedlejšími účinky.
- K omezení nákladů použijte rozpočty na zotavení s trvalými údaji o výdajích.
continueLastTurn
Připojí se k poslední zprávě asistenta opětovným voláním onChatMessage s uloženým tělem požadavku. Odpověď se streamuje jako pokračování: připojí se k existující zprávě asistenta, ne k nové. Nevytváří se žádná syntetická zpráva uživatele.
protected continueLastTurn(
body?: Record<string, unknown>,
options?: SaveMessagesOptions,
): Promise<SaveMessagesResult>;Volá se automaticky výchozí cestou obnovy. Lze ho také zavolat ručně z naplánovaných callbacků nebo jiných vstupních bodů. Volitelný body parametr přepisuje uložené tělo požadavku pro toto pokračování. Předejte options.signal pro zrušení pokračování během jeho běhu.
Ukládání dat pro obnovu do stashe
Použijte this.stash() uvnitř onChatMessage k trvalému uložení dat specifických pro poskytovatele za účelem zotavení. Stash se ukládá v řádku SQLite daného fiberu, odděleně od stavu agenta, a je dostupný jako ctx.recoveryData v onChatRecovery.
export class ChatAgent extends AIChatAgent {
async onChatMessage(_onFinish, options) {
const result = streamText({
model: openai("gpt-5.4"),
messages: await convertToModelMessages(this.messages),
providerOptions: { openai: { store: true } },
includeRawChunks: true,
onChunk: ({ chunk }) => {
if (chunk.type === "raw") {
const raw = chunk.rawValue;
if (raw?.type === "response.created" && raw.response?.id) {
this.stash({ responseId: raw.response.id });
}
}
},
});
return result.toUIMessageStreamResponse();
}
}export class ChatAgent extends AIChatAgent {
async onChatMessage(_onFinish, options) {
const result = streamText({
model: openai("gpt-5.4"),
messages: await convertToModelMessages(this.messages),
providerOptions: { openai: { store: true } },
includeRawChunks: true,
onChunk: ({ chunk }) => {
if (chunk.type === "raw") {
const raw = chunk.rawValue as {
type?: string;
response?: { id?: string };
};
if (raw?.type === "response.created" && raw.response?.id) {
this.stash({ responseId: raw.response.id });
}
}
},
});
return result.toUIMessageStreamResponse();
}
}Strategie obnovení podle poskytovatele
Správná strategie závisí na tom, zda poskytovatel podporuje assistant prefill a zda odpověď pokračuje na straně serveru i po odpojení:
| Poskytovatel | Strategie | Náklady na tokeny |
|---|---|---|
| Workers AI | continueLastTurn() : model pokračuje pomocí assistant prefill |
Low |
| OpenAI (Responses API) | Načíst dokončenou odpověď podle ID: bez plýtvání tokeny | Nula |
| Anthropic | Uložte částečný výsledek a odešlete syntetickou uživatelskou zprávu pro pokračování | Medium |
Stav obnovování na klientovi
Zatímco probíhá obnovení kola, agent vysílá cf_agent_chat_recovering stavový rámec, aby klienti mohli zobrazit indikátor „obnovuje se…“ místo toho, aby aplikace působila zaseknutě. Nastaví se, když je naplánováno pokračování obnovy, a vymaže se při každém konečném výsledku, takže se indikátor nikdy nekroutí do nekonečna. Zpracujte ho pomocí useAgentChat: isRecovering příznak (viz Návratové hodnoty). Signál je informativní a zpětně kompatibilní, klienti, kteří mu nerozumí, ho ignorují.
Opravy transkriptu, tedy zacelení osiřelých volání nástrojů (zachovaných jako chybové výsledky místo smazání, takže záznam zůstává a model nástroj tiše znovu nespustí) a normalizace poškozených nebo chybějících vstupů nástrojů před voláním poskytovatele, se odesílají na transcript kanál pozorovatelnosti.
Jak zotavení chatu zapadá do širšího konceptu dlouhoběžících agentů, se dozvíte v Dlouhodobě běžící agenti: obnova přerušených streamů LLM. Pro podkladové fiber API si přečtěte Durable Execution.
Klientské API
useAgentChat
React hook, který se připojuje k AIChatAgent přes WebSocket. Obaluje useChat s nativním transportem WebSocket.
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "ChatAgent" });
const {
messages,
sendMessage,
clearHistory,
addToolOutput,
addToolApprovalResponse,
setMessages,
status,
isStreaming,
isServerStreaming,
isToolContinuation,
isRecovering,
} = useAgentChat({ agent });
// ...
}import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "ChatAgent" });
const {
messages,
sendMessage,
clearHistory,
addToolOutput,
addToolApprovalResponse,
setMessages,
status,
isStreaming,
isServerStreaming,
isToolContinuation,
isRecovering,
} = useAgentChat({ agent });
// ...
}Možnosti
| Možnost | Typ | Výchozí | Popis |
|---|---|---|---|
agent |
ReturnType<typeof useAgent> |
Povinné | Připojení agenta z useAgent |
onToolCall |
({ toolCall, addToolOutput }) => void |
, | Zpracování spouštění nástrojů na straně klienta |
tools |
Record<string, AITool> |
, | Pokročilé: dynamická registrace nástrojů spouštěných klientem z prohlížeče |
autoContinueAfterToolResult |
boolean |
true |
Automaticky pokračovat v konverzaci po výsledcích klientských nástrojů a schváleních |
resume |
boolean |
true |
Povolte automatické obnovení streamu při opětovném připojení |
cancelOnClientAbort |
boolean |
false |
Zruší kolo serveru, když dojde k obecnému přerušení streamu na straně klienta nebo k úklidu |
body |
object | () => object |
, | Vlastní data odesílaná s každým požadavkem |
prepareSendMessagesRequest |
(options) => { body?, headers? } |
, | Pokročilé přizpůsobení pro jednotlivé požadavky |
getInitialMessages |
(options) => Promise<UIMessage[]> nebo null |
, | Vlastní zavaděč počáteční zprávy. Nastavte na null a zcela přeskočit HTTP požadavek (užitečné, pokud poskytujete messages přímo) |
syncMessagesToServer |
boolean |
true |
Když true, setMessages odesílá přepis na server. Nastavte na false pro hostitele, kde je úložiště přepisů řízeno serverem, aby setMessages aktualizuje pouze lokální zobrazení |
Návratové hodnoty
| Vlastnost | Typ | Popis |
|---|---|---|
messages |
UIMessage[] |
Aktuální zprávy konverzace |
sendMessage |
(message) => void |
Odeslat zprávu |
clearHistory |
() => void |
Vymazat konverzaci (klient a server) |
addToolOutput |
({ toolCallId, output }) => void |
Poskytnutí výstupu pro nástroj na straně klienta |
addToolApprovalResponse |
({ id, approved }) => void |
Schválit nebo zamítnout nástroj vyžadující schválení |
setMessages |
(messages | updater) => void |
Nastavte zprávy přímo (synchronizuje se se serverem) |
status |
string |
"ready", "submitted", "streaming", nebo "error" |
isStreaming |
boolean |
true zatímco agent streamuje nebo čeká na aktivní klientský nástroj |
isServerStreaming |
boolean |
true zatímco probíhá stream iniciovaný serverem nebo aktivní fáze klientského nástroje |
isToolContinuation |
boolean |
true zatímco běží automatické pokračování po výsledku nástroje nebo schválení |
isRecovering |
boolean |
true zatímco probíhá obnova trvalého kola (přerušeného a navazujícího). Na rozdíl od isStreaming : Obnovující se tah zatím negeneruje tokeny. Zobrazte nápovědu "obnovuje se…"; většina uživatelských rozhraní zachází s isStreaming || isRecovering jako "busy" |
Použijte isToolContinuation když má vaše UI rozlišit čerstvé odeslání uživatelem od pokračování po výsledku nástroje. Zobrazte například indikátor psaní pouze pro status === "submitted" && !isToolContinuation, přičemž ovládací prvky načítání ponecháte deaktivované, kdykoli isStreaming je true.
Nástroje
AIChatAgent podporuje tři vzory nástrojů, všechny využívají AI SDK tool() funkce:
| Vzor | Kde běží | Kdy použít |
|---|---|---|
| Na straně serveru | Server (automaticky) | volání API, databázové dotazy, výpočty |
| Na straně klienta | Prohlížeč (přes onToolCall) |
Geolokace, schránka, kamera, místní úložiště |
| Schválení | Server (po schválení uživatelem) | Platby, mazání, externí akce |
Nástroje na straně serveru
Nástroje s execute funkce se na serveru spouští automaticky:
import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
tools: {
getWeather: tool({
description: "Get weather for a city",
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => {
const data = await fetchWeather(city);
return { temperature: data.temp, condition: data.condition };
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
tools: {
getWeather: tool({
description: "Get weather for a city",
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => {
const data = await fetchWeather(city);
return { temperature: data.temp, condition: data.condition };
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}Nástroje na straně klienta
Definujte nástroj na serveru bez execute, poté ho na klientovi zpracujte pomocí onToolCall. Použijte to pro nástroje, které potřebují API prohlížeče.
Server:
tools: {
getLocation: tool({
description: "Get the user's location from the browser",
inputSchema: z.object({}),
// No execute — the client handles it
});
}tools: {
getLocation: tool({
description: "Get the user's location from the browser",
inputSchema: z.object({}),
// No execute — the client handles it
});
}Klient:
const { messages, sendMessage } = useAgentChat({
agent,
onToolCall: async ({ toolCall, addToolOutput }) => {
if (toolCall.toolName === "getLocation") {
const pos = await new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject),
);
addToolOutput({
toolCallId: toolCall.toolCallId,
output: { lat: pos.coords.latitude, lng: pos.coords.longitude },
});
}
},
});const { messages, sendMessage } = useAgentChat({
agent,
onToolCall: async ({ toolCall, addToolOutput }) => {
if (toolCall.toolName === "getLocation") {
const pos = await new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject),
);
addToolOutput({
toolCallId: toolCall.toolCallId,
output: { lat: pos.coords.latitude, lng: pos.coords.longitude },
});
}
},
});Když LLM zavolá getLocation, stream se pozastaví. Aktivuje se onToolCall callback spustí, váš kód poskytne výstup a konverzace pokračuje.
Pro SDK nebo platformy, kde prohlížeč rozhoduje o dostupných nástrojích za běhu, předejte tools objekt na useAgentChat. Nástroje s klientskými execute funkce se automaticky serializují a odesílají na server. Na serveru se options.clientTools a createToolsFromClientSchemas() zůstávají podporovány pro tento dynamický vzor nástrojů.
Schvalování nástrojů (human-in-the-loop)
Použijte needsApproval pro nástroje, které před spuštěním vyžadují potvrzení uživatele.
Server:
tools: {
processPayment: tool({
description: "Process a payment",
inputSchema: z.object({
amount: z.coerce.number(),
recipient: z.string(),
}),
needsApproval: async ({ amount }) => amount > 100,
execute: async ({ amount, recipient }) => charge(amount, recipient),
});
}Klient:
import { getToolName, isToolUIPart } from "ai";
import {
getToolApproval,
getToolCallId,
getToolPartState,
} from "@cloudflare/ai-chat/react";
const { messages, addToolApprovalResponse } = useAgentChat({ agent });
// Render pending approvals from message parts
{
messages.map((msg) =>
msg.parts
.filter(
(part) =>
isToolUIPart(part) && getToolPartState(part) === "waiting-approval",
)
.map((part) => (
<div key={getToolCallId(part)}>
<p>Approve {getToolName(part)}?</p>
<button
onClick={() => {
const approval = getToolApproval(part);
if (!approval) return;
addToolApprovalResponse({
id: approval.id,
approved: true,
});
}}
>
Approve
</button>
<button
onClick={() => {
const approval = getToolApproval(part);
if (!approval) return;
addToolApprovalResponse({
id: approval.id,
approved: false,
});
}}
>
Reject
</button>
</div>
)),
);
}Vlastní zprávy o zamítnutí s addToolOutput
Když uživatel odmítne nástroj, addToolApprovalResponse({ id, approved: false }) nastaví stav nástroje na output-denied s obecnou zprávou. Chcete-li LLM poskytnout konkrétnější důvod zamítnutí, použijte addToolOutput hodnotou state: "output-error" místo toho:
const { addToolOutput } = useAgentChat({ agent });
// Reject with a custom error message
addToolOutput({
toolCallId: part.toolCallId,
state: "output-error",
errorText: "User declined: insufficient budget for this quarter",
});const { addToolOutput } = useAgentChat({ agent });
// Reject with a custom error message
addToolOutput({
toolCallId: part.toolCallId,
state: "output-error",
errorText: "User declined: insufficient budget for this quarter",
});Tím se odešle tool_result modelu LLM s vlastním textem chyby, aby mohl odpovědět přiměřeně (například navrhnout alternativu nebo položit doplňující otázky).
addToolApprovalResponse (s approved: false) automaticky pokračuje v konverzaci, když autoContinueAfterToolResult je zapnuto (výchozí nastavení). addToolOutput hodnotou state: "output-error" provádí ne automatické pokračování: zavolejte sendMessage() následně, pokud chcete, aby na chybu reagoval LLM.
Další vzory najdete v Lidský zásah.
Vlastní data požadavku
Vlastní data zahrňte do každého chatového požadavku pomocí body možnost:
const { messages, sendMessage } = useAgentChat({
agent,
body: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
userId: currentUser.id,
},
});const { messages, sendMessage } = useAgentChat({
agent,
body: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
userId: currentUser.id,
},
});Pro dynamické hodnoty použijte funkci:
body: () => ({
token: getAuthToken(),
timestamp: Date.now(),
});body: () => ({
token: getAuthToken(),
timestamp: Date.now(),
});Přistupte k těmto polím na serveru:
export class ChatAgent extends AIChatAgent {
async onChatMessage(_onFinish, options) {
const { timezone, userId } = options?.body ?? {};
// ...
}
}export class ChatAgent extends AIChatAgent {
async onChatMessage(_onFinish, options) {
const { timezone, userId } = options?.body ?? {};
// ...
}
}Pro pokročilé přizpůsobení pro jednotlivé požadavky (vlastní hlavičky, odlišné tělo pro každý požadavek) použijte prepareSendMessagesRequest:
const { messages, sendMessage } = useAgentChat({
agent,
prepareSendMessagesRequest: async ({ messages, trigger }) => ({
headers: { Authorization: `Bearer ${await getToken()}` },
body: { requestedAt: Date.now() },
}),
});const { messages, sendMessage } = useAgentChat({
agent,
prepareSendMessagesRequest: async ({ messages, trigger }) => ({
headers: { Authorization: `Bearer ${await getToken()}` },
body: { requestedAt: Date.now() },
}),
});Datové části
Datové části vám umožňují ke zprávám připojit typovaný JSON vedle textu: indikátory průběhu, citace zdrojů, využití tokenů nebo jakákoli strukturovaná data, která vaše uživatelské rozhraní potřebuje.
Zápis částí dat (server)
Použijte createUIMessageStream hodnotou writer.write() k odesílání datových částí ze serveru:
import {
streamText,
convertToModelMessages,
createUIMessageStream,
createUIMessageStreamResponse,
} from "ai";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const stream = createUIMessageStream({
execute: async ({ writer }) => {
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
});
// Merge the LLM stream
writer.merge(result.toUIMessageStream());
// Write a data part — persisted to message.parts
writer.write({
type: "data-sources",
id: "src-1",
data: { query: "agents", status: "searching", results: [] },
});
// Later: update the same part in-place (same type + id)
writer.write({
type: "data-sources",
id: "src-1",
data: {
query: "agents",
status: "found",
results: ["Agents SDK docs", "Durable Objects guide"],
},
});
},
});
return createUIMessageStreamResponse({ stream });
}
}import {
streamText,
convertToModelMessages,
createUIMessageStream,
createUIMessageStreamResponse,
} from "ai";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const stream = createUIMessageStream({
execute: async ({ writer }) => {
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
});
// Merge the LLM stream
writer.merge(result.toUIMessageStream());
// Write a data part — persisted to message.parts
writer.write({
type: "data-sources",
id: "src-1",
data: { query: "agents", status: "searching", results: [] },
});
// Later: update the same part in-place (same type + id)
writer.write({
type: "data-sources",
id: "src-1",
data: {
query: "agents",
status: "found",
results: ["Agents SDK docs", "Durable Objects guide"],
},
});
},
});
return createUIMessageStreamResponse({ stream });
}
}Tři vzory
| Vzor | Jak | Uloženo? | Případ použití |
|---|---|---|---|
| Sesouhlasení | Stejný type + id → aktualizuje na místě |
Ano | Postupný stav (hledání → nalezeno) |
| Připojení | Ne id, nebo jiné id → připojí |
Ano | Záznamy protokolu, více citací |
| Dočasný | transient: true → nepřidá se do message.parts |
Ne | Dočasný stav (indikátor přemýšlení) |
Dočasné části se v reálném čase odesílají připojeným klientům, ale jsou vyloučeny z perzistence SQLite a message.parts. Použijte onData callback, který je zpracuje.
Čtení částí dat (klient)
Trvalé datové části se objevují v message.parts. Použijte UIMessage generický typ pro jejich typování:
import { useAgentChat } from "@cloudflare/ai-chat/react";
const { messages } = useAgentChat({ agent });
// Typed access — no casts needed
for (const msg of messages) {
for (const part of msg.parts) {
if (part.type === "data-sources") {
console.log(part.data.results); // string[]
}
}
}import { useAgentChat } from "@cloudflare/ai-chat/react";
import type { UIMessage } from "ai";
type ChatMessage = UIMessage<
unknown,
{
sources: { query: string; status: string; results: string[] };
usage: { model: string; inputTokens: number; outputTokens: number };
}
>;
const { messages } = useAgentChat<unknown, ChatMessage>({ agent });
// Typed access — no casts needed
for (const msg of messages) {
for (const part of msg.parts) {
if (part.type === "data-sources") {
console.log(part.data.results); // string[]
}
}
}Dočasné části s onData
Dočasné datové části nejsou v message.parts. Použijte onData callback:
const [thinking, setThinking] = useState(false);
const { messages } = useAgentChat({
agent,
onData(part) {
if (part.type === "data-thinking") {
setThinking(true);
}
},
});const [thinking, setThinking] = useState(false);
const { messages } = useAgentChat<unknown, ChatMessage>({
agent,
onData(part) {
if (part.type === "data-thinking") {
setThinking(true);
}
},
});Na serveru zapisujte dočasné části pomocí transient: true:
writer.write({
transient: true,
type: "data-thinking",
data: { model: "glm-4.7-flash", startedAt: new Date().toISOString() },
});writer.write({
transient: true,
type: "data-thinking",
data: { model: "glm-4.7-flash", startedAt: new Date().toISOString() },
});onData se spustí ve všech větvích kódu: u nových zpráv, obnovení streamu i vysílání mezi kartami.
Obnovitelné streamování
Streamy se automaticky obnoví, jakmile se klient odpojí a znovu připojí. Není potřeba žádná konfigurace, funguje to bez dalšího nastavování.
Když probíhá streamování:
- Všechny chunky se při svém vzniku ukládají do vyrovnávací paměti v SQLite
- Pokud se klient odpojí, server dál streamuje a ukládá data do vyrovnávací paměti
- Když se klient znovu připojí, obdrží všechny uložené části dat a naváže na živé streamování
Obecné přerušení nebo vyčištění klientského streamu ve výchozím nastavení zůstává pouze v prohlížeči, takže kolo na straně serveru běží dál a lze jej později obnovit. Volání stop() výslovně přesto zruší kolo na serveru:
const { messages, stop } = useAgentChat({ agent });
return <button onClick={stop}>Stop</button>;const { messages, stop } = useAgentChat({ agent });
return <button onClick={stop}>Stop</button>;Nastavte cancelOnClientAbort: true když vaše aplikace záměrně chce, aby životní cyklus prohlížeče řídil životní cyklus serveru, například u toků vázaných na dobu trvání požadavku nebo šetřících tokeny. Explicitní stop() vždy zruší práci na serveru bez ohledu na tuto možnost.
Zakažte pomocí resume: false:
const { messages } = useAgentChat({ agent, resume: false });const { messages } = useAgentChat({ agent, resume: false });Správa úložiště
Ochrana velikosti řádku
Řádky Workers SQLite mají pevně danou maximální velikost 2 MB. Abyste zůstali pod tímto limitem, AIChatAgent začne komprimovat serializovanou zprávu přibližně od 1.8 MB, například když nástroj vrátí velmi velký výstup:
- Komprimace výstupu nástrojů : Velké výstupy nástrojů se nahradí shrnutím vhodným pro LLM, které modelu doporučí navrhnout opětovné spuštění nástroje
- Zkrácení textu : Pokud je zpráva i po komprimaci nástrojů stále příliš velká, textové části se zkrátí a doplní poznámkou
Komprimované zprávy obsahují metadata.compactedToolOutputs aby to klienti mohli rozpoznat a elegantně zobrazit.
Řízení kontextu LLM oproti úložišti
Úložiště (maxPersistedMessages) a kontext LLM jsou nezávislé:
| Aspekt | Ovládání | Rozsah |
|---|---|---|
| Kolik zpráv SQLite ukládá | maxPersistedMessages |
Perzistence |
| Co vidí model | pruneMessages() |
Kontext LLM |
| Limity velikosti řádku | Automatická komprimace | Na zprávu |
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: pruneMessages({
// LLM context limit
messages: await convertToModelMessages(this.messages),
reasoning: "before-last-message",
toolCalls: "before-last-2-messages",
}),
});
return result.toUIMessageStreamResponse();
}
}export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: pruneMessages({
// LLM context limit
messages: await convertToModelMessages(this.messages),
reasoning: "before-last-message",
toolCalls: "before-last-2-messages",
}),
});
return result.toUIMessageStreamResponse();
}
}Používání různých poskytovatelů AI
AIChatAgent funguje s libovolným poskytovatelem kompatibilním s AI SDK. Který model se použije, určuje kód na serveru: klient ho nemusí ručně měnit.
Workers AI (Cloudflare)
import { createWorkersAI } from "workers-ai-provider";
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
});import { createWorkersAI } from "workers-ai-provider";
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: await convertToModelMessages(this.messages),
});OpenAI
import { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({ apiKey: this.env.OPENAI_API_KEY });
const result = streamText({
model: openai.chat("gpt-4o"),
messages: await convertToModelMessages(this.messages),
});import { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({ apiKey: this.env.OPENAI_API_KEY });
const result = streamText({
model: openai.chat("gpt-4o"),
messages: await convertToModelMessages(this.messages),
});Anthropic
import { createAnthropic } from "@ai-sdk/anthropic";
const anthropic = createAnthropic({ apiKey: this.env.ANTHROPIC_API_KEY });
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
messages: await convertToModelMessages(this.messages),
});import { createAnthropic } from "@ai-sdk/anthropic";
const anthropic = createAnthropic({ apiKey: this.env.ANTHROPIC_API_KEY });
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
messages: await convertToModelMessages(this.messages),
});Pokročilé vzory
Protože onChatMessage vám dává plnou kontrolu nad streamText volání můžete přímo použít jakoukoli funkci AI SDK. Všechny níže uvedené vzory fungují bez úprav, není potřeba žádné speciální AIChatAgent konfigurace je nutná.
Dynamické řízení modelu a nástrojů
Použijte prepareStep ↗ pro změnu modelu, dostupných nástrojů nebo systémového promptu mezi kroky ve vícekrokové smyčce agenta:
import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: cheapModel, // Default model for simple steps
messages: await convertToModelMessages(this.messages),
tools: {
search: searchTool,
analyze: analyzeTool,
summarize: summarizeTool,
},
stopWhen: stepCountIs(10),
prepareStep: async ({ stepNumber, messages }) => {
// Phase 1: Search (steps 0-2)
if (stepNumber <= 2) {
return {
activeTools: ["search"],
toolChoice: "required", // Force tool use
};
}
// Phase 2: Analyze with a stronger model (steps 3-5)
if (stepNumber <= 5) {
return {
model: expensiveModel,
activeTools: ["analyze"],
};
}
// Phase 3: Summarize
return { activeTools: ["summarize"] };
},
});
return result.toUIMessageStreamResponse();
}
}import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: cheapModel, // Default model for simple steps
messages: await convertToModelMessages(this.messages),
tools: {
search: searchTool,
analyze: analyzeTool,
summarize: summarizeTool,
},
stopWhen: stepCountIs(10),
prepareStep: async ({ stepNumber, messages }) => {
// Phase 1: Search (steps 0-2)
if (stepNumber <= 2) {
return {
activeTools: ["search"],
toolChoice: "required", // Force tool use
};
}
// Phase 2: Analyze with a stronger model (steps 3-5)
if (stepNumber <= 5) {
return {
model: expensiveModel,
activeTools: ["analyze"],
};
}
// Phase 3: Summarize
return { activeTools: ["summarize"] };
},
});
return result.toUIMessageStreamResponse();
}
}prepareStep se spustí před každým krokem a může vrátit přepsání pro model, activeTools, toolChoice, system, a messages. Použijte ho k:
- Přepínání modelů : pro jednoduché kroky použijte levný model, pro uvažování eskalujte na výkonnější
- Nástroje pro fáze : omezuje, které nástroje jsou dostupné v jednotlivých krocích
- Správa kontextu : ořezává nebo transformuje zprávy, aby se dodržel limit tokenů
- Vynucení volání nástrojů, použijte
toolChoice: { type: "tool", toolName: "search" }k vyžadování konkrétního nástroje
Middleware jazykového modelu
Použijte wrapLanguageModel ↗ pro přidání guardrails, RAG, cachování nebo logování, aniž byste museli upravovat logiku chatu:
import { streamText, convertToModelMessages, wrapLanguageModel } from "ai";
const guardrailMiddleware = {
wrapGenerate: async ({ doGenerate }) => {
const { text, ...rest } = await doGenerate();
// Filter PII or sensitive content from the response
const cleaned = text?.replace(/\b\d{3}-\d{2}-\d{4}\b/g, "[REDACTED]");
return { text: cleaned, ...rest };
},
};
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const model = wrapLanguageModel({
model: baseModel,
middleware: [guardrailMiddleware],
});
const result = streamText({
model,
messages: await convertToModelMessages(this.messages),
});
return result.toUIMessageStreamResponse();
}
}import { streamText, convertToModelMessages, wrapLanguageModel } from "ai";
import type { LanguageModelV3Middleware } from "@ai-sdk/provider";
const guardrailMiddleware: LanguageModelV3Middleware = {
wrapGenerate: async ({ doGenerate }) => {
const { text, ...rest } = await doGenerate();
// Filter PII or sensitive content from the response
const cleaned = text?.replace(/\b\d{3}-\d{2}-\d{4}\b/g, "[REDACTED]");
return { text: cleaned, ...rest };
},
};
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const model = wrapLanguageModel({
model: baseModel,
middleware: [guardrailMiddleware],
});
const result = streamText({
model,
messages: await convertToModelMessages(this.messages),
});
return result.toUIMessageStreamResponse();
}
}AI SDK obsahuje vestavěné middlewary:
extractReasoningMiddleware: zobrazí řetězec úvah (chain-of-thought) z modelů, jako je DeepSeek R1defaultSettingsMiddleware: Použijte výchozí hodnoty temperature, max tokens atd.simulateStreamingMiddleware: Přidejte streamování k modelům bez podpory streamování
Middlewary se skládají v pořadí: middleware: [first, second] platí jako first(second(model)).
Strukturovaný výstup
Použijte generateObject ↗ uvnitř nástrojů pro strukturovanou extrakci dat:
import {
streamText,
generateObject,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: myModel,
messages: await convertToModelMessages(this.messages),
tools: {
extractContactInfo: tool({
description:
"Extract structured contact information from the conversation",
inputSchema: z.object({
text: z.string().describe("The text to extract contact info from"),
}),
execute: async ({ text }) => {
const { object } = await generateObject({
model: myModel,
schema: z.object({
name: z.string(),
email: z.string().email(),
phone: z.string().optional(),
}),
prompt: `Extract contact information from: ${text}`,
});
return object;
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}import {
streamText,
generateObject,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: myModel,
messages: await convertToModelMessages(this.messages),
tools: {
extractContactInfo: tool({
description:
"Extract structured contact information from the conversation",
inputSchema: z.object({
text: z.string().describe("The text to extract contact info from"),
}),
execute: async ({ text }) => {
const { object } = await generateObject({
model: myModel,
schema: z.object({
name: z.string(),
email: z.string().email(),
phone: z.string().optional(),
}),
prompt: `Extract contact information from: ${text}`,
});
return object;
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}Delegování dílčích agentů v rámci procesu
Nástroje mohou delegovat práci na zaměřená dílčí volání s vlastním kontextem. Použijte ToolLoopAgent ↗ k definování znovupoužitelného agenta a poté ho volejte z nástroje execute:
import {
ToolLoopAgent,
streamText,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
// Define a reusable research agent with its own tools and instructions
const researchAgent = new ToolLoopAgent({
model: researchModel,
instructions: "You are a research assistant. Be thorough and cite sources.",
tools: { webSearch: webSearchTool },
stopWhen: stepCountIs(10),
});
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: orchestratorModel,
messages: await convertToModelMessages(this.messages),
tools: {
deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
execute: async ({ topic }) => {
const { text } = await researchAgent.generate({
prompt: topic,
});
return { summary: text };
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}import {
ToolLoopAgent,
streamText,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
// Define a reusable research agent with its own tools and instructions
const researchAgent = new ToolLoopAgent({
model: researchModel,
instructions: "You are a research assistant. Be thorough and cite sources.",
tools: { webSearch: webSearchTool },
stopWhen: stepCountIs(10),
});
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: orchestratorModel,
messages: await convertToModelMessages(this.messages),
tools: {
deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
execute: async ({ topic }) => {
const { text } = await researchAgent.generate({
prompt: topic,
});
return { summary: text };
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}Výzkumný agent běží ve vlastním kontextu, jeho rozpočet tokenů je oddělený od rozpočtu orchestrátoru. Zpět k nadřazenému modelu se vrací pouze shrnutí.
Streamování průběhu s předběžnými výsledky
Ve výchozím nastavení se tool part zobrazuje jako načítající se, dokud execute vrátí. Použijte asynchronní generátor (async function*) ke streamování průběžných aktualizací klientovi, zatímco nástroj ještě pracuje:
deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
async *execute({ topic }) {
// Preliminary result — the client sees "searching" immediately
yield { status: "searching", topic, summary: undefined };
const { text } = await researchAgent.generate({ prompt: topic });
// Final result — sent to the model for its next step
yield { status: "done", topic, summary: text };
},
});deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
async *execute({ topic }) {
// Preliminary result — the client sees "searching" immediately
yield { status: "searching", topic, summary: undefined };
const { text } = await researchAgent.generate({ prompt: topic });
// Final result — sent to the model for its next step
yield { status: "done", topic, summary: text };
},
});Každý yield aktualizuje část nástroje na klientovi v reálném čase (s preliminary: true). Poslední hodnota vrácená pomocí yield se stává konečným výstupem, který model vidí.
Tento vzor se hodí, když:
- Úkol vyžaduje prozkoumání velkého množství informací, které by nafouklo hlavní kontext
- Chcete zobrazovat průběh v reálném čase u dlouho běžících nástrojů
- Chcete paralelizovat nezávislý výzkum (více volání nástrojů běží současně)
- Potřebujete různé modely nebo systémové prompty pro různé dílčí úlohy
Další informace najdete v dokumentace AI SDK Agents ↗, Podřízení agenti ↗, a Předběžné výsledky nástroje ↗.
Synchronizace více klientů
Když se k téže instanci agenta připojí více klientů, zprávy se automaticky odesílají všem připojením. Když jeden klient odešle zprávu, aktualizovaný seznam zpráv obdrží všichni ostatní připojení klienti.
Client A ──── sendMessage("Hello") ────▶ AIChatAgent
│
persist + stream
│
Client A ◀── CF_AGENT_USE_CHAT_RESPONSE ──────┤
Client B ◀── CF_AGENT_CHAT_MESSAGES ──────────┘Klient, který požadavek odeslal, dostává streamovanou odpověď. Všichni ostatní klienti dostávají finální zprávy prostřednictvím CF_AGENT_CHAT_MESSAGES vysílání.
Referenční dokumentace API
Exporty
| Cesta importu | Exporty |
|---|---|
@cloudflare/ai-chat |
AIChatAgent, createToolsFromClientSchemas, ClientToolSchema, ChatRecoveryContext, ChatRecoveryOptions, ChatRecoveryConfig, ChatRecoveryExhaustedContext, ResolvedChatRecoveryConfig, typy životního cyklu |
@cloudflare/ai-chat/react |
useAgentChat, extractClientToolSchemas, getToolPartState, getToolCallId, getToolInput, getToolOutput, getToolApproval |
@cloudflare/ai-chat/types |
MessageType, OutgoingMessage, IncomingMessage |
agents/chat |
Sdílené pokročilé primitivy chatu, jako například SaveMessagesResult, SaveMessagesOptions, CHAT_MESSAGE_TYPES, ROW_MAX_BYTES, a isReplayChunk() |
Protokol WebSocket
Protokol chatu používá typované zprávy JSON přes WebSocket:
| Zpráva | Směr | Účel |
|---|---|---|
CF_AGENT_USE_CHAT_REQUEST |
Klient → Server | Odeslat chatovou zprávu |
CF_AGENT_USE_CHAT_RESPONSE |
Server → Klient | Streamování částí odpovědi |
CF_AGENT_CHAT_MESSAGES |
Server → Klient | Rozesílání aktualizovaných zpráv |
CF_AGENT_CHAT_CLEAR |
Obousměrné | Vymazat konverzaci |
CF_AGENT_CHAT_REQUEST_CANCEL |
Klient → Server | Zrušení aktivního streamu |
CF_AGENT_TOOL_RESULT |
Klient → Server | Poskytnutí výstupu nástroje |
CF_AGENT_TOOL_APPROVAL |
Klient → Server | Schválit nebo zamítnout nástroj |
CF_AGENT_MESSAGE_UPDATED |
Server → Klient | Upozornit na aktualizaci zprávy |
CF_AGENT_STREAM_RESUMING |
Server → Klient | Upozornit na obnovení streamu |
CF_AGENT_STREAM_RESUME_REQUEST |
Klient → Server | Kontrola obnovení proudu požadavku |
CF_AGENT_STREAM_RESUME_ACK |
Server → Klient | Obnovit proud od kurzoru |
CF_AGENT_STREAM_RESUME_NONE |
Server → Klient | Neexistuje žádný obnovitelný stream |
Zastaralá API
Následující API jsou zastaralá a při použití vypíšou varování do konzole. V některé z budoucích verzí budou odstraněna.
| Zastaralé | Náhrada | Poznámky |
|---|---|---|
addToolResult({ toolCallId, result }) |
addToolOutput({ toolCallId, output }) |
Přejmenováno kvůli konzistenci s terminologií AI SDK |
detectToolsRequiringConfirmation() |
Použijte needsApproval na definici nástroje |
Schvalování je nyní nastaveno pro jednotlivé nástroje, nikoli jako globální filtr |
toolsRequiringConfirmation možnost |
Použijte needsApproval na jednotlivých nástrojích |
Schválení pro jednotlivé nástroje nahrazuje globální seznam |
Pokud přecházíte ze starší verze, nahraďte zastaralá volání jejich náhradami. Zastaralá API stále fungují, ale v některé z budoucích hlavních verzí budou odstraněna.
createToolsFromClientSchemas(), extractClientToolSchemas(), a tools možnost na useAgentChat jsou stále podporovány pro dynamické nástroje na straně klienta. Jde o pokročilá API, nikoli o zastaralá API.