Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support custom destination #351

Merged
merged 23 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
88063a4
feat: support custom destination
ZhongpinWang Dec 5, 2024
bd431b1
feat: add destination for all client constructor
ZhongpinWang Dec 5, 2024
4a7bdc3
feat: add destination for openapi request builder with many tests
ZhongpinWang Dec 6, 2024
d48f4fa
fix: lint
ZhongpinWang Dec 6, 2024
be9e150
Merge branch 'main' into feat-support-destination
ZhongpinWang Dec 6, 2024
715b594
fix: destination name in unit test
ZhongpinWang Dec 6, 2024
4501ec8
Merge branch 'main' into feat-support-destination
ZhongpinWang Dec 9, 2024
f3a1af8
chore: update sample code doc for custom destination call
ZhongpinWang Dec 9, 2024
a3d043b
feat: support custom destination in langchain
ZhongpinWang Dec 9, 2024
4d191ed
docs: add instructions for using custom destination
ZhongpinWang Dec 9, 2024
f8116a6
fix: lint
ZhongpinWang Dec 9, 2024
bd04a02
feat: add v2 logic for getting the target url
ZhongpinWang Dec 10, 2024
0e58500
fix: remove destination test in e2e already covered in smoke test
ZhongpinWang Dec 10, 2024
8738ca3
Merge branch 'main' into feat-support-destination
ZhongpinWang Dec 11, 2024
b953d37
fix: Changes from lint
Dec 11, 2024
ca50350
fix: add internal for getTargetUrl
ZhongpinWang Dec 11, 2024
77a9a54
Merge branch 'main' into feat-support-destination
ZhongpinWang Dec 11, 2024
c68f53a
Merge branch 'main' into feat-support-destination
ZhongpinWang Dec 12, 2024
cee35c1
feat: use cache by default
ZhongpinWang Dec 12, 2024
a0feed2
fix: lint
ZhongpinWang Dec 12, 2024
e436a06
chore: changeset
ZhongpinWang Dec 12, 2024
94d219d
fix: apply review comments
ZhongpinWang Dec 12, 2024
5ed951f
Merge branch 'main' into feat-support-destination
ZhongpinWang Dec 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
---

Check warning on line 1 in .changeset/stupid-ligers-pretend.md

View workflow job for this annotation

GitHub Actions / grammar-check

[vale] reported by reviewdog 🐶 [SAP.Readability] The text is very complex! It has a grade score of >14. Raw Output: {"message": "[SAP.Readability] The text is very complex! It has a grade score of \u003e14.", "location": {"path": ".changeset/stupid-ligers-pretend.md", "range": {"start": {"line": 1, "column": 1}}}, "severity": "WARNING"}
'@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);
ZhongpinWang marked this conversation as resolved.
Show resolved Hide resolved
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
Loading