Skip to content

Commit

Permalink
changes
Browse files Browse the repository at this point in the history
  • Loading branch information
shibeshduw committed Oct 16, 2024
1 parent 9be54c8 commit 78b83df
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 108 deletions.
107 changes: 58 additions & 49 deletions sample-code/src/ai-api/deployment-api.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
import { DeploymentApi } from '@sap-ai-sdk/ai-api';
import type {
AiDeploymentBulkModificationResponse,
AiDeploymentCreationResponse,
AiDeploymentDeletionResponse,
AiDeploymentList,
AiDeploymentModificationResponse,
AiDeploymentResponseWithDetails
AiDeploymentModificationRequestList,
AiDeploymentStatus
} from '@sap-ai-sdk/ai-api';

/**
* Get all deployments.
* Get all deployments filtered by status.
* @param resourceGroup - AI-Resource-Group where the resources are available.
* @param status - Optional parameter to filter deployments by status.
* @returns All deployments.
* @param status - Optional query parameter to filter deployments by status.
* @returns List of deployments.
*/
export async function getDeployments(
resourceGroup: string,
status?:
| 'PENDING'
| 'RUNNING'
| 'COMPLETED'
| 'DEAD'
| 'STOPPING'
| 'STOPPED'
| 'UNKNOWN'
status?: AiDeploymentStatus
): Promise<AiDeploymentList> {
// check for optional query parameters.
const queryParams = status ? { status } : {};
Expand All @@ -31,23 +25,6 @@ export async function getDeployments(
}).execute();
}

/**
* Get information about specific deployment.
* @param deploymentId - ID of the specific deployment.
* @param resourceGroup - AI-Resource-Group where the resources are available.
* @returns Details for deplyoment with deploymentId.
*/
export async function getDeployment(
deploymentId: string,
resourceGroup: string
): Promise<AiDeploymentResponseWithDetails> {
return DeploymentApi.deploymentGet(
deploymentId,
{},
{ 'AI-Resource-Group': resourceGroup }
).execute();
}

