← Vytvářejte agenty na Cloudflare / agents / runtime / execution
Opakování
Opakujte neúspěšné operace s exponenciálním odkladem a jitterem. Agents SDK poskytuje vestavěnou podporu opakování pro naplánované úlohy, zařazené úlohy a univerzální this.retry() metodu pro váš vlastní kód.
Přehled
Dočasné chyby jsou běžné při volání externích API, komunikaci s jinými službami nebo spouštění úloh na pozadí. Systém opakování je řeší automaticky:
- Exponenciální prodlení : každé opakování čeká déle než to předchozí
- Jitter : náhodné prodlevy brání hromadnému náporu požadavků (tzv. thundering herd)
- Konfigurovatelné : ladí počet pokusů, prodlevy a limity pro jednotlivá místa volání
- Vestavěné : operace plánování, fronty a workflow se opakují automaticky
Rychlý start
Použijte this.retry() k opakování libovolné asynchronní operace:
import { Agent } from "agents";
export class MyAgent extends Agent {
async fetchWithRetry(url) {
const response = await this.retry(async () => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
return response;
}
}import { Agent } from "agents";
export class MyAgent extends Agent {
async fetchWithRetry(url: string) {
const response = await this.retry(async () => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
return response;
}
}Standardně this.retry() opakuje pokusy až třikrát s exponenciálně narůstajícím prodlením a náhodnou odchylkou (jitter).
this.retry()
retry() metoda je k dispozici na každém Agent instance. Ve výchozím nastavení opakuje zadanou funkci při jakékoli vyvolané chybě.
async retry<T>(
fn: (attempt: number) => Promise<T>,
options?: RetryOptions & {
shouldRetry?: (err: unknown, nextAttempt: number) => boolean;
}
): Promise<T>Parametry:
fn: asynchronní funkce, která se má opakovat. Přijímá číslo aktuálního pokusu (číslováno od 1).options: volitelná konfigurace opakování (viz RetryOptions níže). Možnosti se ověřují ihned: neplatné hodnoty vyvolají výjimku okamžitě.options.shouldRetry: volitelný predikát volaný s vyvolanou chybou a číslem dalšího pokusu. Vraťtefalsea okamžitě zastavit opakování. Pokud není zadáno, opakují se všechny chyby.
Vrací: výsledek fn při úspěchu.
Vyhazuje: poslední chybu, pokud všechny pokusy selžou, nebo shouldRetry vrací false.
Příklady
Základní opakování:
const data = await this.retry(() => fetch("https://api.example.com/data"));const data = await this.retry(() => fetch("https://api.example.com/data"));Vlastní možnosti opakování:
const data = await this.retry(
async () => {
const res = await fetch("https://slow-api.example.com/data");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{
maxAttempts: 5,
baseDelayMs: 500,
maxDelayMs: 10000,
},
);const data = await this.retry(
async () => {
const res = await fetch("https://slow-api.example.com/data");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{
maxAttempts: 5,
baseDelayMs: 500,
maxDelayMs: 10000,
},
);Používání čísla pokusu:
const result = await this.retry(async (attempt) => {
console.log(`Attempt ${attempt}...`);
return await this.callExternalService();
});const result = await this.retry(async (attempt) => {
console.log(`Attempt ${attempt}...`);
return await this.callExternalService();
});Selektivní opakování pokusu s shouldRetry:
Použijte shouldRetry a zastavit opakování u konkrétních chyb. Predikát dostává jak chybu, tak číslo dalšího pokusu:
const data = await this.retry(
async () => {
const res = await fetch("https://api.example.com/data");
if (!res.ok) throw new HttpError(res.status, await res.text());
return res.json();
},
{
maxAttempts: 5,
shouldRetry: (err, nextAttempt) => {
// Do not retry 4xx client errors — our request is wrong
if (err instanceof HttpError && err.status >= 400 && err.status < 500) {
return false;
}
return true; // retry everything else (5xx, network errors, etc.)
},
},
);const data = await this.retry(
async () => {
const res = await fetch("https://api.example.com/data");
if (!res.ok) throw new HttpError(res.status, await res.text());
return res.json();
},
{
maxAttempts: 5,
shouldRetry: (err, nextAttempt) => {
// Do not retry 4xx client errors — our request is wrong
if (err instanceof HttpError && err.status >= 400 && err.status < 500) {
return false;
}
return true; // retry everything else (5xx, network errors, etc.)
},
},
);Opakování v plánech
Při vytváření plánu předejte možnosti opakování:
// Retry up to 5 times if the callback fails
await this.schedule(
"processTask",
60,
{ taskId: "123" },
{
retry: { maxAttempts: 5 },
},
);
// Retry with custom backoff
await this.schedule(
new Date("2026-03-01T09:00:00Z"),
"sendReport",
{},
{
retry: {
maxAttempts: 3,
baseDelayMs: 1000,
maxDelayMs: 30000,
},
},
);
// Cron with retries
await this.schedule(
"0 8 * * *",
"dailyDigest",
{},
{
retry: { maxAttempts: 3 },
},
);
// Interval with retries
await this.scheduleEvery(
30,
"poll",
{ source: "api" },
{
retry: { maxAttempts: 5, baseDelayMs: 200 },
},
);// Retry up to 5 times if the callback fails
await this.schedule(
"processTask",
60,
{ taskId: "123" },
{
retry: { maxAttempts: 5 },
},
);
// Retry with custom backoff
await this.schedule(
new Date("2026-03-01T09:00:00Z"),
"sendReport",
{},
{
retry: {
maxAttempts: 3,
baseDelayMs: 1000,
maxDelayMs: 30000,
},
},
);
// Cron with retries
await this.schedule(
"0 8 * * *",
"dailyDigest",
{},
{
retry: { maxAttempts: 3 },
},
);
// Interval with retries
await this.scheduleEvery(
30,
"poll",
{ source: "api" },
{
retry: { maxAttempts: 5, baseDelayMs: 200 },
},
);Pokud callback vyvolá chybu, zopakuje se podle nastavení opakování. Pokud selžou všechny pokusy, chyba se zaznamená do logu a přesměruje přes onError(). Plán se odstraní (u jednorázových plánů), nebo znovu naplánuje (u cron/interval), bez ohledu na to, zda skončil úspěchem, nebo chybou.
Opakování ve frontách
Při přidávání úlohy do fronty předejte možnosti opakování:
await this.queue(
"sendEmail",
{ to: "[email protected]" },
{
retry: { maxAttempts: 5 },
},
);
await this.queue("processWebhook", webhookData, {
retry: {
maxAttempts: 3,
baseDelayMs: 500,
maxDelayMs: 5000,
},
});await this.queue(
"sendEmail",
{ to: "[email protected]" },
{
retry: { maxAttempts: 5 },
},
);
await this.queue("processWebhook", webhookData, {
retry: {
maxAttempts: 3,
baseDelayMs: 500,
maxDelayMs: 5000,
},
});Pokud callback vyvolá chybu, zopakuje se ještě předtím, než je úloha vyřazena z fronty. Jakmile se vyčerpají všechny pokusy, úloha se vyřadí z fronty a chyba se zaznamená do logu.
Validace
Možnosti opakování se ověřují ihned při volání this.retry(), queue(), schedule(), nebo scheduleEvery(). Neplatné možnosti vyvolají chybu okamžitě, místo aby selhaly až později při spuštění:
// Throws immediately: "retry.maxAttempts must be >= 1"
await this.queue("sendEmail", data, {
retry: { maxAttempts: 0 },
});
// Throws immediately: "retry.baseDelayMs must be > 0"
await this.schedule(
60,
"process",
{},
{
retry: { baseDelayMs: -100 },
},
);
// Throws immediately: "retry.maxAttempts must be an integer"
await this.retry(() => fetch(url), { maxAttempts: 2.5 });
// Throws immediately: "retry.baseDelayMs must be <= retry.maxDelayMs"
// because baseDelayMs: 5000 exceeds the default maxDelayMs: 3000
await this.queue("sendEmail", data, {
retry: { baseDelayMs: 5000 },
});// Throws immediately: "retry.maxAttempts must be >= 1"
await this.queue("sendEmail", data, {
retry: { maxAttempts: 0 },
});
// Throws immediately: "retry.baseDelayMs must be > 0"
await this.schedule(
60,
"process",
{},
{
retry: { baseDelayMs: -100 },
},
);
// Throws immediately: "retry.maxAttempts must be an integer"
await this.retry(() => fetch(url), { maxAttempts: 2.5 });
// Throws immediately: "retry.baseDelayMs must be <= retry.maxDelayMs"
// because baseDelayMs: 5000 exceeds the default maxDelayMs: 3000
await this.queue("sendEmail", data, {
retry: { baseDelayMs: 5000 },
});Validace nejprve slučuje částečné možnosti s výchozími hodnotami na úrovni třídy nebo vestavěnými výchozími hodnotami a teprve poté kontroluje omezení napříč poli. To znamená { baseDelayMs: 5000 } se zachytí okamžitě, jakmile se vyřešený maxDelayMs je 3000, místo aby to selhalo až později při provádění.
Výchozí chování
I bez explicitních možností opakování se naplánované a zařazené callbacky opakují s rozumnými výchozími hodnotami:
| Nastavení | Výchozí |
|---|---|
maxAttempts |
3 |
baseDelayMs |
100 |
maxDelayMs |
3000 |
Tyto výchozí hodnoty platí pro this.retry(), queue(), schedule(), a scheduleEvery(). Možnosti nastavené pro konkrétní místo volání je přepíší.
Výchozí hodnoty na úrovni třídy
Přepište výchozí hodnoty pro celého agenta pomocí static options:
class MyAgent extends Agent {
static options = {
retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 },
};
}class MyAgent extends Agent {
static options = {
retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 },
};
}Stačí zadat pouze pole, která chcete změnit, nenastavená pole se vrátí k výchozím vestavěným hodnotám:
class MyAgent extends Agent {
// Only override maxAttempts; baseDelayMs (100) and maxDelayMs (3000) stay default
static options = {
retry: { maxAttempts: 10 },
};
}class MyAgent extends Agent {
// Only override maxAttempts; baseDelayMs (100) and maxDelayMs (3000) stay default
static options = {
retry: { maxAttempts: 10 },
};
}Výchozí hodnoty na úrovni třídy se použijí jako záložní řešení, pokud místo volání nezadá možnosti opakování. Možnosti zadané na úrovni jednotlivého volání mají vždy přednost:
// Uses class-level defaults (10 attempts)
await this.retry(() => fetch(url));
// Overrides to 2 attempts for this specific call
await this.retry(() => fetch(url), { maxAttempts: 2 });// Uses class-level defaults (10 attempts)
await this.retry(() => fetch(url));
// Overrides to 2 attempts for this specific call
await this.retry(() => fetch(url), { maxAttempts: 2 });Chcete-li u konkrétní úlohy vypnout opakování, nastavte maxAttempts: 1:
await this.schedule(
60,
"oneShot",
{},
{
retry: { maxAttempts: 1 },
},
);await this.schedule(
60,
"oneShot",
{},
{
retry: { maxAttempts: 1 },
},
);RetryOptions
interface RetryOptions {
/** Maximum number of attempts (including the first). Must be an integer >= 1. Default: 3 */
maxAttempts?: number;
/** Base delay in milliseconds for exponential backoff. Must be > 0 and <= maxDelayMs. Default: 100 */
baseDelayMs?: number;
/** Maximum delay cap in milliseconds. Must be > 0. Default: 3000 */
maxDelayMs?: number;
}Zpoždění mezi opakováními používá exponenciální odklad s plným jitterem:
delay = random(0, min(2^attempt * baseDelayMs, maxDelayMs))To znamená, že první pokusy o opakování proběhnou rychle (často do 200ms), zatímco pozdější pokusy se postupně odkládají, aby nedošlo k přetížení selhávající služby. Náhodné rozptýlení (jitter) zabraňuje tomu, aby více agentů provádělo nový pokus přesně ve stejný okamžik.
Jak to funguje
Strategie prodlení
Systém opakování používá strategii „Full Jitter“ z AWS Architecture Blog ↗. Při 3 pokusech s výchozím nastavením:
| Pokus | Horní mez | Skutečné zpoždění |
|---|---|---|
| 1 | min(2^1 * 100, 3000) = 200ms | random(0, 200ms) |
| 2 | min(2^2 * 100, 3000) = 400ms | random(0, 400ms) |
| 3 | (bez dalšího opakování, poslední pokus) | , |
S maxAttempts: 5 a baseDelayMs: 500:
| Pokus | Horní mez | Skutečné zpoždění |
|---|---|---|
| 1 | min(2 * 500, 3000) = 1000ms | random(0, 1000ms) |
| 2 | min(4 * 500, 3000) = 2000ms | random(0, 2000ms) |
| 3 | min(8 * 500, 3000) = 3000ms | random(0, 3000ms) |
| 4 | min(16 * 500, 3000) = 3000ms | random(0, 3000ms) |
| 5 | (bez dalšího opakování, poslední pokus) | , |
Opakování serveru MCP
Při přidávání serveru MCP můžete nakonfigurovat možnosti opakování pro pokusy o připojení a opětovné připojení:
await this.addMcpServer("github", "https://mcp.github.com", {
retry: { maxAttempts: 5, baseDelayMs: 1000, maxDelayMs: 10000 },
});await this.addMcpServer("github", "https://mcp.github.com", {
retry: { maxAttempts: 5, baseDelayMs: 1000, maxDelayMs: 10000 },
});Tyto možnosti se ukládají trvale a použijí se, když:
- Obnovení připojení serveru po hibernaci
- Navazování připojení po dokončení OAuth
Výchozí hodnota: 3 pokusy, základní prodleva 500 ms, maximální prodleva 5 s.
Vzory
Opakování s logováním
class MyAgent extends Agent {
async resilientTask(payload) {
try {
const result = await this.retry(
async (attempt) => {
if (attempt > 1) {
console.log(`Retrying ${payload.url} (attempt ${attempt})...`);
}
const res = await fetch(payload.url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{ maxAttempts: 5 },
);
console.log("Success:", result);
} catch (e) {
console.error("All retries failed:", e);
}
}
}class MyAgent extends Agent {
async resilientTask(payload: { url: string }) {
try {
const result = await this.retry(
async (attempt) => {
if (attempt > 1) {
console.log(`Retrying ${payload.url} (attempt ${attempt})...`);
}
const res = await fetch(payload.url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{ maxAttempts: 5 },
);
console.log("Success:", result);
} catch (e) {
console.error("All retries failed:", e);
}
}
}Opakování s fallbackem
class MyAgent extends Agent {
async fetchData() {
try {
return await this.retry(
() => fetch("https://primary-api.example.com/data"),
{ maxAttempts: 3, baseDelayMs: 200 },
);
} catch {
// Primary failed, try fallback
return await this.retry(
() => fetch("https://fallback-api.example.com/data"),
{ maxAttempts: 2 },
);
}
}
}class MyAgent extends Agent {
async fetchData() {
try {
return await this.retry(
() => fetch("https://primary-api.example.com/data"),
{ maxAttempts: 3, baseDelayMs: 200 },
);
} catch {
// Primary failed, try fallback
return await this.retry(
() => fetch("https://fallback-api.example.com/data"),
{ maxAttempts: 2 },
);
}
}
}Kombinace opakování s plánováním
Pro operace, jejichž zotavení může trvat dlouho (minuty až hodiny), zkombinujte this.retry() pro okamžitá opakování s this.schedule() pro odložená opakování:
class MyAgent extends Agent {
async syncData(payload) {
const attempt = payload.attempt ?? 1;
try {
// Immediate retries for transient failures (seconds)
await this.retry(() => this.fetchAndProcess(payload.source), {
maxAttempts: 3,
baseDelayMs: 1000,
});
} catch (e) {
if (attempt >= 5) {
console.error("Giving up after 5 scheduled attempts");
return;
}
// Schedule a retry in 5 minutes for longer outages
const delaySeconds = 300 * attempt;
await this.schedule(delaySeconds, "syncData", {
source: payload.source,
attempt: attempt + 1,
});
console.log(`Scheduled retry ${attempt + 1} in ${delaySeconds}s`);
}
}
}class MyAgent extends Agent {
async syncData(payload: { source: string; attempt?: number }) {
const attempt = payload.attempt ?? 1;
try {
// Immediate retries for transient failures (seconds)
await this.retry(() => this.fetchAndProcess(payload.source), {
maxAttempts: 3,
baseDelayMs: 1000,
});
} catch (e) {
if (attempt >= 5) {
console.error("Giving up after 5 scheduled attempts");
return;
}
// Schedule a retry in 5 minutes for longer outages
const delaySeconds = 300 * attempt;
await this.schedule(delaySeconds, "syncData", {
source: payload.source,
attempt: attempt + 1,
});
console.log(`Scheduled retry ${attempt + 1} in ${delaySeconds}s`);
}
}
}Omezení
- Žádná fronta nedoručených zpráv. Pokud úloha ve frontě nebo naplánovaná úloha vyčerpá všechny pokusy o opakování, odebere se. Pokud potřebujete sledovat neúspěšné úlohy, implementujte si vlastní perzistenci.
- Prodlevy při opakování blokují agenta. Během zpoždění při prodlužujícím se intervalu je Durable Object probuzený, ale nečinný. U krátkých zpoždění (pod 3 sekundy) to nevadí. Pro delší doby obnovy použijte
this.schedule()místo toho. - Opakované pokusy ve frontě podléhají blokování head-of-line. Položky fronty se zpracovávají postupně. Pokud se u jedné položky opakují pokusy s dlouhými prodlevami, blokuje to všechny následující položky. Potřebujete-li nezávislé chování při opakování, použijte
this.retry()uvnitř callbacku, místo možností opakování na úrovni jednotlivé úlohy uqueue(). - Žádný circuit breaker. Systém opakování nesleduje míru selhání napříč voláními. Pokud je služba trvale nedostupná, každá úloha vyčerpá svůj limit opakování samostatně.
shouldRetryje dostupný pouze nathis.retry().shouldRetrypredikát nelze použít sschedule()neboqueue()protože funkce nelze serializovat do databáze. U plánovaných nebo frontových úloh ošetřete neopakovatelné chyby přímo uvnitř callbacku.