diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index cff23d2d..085bc993 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -33,7 +33,7 @@ import { } from '../telemetry/types.js'; import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js'; import { hasCycleInSchema } from '../tools/tools.js'; -import { isStructuredError } from '../utils/quotaErrorDetection.js'; +import { StructuredError } from './turn.js'; /** * Returns true if the response is valid, false otherwise. @@ -352,7 +352,6 @@ export class GeminiChat { } catch (error) { const durationMs = Date.now() - startTime; this._logApiError(durationMs, error, prompt_id); - await this.maybeIncludeSchemaDepthContext(error); this.sendPromise = Promise.resolve(); throw error; } @@ -452,7 +451,6 @@ export class GeminiChat { const durationMs = Date.now() - startTime; this._logApiError(durationMs, error, prompt_id); this.sendPromise = Promise.resolve(); - await this.maybeIncludeSchemaDepthContext(error); throw error; } } @@ -523,6 +521,34 @@ export class GeminiChat { return lastChunkWithMetadata?.usageMetadata; } + async maybeIncludeSchemaDepthContext(error: StructuredError): Promise { + // Check for potentially problematic cyclic tools with cyclic schemas + // and include a recommendation to remove potentially problematic tools. + if ( + isSchemaDepthError(error.message) || + isInvalidArgumentError(error.message) + ) { + const tools = (await this.config.getToolRegistry()).getAllTools(); + const cyclicSchemaTools: string[] = []; + for (const tool of tools) { + if ( + (tool.schema.parametersJsonSchema && + hasCycleInSchema(tool.schema.parametersJsonSchema)) || + (tool.schema.parameters && hasCycleInSchema(tool.schema.parameters)) + ) { + cyclicSchemaTools.push(tool.displayName); + } + } + if (cyclicSchemaTools.length > 0) { + const extraDetails = + `\n\nThis error was probably caused by cyclic schema references in one of the following tools, try disabling them with excludeTools:\n\n - ` + + cyclicSchemaTools.join(`\n - `) + + `\n`; + error.message += extraDetails; + } + } + } + private async *processStreamResponse( streamResponse: AsyncGenerator, inputContent: Content, @@ -684,34 +710,13 @@ export class GeminiChat { content.parts[0].thought === true ); } - - private async maybeIncludeSchemaDepthContext(error: unknown): Promise { - // Check for potentially problematic cyclic tools with cyclic schemas - // and include a recommendation to remove potentially problematic tools. - if (isStructuredError(error) && isSchemaDepthError(error.message)) { - const tools = (await this.config.getToolRegistry()).getAllTools(); - const cyclicSchemaTools: string[] = []; - for (const tool of tools) { - if ( - (tool.schema.parametersJsonSchema && - hasCycleInSchema(tool.schema.parametersJsonSchema)) || - (tool.schema.parameters && hasCycleInSchema(tool.schema.parameters)) - ) { - cyclicSchemaTools.push(tool.displayName); - } - } - if (cyclicSchemaTools.length > 0) { - const extraDetails = - `\n\nThis error was probably caused by cyclic schema references in one of the following tools, try disabling them:\n\n - ` + - cyclicSchemaTools.join(`\n - `) + - `\n`; - error.message += extraDetails; - } - } - } } /** Visible for Testing */ export function isSchemaDepthError(errorMessage: string): boolean { return errorMessage.includes('maximum schema depth exceeded'); } + +export function isInvalidArgumentError(errorMessage: string): boolean { + return errorMessage.includes('Request contains an invalid argument'); +} diff --git a/packages/core/src/core/turn.test.ts b/packages/core/src/core/turn.test.ts index 2a557927..7144d16b 100644 --- a/packages/core/src/core/turn.test.ts +++ b/packages/core/src/core/turn.test.ts @@ -17,12 +17,14 @@ import { GeminiChat } from './geminiChat.js'; const mockSendMessageStream = vi.fn(); const mockGetHistory = vi.fn(); +const mockMaybeIncludeSchemaDepthContext = vi.fn(); vi.mock('@google/genai', async (importOriginal) => { const actual = await importOriginal(); const MockChat = vi.fn().mockImplementation(() => ({ sendMessageStream: mockSendMessageStream, getHistory: mockGetHistory, + maybeIncludeSchemaDepthContext: mockMaybeIncludeSchemaDepthContext, })); return { ...actual, @@ -46,6 +48,7 @@ describe('Turn', () => { type MockedChatInstance = { sendMessageStream: typeof mockSendMessageStream; getHistory: typeof mockGetHistory; + maybeIncludeSchemaDepthContext: typeof mockMaybeIncludeSchemaDepthContext; }; let mockChatInstance: MockedChatInstance; @@ -54,6 +57,7 @@ describe('Turn', () => { mockChatInstance = { sendMessageStream: mockSendMessageStream, getHistory: mockGetHistory, + maybeIncludeSchemaDepthContext: mockMaybeIncludeSchemaDepthContext, }; turn = new Turn(mockChatInstance as unknown as GeminiChat, 'prompt-id-1'); mockGetHistory.mockReturnValue([]); @@ -200,7 +204,7 @@ describe('Turn', () => { { role: 'model', parts: [{ text: 'Previous history' }] }, ]; mockGetHistory.mockReturnValue(historyContent); - + mockMaybeIncludeSchemaDepthContext.mockResolvedValue(undefined); const events = []; for await (const event of turn.run( reqParts, diff --git a/packages/core/src/core/turn.ts b/packages/core/src/core/turn.ts index ee32c309..8ede1fa4 100644 --- a/packages/core/src/core/turn.ts +++ b/packages/core/src/core/turn.ts @@ -275,6 +275,7 @@ export class Turn { message: getErrorMessage(error), status, }; + await this.chat.maybeIncludeSchemaDepthContext(structuredError); yield { type: GeminiEventType.Error, value: { error: structuredError } }; return; }