Skip to content

Commit bfed500

Browse files
authored
chore: Improve error handling by using ErrorWithCause (#581)
1 parent 2621de7 commit bfed500

File tree

16 files changed

+609
-413
lines changed

16 files changed

+609
-413
lines changed

.changeset/itchy-months-wash.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@sap-ai-sdk/ai-api': patch
3+
'@sap-ai-sdk/core': patch
4+
---
5+
6+
[Fixed Issue] Add missing cause in the error object for failing HTTP requests by using `ErrorWithCause`, providing more context for debugging.
7+
8+
[Compatibility Note] Due to the introduction of `ErrorWithCause`, `AxiosError` is now wrapped inside the `cause` property.
9+
For example, use `error.cause.response.data` instead of `error.response.data` to access the error response from the server.

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Setup your SAP AI Core instance with SAP Cloud SDK for AI.
1818
- [@sap-ai-sdk/langchain](#sap-ai-sdklangchain)
1919
- [@sap-ai-sdk/orchestration](#sap-ai-sdkorchestration)
2020
- [SAP Cloud SDK for AI Sample Project](#sap-cloud-sdk-for-ai-sample-project)
21+
- [Error Handling](#error-handling)
22+
- [Accessing Error Information](#accessing-error-information)
2123
- [Local Testing](#local-testing)
2224
- [Support, Feedback, Contribution](#support-feedback-contribution)
2325
- [Security / Disclosure](#security--disclosure)
@@ -93,6 +95,42 @@ For details on orchestration client, refer to this [document](https://github.com
9395
We have created a sample project demonstrating the different clients' usage of the SAP Cloud SDK for AI for TypeScript/JavaScript.
9496
The [project README](https://github.com/SAP/ai-sdk-js/blob/main/sample-code/README.md) outlines the set-up needed to build and run it locally.
9597

98+
## Error Handling
99+
100+
A common error scenario is `Request failed with status code STATUS_CODE` coming from `AxiosError`.
101+
In this case, SAP Cloud SDK for AI uses [`ErrorWithCause`](https://sap.github.io/cloud-sdk/docs/js/features/error-handling) to provide more detailed error information.
102+
103+
### Accessing Error Information
104+
105+
For example, for the following nested `ErrorWithCause`
106+
107+
```ts
108+
const rootCause = new Error('The root cause is a bug!');
109+
const lowerLevelErrorWithCause = new ErrorWithCause('Failed to call function foo().', rootCause);
110+
const upperLevelErrorWithCause = new ErrorWithCause('Process crashed.', lowerLevelErrorWithCause);
111+
throw upperLevelErrorWithCause;
112+
```
113+
114+
The error stack will look like this:
115+
116+
```txt
117+
ErrorWithCause: Process crashed.
118+
at ...
119+
Caused by:
120+
ErrorWithCause: Failed to call function foo().
121+
at ...
122+
Caused by:
123+
Error: The root cause is a bug!
124+
at ...
125+
```
126+
127+
- `error.stack` will contain the above stack trace.
128+
- `error.message` will be `Process crashed.`.
129+
- `error.cause.message` will be `Failed to call function foo().`.
130+
- `error.rootCause.message` will be `The root cause is a bug!`.
131+
132+
In case of `AxiosError`, the response data will be part of the error stack and can be accessed via `error.cause.response.data`.
133+
96134
## Local Testing
97135

98136
To test SAP Cloud SDK for AI features locally during application development, follow these steps:

packages/ai-api/README.md

+29-55
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ We maintain a list of [currently available and tested AI Core APIs](https://gith
2222
- [Create a Deployment](#create-a-deployment)
2323
- [Delete a Deployment](#delete-a-deployment)
2424
- [Custom Destination](#custom-destination)
25+
- [Error Handling](#error-handling)
2526
- [Local Testing](#local-testing)
2627
- [Support, Feedback, Contribution](#support-feedback-contribution)
2728
- [License](#license)
@@ -73,17 +74,11 @@ async function createArtifact() {
7374
scenarioId: 'foundation-models'
7475
};
7576

76-
try {
77-
const responseData: ArtifactCreationResponse =
78-
await ArtifactApi.artifactCreate(requestBody, {
79-
'AI-Resource-Group': 'default'
80-
}).execute();
81-
return responseData;
82-
} catch (errorData) {
83-
const apiError = errorData.response.data.error as ApiError;
84-
console.error('Status code:', errorData.response.status);
85-
throw new Error(`Artifact creation failed: ${apiError.message}`);
86-
}
77+
const responseData: ArtifactCreationResponse =
78+
await ArtifactApi.artifactCreate(requestBody, {
79+
'AI-Resource-Group': 'default'
80+
}).execute();
81+
return responseData;
8782
}
8883
```
8984

@@ -108,39 +103,26 @@ async function createConfiguration() {
108103
inputArtifactBindings: []
109104
};
110105

111-
try {
112-
const responseData: ConfigurationCreationResponse =
113-
await ConfigurationApi.configurationCreate(requestBody, {
114-
'AI-Resource-Group': 'default'
115-
}).execute();
116-
return responseData;
117-
} catch (errorData) {
118-
const apiError = errorData.response.data.error as ApiError;
119-
console.error('Status code:', errorData.response.status);
120-
throw new Error(`Configuration creation failed: ${apiError.message}`);
121-
}
106+
const responseData: ConfigurationCreationResponse =
107+
await ConfigurationApi.configurationCreate(requestBody, {
108+
'AI-Resource-Group': 'default'
109+
}).execute();
110+
return responseData;
122111
}
123112
```
124113

125114
### Create a Deployment
126115

127-
```TypeScript
116+
```ts
128117
async function createDeployment() {
129-
130-
const requestBody: DeploymentCreationRequest = {
131-
configurationId: '0a1b2c3d-4e5f6g7h'
132-
};
133-
134-
try{
135-
const responseData: DeploymentCreationResponse = await DeploymentApi
136-
.deploymentCreate(requestBody, {'AI-Resource-Group': 'default'})
137-
.execute();
138-
return responseData;
139-
} catch (errorData) {
140-
const apiError = errorData.response.data.error as ApiError;
141-
console.error('Status code:', errorData.response.status);
142-
throw new Error(`Deployment creation failed: ${apiError.message}`);
143-
}
118+
const requestBody: DeploymentCreationRequest = {
119+
configurationId: '0a1b2c3d-4e5f6g7h'
120+
};
121+
const responseData: DeploymentCreationResponse =
122+
await DeploymentApi.deploymentCreate(requestBody, {
123+
'AI-Resource-Group': 'default'
124+
}).execute();
125+
return responseData;
144126
}
145127
```
146128

@@ -166,26 +148,14 @@ async function modifyDeployment() {
166148
targetStatus: 'STOPPED'
167149
};
168150

169-
try {
170-
await DeploymentApi.deploymentModify(deploymentId, requestBody, {
171-
'AI-Resource-Group': 'default'
172-
}).execute();
173-
} catch (errorData) {
174-
const apiError = errorData.response.data.error as ApiError;
175-
console.error('Status code:', errorData.response.status);
176-
throw new Error(`Deployment modification failed: ${apiError.message}`);
177-
}
178-
}
179-
// Wait a few seconds for the deployment to stop
180-
try {
181-
return DeploymentApi.deploymentDelete(deploymentId, {
151+
await DeploymentApi.deploymentModify(deploymentId, requestBody, {
182152
'AI-Resource-Group': 'default'
183153
}).execute();
184-
} catch (errorData) {
185-
const apiError = errorData.response.data.error as ApiError;
186-
console.error('Status code:', errorData.response.status);
187-
throw new Error(`Deployment deletion failed: ${apiError.message}`);
188154
}
155+
// Wait a few seconds for the deployment to stop
156+
return DeploymentApi.deploymentDelete(deploymentId, {
157+
'AI-Resource-Group': 'default'
158+
}).execute();
189159
}
190160
```
191161

@@ -206,6 +176,10 @@ return DeploymentApi.deploymentQuery(queryParams, {
206176
By default, the fetched destination is cached.
207177
To disable caching, set the `useCache` parameter to `false` together with the `destinationName` parameter.
208178

179+
## Error Handling
180+
181+
For error handling instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#error-handling).
182+
209183
## Local Testing
210184

211185
For local testing instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#local-testing).

packages/ai-api/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
},
3030
"dependencies": {
3131
"@sap-ai-sdk/core": "workspace:^",
32-
"@sap-cloud-sdk/connectivity": "^4.0.1"
32+
"@sap-cloud-sdk/connectivity": "^4.0.1",
33+
"@sap-cloud-sdk/util": "^4.0.1"
3334
}
3435
}

packages/ai-api/src/utils/deployment-resolver.test.ts

+43-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import {
44
aiCoreDestination,
55
mockDeploymentsList
66
} from '../../../../test-util/mock-http.js';
7-
import { type AiDeployment } from '../client/AI_CORE_API';
8-
import { resolveDeploymentId } from './deployment-resolver.js';
7+
import { type AiDeployment } from '../client/AI_CORE_API/index.js';
8+
import {
9+
getAllDeployments,
10+
resolveDeploymentId
11+
} from './deployment-resolver.js';
912
import { deploymentCache } from './deployment-cache.js';
1013

1114
describe('deployment resolver', () => {
@@ -114,6 +117,44 @@ describe('deployment resolver', () => {
114117

115118
expect(id).toEqual('5');
116119
});
120+
121+
describe('get all deployments', () => {
122+
it('should return all deployments', async () => {
123+
mockResponse();
124+
const expected = [
125+
{
126+
id: '1',
127+
details: {
128+
resources: {
129+
backendDetails: {
130+
model: {
131+
name: 'gpt-4o',
132+
version: 'latest'
133+
}
134+
}
135+
}
136+
}
137+
},
138+
{
139+
id: '2',
140+
details: {
141+
resources: {
142+
backendDetails: {
143+
model: {
144+
name: 'gpt-4o',
145+
version: '0613'
146+
}
147+
}
148+
}
149+
}
150+
}
151+
];
152+
const deployments = await getAllDeployments({
153+
scenarioId: 'foundation-models'
154+
});
155+
expect(deployments).toStrictEqual(expected);
156+
});
157+
});
117158
});
118159

119160
function mockResponse() {

packages/ai-api/src/utils/deployment-resolver.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ErrorWithCause } from '@sap-cloud-sdk/util';
12
import {
23
type AiDeployment,
34
DeploymentApi
@@ -138,7 +139,13 @@ export async function resolveDeploymentId(
138139
return deployments[0].id;
139140
}
140141

141-
async function getAllDeployments(
142+
/**
143+
* Get all deployments that match the given criteria.
144+
* @param opts - The options for the deployment resolution.
145+
* @returns A promise of an array of deployments.
146+
* @internal
147+
*/
148+
export async function getAllDeployments(
142149
opts: DeploymentResolutionOptions
143150
): Promise<AiDeployment[]> {
144151
const {
@@ -160,8 +167,8 @@ async function getAllDeployments(
160167
deploymentCache.setAll(opts, resources);
161168

162169
return resources;
163-
} catch (error) {
164-
throw new Error('Failed to fetch the list of deployments: ' + error);
170+
} catch (error: any) {
171+
throw new ErrorWithCause('Failed to fetch the list of deployments.', error);
165172
}
166173
}
167174

packages/core/src/http-client.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ErrorWithCause,
23
mergeIgnoreCase,
34
removeLeadingSlashes,
45
removeTrailingSlashes
@@ -66,13 +67,21 @@ export async function executeRequest(
6667
data: JSON.stringify(data)
6768
};
6869

69-
return executeHttpRequest(
70-
{ ...aiCoreDestination, url: getTargetUrl(aiCoreDestination.url, url) },
71-
mergedRequestConfig,
72-
{
73-
fetchCsrfToken: false
74-
}
75-
);
70+
try {
71+
const response = await executeHttpRequest(
72+
{ ...aiCoreDestination, url: getTargetUrl(aiCoreDestination.url, url) },
73+
mergedRequestConfig,
74+
{
75+
fetchCsrfToken: false
76+
}
77+
);
78+
return response;
79+
} catch (error: any) {
80+
throw new ErrorWithCause(
81+
`Request failed with status code ${error.status}.`,
82+
error
83+
);
84+
}
7685
}
7786

7887
function mergeWithDefaultRequestConfig(

packages/document-grounding/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This package incorporates generative AI document grounding capabilities into you
1515
- [Create a Collection](#create-a-collection)
1616
- [Create a Document](#create-a-document)
1717
- [Custom Destination](#custom-destination)
18+
- [Error Handling](#error-handling)
1819
- [Local Testing](#local-testing)
1920
- [Support, Feedback, Contribution](#support-feedback-contribution)
2021
- [License](#license)
@@ -106,6 +107,10 @@ const response = await VectorApi.deleteCollectionById(collectionId, {
106107
});
107108
```
108109
110+
## Error Handling
111+
112+
For error handling instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#error-handling).
113+
109114
## Local Testing
110115
111116
For local testing instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#local-testing).

packages/foundation-models/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ This package incorporates generative AI foundation models into your AI activitie
1515
- [Azure OpenAI Embedding Client](#azure-openai-embedding-client)
1616
- [Custom Request Configuration](#custom-request-configuration)
1717
- [Custom Destination](#custom-destination)
18+
- [Overwriting API Version](#overwriting-api-version)
19+
- [Error Handling](#error-handling)
1820
- [Local Testing](#local-testing)
1921
- [Support, Feedback, Contribution](#support-feedback-contribution)
2022
- [License](#license)
@@ -293,6 +295,10 @@ client.run(
293295
);
294296
```
295297

298+
## Error Handling
299+
300+
For error handling instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#error-handling).
301+
296302
## Local Testing
297303

298304
For local testing instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#local-testing).

packages/langchain/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This package provides LangChain clients built on top of the foundation model and
1111
- [Usage](#usage)
1212
- [Orchestration Client](#orchestration-client)
1313
- [Azure OpenAI Client](#azure-openai-client)
14+
- [Error Handling](#error-handling)
1415
- [Local Testing](#local-testing)
1516
- [Support, Feedback, Contribution](#support-feedback-contribution)
1617
- [License](#license)
@@ -52,6 +53,10 @@ For more information about the Orchestration client, refer to the [documentation
5253

5354
For more information about Azure OpenAI client, refer to the [documentation](https://github.com/SAP/ai-sdk-js/tree/main/packages/langchain/src/openai/README.md).
5455

56+
## Error Handling
57+
58+
For error handling instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#error-handling).
59+
5560
## Local Testing
5661

5762
For local testing instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#local-testing).

packages/langchain/src/orchestration/client.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ describe('orchestration service client', () => {
7676
maxRetries: 1
7777
});
7878

79-
await expect(client.invoke([])).rejects.toThrowErrorMatchingInlineSnapshot(
80-
'"Request failed with status code 500"'
79+
await expect(client.invoke([])).rejects.toThrow(
80+
'Request failed with status code 500'
8181
);
8282
});
8383

0 commit comments

Comments
 (0)