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**.

323 lines 13 kB
import { createLogger } from '@sap-cloud-sdk/util'; const logger = createLogger({ package: 'orchestration', messageContext: 'orchestration-utils' }); /** * @internal */ export function constructCompletionPostRequestFromJsonModuleConfig(config, prompt, stream) { if (stream) { config = { ...config, stream: { ...(config.stream || {}), enabled: true } }; } else { delete config.stream; } return { messages_history: prompt?.messagesHistory || [], placeholder_values: prompt?.placeholderValues || {}, config }; } /** * @internal */ export function constructCompletionPostRequestFromConfigReference(configRef, request) { // Route request.messages into messages_history since there is no local // prompt.template to merge them into for config references. const messagesHistory = request?.messages?.length ? [...(request.messagesHistory || []), ...request.messages] : request?.messagesHistory; return { config_ref: configRef, ...(request?.placeholderValues && { placeholder_values: request.placeholderValues }), ...(messagesHistory && { messages_history: messagesHistory }) }; } /** * @internal */ export function addStreamOptionsToPromptTemplatingModuleConfig(promptTemplatingModuleConfig, streamOptions) { if (streamOptions?.promptTemplating === null) { return promptTemplatingModuleConfig; } return { ...promptTemplatingModuleConfig, model: { ...promptTemplatingModuleConfig.model, params: { ...(promptTemplatingModuleConfig.model.params || {}), ...(streamOptions?.promptTemplating !== null && { stream_options: { include_usage: true, ...(promptTemplatingModuleConfig.model.params?.stream_options || {}), ...(streamOptions?.promptTemplating || {}) } }) } } }; } /** * @internal */ export function addStreamOptionsToOutputFilteringConfig(outputFilteringConfig, filteringStreamOptions) { return { ...outputFilteringConfig, stream_options: { ...(outputFilteringConfig.stream_options || {}), ...filteringStreamOptions } }; } function warnAboutUnusedOverrides(streamOptions, configurationCount) { const overrideKeys = Object.keys(streamOptions?.overrides || []); if (!overrideKeys.length) { return; } const unusedOverrides = overrideKeys.filter(key => { const index = parseFloat(key); return !Number.isInteger(index) || index < 0 || index >= configurationCount; }); if (unusedOverrides.length) { logger.debug(`The following override keys do not correspond to any module configuration and will be ignored: ${unusedOverrides.join(', ')}.`); } } function warnAboutShortOverridesArray(streamOptions, configurationCount) { if (!Array.isArray(streamOptions?.overrides) || !streamOptions.overrides.length) { return; } const arrayLength = streamOptions.overrides.length; logger.debug(`Override array has ${arrayLength} element(s) but there are ${configurationCount} module configuration(s). ` + `Configs at indices ${arrayLength}-${configurationCount - 1} will use shared options. ` + 'If this is intentional, use object input to silence this warning: `{...streamOptionsArray}`'); } /** * Gets stream options for a specific module configuration. * Returns the override for the given index if it exists, otherwise returns shared options. * @param streamOptions - The stream options containing shared settings and optional overrides. * @param index - The index of the module configuration. * @returns Override options for this index if present, otherwise shared options, or undefined if streamOptions is not provided. */ function getStreamOptionsForItem(streamOptions, index) { if (!streamOptions) { return undefined; } const { global: _global, overrides, ...shared } = streamOptions; return (overrides && overrides[index]) || shared; } /** * @internal * Warns if output filtering stream options are provided but some configs lack output filtering. * Checks both shared options and per-config overrides. * @param moduleConfigs - Single or array of module configurations. * @param streamOptions - Stream options (shared settings and optional per-config overrides). */ function warnAboutMissingOutputFiltering(moduleConfigs, streamOptions) { const configs = Array.isArray(moduleConfigs) ? moduleConfigs : [moduleConfigs]; // Collect indices where output filtering options are set but config lacks output filtering const configsWithoutFilter = configs .map((cfg, idx) => { const opts = getStreamOptionsForItem(streamOptions, idx); // Only flag if stream options request output filtering but config doesn't have it if (opts?.outputFiltering && !cfg.filtering?.output) { return idx; } return undefined; }) .filter(idx => idx !== undefined); if (configsWithoutFilter.length === 0) { return; } // Three scenarios: // 1. All configs with output filtering options lack output filtering - warn that options are unused // 2. Some configs lack output filtering - warn about specific configs affected // 3. All configs have output filtering - no warning needed if (configsWithoutFilter.length === configs.length) { logger.warn('Output filter stream options are not applied because no module configuration has output filtering enabled.'); } else if (configsWithoutFilter.length > 0) { const configWord = configsWithoutFilter.length > 1 ? 'configurations' : 'configuration'; const positions = configsWithoutFilter.map(i => `#${i + 1}`).join(', '); logger.warn(`Output filter stream options will not be applied to ${configWord} ${positions} because output filtering is not configured for those modules.`); } } /** * @internal * Adds stream options to module configurations for streaming requests. */ function buildModules(configs, options) { return configs.map((config, index) => { const itemStreamOptions = getStreamOptionsForItem(options, index); return addStreamOptionsToSingleModuleConfig(config, itemStreamOptions); }); } /** * @internal * Adds stream options to module configurations and returns an orchestration config for streaming requests. * Validates overrides and warns about unused or invalid override indices. * @param moduleConfigs - Single or array of module configurations. * @param streamOptions - Stream options with optional per-config overrides. * @returns Orchestration config with stream enabled and processed modules. */ export function addStreamOptions(moduleConfigs, streamOptions) { const configs = Array.isArray(moduleConfigs) ? moduleConfigs : [moduleConfigs]; if (!Array.isArray(moduleConfigs) && streamOptions?.overrides) { throw new Error('Overrides in stream options are not supported when a single module configuration is provided.'); } warnAboutUnusedOverrides(streamOptions, configs.length); warnAboutShortOverridesArray(streamOptions, configs.length); warnAboutMissingOutputFiltering(moduleConfigs, streamOptions); const modules = buildModules(configs, streamOptions); return { stream: { enabled: true, ...(streamOptions?.global || {}) }, modules: Array.isArray(moduleConfigs) ? modules : modules[0] }; } function addStreamOptionsToSingleModuleConfig(moduleConfigs, streamOptions) { const { prompt_templating, filtering } = moduleConfigs; const outputFiltering = streamOptions?.outputFiltering; return { ...moduleConfigs, prompt_templating: addStreamOptionsToPromptTemplatingModuleConfig(prompt_templating, streamOptions), ...(outputFiltering && filtering?.output && { filtering: { ...filtering, output: addStreamOptionsToOutputFilteringConfig(filtering.output, outputFiltering) } }) }; } /** @internal */ export function constructCompletionPostRequest(config, request, stream, streamOptions) { // Preserve format: single config → ModuleConfigs, array → ModuleConfigs[] // The orchestration service expects the config structure to match the input: // - Single config (OrchestrationModuleConfig) → single ModuleConfigs object // - Config array (OrchestrationModuleConfigList) → array of ModuleConfigs for fallback behavior // When any config uses a TemplateRef, messages cannot be merged into prompt.template // (the template lives remotely). Route them to messages_history instead. const configs = Array.isArray(config) ? config : [config]; const routeMessagesToHistory = configs.some(c => isTemplateRef(c?.promptTemplating?.prompt || {})); const moduleRequest = routeMessagesToHistory && request ? { ...request, messages: undefined } : request; /** * Module configurations for the orchestration request. */ const moduleConfigurations = Array.isArray(config) ? config.map(c => buildCompletionModulesConfig(c, moduleRequest)) : buildCompletionModulesConfig(config, moduleRequest); /** * Orchestration configuration with or without streaming enabled. */ const configWithStream = stream ? Array.isArray(moduleConfigurations) ? addStreamOptions(moduleConfigurations, streamOptions) : addStreamOptions(moduleConfigurations, streamOptions) : { modules: moduleConfigurations }; // When routing messages to history, append request.messages after messagesHistory const messagesHistory = routeMessagesToHistory && request?.messages?.length ? [...(request.messagesHistory || []), ...request.messages] : request?.messagesHistory; return { config: configWithStream, ...(request?.placeholderValues && { placeholder_values: request.placeholderValues }), ...(messagesHistory && { messages_history: messagesHistory }) }; } function buildCompletionModulesConfig(config, request) { const { promptTemplating, filtering, masking, grounding, translation } = config; // prompt is not a string here as it is already parsed in `parseAndMergeTemplating` method const prompt = { ...promptTemplating.prompt }; // If promptTemplating.prompt is not defined, we initialize it with an empty Template object promptTemplating.prompt = promptTemplating.prompt || { template: [] }; if (isTemplate(prompt)) { if (!prompt.template?.length && !request?.messages?.length) { throw new Error('Either a prompt template or messages must be defined.'); } prompt.template = [ ...(prompt.template || []), ...(request?.messages || []) ]; } return { prompt_templating: { ...promptTemplating, prompt }, ...(filtering && Object.keys(filtering).length && { filtering }), ...(masking && Object.keys(masking).length && { masking }), ...(grounding && Object.keys(grounding).length && { grounding }), ...(translation && Object.keys(translation).length && { translation }) }; } function isTemplate(templating) { return (!!templating && typeof templating === 'object' && !('template_ref' in templating)); } function isTemplateRef(templating) { return (!!templating && typeof templating === 'object' && 'template_ref' in templating); } /** * Constructs an embedding post request from the given configuration and request. * @internal */ export function constructEmbeddingPostRequest(config, request) { const orchestrationConfig = { modules: buildEmbeddingModulesConfig(config) }; const embeddingRequest = { config: orchestrationConfig, input: { text: request.input, ...(request.type && { type: request.type }) } }; return embeddingRequest; } function buildEmbeddingModulesConfig(config) { const { embeddings, masking } = config; const { model } = embeddings; const { name, version, params } = model; const modules = { embeddings: { model: { name, ...(version && { version }), ...(params && { params }) } }, ...(masking && Object.keys(masking).length && { masking }) }; return modules; } //# sourceMappingURL=module-config.js.map