Skip to content

Commit

Permalink
feat: Support custom destination (#351)
Browse files Browse the repository at this point in the history
* feat: support custom destination

* feat: add destination for all client constructor

* feat: add destination for openapi request builder with many tests

* fix: lint

* fix: destination name in unit test

* chore: update sample code doc for custom destination call

* feat: support custom destination in langchain

* docs: add instructions for using custom destination

* fix: lint

* feat: add v2 logic for getting the target url

* fix: remove destination test in e2e already covered in smoke test

* fix: Changes from lint

* fix: add internal for getTargetUrl

* feat: use cache by default

* fix: lint

* chore: changeset

* fix: apply review comments

---------

Co-authored-by: cloud-sdk-js <[email protected]>
  • Loading branch information
ZhongpinWang and cloud-sdk-js authored Dec 13, 2024
1 parent ebb7429 commit b4a5506
Show file tree
Hide file tree
Showing 29 changed files with 645 additions and 203 deletions.
9 changes: 9 additions & 0 deletions .changeset/stupid-ligers-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@sap-ai-sdk/foundation-models': minor
'@sap-ai-sdk/orchestration': minor
'@sap-ai-sdk/langchain': minor
'@sap-ai-sdk/ai-api': minor
'@sap-ai-sdk/core': minor
---

[New Functionality] Add support for providing custom destination for AI Core besides using environment variable and service binding.
179 changes: 102 additions & 77 deletions packages/ai-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ This package provides tools to manage scenarios and workflows in SAP AI Core.

We maintain a list of [currently available and tested AI Core APIs](https://github.com/SAP/ai-sdk-js/blob/main/docs/list-tested-APIs.md)

## Table of Contents
### Table of Contents

- [Table of Contents](#table-of-contents)
- [Installation](#installation)
- [Version Management](#version-management)
- [Prerequisites](#prerequisites)
Expand All @@ -22,6 +21,7 @@ We maintain a list of [currently available and tested AI Core APIs](https://gith
- [Create a Configuration](#create-a-configuration)
- [Create a Deployment](#create-a-deployment)
- [Delete a Deployment](#delete-a-deployment)
- [Custom Destination](#custom-destination)
- [Local Testing](#local-testing)
- [Support, Feedback, Contribution](#support-feedback-contribution)
- [License](#license)
Expand Down Expand Up @@ -64,60 +64,61 @@ In addition to the examples below, you can find more **sample code** [here](http

### Create an Artifact

```TypeScript
```ts
async function createArtifact() {

const requestBody: ArtifactPostData = {
name: 'training-test-dataset',
kind: 'dataset',
url: 'https://ai.example.com',
scenarioId: 'foundation-models'
}

try {
const responseData: ArtifactCreationResponse = await ArtifactApi
.artifactCreate(requestBody, {'AI-Resource-Group': 'default'})
.execute();
return responseData;
} catch (errorData) {
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Artifact creation failed: ${apiError.message}`);
}
const requestBody: ArtifactPostData = {
name: 'training-test-dataset',
kind: 'dataset',
url: 'https://ai.example.com',
scenarioId: 'foundation-models'
};

try {
const responseData: ArtifactCreationResponse =
await ArtifactApi.artifactCreate(requestBody, {
'AI-Resource-Group': 'default'
}).execute();
return responseData;
} catch (errorData) {
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Artifact creation failed: ${apiError.message}`);
}
}
```

### Create a Configuration

```TypeScript
```ts
async function createConfiguration() {
const requestBody: ConfigurationBaseData = {
name: 'gpt-35-turbo',
executableId: 'azure-openai',
scenarioId: 'foundation-models',
parameterBindings: [
{
"key": "modelName",
"value": "gpt-35-turbo"
},
{
"key": "modelVersion",
"value": "latest"
}
],
inputArtifactBindings: []
}

try {
const responseData: ConfigurationCreationResponse = await ConfigurationApi
.configurationCreate(requestBody, {'AI-Resource-Group': 'default'})
.execute();
return responseData;
} catch (errorData) {
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Configuration creation failed: ${apiError.message}`);
}
const requestBody: ConfigurationBaseData = {
name: 'gpt-35-turbo',
executableId: 'azure-openai',
scenarioId: 'foundation-models',
parameterBindings: [
{
key: 'modelName',
value: 'gpt-35-turbo'
},
{
key: 'modelVersion',
value: 'latest'
}
],
inputArtifactBindings: []
};

try {
const responseData: ConfigurationCreationResponse =
await ConfigurationApi.configurationCreate(requestBody, {
'AI-Resource-Group': 'default'
}).execute();
return responseData;
} catch (errorData) {
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Configuration creation failed: ${apiError.message}`);
}
}
```

Expand Down Expand Up @@ -148,42 +149,65 @@ async function createDeployment() {
Only deployments with `targetStatus: STOPPED` can be deleted.
Thus, a modification request must be sent before deletion can occur.

```TypeScript
```ts
async function modifyDeployment() {
let deploymentId: string = '0a1b2c3d4e5f';

const deployment: DeploymentResponseWithDetails =
await DeploymentApi.deploymentGet(
deploymentId,
{},
{ 'AI-Resource-Group': 'default' }
).execute();

if (deployment.targetStatus === 'RUNNING') {
// Only RUNNING deployments can be STOPPED.
const requestBody: DeploymentModificationRequest = {
targetStatus: 'STOPPED'
};

let deploymentId: string = '0a1b2c3d4e5f';

const deployment: DeploymentResponseWithDetails = await DeploymentApi
.deploymentGet(deploymentId, {}, {'AI-Resource-Group': 'default'})
.execute();

if(deployment.targetStatus === 'RUNNING') {
// Only RUNNING deployments can be STOPPED.
const requestBody: DeploymentModificationRequest = {
targetStatus: 'STOPPED',
};

try {
await DeploymentApi
.deploymentModify(deploymentId, requestBody, {'AI-Resource-Group': 'default'})
.execute();
} catch (errorData) {
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Deployment modification failed: ${apiError.message}`);
}
}
// Wait a few seconds for the deployment to stop
try {
return DeploymentApi.deploymentDelete(deploymentId, { 'AI-Resource-Group': 'default' }).execute();
await DeploymentApi.deploymentModify(deploymentId, requestBody, {
'AI-Resource-Group': 'default'
}).execute();
} catch (errorData) {
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Deployment deletion failed: ${apiError.message}`);
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Deployment modification failed: ${apiError.message}`);
}
}
// Wait a few seconds for the deployment to stop
try {
return DeploymentApi.deploymentDelete(deploymentId, {
'AI-Resource-Group': 'default'
}).execute();
} catch (errorData) {
const apiError = errorData.response.data.error as ApiError;
console.error('Status code:', errorData.response.status);
throw new Error(`Deployment deletion failed: ${apiError.message}`);
}
}
```

### Custom Destination

When calling the `execute()` method, it is possible to provide a custom destination.
For example, when querying deployments targeting a destination with the name `my-destination`, the following code can be used:

```ts
const queryParams = status ? { status } : {};
return DeploymentApi.deploymentQuery(queryParams, {
'AI-Resource-Group': resourceGroup
}).execute({
destinationName: 'my-destination'
});
```

By default, the fetched destination is cached.
To disable caching, set the `useCache` parameter to `false` together with the `destinationName` parameter.

```ts

## Local Testing

For local testing instructions, refer to this [section](https://github.com/SAP/ai-sdk-js/blob/main/README.md#local-testing).
Expand All @@ -198,3 +222,4 @@ For more information about how to contribute, the project structure, as well as
## License

The SAP Cloud SDK for AI is released under the [Apache License Version 2.0.](http://www.apache.org/licenses/).
```
33 changes: 32 additions & 1 deletion packages/ai-api/src/tests/deployment-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import nock from 'nock';
import { DeploymentApi } from '../client/AI_CORE_API';
import {
aiCoreDestination,
mockClientCredentialsGrantCall
mockClientCredentialsGrantCall,
mockDestination
} from '../../../../test-util/mock-http.js';
import type {
AiDeploymentCreationRequest,
Expand Down Expand Up @@ -171,4 +172,34 @@ describe('deployment', () => {

expect(result).toEqual(expectedResponse);
});

it('parses a successful response for delete request with custom destination', async () => {
mockDestination();

const deploymentId = '4e5f6g7h';
const expectedResponse: AiDeploymentDeletionResponse = {
id: '4e5f6g7h',
message: 'Deletion scheduled',
targetStatus: 'DELETED'
};

nock('http://example.com', {
reqheaders: {
'AI-Resource-Group': 'default'
}
})
.delete(`/v2/lm/deployments/${deploymentId}`)
.reply(200, expectedResponse, {
'Content-Type': 'application/json'
});

const result: AiDeploymentDeletionResponse =
await DeploymentApi.deploymentDelete(deploymentId, {
'AI-Resource-Group': 'default'
}).execute({
destinationName: 'aicore'
});

expect(result).toEqual(expectedResponse);
});
});
31 changes: 28 additions & 3 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { createLogger } from '@sap-cloud-sdk/util';
import {
assertHttpDestination,
getServiceBinding,
transformServiceBindingToDestination
transformServiceBindingToDestination,
useOrFetchDestination
} from '@sap-cloud-sdk/connectivity';
import type {
HttpDestination,
HttpDestinationOrFetchOptions,
Service,
ServiceCredentials
} from '@sap-cloud-sdk/connectivity';
Expand All @@ -17,10 +20,32 @@ const logger = createLogger({
let aiCoreServiceBinding: Service | undefined;

/**
* Returns a destination object from AI Core service binding.
* Returns a destination object.
* @param destination - The destination to use for the request.
* @returns The destination object.
*/
export async function getAiCoreDestination(): Promise<HttpDestination> {
export async function getAiCoreDestination(
destination?: HttpDestinationOrFetchOptions
): Promise<HttpDestination> {
// If Destination is provided, get the destination and return it.
if (destination) {
// If fetch options provided, by default cache the destination.
if (
destination.destinationName !== undefined &&
destination.useCache === undefined
) {
destination.useCache = true;
}

const resolvedDestination = await useOrFetchDestination(destination);
if (!resolvedDestination) {
throw new Error('Could not resolve destination.');
}
assertHttpDestination(resolvedDestination);
return resolvedDestination;
}

// Otherwise, get the destination from env or service binding with default service name "aicore".
if (!aiCoreServiceBinding) {
aiCoreServiceBinding =
getAiCoreServiceKeyFromEnv() || getServiceBinding('aicore');
Expand Down
Loading

0 comments on commit b4a5506

Please sign in to comment.