@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
JavaScript
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