UNPKG

@sap-ai-sdk/foundation-models

Version:

SAP Cloud SDK for AI is the official Software Development Kit (SDK) for **SAP AI Core**, **SAP Generative AI Hub**, and **Orchestration Service**.

162 lines 6.71 kB
import { createLogger } from '@sap-cloud-sdk/util'; import { SseStream } from '@sap-ai-sdk/core'; import { AzureOpenAiChatCompletionStreamChunkResponse } from './azure-openai-chat-completion-stream-chunk-response.js'; import { isMessageToolCall, mergeToolCallChunk } from './util/index.js'; const logger = createLogger({ package: 'foundation-models', messageContext: 'azure-openai-chat-completion-stream' }); /** * Chat completion stream containing post-processing functions. */ export class AzureOpenAiChatCompletionStream extends SseStream { iterator; /** * Create a chat completion stream based on the http response. * @param response - Http response. * @returns Chat completion stream. * @internal */ static _create(response, controller) { const stream = SseStream.transformToSseStream(response, controller); return new AzureOpenAiChatCompletionStream(stream.iterator, controller); } /** * Wrap raw chunk data with chunk response class to provide helper functions. * @param stream - Chat completion stream. * @internal */ static async *_processChunk(stream) { for await (const chunk of stream) { yield new AzureOpenAiChatCompletionStreamChunkResponse(chunk); } } /** * @internal */ static async *_processToolCalls(stream, response) { if (!response) { throw new Error('Response is required to process tool calls.'); } for await (const chunk of stream) { chunk._data.choices.forEach(choice => { const choiceIndex = choice.index; const toolCallsChunks = chunk.getDeltaToolCalls(choiceIndex); if (toolCallsChunks) { let toolCallAccumulators = response ._getToolCallsAccumulators() .get(choiceIndex); if (!toolCallAccumulators) { toolCallAccumulators = new Map(); response ._getToolCallsAccumulators() .set(choiceIndex, toolCallAccumulators); } toolCallsChunks.forEach(toolCallChunk => { const toolCallId = toolCallChunk.index; const toolCallAccumulator = mergeToolCallChunk(toolCallChunk, toolCallAccumulators.get(toolCallId)); toolCallAccumulators.set(toolCallId, toolCallAccumulator); }); } }); yield chunk; } for (const [choiceIndex, toolCallsAccumulators] of response._getToolCallsAccumulators()) { const toolCalls = []; for (const [id, acc] of toolCallsAccumulators.entries()) { if (isMessageToolCall(acc)) { toolCalls.push(acc); } else { logger.error(`Error while parsing tool calls for choice index ${choiceIndex}: Tool call with id ${id} was incomplete.`); } } response._setToolCalls(choiceIndex, toolCalls); } } /** * @internal */ static async *_processFinishReason(stream, response) { for await (const chunk of stream) { chunk._data.choices.forEach(choice => { const choiceIndex = choice.index; if (choiceIndex >= 0) { const finishReason = chunk.getFinishReason(choiceIndex); if (finishReason) { if (response) { response._getFinishReasons().set(choiceIndex, finishReason); } switch (finishReason) { case 'content_filter': logger.error(`Choice ${choiceIndex}: Stream finished with content filter hit.`); break; case 'length': logger.error(`Choice ${choiceIndex}: Stream finished with token length exceeded.`); break; case 'stop': case 'tool_calls': case 'function_call': logger.debug(`Choice ${choiceIndex}: Stream finished.`); break; default: logger.error(`Choice ${choiceIndex}: Stream finished with unknown reason '${finishReason}'.`); } } } }); yield chunk; } } /** * @internal */ static async *_processTokenUsage(stream, response) { for await (const chunk of stream) { const usage = chunk.getTokenUsage(); if (usage) { if (response) { response._setTokenUsage(usage); } logger.debug(`Token usage: ${JSON.stringify(usage)}`); } yield chunk; } } /** * Transform a stream of chunks into a stream of content strings. * @param stream - Chat completion stream. * @param choiceIndex - The index of the choice to parse. * @internal */ static async *_processContentStream(stream, choiceIndex = 0) { for await (const chunk of stream) { const deltaContent = chunk.getDeltaContent(choiceIndex); if (!deltaContent) { continue; } yield deltaContent; } } constructor(iterator, controller) { super(iterator, controller); this.iterator = iterator; } /** * Pipe the stream through a processing function. * @param processFn - The function to process the input stream. * @param response - The `AzureOpenAiChatCompletionStreamResponse` object for process function to store finish reason, token usage, etc. * @returns The output stream containing processed items. * @internal */ _pipe(processFn, response) { if (response) { return new AzureOpenAiChatCompletionStream(() => processFn(this, response), this.controller); } return new AzureOpenAiChatCompletionStream(() => processFn(this), this.controller); } toContentStream(choiceIndex) { return new AzureOpenAiChatCompletionStream(() => AzureOpenAiChatCompletionStream._processContentStream(this, choiceIndex), this.controller); } } //# sourceMappingURL=azure-openai-chat-completion-stream.js.map