UNPKG

@sap-ai-sdk/orchestration

Version:

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

155 lines 6.1 kB
import { createLogger } from '@sap-cloud-sdk/util'; import { SseStream } from '@sap-ai-sdk/core'; import { OrchestrationStreamChunkResponse } from './orchestration-stream-chunk-response.js'; import { mergeToolCallChunk } from './internal.js'; const logger = createLogger({ package: 'orchestration', messageContext: 'orchestration-chat-completion-stream' }); /** * Orchestration stream containing post-processing functions. */ export class OrchestrationStream extends SseStream { iterator; /** * Create an orchestration stream based on the http response. * @param response - Http response. * @returns An orchestration stream. * @internal */ static _create(response, controller) { const stream = SseStream.transformToSseStream(response, controller); return new OrchestrationStream(stream.iterator, controller); } /** * Wrap raw chunk data with chunk response class to provide helper functions. * @param stream - Orchestration stream. * @internal */ static async *_processChunk(stream) { for await (const chunk of stream) { yield new OrchestrationStreamChunkResponse(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.orchestration_result?.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.map(toolCallChunk => { const toolCallId = toolCallChunk.index; const toolCallAccumulator = mergeToolCallChunk(toolCallChunk, toolCallAccumulators.get(toolCallId)); toolCallAccumulators.set(toolCallId, toolCallAccumulator); }); } }); yield chunk; } } /** * @internal */ static async *_processFinishReason(stream, response) { if (!response) { throw new Error('Response is required to process finish reasons.'); } for await (const chunk of stream) { chunk.data.orchestration_result?.choices.forEach(choice => { const choiceIndex = choice.index; const finishReason = chunk.getFinishReason(choiceIndex); if (finishReason) { 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) { if (!response) { throw new Error('Response is required to process token usage.'); } for await (const chunk of stream) { const usage = chunk.getTokenUsage(); if (usage) { 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 - Orchestration stream. * @param choiceIndex - The index of the choice to parse. * @internal */ static async *_processContentStream(stream) { for await (const chunk of stream) { const deltaContent = chunk.getDeltaContent(); 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 `OrchestrationStreamResponse` 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 OrchestrationStream(() => processFn(this, response), this.controller); } return new OrchestrationStream(() => processFn(this), this.controller); } /** * Transform the stream of chunks into a stream of content strings. * @param this - Orchestration stream. * @returns A stream of content strings. */ toContentStream() { return new OrchestrationStream(() => OrchestrationStream._processContentStream(this), this.controller); } } //# sourceMappingURL=orchestration-stream.js.map