diff --git a/.changeset/stale-pans-approve.md b/.changeset/stale-pans-approve.md new file mode 100644 index 00000000..2580d3a4 --- /dev/null +++ b/.changeset/stale-pans-approve.md @@ -0,0 +1,6 @@ +--- +'@sap-ai-sdk/foundation-models': patch +'@sap-ai-sdk/orchestration': patch +--- + +[Fixed Issue] Get choice via index by comparing the `index` property instead of using array index. diff --git a/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.test.ts b/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.test.ts index f423168e..3fd6efad 100644 --- a/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.test.ts +++ b/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.test.ts @@ -1,5 +1,3 @@ -import { createLogger } from '@sap-cloud-sdk/util'; -import { jest } from '@jest/globals'; import { parseMockResponse } from '../../../../test-util/mock-http.js'; import { AzureOpenAiChatCompletionResponse } from './azure-openai-chat-completion-response.js'; import type { HttpResponse } from '@sap-cloud-sdk/http-client'; @@ -51,14 +49,7 @@ describe('OpenAI chat completion response', () => { }); it('should return undefined when convenience function is called with incorrect index', () => { - const logger = createLogger({ - package: 'foundation-models', - messageContext: 'azure-openai-chat-completion-response' - }); - const errorSpy = jest.spyOn(logger, 'error'); expect(azureOpenAiChatResponse.getFinishReason(1)).toBeUndefined(); - expect(errorSpy).toHaveBeenCalledWith('Choice index 1 is out of bounds.'); expect(azureOpenAiChatResponse.getContent(1)).toBeUndefined(); - expect(errorSpy).toHaveBeenCalledTimes(2); }); }); diff --git a/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.ts b/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.ts index 3765b737..68fbed7c 100644 --- a/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.ts +++ b/packages/foundation-models/src/azure-openai/azure-openai-chat-completion-response.ts @@ -1,12 +1,6 @@ -import { createLogger } from '@sap-cloud-sdk/util'; import type { HttpResponse } from '@sap-cloud-sdk/http-client'; import type { AzureOpenAiCreateChatCompletionResponse } from './client/inference/schema/index.js'; -const logger = createLogger({ - package: 'foundation-models', - messageContext: 'azure-openai-chat-completion-response' -}); - /** * Azure OpenAI chat completion response. */ @@ -32,11 +26,8 @@ export class AzureOpenAiChatCompletionResponse { * @param choiceIndex - The index of the choice to parse. * @returns The finish reason. */ - getFinishReason( - choiceIndex = 0 - ): this['data']['choices'][0]['finish_reason'] { - this.logInvalidChoiceIndex(choiceIndex); - return this.data.choices[choiceIndex]?.finish_reason; + getFinishReason(choiceIndex = 0): string | undefined { + return this.data.choices.find(c => c.index === choiceIndex)?.finish_reason; } /** @@ -45,13 +36,7 @@ export class AzureOpenAiChatCompletionResponse { * @returns The message content. */ getContent(choiceIndex = 0): string | undefined | null { - this.logInvalidChoiceIndex(choiceIndex); - return this.data.choices[choiceIndex]?.message?.content; - } - - private logInvalidChoiceIndex(choiceIndex: number): void { - if (choiceIndex < 0 || choiceIndex >= this.data.choices.length) { - logger.error(`Choice index ${choiceIndex} is out of bounds.`); - } + return this.data.choices.find(c => c.index === choiceIndex)?.message + ?.content; } } diff --git a/packages/orchestration/package.json b/packages/orchestration/package.json index 0aef09f0..84c90d58 100644 --- a/packages/orchestration/package.json +++ b/packages/orchestration/package.json @@ -33,7 +33,6 @@ "dependencies": { "@sap-ai-sdk/core": "workspace:^", "@sap-ai-sdk/ai-api": "workspace:^", - "@sap-cloud-sdk/http-client": "^3.22.2", - "@sap-cloud-sdk/util": "^3.22.2" + "@sap-cloud-sdk/http-client": "^3.22.2" } } diff --git a/packages/orchestration/src/orchestration-response.test.ts b/packages/orchestration/src/orchestration-response.test.ts index d80032df..398a26b4 100644 --- a/packages/orchestration/src/orchestration-response.test.ts +++ b/packages/orchestration/src/orchestration-response.test.ts @@ -1,5 +1,3 @@ -import { createLogger } from '@sap-cloud-sdk/util'; -import { jest } from '@jest/globals'; import { parseMockResponse } from '../../../test-util/mock-http.js'; import { OrchestrationResponse } from './orchestration-response.js'; import type { HttpResponse } from '@sap-cloud-sdk/http-client'; @@ -48,15 +46,8 @@ describe('OrchestrationResponse', () => { }); it('should return undefined when convenience function is called with incorrect index', () => { - const logger = createLogger({ - package: 'orchestration', - messageContext: 'orchestration-response' - }); - const errorSpy = jest.spyOn(logger, 'error'); expect(orchestrationResponse.getFinishReason(1)).toBeUndefined(); - expect(errorSpy).toHaveBeenCalledWith('Choice index 1 is out of bounds.'); expect(orchestrationResponse.getContent(1)).toBeUndefined(); - expect(errorSpy).toHaveBeenCalledTimes(2); }); it('should throw if content that was filtered is accessed', () => { diff --git a/packages/orchestration/src/orchestration-response.ts b/packages/orchestration/src/orchestration-response.ts index 9197a377..13f5d281 100644 --- a/packages/orchestration/src/orchestration-response.ts +++ b/packages/orchestration/src/orchestration-response.ts @@ -1,15 +1,9 @@ -import { createLogger } from '@sap-cloud-sdk/util'; import type { HttpResponse } from '@sap-cloud-sdk/http-client'; import type { CompletionPostResponse, TokenUsage } from './client/api/schema/index.js'; -const logger = createLogger({ - package: 'orchestration', - messageContext: 'orchestration-response' -}); - /** * Representation of an orchestration response. */ @@ -35,8 +29,7 @@ export class OrchestrationResponse { * @returns The finish reason. */ getFinishReason(choiceIndex = 0): string | undefined { - this.logInvalidChoiceIndex(choiceIndex); - return this.getChoices()[choiceIndex]?.finish_reason; + return this.getChoices().find(c => c.index === choiceIndex)?.finish_reason; } /** @@ -46,25 +39,19 @@ export class OrchestrationResponse { * @returns The message content. */ getContent(choiceIndex = 0): string | undefined { - this.logInvalidChoiceIndex(choiceIndex); + const choice = this.getChoices().find(c => c.index === choiceIndex); if ( - this.getChoices()[choiceIndex]?.message?.content === '' && - this.getChoices()[choiceIndex]?.finish_reason === 'content_filter' + choice?.message?.content === '' && + choice?.finish_reason === 'content_filter' ) { throw new Error( 'Content generated by the LLM was filtered by the output filter. Please try again with a different prompt or filter configuration.' ); } - return this.getChoices()[choiceIndex]?.message?.content; + return choice?.message?.content; } private getChoices() { return this.data.orchestration_result.choices; } - - private logInvalidChoiceIndex(choiceIndex: number): void { - if (choiceIndex < 0 || choiceIndex >= this.getChoices().length) { - logger.error(`Choice index ${choiceIndex} is out of bounds.`); - } - } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9870433..ca2b218d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -166,9 +166,6 @@ importers: '@sap-cloud-sdk/http-client': specifier: ^3.22.2 version: 3.22.2 - '@sap-cloud-sdk/util': - specifier: ^3.22.2 - version: 3.22.2 sample-cap: dependencies: @@ -5382,7 +5379,7 @@ snapshots: eslint: 9.14.0 eslint-config-prettier: 9.1.0(eslint@9.14.0) eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint@9.14.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint@9.14.0))(eslint@9.14.0) eslint-plugin-jsdoc: 50.3.2(eslint@9.14.0) eslint-plugin-prettier: 5.2.1(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@9.14.0))(eslint@9.14.0)(prettier@3.3.3) eslint-plugin-regex: 1.10.0(eslint@9.14.0) @@ -6711,7 +6708,7 @@ snapshots: is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint@9.14.0))(eslint@9.14.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -6729,7 +6726,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint@9.14.0))(eslint@9.14.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8