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: Adjust embedding types and access #220

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/grumpy-apes-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---

Check warning on line 1 in .changeset/grumpy-apes-beam.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/grumpy-apes-beam.md", "range": {"start": {"line": 1, "column": 1}}}, "severity": "WARNING"}
'@sap-ai-sdk/foundation-models': minor
---

[New Functionality] Add convenience method to access all embeddings in an Azure OpenAI response (`AzureOpenAiEmbeddingResponse`).
5 changes: 5 additions & 0 deletions .changeset/many-zebras-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-ai-sdk/foundation-models': minor
---

[Compatibility Note] Adjust `AzureOpenAiEmbeddingOutput` type to include multiple embedding responses as opposed to one.
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,57 @@ import { createLogger } from '@sap-cloud-sdk/util';
import { jest } from '@jest/globals';
import { parseMockResponse } from '../../../../test-util/mock-http.js';
import { AzureOpenAiEmbeddingResponse } from './azure-openai-embedding-response.js';
import type { HttpResponse } from '@sap-cloud-sdk/http-client';
import type { AzureOpenAiEmbeddingOutput } from './azure-openai-embedding-types.js';

describe('Azure OpenAI embedding response', () => {
const mockResponse = parseMockResponse<AzureOpenAiEmbeddingResponse>(
'foundation-models',
'azure-openai-embeddings-success-response.json'
);
const rawResponse = {
data: mockResponse,
status: 200,
headers: {},
request: {}
};
const embeddingResponse = new AzureOpenAiEmbeddingResponse(rawResponse);
let embeddingResponse: AzureOpenAiEmbeddingResponse;
let rawResponse: HttpResponse;
let mockedData: AzureOpenAiEmbeddingOutput;
beforeAll(async () => {
mockedData = await parseMockResponse<AzureOpenAiEmbeddingOutput>(
'foundation-models',
'azure-openai-embeddings-success-response.json'
);

rawResponse = {
data: mockedData,
status: 200,
headers: {},
request: {}
};
embeddingResponse = new AzureOpenAiEmbeddingResponse(rawResponse);
});

it('should return the embedding response', () => {
expect(embeddingResponse.data).toStrictEqual(mockResponse);
expect(embeddingResponse.data).toStrictEqual(mockedData);
});

it('should return raw response', () => {
expect(embeddingResponse.rawResponse).toBe(rawResponse);
});

it('should return the first embedding', () => {
expect(embeddingResponse.getEmbedding()).toEqual(
mockedData.data[0].embedding
);
});

it('should return undefined when convenience function is called with incorrect index', () => {
const logger = createLogger({
package: 'foundation-models',
messageContext: 'azure-openai-embedding-response'
});
const errorSpy = jest.spyOn(logger, 'error');
expect(embeddingResponse.getEmbedding(1)).toBeUndefined();
expect(errorSpy).toHaveBeenCalledWith('Data index 1 is out of bounds.');
expect(embeddingResponse.getEmbedding(2)).toBeUndefined();
expect(errorSpy).toHaveBeenCalledWith('Data index 2 is out of bounds.');
expect(errorSpy).toHaveBeenCalledTimes(1);
});

it('should return all embeddings', () => {
expect(embeddingResponse.getEmbeddings()).toEqual([
mockedData.data[0].embedding,
mockedData.data[1]?.embedding
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export class AzureOpenAiEmbeddingResponse {
return this.data.data[dataIndex]?.embedding;
}

/**
* Parses the Azure OpenAI response and returns all embeddings.
* @returns The embedding vectors.
*/
getEmbeddings(): number[][] {
return this.data.data.map(({ embedding }) => embedding);
}

private logInvalidDataIndex(dataIndex: number): void {
if (dataIndex < 0 || dataIndex >= this.data.data.length) {
logger.error(`Data index ${dataIndex} is out of bounds.`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,20 @@ export interface AzureOpenAiEmbeddingOutput {
/**
* Array of result candidates.
*/
data: [
{
/**
* Embedding object.
*/
object: 'embedding';
/**
* Array of size `1536` (Azure OpenAI's embedding size) containing embedding vector.
*/
embedding: number[];
/**
* Index of choice.
*/
index: number;
}
];
data: {
/**
* Embedding object.
*/
object: 'embedding';
/**
* Array of size `1536` (Azure OpenAI's embedding size) containing embedding vector.
*/
embedding: number[];
/**
* Index of choice.
*/
index: number;
}[];
/**
* Token Usage.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
],
"index": 0,
"object": "embedding"
},
{
"embedding": [
-0.011352594, -0.006521842, 0.0059352037, -0.021066532, -0.0062957075,
0.024606025, -0.02332132, 0.016792923, 0.0029676019, -0.04000937,
0.0143283885, 0.025274595, -0.0006419426, -0.0014493892, -0.006269489
],
"index": 1,
"object": "embedding"
}
],
"model": "ada",
Expand Down
32 changes: 15 additions & 17 deletions test-util/mock-http.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import {
DestinationAuthToken,
HttpDestination,
ServiceCredentials
} from '@sap-cloud-sdk/connectivity';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import nock from 'nock';
import { type EndpointOptions } from '@sap-ai-sdk/core';
import {
type FoundationModel,
type DeploymentResolutionOptions
} from '@sap-ai-sdk/ai-api/internal.js';
import { dummyToken } from './mock-jwt.js';
import type {
DestinationAuthToken,
HttpDestination,
ServiceCredentials
} from '@sap-cloud-sdk/connectivity';

// Get the directory of this file
const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -121,7 +121,7 @@ export function mockDeploymentsList(
): nock.Scope {
const nockOpts = {
reqheaders: {
'ai-resource-group': opts?.resourceGroup ?? 'default',
'ai-resource-group': opts?.resourceGroup ?? 'default'
}
};
const query = {
Expand All @@ -143,14 +143,12 @@ export function mockDeploymentsList(
/**
* @internal
*/
export function parseMockResponse<T>(client: string, fileName: string): T {
const fileContent = fs.readFileSync(
path.join(
__dirname,
'data',
client,
fileName
),
export async function parseMockResponse<T>(
client: string,
fileName: string
): Promise<T> {
const fileContent = await readFile(
path.join(__dirname, 'data', client, fileName),
'utf-8'
);
return JSON.parse(fileContent);
Expand Down
Loading