From 9eb82c3a404d80ba68b324ce55f1e0738043b4f9 Mon Sep 17 00:00:00 2001 From: Qiao Wang Date: Thu, 29 Aug 2024 10:52:06 -0700 Subject: [PATCH] feat: Add GoogleApi error in ClientError.cause Co-authored-by: tjenkinson FUTURE_COPYBARA_INTEGRATE_REVIEW=https://github.com/googleapis/nodejs-vertexai/pull/418 from googleapis:release-please--branches--main--components--vertexai ac43919b29f23f42ce14f93f5e2ddb5454851670 PiperOrigin-RevId: 668992333 --- src/functions/post_fetch_processing.ts | 17 +++++++- src/functions/test/functions_test.ts | 33 +++++++++------- src/types/errors.ts | 29 ++++++++++++++ system_test/end_to_end_sample_test.ts | 54 ++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 15 deletions(-) diff --git a/src/functions/post_fetch_processing.ts b/src/functions/post_fetch_processing.ts index 7a46c74d..23669430 100644 --- a/src/functions/post_fetch_processing.ts +++ b/src/functions/post_fetch_processing.ts @@ -26,7 +26,11 @@ import { StreamGenerateContentResult, } from '../types/content'; import {constants} from '../util'; -import {ClientError, GoogleGenerativeAIError} from '../types/errors'; +import { + ClientError, + GoogleApiError, + GoogleGenerativeAIError, +} from '../types/errors'; export async function throwErrorIfNotOK(response: Response | undefined) { if (response === undefined) { @@ -40,7 +44,16 @@ export async function throwErrorIfNotOK(response: Response | undefined) { errorBody )}`; if (status >= 400 && status < 500) { - throw new ClientError(errorMessage); + const error = new ClientError( + errorMessage, + new GoogleApiError( + errorBody.error.message, + errorBody.error.code, + errorBody.error.status, + errorBody.error.details + ) + ); + throw error; } throw new GoogleGenerativeAIError(errorMessage); } diff --git a/src/functions/test/functions_test.ts b/src/functions/test/functions_test.ts index 8b9afb58..8ab3c283 100644 --- a/src/functions/test/functions_test.ts +++ b/src/functions/test/functions_test.ts @@ -16,6 +16,7 @@ */ import { + ClientError, CountTokensRequest, FinishReason, FunctionDeclarationSchemaType, @@ -23,6 +24,7 @@ import { GenerateContentResponse, GenerateContentResponseHandler, GenerateContentResult, + GoogleApiError, HarmBlockThreshold, HarmCategory, HarmProbability, @@ -31,7 +33,6 @@ import { SafetySetting, StreamGenerateContentResult, Tool, - ToolConfig, } from '../../types'; import {constants} from '../../util'; import {countTokens} from '../count_tokens'; @@ -194,8 +195,6 @@ const TEST_MULTIPART_MESSAGE_BASE64 = [ const TEST_EMPTY_TOOLS: Tool[] = []; -const TEST_EMPTY_TOOL_CONFIG: ToolConfig = {}; - const TEST_TOOLS_WITH_FUNCTION_DECLARATION: Tool[] = [ { functionDeclarations: [ @@ -317,29 +316,39 @@ describe('countTokens', () => { ).toBeRejected(); }); - it('throw ClientError when not OK and 4XX', async () => { + it('throw ApiClientError when not OK and 4XX', async () => { const fetch400Obj = { status: 400, statusText: 'Bad Request', ok: false, }; const body = { - code: 400, - message: 'request is invalid', - status: 'INVALID_ARGUMENT', + error: { + code: 400, + message: 'request is invalid', + status: 'INVALID_ARGUMENT', + }, }; const response = new Response(JSON.stringify(body), fetch400Obj); spyOn(global, 'fetch').and.resolveTo(response); - await expectAsync( - countTokens( + let error: any; + try { + await countTokens( TEST_LOCATION, TEST_RESOURCE_PATH, TEST_TOKEN_PROMISE, req, TEST_API_ENDPOINT - ) - ).toBeRejected(); + ); + } catch (e) { + error = e; + } + + expect(error).toBeInstanceOf(ClientError); + expect(error.cause).toBeInstanceOf(GoogleApiError); + expect(error.cause.code).toBe(400); + expect(error.cause.status).toEqual('INVALID_ARGUMENT'); }); }); @@ -369,7 +378,6 @@ describe('generateContent', () => { TEST_GENERATION_CONFIG, TEST_SAFETY_SETTINGS, TEST_EMPTY_TOOLS, - TEST_EMPTY_TOOL_CONFIG, TEST_REQUEST_OPTIONS ) ).toBeRejected(); @@ -688,7 +696,6 @@ describe('generateContentStream', () => { TEST_GENERATION_CONFIG, TEST_SAFETY_SETTINGS, TEST_EMPTY_TOOLS, - TEST_EMPTY_TOOL_CONFIG, TEST_REQUEST_OPTIONS ) ).toBeRejected(); diff --git a/src/types/errors.ts b/src/types/errors.ts index 5b1b10e4..4b9b5ba0 100644 --- a/src/types/errors.ts +++ b/src/types/errors.ts @@ -42,6 +42,34 @@ class ClientError extends Error { } } +/** + * Google API Error Details object that may be included in an error response. + * See https://cloud.google.com/apis/design/errors + * @public + */ +export declare interface ErrorDetails { + '@type'?: string; + reason?: string; + domain?: string; + metadata?: Record; + [key: string]: unknown; +} + +/** + * GoogleApiError is thrown when http 4XX status is received. + * See https://cloud.google.com/apis/design/errors + */ +class GoogleApiError extends Error { + constructor( + message: string, + public code?: number, + public status?: string, + public errorDetails?: ErrorDetails[] + ) { + super(message); + } +} + /** * GoogleGenerativeAIError is thrown when http response is not ok and status code is not 4XX * For details please refer to https://developer.mozilla.org/en-US/docs/Web/HTTP/Status @@ -78,6 +106,7 @@ function constructErrorMessage( export { ClientError, + GoogleApiError, GoogleAuthError, GoogleGenerativeAIError, IllegalArgumentError, diff --git a/system_test/end_to_end_sample_test.ts b/system_test/end_to_end_sample_test.ts index 6f442450..7cb8ef5c 100644 --- a/system_test/end_to_end_sample_test.ts +++ b/system_test/end_to_end_sample_test.ts @@ -345,6 +345,9 @@ describe('generateContentStream', () => { `sys test failure on generateContentStream when having bad request got wrong error message: ${e.message}` ); + expect(e.cause.status).toBe(400); + expect(e.cause.statusText).toBe('INVALID_ARGUMENT'); + expect(e.cause.message).toBeInstanceOf(String); }); }); it('in preview should throw ClientError when having invalid input', async () => { @@ -368,6 +371,9 @@ describe('generateContentStream', () => { `sys test failure on generateContentStream in preview when having bad request got wrong error message: ${e.message}` ); + expect(e.cause.status).toBe(400); + expect(e.cause.statusText).toBe('INVALID_ARGUMENT'); + expect(e.cause.message).toBeInstanceOf(String); }); }); @@ -535,6 +541,9 @@ describe('generateContentStream', () => { `sys test failure on generateContentStream for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContentStream for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContentStream for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -554,6 +563,9 @@ describe('generateContentStream', () => { `sys test failure on generateContentStream for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContentStream for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContentStream for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -573,6 +585,9 @@ describe('generateContentStream', () => { `sys test failure on generateContentStream in preview for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContentStream in preview for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContentStream in preview for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -592,6 +607,9 @@ describe('generateContentStream', () => { `sys test failure on generateContentStream in preview for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContentStream in preview for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContentStream in preview for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -669,6 +687,9 @@ describe('generateContent', () => { `sys test failure on generateContent for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContent for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContent for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -688,6 +709,9 @@ describe('generateContent', () => { `sys test failure on generateContent for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContent for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContent for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -707,6 +731,9 @@ describe('generateContent', () => { `sys test failure on generateContent in preview for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContent in preview for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContent in preview for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -726,6 +753,9 @@ describe('generateContent', () => { `sys test failure on generateContent in preview for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on generateContent in preview for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on generateContent in preview for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -894,6 +924,9 @@ describe('sendMessage', () => { `sys test failure on sendMessage for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on sendMessage for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on sendMessage for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -913,6 +946,9 @@ describe('sendMessage', () => { `sys test failure on sendMessage for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on sendMessage for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on sendMessage for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -931,6 +967,9 @@ describe('sendMessage', () => { `sys test failure on sendMessage in preview for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on sendMessage in preview for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on sendMessage in preview for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -950,6 +989,9 @@ describe('sendMessage', () => { `sys test failure on sendMessage in preview for grounding metadata: ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on sendMessage in preview for grounding attributions: ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on sendMessage in preview for web search queries: ${groundingMetadata.webSearchQueries}` ); @@ -1153,6 +1195,9 @@ describe('sendMessageStream', () => { `sys test failure on groundingMetadata, ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on groundingMetadata.groundingAttributions, ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on groundingMetadata.webSearchQueries, ${groundingMetadata.webSearchQueries}` ); @@ -1172,6 +1217,9 @@ describe('sendMessageStream', () => { `sys test failure on groundingMetadata, ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on groundingMetadata.groundingAttributions, ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on groundingMetadata.webSearchQueries, ${groundingMetadata.webSearchQueries}` ); @@ -1190,6 +1238,9 @@ describe('sendMessageStream', () => { `sys test failure on groundingMetadata in preview, ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on groundingMetadata.groundingAttributions in preview, ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on groundingMetadata.webSearchQueries in preview, ${groundingMetadata.webSearchQueries}` ); @@ -1209,6 +1260,9 @@ describe('sendMessageStream', () => { `sys test failure on groundingMetadata in preview, ${groundingMetadata}` ); if (groundingMetadata) { + expect(!!groundingMetadata.groundingAttributions).toBeTruthy( + `sys test failure on groundingMetadata.groundingAttributions in preview, ${groundingMetadata.groundingAttributions}` + ); expect(!!groundingMetadata.webSearchQueries).toBeTruthy( `sys test failure on groundingMetadata.webSearchQueries in preview, ${groundingMetadata.webSearchQueries}` );