@sap-ai-sdk/langchain
Version:
SAP Cloud SDK for AI is the official Software Development Kit (SDK) for **SAP AI Core**, **SAP Generative AI Hub**, and **Orchestration Service**.
269 lines • 9.75 kB
JavaScript
import { v4 as uuidv4 } from 'uuid';
import { isInteropZodSchema } from '@langchain/core/utils/types';
import { toJsonSchema } from '@langchain/core/utils/json_schema';
import { AIMessage, AIMessageChunk } from '@langchain/core/messages';
/**
* Maps a {@link ChatOrchestrationToolType} to {@link FunctionObject}.
* @param tool - Base class for tools that accept input of any shape defined by a Zod schema.
* @param strict - Whether to enforce strict mode for the function call.
* @returns The Orchestration chat completion function.
* @internal
*/
export function mapToolToOrchestrationFunction(tool, strict) {
if (isToolDefinitionLike(tool)) {
return {
name: tool.function.name,
description: tool.function.description,
parameters: tool.function.parameters ?? {
type: 'object',
properties: {}
},
... // If strict defined in kwargs
((strict !== undefined && { strict }) ||
// If strict defined in Orchestration tool function, e.g., set previously when calling `bindTools()`.
// Notice that LangChain ToolDefinition does not have strict property.
('strict' in tool.function &&
tool.function.strict !== undefined && {
strict: tool.function.strict
}))
};
}
// StructuredTool like object
return {
name: tool.name,
description: tool.description,
parameters: isInteropZodSchema(tool.schema)
? toJsonSchema(tool.schema)
: tool.schema,
...(strict !== undefined && { strict })
};
}
/**
* Maps a LangChain {@link ChatOrchestrationToolType} to {@link ChatCompletionTool}.
* @param tool - Base class for tools that accept input of any shape defined by a Zod schema.
* @param strict - Whether to enforce strict mode for the function call.
* @returns The Orchestration chat completion tool.
* @internal
*/
export function mapToolToChatCompletionTool(tool, strict) {
return {
type: 'function',
function: mapToolToOrchestrationFunction(tool, strict)
};
}
/**
* Checks if the object is a {@link TemplateRef}.
* @param object - The object to check.
* @returns True if the object is a {@link TemplateRef}.
* @internal
*/
export function isTemplateRef(object) {
return object && typeof object === 'object' && 'template_ref' in object;
}
/**
* Maps {@link BaseMessage} to {@link ChatMessage}.
* @param message - The message to map.
* @returns The {@link ChatMessage}.
*/
// TODO: Add mapping of refusal property, once LangChain base class supports it natively.
function mapBaseMessageToChatMessage(message) {
switch (message.type) {
case 'ai':
return mapAiMessageToOrchestrationAssistantMessage(message);
case 'human':
return mapHumanMessageToChatMessage(message);
case 'system':
return mapSystemMessageToOrchestrationSystemMessage(message);
case 'tool':
return mapToolMessageToOrchestrationToolMessage(message);
default:
throw new Error(`Unsupported message type: ${message.type}`);
}
}
/**
* Maps LangChain's {@link ToolCall} to Orchestration's {@link MessageToolCalls}.
* @param toolCalls - The {@link ToolCall} to map.
* @returns The Orchestration {@link MessageToolCalls}.
*/
function mapLangChainToolCallToOrchestrationToolCall(toolCalls) {
if (toolCalls) {
return toolCalls.map(toolCall => ({
id: toolCall.id || uuidv4(),
type: 'function',
function: {
name: toolCall.name,
arguments: JSON.stringify(toolCall.args)
}
}));
}
}
/**
* Maps LangChain's {@link AIMessage} to Orchestration's {@link AssistantChatMessage}.
* @param message - The {@link AIMessage} to map.
* @returns The Orchestration {@link AssistantChatMessage}.
*/
function mapAiMessageToOrchestrationAssistantMessage(message) {
const tool_calls = mapLangChainToolCallToOrchestrationToolCall(message.tool_calls) ??
message.additional_kwargs.tool_calls;
return {
...(tool_calls?.length ? { tool_calls } : {}),
content: message.content,
role: 'assistant'
};
}
function mapHumanMessageToChatMessage(message) {
if (Array.isArray(message.content)) {
message.content = message.content.map(content => ({
...content,
...(content.type === 'image_url' && typeof content.image_url === 'string'
? {
image_url: {
url: content.image_url
}
}
: {})
}));
}
return {
role: 'user',
content: message.content
};
}
function mapSystemMessageToOrchestrationSystemMessage(message) {
if (typeof message.content !== 'string' &&
message.content.some(content => content.type !== 'text')) {
throw new Error('The content type of system message can only be "text" in the Orchestration Client.');
}
return {
role: 'system',
content: message.content
};
}
function mapToolMessageToOrchestrationToolMessage(message) {
if (typeof message.content !== 'string' &&
message.content.some(content => content.type !== 'text')) {
throw new Error('The content type of tool message can only be "text" in the Orchestration Client.');
}
return {
role: 'tool',
content: message.content,
tool_call_id: message.tool_call_id
};
}
/**
* Maps LangChain messages to orchestration messages.
* @param messages - The LangChain messages to map.
* @returns The orchestration messages mapped from LangChain messages.
* @internal
*/
export function mapLangChainMessagesToOrchestrationMessages(messages) {
return messages.map(mapBaseMessageToChatMessage);
}
/**
* Maps {@link MessageToolCalls} to LangChain's {@link ToolCall}.
* @param toolCalls - The {@link MessageToolCalls} response.
* @returns The LangChain {@link ToolCall}.
*/
function mapOrchestrationToLangChainToolCall(toolCalls) {
if (toolCalls) {
return toolCalls.map(toolCall => ({
id: toolCall.id,
name: toolCall.function.name,
args: JSON.parse(toolCall.function.arguments),
type: 'tool_call'
}));
}
}
/**
* Maps {@link OrchestrationToolCallChunk} to LangChain's {@link ToolCallChunk}.
* @param toolCallChunks - The {@link OrchestrationToolCallChunk} in a stream response chunk.
* @returns An array of LangChain {@link ToolCallChunk}.
*/
function mapOrchestrationToLangChainToolCallChunk(toolCallChunks) {
return toolCallChunks.map(chunk => ({
name: chunk.function?.name,
args: chunk.function?.arguments,
id: chunk.id,
index: chunk.index,
type: 'tool_call_chunk'
}));
}
/**
* Maps the completion response to a {@link ChatResult}.
* @param completionResponse - The completion response to map.
* @returns The mapped {@link ChatResult}.
* @internal
*/
export function mapOutputToChatResult(completionResponse) {
const { final_result, intermediate_results, request_id } = completionResponse;
const { choices, created, id, model, object, usage, system_fingerprint } = final_result;
return {
generations: choices.map(choice => ({
text: choice.message.content ?? '',
message: new AIMessage({
content: choice.message.content ?? '',
tool_calls: mapOrchestrationToLangChainToolCall(choice.message.tool_calls),
usage_metadata: {
input_tokens: usage?.prompt_tokens ?? 0,
output_tokens: usage?.completion_tokens ?? 0,
total_tokens: usage?.total_tokens ?? 0
}
}),
additional_kwargs: {
tool_calls: choice.message.tool_calls,
intermediate_results
},
generationInfo: {
finish_reason: choice.finish_reason,
index: choice.index,
tool_calls: choice.message.tool_calls,
request_id
}
})),
llmOutput: {
created,
id,
model,
object,
system_fingerprint,
tokenUsage: {
completionTokens: usage?.completion_tokens ?? 0,
promptTokens: usage?.prompt_tokens ?? 0,
totalTokens: usage?.total_tokens ?? 0
}
}
};
}
/**
* @internal
*/
export function isToolDefinitionLike(tool) {
return (typeof tool === 'object' &&
tool !== null &&
'type' in tool &&
'function' in tool &&
tool.function !== null &&
'name' in tool.function);
}
/**
* Converts orchestration stream chunk to a LangChain message chunk.
* @param chunk - The orchestration stream chunk.
* @returns An {@link AIMessageChunk}
* @internal
*/
export function mapOrchestrationChunkToLangChainMessageChunk(chunk) {
const choice = chunk._data.final_result?.choices[0];
const content = chunk.getDeltaContent() ?? '';
const toolCallChunks = choice?.delta.tool_calls;
return new AIMessageChunk({
content,
additional_kwargs: {
// TODO: Fix duplicated intermediate results when using concat() method for streaming chunks.
intermediate_results: chunk._data.intermediate_results
},
...(toolCallChunks && {
tool_call_chunks: mapOrchestrationToLangChainToolCallChunk(toolCallChunks)
})
});
}
//# sourceMappingURL=util.js.map