mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(core): plumb max attempts for retry to generate options in baseLLMClient (#9518)
This commit is contained in:
@@ -180,6 +180,36 @@ describe('BaseLlmClient', () => {
|
|||||||
customPromptId,
|
customPromptId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pass maxAttempts to retryWithBackoff when provided', async () => {
|
||||||
|
const mockResponse = createMockResponse('{"color": "cyan"}');
|
||||||
|
mockGenerateContent.mockResolvedValue(mockResponse);
|
||||||
|
const customMaxAttempts = 3;
|
||||||
|
|
||||||
|
const options: GenerateJsonOptions = {
|
||||||
|
...defaultOptions,
|
||||||
|
maxAttempts: customMaxAttempts,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client.generateJson(options);
|
||||||
|
|
||||||
|
expect(retryWithBackoff).toHaveBeenCalledTimes(1);
|
||||||
|
expect(retryWithBackoff).toHaveBeenCalledWith(expect.any(Function), {
|
||||||
|
maxAttempts: customMaxAttempts,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call retryWithBackoff without maxAttempts when not provided', async () => {
|
||||||
|
const mockResponse = createMockResponse('{"color": "indigo"}');
|
||||||
|
mockGenerateContent.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
// No maxAttempts in defaultOptions
|
||||||
|
await client.generateJson(defaultOptions);
|
||||||
|
|
||||||
|
expect(retryWithBackoff).toHaveBeenCalledWith(expect.any(Function), {
|
||||||
|
maxAttempts: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateJson - Response Cleaning', () => {
|
describe('generateJson - Response Cleaning', () => {
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ export interface GenerateJsonOptions {
|
|||||||
* A unique ID for the prompt, used for logging/telemetry correlation.
|
* A unique ID for the prompt, used for logging/telemetry correlation.
|
||||||
*/
|
*/
|
||||||
promptId: string;
|
promptId: string;
|
||||||
|
/**
|
||||||
|
* The maximum number of attempts for the request.
|
||||||
|
*/
|
||||||
|
maxAttempts?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,6 +82,7 @@ export class BaseLlmClient {
|
|||||||
abortSignal,
|
abortSignal,
|
||||||
systemInstruction,
|
systemInstruction,
|
||||||
promptId,
|
promptId,
|
||||||
|
maxAttempts,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const requestConfig: GenerateContentConfig = {
|
const requestConfig: GenerateContentConfig = {
|
||||||
@@ -100,7 +105,7 @@ export class BaseLlmClient {
|
|||||||
promptId,
|
promptId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await retryWithBackoff(apiCall);
|
const result = await retryWithBackoff(apiCall, { maxAttempts });
|
||||||
|
|
||||||
let text = getResponseText(result)?.trim();
|
let text = getResponseText(result)?.trim();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ export async function FixLLMEditWithInstruction(
|
|||||||
model: DEFAULT_GEMINI_FLASH_MODEL,
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
||||||
systemInstruction: EDIT_SYS_PROMPT,
|
systemInstruction: EDIT_SYS_PROMPT,
|
||||||
promptId,
|
promptId,
|
||||||
|
maxAttempts: 1,
|
||||||
})) as unknown as SearchReplaceEdit;
|
})) as unknown as SearchReplaceEdit;
|
||||||
|
|
||||||
editCorrectionWithInstructionCache.set(cacheKey, result);
|
editCorrectionWithInstructionCache.set(cacheKey, result);
|
||||||
|
|||||||
@@ -99,6 +99,23 @@ describe('retryWithBackoff', () => {
|
|||||||
expect(mockFn).toHaveBeenCalledTimes(3);
|
expect(mockFn).toHaveBeenCalledTimes(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should default to 5 maxAttempts if no options are provided', async () => {
|
||||||
|
// This function will fail more than 5 times to ensure all retries are used.
|
||||||
|
const mockFn = createFailingFunction(10);
|
||||||
|
|
||||||
|
const promise = retryWithBackoff(mockFn);
|
||||||
|
|
||||||
|
// Expect it to fail with the error from the 5th attempt.
|
||||||
|
// eslint-disable-next-line vitest/valid-expect
|
||||||
|
const assertionPromise = expect(promise).rejects.toThrow(
|
||||||
|
'Simulated error attempt 5',
|
||||||
|
);
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
await assertionPromise;
|
||||||
|
|
||||||
|
expect(mockFn).toHaveBeenCalledTimes(5);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not retry if shouldRetry returns false', async () => {
|
it('should not retry if shouldRetry returns false', async () => {
|
||||||
const mockFn = vi.fn(async () => {
|
const mockFn = vi.fn(async () => {
|
||||||
throw new NonRetryableError('Non-retryable error');
|
throw new NonRetryableError('Non-retryable error');
|
||||||
@@ -114,6 +131,18 @@ describe('retryWithBackoff', () => {
|
|||||||
expect(mockFn).toHaveBeenCalledTimes(1);
|
expect(mockFn).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error if maxAttempts is not a positive number', async () => {
|
||||||
|
const mockFn = createFailingFunction(1);
|
||||||
|
|
||||||
|
// Test with 0
|
||||||
|
await expect(retryWithBackoff(mockFn, { maxAttempts: 0 })).rejects.toThrow(
|
||||||
|
'maxAttempts must be a positive number.',
|
||||||
|
);
|
||||||
|
|
||||||
|
// The function should not be called at all if validation fails
|
||||||
|
expect(mockFn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should use default shouldRetry if not provided, retrying on 429', async () => {
|
it('should use default shouldRetry if not provided, retrying on 429', async () => {
|
||||||
const mockFn = vi.fn(async () => {
|
const mockFn = vi.fn(async () => {
|
||||||
const error = new Error('Too Many Requests') as any;
|
const error = new Error('Too Many Requests') as any;
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ export async function retryWithBackoff<T>(
|
|||||||
fn: () => Promise<T>,
|
fn: () => Promise<T>,
|
||||||
options?: Partial<RetryOptions>,
|
options?: Partial<RetryOptions>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
|
if (options?.maxAttempts !== undefined && options.maxAttempts <= 0) {
|
||||||
|
throw new Error('maxAttempts must be a positive number.');
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
initialDelayMs,
|
initialDelayMs,
|
||||||
|
|||||||
Reference in New Issue
Block a user