Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/ai/src/utils/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,19 @@ export function retryDelayMs(policy: Pick<RetryPolicy, "baseDelayMs" | "maxAgent
return Math.min(safeDelay, policy.maxAgentDelayMs ?? DEFAULT_MAX_AGENT_RETRY_DELAY_MS);
}

/**
* {@link retryDelayMs} with "equal jitter": half the delay is kept, the other half is random.
* Sessions that failed together then retry spread out instead of all at once.
*/
export function jitteredRetryDelayMs(
policy: Pick<RetryPolicy, "baseDelayMs" | "maxAgentDelayMs">,
attempt: number,
random: () => number = Math.random,
): number {
const delay = retryDelayMs(policy, attempt);
return Math.round(delay / 2 + (delay / 2) * random());
}

/** Optional callbacks emitted by {@link retryAssistantCall} around each retry. */
export interface RetryCallbacks {
/** Emitted before the backoff sleep of each retry attempt (1-indexed). */
Expand Down
20 changes: 19 additions & 1 deletion packages/ai/test/retry.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { describe, expect, it, vi } from "vitest";
import { fauxAssistantMessage } from "../src/providers/faux.ts";
import { isRetryableAssistantError, type RetryPolicy, retryAssistantCall, retryDelayMs } from "../src/utils/retry.ts";
import {
isRetryableAssistantError,
jitteredRetryDelayMs,
type RetryPolicy,
retryAssistantCall,
retryDelayMs,
} from "../src/utils/retry.ts";

const openAIExplicitRetryMessage =
"An error occurred while processing your request. You can retry your request, or contact us through our help center at help.openai.com if the error persists. Please include the request ID req_******** in your message.";
Expand Down Expand Up @@ -151,6 +157,18 @@ describe("retryDelayMs", () => {
});
});

describe("jitteredRetryDelayMs", () => {
it("keeps the delay between half and all of the unjittered one", () => {
expect(jitteredRetryDelayMs({ baseDelayMs: 2000 }, 3, () => 0)).toBe(4000);
expect(jitteredRetryDelayMs({ baseDelayMs: 2000 }, 3, () => 1)).toBe(8000);
expect(jitteredRetryDelayMs({ baseDelayMs: 2000 }, 3, () => 0.5)).toBe(6000);
});

it("still respects the cap", () => {
expect(jitteredRetryDelayMs({ baseDelayMs: 2000, maxAgentDelayMs: 5000 }, 5, () => 1)).toBe(5000);
});
});

describe("retryAssistantCall", () => {
const disabled: RetryPolicy = { enabled: false, maxRetries: 3, baseDelayMs: 0 };
const enabled: RetryPolicy = { enabled: true, maxRetries: 3, baseDelayMs: 0 };
Expand Down
Loading