/**
* Create a deployment using the configuration specified by configurationId.
* @param configurationId - ID of the configuration to be used.
Expand All @@ -65,35 +42,67 @@ export async function createDeployment(
}

/**
* Update target status of a specific deployment to stop it.
* Stop all deployments with the specific configuration ID.
* Only deployments with 'status': 'RUNNING' can be stopped.
* @param deploymentId - ID of the specific deployment.
* @param configurationId - ID of the configuration to be used.
* @param resourceGroup - AI-Resource-Group where the resources are available.
* @returns Deployment modification response with 'targetStatus': 'STOPPED'.
* @returns Deployment modification response list with 'targetStatus': 'STOPPED'.
*/
export async function stopDeployment(
deploymentId: string,
export async function stopDeployments(
configurationId: string,
resourceGroup: string
): Promise<AiDeploymentModificationResponse> {
return DeploymentApi.deploymentModify(
deploymentId,
{ targetStatus: 'STOPPED' },
): Promise<AiDeploymentBulkModificationResponse> {
// Get all RUNNING deployments with configurationId
const deployments: AiDeploymentList = await DeploymentApi.deploymentQuery(
{ status: 'RUNNING', configurationId },
{ 'AI-Resource-Group': resourceGroup }
).execute();

// Map the deployment Ids and add property targetStatus: 'STOPPED'
const deploymentsToStop: any = deployments.resources.map(deployment => ({
id: deployment.id,
targetStatus: 'STOPPED'
}));

// Send batch modify request to stop deployments
return DeploymentApi.deploymentBatchModify(
{ deployments: deploymentsToStop as AiDeploymentModificationRequestList },
{ 'AI-Resource-Group': resourceGroup }
).execute();
}

/**
* Mark deployment with deploymentId as deleted.
* Only deployments with 'status': 'STOPPED' can be deleted.
* @param deploymentId - ID of the specific deployment.
* Delete all deployments.
* Only deployments with 'status': 'STOPPED' and 'status': 'UNKNOWN' can be deleted.
* @param resourceGroup - AI-Resource-Group where the resources are available.
* @returns Deployment deletion response with 'targetStatus': 'DELETED'.
* @returns Deployment deletion response list with 'targetStatus': 'DELETED'.
*/
export async function deleteDeployment(
deploymentId: string,
export async function deleteDeployments(
resourceGroup: string
): Promise<AiDeploymentDeletionResponse> {
return DeploymentApi.deploymentDelete(deploymentId, {
'AI-Resource-Group': resourceGroup
}).execute();
): Promise<AiDeploymentDeletionResponse[]> {
// Get all STOPPED and UNKNOWN deployments
const [runningDeployments, unknownDeployments] = await Promise.all([
DeploymentApi.deploymentQuery(
{ status: 'STOPPED' },
{ 'AI-Resource-Group': resourceGroup }
).execute(),
DeploymentApi.deploymentQuery(
{ status: 'UNKNOWN' },
{ 'AI-Resource-Group': resourceGroup }
).execute()
]);

const deploymentsToDelete = [
...runningDeployments.resources,
...unknownDeployments.resources
];

// Delete all deployments
return Promise.all(
deploymentsToDelete.map(deployment =>
DeploymentApi.deploymentDelete(deployment.id, {
'AI-Resource-Group': resourceGroup
}).execute()
)
);
}
5 changes: 2 additions & 3 deletions sample-code/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ export {
invokeRagChain
} from './langchain-azure-openai.js';
export {
getDeployment,
getDeployments,
createDeployment,
stopDeployment,
deleteDeployment
stopDeployments,
deleteDeployments
// eslint-disable-next-line import/no-internal-modules
} from './ai-api/deployment-api.js';
export {
Expand Down
57 changes: 51 additions & 6 deletions sample-code/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import {
} from './orchestration.js';
import {
getDeployments,
createDeployment
createDeployment,
stopDeployments,
deleteDeployments
// eslint-disable-next-line import/no-internal-modules
} from './ai-api/deployment-api.js';
import {
getScenarios,
getModelsInScenario
// eslint-disable-next-line import/no-internal-modules
} from './ai-api/scenario-api.js';
Expand All @@ -26,7 +29,7 @@ import {
invokeRagChain,
invoke
} from './langchain-azure-openai.js';
import type { AiApiError } from '@sap-ai-sdk/ai-api';
import type { AiApiError, AiDeploymentStatus } from '@sap-ai-sdk/ai-api';
import type { OrchestrationResponse } from '@sap-ai-sdk/orchestration';

const app = express();
Expand Down Expand Up @@ -96,9 +99,11 @@ app.get('/orchestration/:sampleCase', async (req, res) => {
}
});

app.get('/ai-api/get-deployments', async (req, res) => {
app.get('/ai-api/deployments', async (req, res) => {
try {
res.send(await getDeployments('default'));
res.send(
await getDeployments('default', req.query.status as AiDeploymentStatus)
);
} catch (error: any) {
console.error(error);
const apiError = error.response.data.error as AiApiError;
Expand All @@ -108,7 +113,7 @@ app.get('/ai-api/get-deployments', async (req, res) => {
}
});

app.post('/ai-api/create-deployment', async (req, res) => {
app.post('/ai-api/deployment/create', express.json(), async (req, res) => {
try {
res.send(await createDeployment(req.body.configurationId, 'default'));
} catch (error: any) {
Expand All @@ -120,7 +125,47 @@ app.post('/ai-api/create-deployment', async (req, res) => {
}
});

app.get('/ai-api/get-models-in-scenario', async (req, res) => {
app.patch('/ai-api/deployment/batch-stop', express.json(), async (req, res) => {
try {
res.send(await stopDeployments(req.body.configurationId, 'default'));
} catch (error: any) {
console.error(error);
const apiError = error.response.data.error as AiApiError;
res
.status(error.response.status)
.send('Yikes, vibes are off apparently 😬 -> ' + apiError.message);
}
});

app.delete(
'/ai-api/deployment/batch-delete',
express.json(),
async (req, res) => {
try {
res.send(await deleteDeployments('default'));
} catch (error: any) {
console.error(error);
const apiError = error.response.data.error as AiApiError;
res
.status(error.response.status)
.send('Yikes, vibes are off apparently 😬 -> ' + apiError.message);
}
}
);

app.get('/ai-api/scenarios', async (req, res) => {
try {
res.send(await getScenarios('default'));
} catch (error: any) {
console.error(error);
const apiError = error.response.data.error as AiApiError;
res
.status(error.response.status)
.send('Yikes, vibes are off apparently 😬 -> ' + apiError.message);
}
});

app.get('/ai-api/models', async (req, res) => {
try {
res.send(await getModelsInScenario('foundation-models', 'default'));
} catch (error: any) {
Expand Down
5 changes: 1 addition & 4 deletions tests/e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@
},
"scripts": {
"compile": "tsc",
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
"test:deployment-api-create": "NODE_OPTIONS=--experimental-vm-modules jest -t \"create deployment\"",
"test:deployment-api-stop": "NODE_OPTIONS=--experimental-vm-modules jest -t \"stop deployment\"",
"test:deployment-api-delete": "NODE_OPTIONS=--experimental-vm-modules jest -t \"delete deployment\"",
"test": "NODE_OPTIONS=--experimental-vm-modules jest deployment-api.test.ts",
"cleanup-deployments": "node --loader ts-node/esm ./src/utils/cleanup-deployments.ts",
"lint": "eslint . && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -c",
"lint:fix": "eslint . --fix && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -w --log-level error"
Expand Down
55 changes: 31 additions & 24 deletions tests/e2e-tests/src/deployment-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import {
getDeployment,
getDeployments,
createDeployment,
stopDeployment,
deleteDeployment
} from '@sap-ai-sdk/sample-code';
import { DeploymentApi } from '@sap-ai-sdk/ai-api';
import { getDeployments, createDeployment } from '@sap-ai-sdk/sample-code';
import { loadEnv } from './utils/load-env.js';
import {
configurationId,
Expand All @@ -17,12 +12,12 @@ loadEnv();

describe('DeploymentApi', () => {
let createdDeploymentId: string | undefined;
let initialState: AiDeploymentList | undefined;
let initialDeployments: AiDeploymentList | undefined;

beforeAll(async () => {
const queryResponse = await getDeployments(resourceGroup);
expect(queryResponse).toBeDefined();
initialState = queryResponse;
initialDeployments = queryResponse;
});

it('should create a deployment and wait for it to run', async () => {
Expand Down Expand Up @@ -57,7 +52,11 @@ describe('DeploymentApi', () => {
'RUNNING'
);

const modifyResponse = await stopDeployment(deploymentId, resourceGroup);
const modifyResponse = await DeploymentApi.deploymentModify(
deploymentId,
{ targetStatus: 'STOPPED' },
{ 'AI-Resource-Group': resourceGroup }
).execute();
expect(modifyResponse).toEqual(
expect.objectContaining({
message: 'Deployment modification scheduled'
Expand All @@ -81,7 +80,9 @@ describe('DeploymentApi', () => {
'STOPPED'
);

const deleteResponse = await deleteDeployment(deploymentId, resourceGroup);
const deleteResponse = await DeploymentApi.deploymentDelete(deploymentId, {
'AI-Resource-Group': resourceGroup
}).execute();
expect(deleteResponse).toEqual(
expect.objectContaining({
message: 'Deletion scheduled'
Expand All @@ -90,33 +91,39 @@ describe('DeploymentApi', () => {

// Wait for deletion to complete
await new Promise(r => setTimeout(r, 30000));
await expect(getDeployment(deploymentId, resourceGroup)).rejects.toThrow();
}, 100000);
await expect(
DeploymentApi.deploymentGet(
deploymentId,
{},
{ 'AI-Resource-Group': resourceGroup }
).execute()
).rejects.toThrow();
}, 150000);

it('should validate consistency of deployments after test flow', async () => {
const queryResponse = await getDeployments(resourceGroup);
expect(queryResponse).toBeDefined();

const sanitizedInitialState = sanitizedState(
initialState,
const initialFilteredDeployments = filterDeployments(
initialDeployments,
createdDeploymentId
);
const sanitizedEndState = sanitizedState(
const finalFilteredDeployments = filterDeployments(
queryResponse,
createdDeploymentId
);
expect(sanitizedEndState.resources).toStrictEqual(
sanitizedInitialState.resources
expect(finalFilteredDeployments.resources).toStrictEqual(
initialFilteredDeployments.resources
);
});
});

const sanitizedState = (
state: AiDeploymentList | undefined,
const filterDeployments = (
deployments: AiDeploymentList | undefined,
createdDeployentId: string | undefined
) => ({
...state,
resources: state?.resources
...deployments,
resources: deployments?.resources
.filter(deployment => deployment.id === createdDeployentId)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(({ modifiedAt, ...rest }) => rest)
Expand All @@ -126,10 +133,10 @@ async function checkCreatedDeployment(
deploymentId: string | undefined,
status: 'RUNNING' | 'STOPPED'
): Promise<string> {
if (deploymentId === undefined) {
if (!deploymentId) {
try {
const response = await getDeployments(resourceGroup, status);
if (response.count === 0) {
if (!response.count) {
throw new Error(
`No ${status} deployments found, please ${status === 'RUNNING' ? 'create' : 'stop'} a deployment first to ${status === 'RUNNING' ? 'modify' : 'delete'} it.`
);
Expand Down
Loading

0 comments on commit 78b83df

Please sign in to comment.