diff --git a/packages/gen-ai-hub/src/client/openai/openai-client.test.ts b/packages/gen-ai-hub/src/client/openai/openai-client.test.ts index 3ebf2630..9b0284cb 100644 --- a/packages/gen-ai-hub/src/client/openai/openai-client.test.ts +++ b/packages/gen-ai-hub/src/client/openai/openai-client.test.ts @@ -1,14 +1,14 @@ -import fs from 'fs'; -import path from 'path'; import nock from 'nock'; import { HttpDestination } from '@sap-cloud-sdk/connectivity'; import { mockGetAiCoreDestination } from '../../../test-util/mock-context.js'; import { - BaseLlmParameters, BaseLlmParametersWithDeploymentId, EndpointOptions } from '../../core/http-client.js'; -import { mockInference } from '../../../test-util/mock-http.js'; +import { + mockInference, + parseMockResponse +} from '../../../test-util/mock-http.js'; import { OpenAiClient } from './openai-client.js'; import { OpenAiChatCompletionOutput, @@ -20,17 +20,15 @@ import { describe('openai client', () => { let destination: HttpDestination; - let deploymentConfig: BaseLlmParameters; + const deploymentConfiguration: BaseLlmParametersWithDeploymentId = { + deploymentId: 'deployment-id' + }; let chatCompletionEndpoint: EndpointOptions; let embeddingsEndpoint: EndpointOptions; beforeAll(() => { destination = mockGetAiCoreDestination(); - deploymentConfig = { - deploymentConfiguration: { - deploymentId: 'deployment-id' - } as BaseLlmParametersWithDeploymentId - }; + chatCompletionEndpoint = { url: 'chat/completions', apiVersion: '2024-02-01' @@ -58,16 +56,11 @@ describe('openai client', () => { }; const request: OpenAiChatCompletionParameters = { ...prompt, - ...deploymentConfig + deploymentConfiguration }; - const mockResponse = fs.readFileSync( - path.join( - 'test-util', - 'mock-data', - 'openai', - 'openai-chat-completion-success-response.json' - ), - 'utf8' + const mockResponse = parseMockResponse( + 'openai', + 'openai-chat-completion-success-response.json' ); mockInference( @@ -75,35 +68,27 @@ describe('openai client', () => { data: request }, { - data: JSON.parse(mockResponse), + data: mockResponse, status: 200 }, destination, chatCompletionEndpoint ); - const result: OpenAiChatCompletionOutput = - await new OpenAiClient().chatCompletion(request); - const expectedResponse: OpenAiChatCompletionOutput = - JSON.parse(mockResponse); - - expect(result).toEqual(expectedResponse); + expect(new OpenAiClient().chatCompletion(request)).resolves.toEqual( + mockResponse + ); }); it('throws on bad request', async () => { const prompt = { messages: [] }; const request: OpenAiChatCompletionParameters = { ...prompt, - ...deploymentConfig + deploymentConfiguration }; - const mockResponse = fs.readFileSync( - path.join( - 'test-util', - 'mock-data', - 'openai', - 'openai-error-response.json' - ), - 'utf8' + const mockResponse = parseMockResponse( + 'openai', + 'openai-error-response.json' ); mockInference( @@ -111,7 +96,7 @@ describe('openai client', () => { data: request }, { - data: JSON.parse(mockResponse), + data: mockResponse, status: 400 }, destination, @@ -129,16 +114,11 @@ describe('openai client', () => { const prompt = { input: ['AI is fascinating'] }; const request: OpenAiEmbeddingParameters = { ...prompt, - ...deploymentConfig + deploymentConfiguration }; - const mockResponse = fs.readFileSync( - path.join( - 'test-util', - 'mock-data', - 'openai', - 'openai-embeddings-success-response.json' - ), - 'utf8' + const mockResponse = parseMockResponse( + 'openai', + 'openai-embeddings-success-response.json' ); mockInference( @@ -146,34 +126,27 @@ describe('openai client', () => { data: request }, { - data: JSON.parse(mockResponse), + data: mockResponse, status: 200 }, destination, embeddingsEndpoint ); - const result: OpenAiEmbeddingOutput = await new OpenAiClient().embeddings( - request + expect(new OpenAiClient().embeddings(request)).resolves.toEqual( + mockResponse ); - const expectedResponse: OpenAiEmbeddingOutput = JSON.parse(mockResponse); - expect(result).toEqual(expectedResponse); }); it('throws on bad request', async () => { const prompt = { input: [] }; const request: OpenAiEmbeddingParameters = { ...prompt, - ...deploymentConfig + deploymentConfiguration }; - const mockResponse = fs.readFileSync( - path.join( - 'test-util', - 'mock-data', - 'openai', - 'openai-error-response.json' - ), - 'utf8' + const mockResponse = parseMockResponse( + 'openai', + 'openai-error-response.json' ); mockInference( @@ -181,14 +154,14 @@ describe('openai client', () => { data: request }, { - data: JSON.parse(mockResponse), + data: mockResponse, status: 400 }, destination, embeddingsEndpoint ); - await expect(new OpenAiClient().embeddings(request)).rejects.toThrow(); + expect(new OpenAiClient().embeddings(request)).rejects.toThrow(); }); }); }); diff --git a/packages/gen-ai-hub/src/orchestration/api/schema/chat-message.ts b/packages/gen-ai-hub/src/orchestration/api/schema/chat-message.ts index 7c442063..ea30476f 100644 --- a/packages/gen-ai-hub/src/orchestration/api/schema/chat-message.ts +++ b/packages/gen-ai-hub/src/orchestration/api/schema/chat-message.ts @@ -8,6 +8,6 @@ * Representation of the 'ChatMessage' schema. */ export type ChatMessage = { - role: string; + role: 'user' | 'assistant' | 'system'; content: string; } & Record; diff --git a/packages/gen-ai-hub/src/orchestration/orchestration-client.test.ts b/packages/gen-ai-hub/src/orchestration/orchestration-client.test.ts index 7be29d2b..1b1131b3 100644 --- a/packages/gen-ai-hub/src/orchestration/orchestration-client.test.ts +++ b/packages/gen-ai-hub/src/orchestration/orchestration-client.test.ts @@ -1,25 +1,33 @@ -import fs from 'fs'; -import path from 'path'; import nock from 'nock'; import { HttpDestination } from '@sap-cloud-sdk/connectivity'; -import { BaseLlmParametersWithDeploymentId } from '../core/index.js'; import { mockGetAiCoreDestination } from '../../test-util/mock-context.js'; -import { mockInference } from '../../test-util/mock-http.js'; +import { mockInference, parseMockResponse } from '../../test-util/mock-http.js'; +import { BaseLlmParametersWithDeploymentId } from '../core/index.js'; import { GenAiHubClient, GenAiHubCompletionParameters } from './orchestration-client.js'; -import { CompletionPostResponse, ModuleConfigs } from './api/index.js'; +import { + CompletionPostResponse, + LLMModuleConfig, + ModuleConfigs +} from './api/index.js'; describe('GenAiHubClient', () => { let destination: HttpDestination; - let deploymentConfiguration: BaseLlmParametersWithDeploymentId; let client: GenAiHubClient; + const deploymentConfiguration: BaseLlmParametersWithDeploymentId = { + deploymentId: 'deployment-id' + }; + const llm_module_config: LLMModuleConfig = { + model_name: 'gpt-35-turbo-16k', + model_params: { + max_tokens: 50, + temperature: 0.1 + } + }; beforeAll(() => { - deploymentConfiguration = { - deploymentId: 'deployment-id' - }; destination = mockGetAiCoreDestination(); client = new GenAiHubClient(); }); @@ -28,32 +36,21 @@ describe('GenAiHubClient', () => { nock.cleanAll(); }); - it(' calls chatCompletion and parses response', async () => { + it('calls chatCompletion with minimum configuration and parses response', async () => { const module_configurations: ModuleConfigs = { templating_module_config: { template: [{ role: 'user', content: 'Hello!' }] }, - llm_module_config: { - model_name: 'gpt-35-turbo-16k', - model_params: { - max_tokens: 50, - temperature: 0.1 - } - } + llm_module_config }; const request: GenAiHubCompletionParameters = { deploymentConfiguration, orchestration_config: { module_configurations } }; - const mockResponse = fs.readFileSync( - path.join( - 'test-util', - 'mock-data', - 'orchestration', - 'genaihub-chat-completion-success-response.json' - ), - 'utf-8' + const mockResponse = parseMockResponse( + 'orchestration', + 'genaihub-chat-completion-success-response.json' ); mockInference( @@ -61,7 +58,7 @@ describe('GenAiHubClient', () => { data: { ...request, input_params: {} } }, { - data: JSON.parse(mockResponse), + data: mockResponse, status: 200 }, destination, @@ -69,51 +66,55 @@ describe('GenAiHubClient', () => { url: 'completion' } ); - const result = await client.chatCompletion(request); - const expectedResponse: CompletionPostResponse = JSON.parse(mockResponse); - expect(result).toEqual(expectedResponse); + expect(client.chatCompletion(request)).resolves.toEqual(mockResponse); }); - it('throws error for incorrect input parameters', async () => { + it('sends message history together with templating config', async () => { const module_configurations: ModuleConfigs = { templating_module_config: { - template: [{ role: 'actor', content: 'Hello' }] + template: [{ role: 'user', content: "What's my name?" }] }, - llm_module_config: { - model_name: 'gpt-35-turbo-16k', - model_params: { - max_tokens: 50, - temperature: 0.1 - } - } + llm_module_config }; const request: GenAiHubCompletionParameters = { deploymentConfiguration, - orchestration_config: { module_configurations } + orchestration_config: { module_configurations }, + messages_history: [ + { + role: 'system', + content: + 'You are a helpful assistant who remembers all details the user shares with you.' + }, + { + role: 'user', + content: 'Hi! Im Bob' + }, + { + role: 'assistant', + content: + "Hi Bob, nice to meet you! I'm an AI assistant. I'll remember that your name is Bob as we continue our conversation." + } + ] }; - const mockResponse = fs.readFileSync( - path.join( - 'test-util', - 'mock-data', - 'orchestration', - 'genaihub-error-response.json' - ), - 'utf-8' - ); + const mockResponse = parseMockResponse( + 'orchestration', + 'genaihub-chat-completion-message-history.json' + ); mockInference( { data: { ...request, input_params: {} } }, { - data: JSON.parse(mockResponse), - status: 400 + data: mockResponse, + status: 200 }, destination, { url: 'completion' } ); - await expect(client.chatCompletion(request)).rejects.toThrow(); + + expect(client.chatCompletion(request)).resolves.toEqual(mockResponse); }); }); diff --git a/packages/gen-ai-hub/src/orchestration/orchestration-client.ts b/packages/gen-ai-hub/src/orchestration/orchestration-client.ts index fc730c1d..d09b9090 100644 --- a/packages/gen-ai-hub/src/orchestration/orchestration-client.ts +++ b/packages/gen-ai-hub/src/orchestration/orchestration-client.ts @@ -12,7 +12,7 @@ import { * Input Parameters for GenAI hub chat completion. */ export type GenAiHubCompletionParameters = BaseLlmParameters & - Pick; + Pick; /** * Get the orchestration client. diff --git a/packages/gen-ai-hub/test-util/mock-data/orchestration/genaihub-chat-completion-message-history.json b/packages/gen-ai-hub/test-util/mock-data/orchestration/genaihub-chat-completion-message-history.json new file mode 100644 index 00000000..1e7d45fb --- /dev/null +++ b/packages/gen-ai-hub/test-util/mock-data/orchestration/genaihub-chat-completion-message-history.json @@ -0,0 +1,65 @@ +{ + "module_results": { + "llm": { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "Your name is Bob.", + "role": "assistant" + } + } + ], + "created": 1721229750, + "id": "chatcmpl-9m0m6iwV6nY8IsW6jY622P6xISg3q", + "model": "gpt-35-turbo-16k", + "object": "chat.completion", + "usage": { + "completion_tokens": 5, + "prompt_tokens": 71, + "total_tokens": 76 + } + }, + "templating": [ + { + "content": "You are a helpful assistant who remembers all details the user shares with you.", + "role": "system" + }, + { + "content": "Hi! Im Bob", + "role": "user" + }, + { + "content": "Hi Bob, nice to meet you! I'm an AI assistant. I'll remember that your name is Bob as we continue our conversation.", + "role": "assistant" + }, + { + "content": "What's my name?", + "role": "user" + } + ] + }, + "orchestration_result": { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "Your name is Bob.", + "role": "assistant" + } + } + ], + "created": 1721229750, + "id": "chatcmpl-9m0m6iwV6nY8IsW6jY622P6xISg3q", + "model": "gpt-35-turbo-16k", + "object": "chat.completion", + "usage": { + "completion_tokens": 5, + "prompt_tokens": 71, + "total_tokens": 76 + } + }, + "request_id": "841b6d08-733c-47d4-bf9f-0c948e25cf01" +} diff --git a/packages/gen-ai-hub/test-util/mock-data/orchestration/genaihub-error-response.json b/packages/gen-ai-hub/test-util/mock-data/orchestration/genaihub-error-response.json deleted file mode 100644 index 2e5416f1..00000000 --- a/packages/gen-ai-hub/test-util/mock-data/orchestration/genaihub-error-response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "request_id": "request-id", - "code": 400, - "message": "[] should be non-empty - 'messages'", - "location": "OpenAIClient", - "module_results": { - "templating": [ - { - "role": "actor", - "content": "Hello" - } - ] - } -} diff --git a/packages/gen-ai-hub/test-util/mock-http.ts b/packages/gen-ai-hub/test-util/mock-http.ts index 57cbc392..917ac6c2 100644 --- a/packages/gen-ai-hub/test-util/mock-http.ts +++ b/packages/gen-ai-hub/test-util/mock-http.ts @@ -1,3 +1,5 @@ +import fs from 'fs'; +import path from 'path'; import { HttpDestination } from '@sap-cloud-sdk/connectivity'; import nock from 'nock'; import { @@ -19,7 +21,6 @@ export function mockInference( response: { data: any; status?: number; - headers?: Record; }, destination: HttpDestination, endpoint: EndpointOptions = mockEndpoint @@ -40,3 +41,12 @@ export function mockInference( .query(apiVersion ? { 'api-version': apiVersion } : {}) .reply(response.status, response.data); } + +export function parseMockResponse(client: string, fileName: string): T { + const fileContent = fs.readFileSync( + path.join('test-util', 'mock-data', client, fileName), + 'utf-8' + ); + + return JSON.parse(fileContent); +} diff --git a/type-tests/orchestration.test-d.ts b/type-tests/orchestration.test-d.ts index f8015043..5de0d6fc 100644 --- a/type-tests/orchestration.test-d.ts +++ b/type-tests/orchestration.test-d.ts @@ -122,3 +122,22 @@ expectError( } }) ); + +expectError( + client.chatCompletion({ + deploymentConfiguration: { deploymentId: 'id' }, + orchestration_config: { + module_configurations: { + templating_module_config: { + template: [{ role: 'test', content: 'some content' }] + }, + llm_module_config: { + model_name: 'gpt-35-turbo-16k', + model_params: { + max_tokens: 50 + } + } + } + } + }) +);