UNPKG

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

304 lines 11.2 kB
import { AIMessage, AIMessageChunk } from '@langchain/core/messages'; import { v4 as uuidv4 } from 'uuid'; import { isInteropZodSchema } from '@langchain/core/utils/types'; import { toJsonSchema } from '@langchain/core/utils/json_schema'; /** * Determines if the model is a reasoning model. * @param modelName - The name of the model. * @returns True if the model is a reasoning model, false otherwise. * @internal */ export function isReasoningModel(modelName) { if (!modelName) { return false; } if (/^o\d/.test(modelName ?? '')) { return true; } if (modelName.startsWith('gpt-5') && !modelName.startsWith('gpt-5-chat')) { return true; } return false; } /** * Maps a {@link ChatAzureOpenAIToolType} to {@link AzureOpenAiFunctionObject}. * @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 OpenAI chat completion function. * @internal */ export function mapToolToOpenAiFunction(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 Azure OpenAI function, e.g., set previously when calling `bindTools()`. // Notice that LangChain ToolDeifnition 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 ChatAzureOpenAIToolType} to {@link AzureOpenAiChatCompletionTool}. * @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 OpenAI chat completion tool. * @internal */ export function mapToolToOpenAiTool(tool, strict) { return { type: 'function', function: mapToolToOpenAiFunction(tool, strict) }; } /** * Maps {@link AzureOpenAiChatCompletionMessageToolCalls} to LangChain's {@link ToolCall} array. * @param toolCalls - The {@link AzureOpenAiChatCompletionMessageToolCalls} response. * @returns The LangChain {@link ToolCall}. */ function mapAzureOpenAiToLangChainToolCall(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 AzureOpenAiCreateChatCompletionResponse} to LangChain's {@link ChatResult}. * @param completionResponse - The {@link AzureOpenAiCreateChatCompletionResponse} response. * @returns The LangChain {@link ChatResult} * @internal */ export function mapOutputToChatResult(completionResponse) { return { generations: completionResponse.choices.map(choice => ({ text: choice.message.content ?? '', message: new AIMessage({ content: choice.message.content ?? '', tool_calls: mapAzureOpenAiToLangChainToolCall(choice.message.tool_calls), usage_metadata: { input_tokens: choice.message.usage?.prompt_tokens ?? 0, output_tokens: choice.message.usage?.completion_tokens ?? 0, total_tokens: choice.message.usage?.total_tokens ?? 0 }, additional_kwargs: { function_call: choice.message.function_call, tool_calls: choice.message.tool_calls } }), generationInfo: { finish_reason: choice.finish_reason, index: choice.index, function_call: choice.message.function_call, tool_calls: choice.message.tool_calls } })), llmOutput: { created: completionResponse.created, id: completionResponse.id, model: completionResponse.model, object: completionResponse.object, promptFilterResults: completionResponse.prompt_filter_results, tokenUsage: { completionTokens: completionResponse.usage?.completion_tokens ?? 0, promptTokens: completionResponse.usage?.prompt_tokens ?? 0, totalTokens: completionResponse.usage?.total_tokens ?? 0 } } }; } /** * Maps LangChain's {@link ToolCall} to Azure OpenAI's {@link AzureOpenAiChatCompletionMessageToolCalls}. * @param toolCalls - The {@link ToolCall} to map. * @returns The Azure OpenAI {@link AzureOpenAiChatCompletionMessageToolCalls}. */ function mapLangChainToolCallToAzureOpenAiToolCall(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 Azure OpenAI's {@link AzureOpenAiChatCompletionRequestAssistantMessage}. * @param message - The {@link AIMessage} to map. * @returns The Azure OpenAI {@link AzureOpenAiChatCompletionRequestAssistantMessage}. */ function mapAiMessageToAzureOpenAiAssistantMessage(message) { const tool_calls = mapLangChainToolCallToAzureOpenAiToolCall(message.tool_calls) ?? message.additional_kwargs.tool_calls; return { name: message.name, ...(tool_calls?.length ? { tool_calls } : {}), function_call: message.additional_kwargs.function_call, content: message.content, role: 'assistant' }; } function mapHumanMessageToAzureOpenAiUserMessage(message) { return { role: 'user', content: message.content, name: message.name }; } function mapToolMessageToAzureOpenAiToolMessage(message) { return { role: 'tool', content: message.content, tool_call_id: message.tool_call_id }; } function mapFunctionMessageToAzureOpenAiFunctionMessage(message) { return { role: 'function', content: message.content, name: message.name || 'default' }; } function mapSystemMessageToAzureOpenAiSystemMessage(message) { return { role: 'system', content: message.content, name: message.name }; } /** * Maps {@link BaseMessage} to {@link AzureOpenAiChatCompletionRequestMessage}. * @param message - The message to map. * @returns The {@link AzureOpenAiChatCompletionRequestMessage}. */ // TODO: Add mapping of refusal property, once LangChain base class supports it natively. function mapBaseMessageToAzureOpenAiChatMessage(message) { switch (message.type) { case 'ai': return mapAiMessageToAzureOpenAiAssistantMessage(message); case 'human': return mapHumanMessageToAzureOpenAiUserMessage(message); case 'system': return mapSystemMessageToAzureOpenAiSystemMessage(message); case 'function': return mapFunctionMessageToAzureOpenAiFunctionMessage(message); case 'tool': return mapToolMessageToAzureOpenAiToolMessage(message); default: throw new Error(`Unsupported message type: ${message.type}`); } } /** * Maps LangChain's input interface to the AI SDK client's input interface * @param client The LangChain Azure OpenAI client * @param options The {@link AzureOpenAiChatCallOptions} * @param messages The messages to be send * @returns An AI SDK compatibile request * @internal */ export function mapLangChainToAiClient(client, messages, options) { const params = { messages: messages.map(mapBaseMessageToAzureOpenAiChatMessage), presence_penalty: client.presence_penalty, frequency_penalty: client.frequency_penalty, temperature: client.temperature, top_p: client.top_p, logit_bias: client.logit_bias, user: client.user, data_sources: options?.data_sources, n: options?.n, response_format: options?.response_format, seed: options?.seed, logprobs: options?.logprobs, top_logprobs: options?.top_logprobs, function_call: options?.function_call, stop: options?.stop ?? client.stop, functions: options?.functions?.map(f => mapToolToOpenAiFunction(f)), tools: options?.tools?.map(t => mapToolToOpenAiTool(t)), tool_choice: options?.tool_choice }; if (isReasoningModel(client.modelName)) { params.max_completion_tokens = client.max_tokens === -1 ? undefined : client.max_tokens; } else { params.max_tokens = client.max_tokens === -1 ? undefined : client.max_tokens; } return removeUndefinedProperties(params); } /** * Converts Azure OpenAI stream chunk to a LangChain message chunk. * @param chunk - The Azure OpenAI stream chunk. * @returns An {@link AIMessageChunk} * @internal */ export function mapAzureOpenAiChunkToLangChainMessageChunk(chunk) { const choice = chunk._data.choices[0]; const content = choice?.delta.content ?? ''; const toolCallChunks = choice?.delta.tool_calls; return new AIMessageChunk({ content, ...(toolCallChunks && { tool_call_chunks: mapAzureOpenAIToLangChainToolCallChunk(toolCallChunks) }) }); } /** * Maps {@link AzureOpenAiChatCompletionMessageToolCallChunk} to LangChain's {@link ToolCallChunk}. * @param toolCallChunks - The {@link AzureOpenAiChatCompletionMessageToolCallChunk} in a stream response chunk. * @returns An array of LangChain {@link ToolCallChunk}. */ function mapAzureOpenAIToLangChainToolCallChunk(toolCallChunks) { return toolCallChunks.map(chunk => ({ name: chunk.function?.name, args: chunk.function?.arguments, id: chunk.id, index: chunk.index, type: 'tool_call_chunk' })); } function removeUndefinedProperties(obj) { const result = { ...obj }; for (const key in result) { if (result[key] === undefined) { delete result[key]; } } return result; } /** * @internal */ export function isToolDefinitionLike(tool) { return (typeof tool === 'object' && tool !== null && 'type' in tool && 'function' in tool && tool.function !== null && 'name' in tool.function); } //# sourceMappingURL=util.js.map