UNPKG

dtamind-components

Version:

Apps integration for Dtamind. Contain Nodes and Credentials.

555 lines 20.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMessageAuthor = getMessageAuthor; exports.convertAuthorToRole = convertAuthorToRole; exports.convertMessageContentToParts = convertMessageContentToParts; exports.convertBaseMessagesToContent = convertBaseMessagesToContent; exports.mapGenerateContentResultToChatResult = mapGenerateContentResultToChatResult; exports.convertResponseContentToChatGenerationChunk = convertResponseContentToChatGenerationChunk; exports.convertToGenerativeAITools = convertToGenerativeAITools; const messages_1 = require("@langchain/core/messages"); const outputs_1 = require("@langchain/core/outputs"); const function_calling_1 = require("@langchain/core/utils/function_calling"); const base_1 = require("@langchain/core/language_models/base"); const uuid_1 = require("uuid"); const zod_to_genai_parameters_js_1 = require("./zod_to_genai_parameters.js"); function getMessageAuthor(message) { const type = message._getType(); if (messages_1.ChatMessage.isInstance(message)) { return message.role; } if (type === 'tool') { return type; } return message.name ?? type; } /** * Maps a message type to a Google Generative AI chat author. * @param message The message to map. * @param model The model to use for mapping. * @returns The message type mapped to a Google Generative AI chat author. */ function convertAuthorToRole(author) { switch (author) { /** * Note: Gemini currently is not supporting system messages * we will convert them to human messages and merge with following * */ case 'supervisor': case 'ai': case 'model': // getMessageAuthor returns message.name. code ex.: return message.name ?? type; return 'model'; case 'system': return 'system'; case 'human': return 'user'; case 'tool': case 'function': return 'function'; default: return 'user'; // return user as default instead of throwing error } } function messageContentMedia(content) { if ('mimeType' in content && 'data' in content) { return { inlineData: { mimeType: content.mimeType, data: content.data } }; } if ('mimeType' in content && 'fileUri' in content) { return { fileData: { mimeType: content.mimeType, fileUri: content.fileUri } }; } throw new Error('Invalid media content'); } function inferToolNameFromPreviousMessages(message, previousMessages) { return previousMessages .map((msg) => { if ((0, messages_1.isAIMessage)(msg)) { return msg.tool_calls ?? []; } return []; }) .flat() .find((toolCall) => { return toolCall.id === message.tool_call_id; })?.name; } function _getStandardContentBlockConverter(isMultimodalModel) { const standardContentBlockConverter = { providerName: 'Google Gemini', fromStandardTextBlock(block) { return { text: block.text }; }, fromStandardImageBlock(block) { if (!isMultimodalModel) { throw new Error('This model does not support images'); } if (block.source_type === 'url') { const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url }); if (data) { return { inlineData: { mimeType: data.mime_type, data: data.data } }; } else { return { fileData: { mimeType: block.mime_type ?? '', fileUri: block.url } }; } } if (block.source_type === 'base64') { return { inlineData: { mimeType: block.mime_type ?? '', data: block.data } }; } throw new Error(`Unsupported source type: ${block.source_type}`); }, fromStandardAudioBlock(block) { if (!isMultimodalModel) { throw new Error('This model does not support audio'); } if (block.source_type === 'url') { const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url }); if (data) { return { inlineData: { mimeType: data.mime_type, data: data.data } }; } else { return { fileData: { mimeType: block.mime_type ?? '', fileUri: block.url } }; } } if (block.source_type === 'base64') { return { inlineData: { mimeType: block.mime_type ?? '', data: block.data } }; } throw new Error(`Unsupported source type: ${block.source_type}`); }, fromStandardFileBlock(block) { if (!isMultimodalModel) { throw new Error('This model does not support files'); } if (block.source_type === 'text') { return { text: block.text }; } if (block.source_type === 'url') { const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url }); if (data) { return { inlineData: { mimeType: data.mime_type, data: data.data } }; } else { return { fileData: { mimeType: block.mime_type ?? '', fileUri: block.url } }; } } if (block.source_type === 'base64') { return { inlineData: { mimeType: block.mime_type ?? '', data: block.data } }; } throw new Error(`Unsupported source type: ${block.source_type}`); } }; return standardContentBlockConverter; } function _convertLangChainContentToPart(content, isMultimodalModel) { if ((0, messages_1.isDataContentBlock)(content)) { return (0, messages_1.convertToProviderContentBlock)(content, _getStandardContentBlockConverter(isMultimodalModel)); } if (content.type === 'text') { return { text: content.text }; } else if (content.type === 'executableCode') { return { executableCode: content.executableCode }; } else if (content.type === 'codeExecutionResult') { return { codeExecutionResult: content.codeExecutionResult }; } else if (content.type === 'image_url') { if (!isMultimodalModel) { throw new Error(`This model does not support images`); } let source; if (typeof content.image_url === 'string') { source = content.image_url; } else if (typeof content.image_url === 'object' && 'url' in content.image_url) { source = content.image_url.url; } else { throw new Error('Please provide image as base64 encoded data URL'); } const [dm, data] = source.split(','); if (!dm.startsWith('data:')) { throw new Error('Please provide image as base64 encoded data URL'); } const [mimeType, encoding] = dm.replace(/^data:/, '').split(';'); if (encoding !== 'base64') { throw new Error('Please provide image as base64 encoded data URL'); } return { inlineData: { data, mimeType } }; } else if (content.type === 'media') { return messageContentMedia(content); } else if (content.type === 'tool_use') { return { functionCall: { name: content.name, args: content.input } }; } else if (content.type?.includes('/') && // Ensure it's a single slash. content.type.split('/').length === 2 && 'data' in content && typeof content.data === 'string') { return { inlineData: { mimeType: content.type, data: content.data } }; } else if ('functionCall' in content) { // No action needed here — function calls will be added later from message.tool_calls return undefined; } else { if ('type' in content) { throw new Error(`Unknown content type ${content.type}`); } else { throw new Error(`Unknown content ${JSON.stringify(content)}`); } } } function convertMessageContentToParts(message, isMultimodalModel, previousMessages) { if ((0, messages_1.isToolMessage)(message)) { const messageName = message.name ?? inferToolNameFromPreviousMessages(message, previousMessages); if (messageName === undefined) { throw new Error(`Google requires a tool name for each tool call response, and we could not infer a called tool name for ToolMessage "${message.id}" from your passed messages. Please populate a "name" field on that ToolMessage explicitly.`); } const result = Array.isArray(message.content) ? message.content.map((c) => _convertLangChainContentToPart(c, isMultimodalModel)).filter((p) => p !== undefined) : message.content; if (message.status === 'error') { return [ { functionResponse: { name: messageName, // The API expects an object with an `error` field if the function call fails. // `error` must be a valid object (not a string or array), so we wrap `message.content` here response: { error: { details: result } } } } ]; } return [ { functionResponse: { name: messageName, // again, can't have a string or array value for `response`, so we wrap it as an object here response: { result } } } ]; } let functionCalls = []; const messageParts = []; if (typeof message.content === 'string' && message.content) { messageParts.push({ text: message.content }); } if (Array.isArray(message.content)) { messageParts.push(...message.content.map((c) => _convertLangChainContentToPart(c, isMultimodalModel)).filter((p) => p !== undefined)); } if ((0, messages_1.isAIMessage)(message) && message.tool_calls?.length) { functionCalls = message.tool_calls.map((tc) => { return { functionCall: { name: tc.name, args: tc.args } }; }); } return [...messageParts, ...functionCalls]; } function convertBaseMessagesToContent(messages, isMultimodalModel, convertSystemMessageToHumanContent = false) { return messages.reduce((acc, message, index) => { if (!(0, messages_1.isBaseMessage)(message)) { throw new Error('Unsupported message input'); } const author = getMessageAuthor(message); if (author === 'system' && index !== 0) { throw new Error('System message should be the first one'); } const role = convertAuthorToRole(author); const prevContent = acc.content[acc.content.length]; if (!acc.mergeWithPreviousContent && prevContent && prevContent.role === role) { throw new Error('Google Generative AI requires alternate messages between authors'); } const parts = convertMessageContentToParts(message, isMultimodalModel, messages.slice(0, index)); if (acc.mergeWithPreviousContent) { const prevContent = acc.content[acc.content.length - 1]; if (!prevContent) { throw new Error('There was a problem parsing your system message. Please try a prompt without one.'); } prevContent.parts.push(...parts); return { mergeWithPreviousContent: false, content: acc.content }; } let actualRole = role; if (actualRole === 'function' || (actualRole === 'system' && !convertSystemMessageToHumanContent)) { // GenerativeAI API will throw an error if the role is not "user" or "model." actualRole = 'user'; } const content = { role: actualRole, parts }; return { mergeWithPreviousContent: author === 'system' && !convertSystemMessageToHumanContent, content: [...acc.content, content] }; }, { content: [], mergeWithPreviousContent: false }).content; } function mapGenerateContentResultToChatResult(response, extra) { // if rejected or error, return empty generations with reason in filters if (!response.candidates || response.candidates.length === 0 || !response.candidates[0]) { return { generations: [], llmOutput: { filters: response.promptFeedback } }; } const functionCalls = response.functionCalls(); const [candidate] = response.candidates; const { content: candidateContent, ...generationInfo } = candidate; let content; if (Array.isArray(candidateContent?.parts) && candidateContent.parts.length === 1 && candidateContent.parts[0].text) { content = candidateContent.parts[0].text; } else if (Array.isArray(candidateContent?.parts) && candidateContent.parts.length > 0) { content = candidateContent.parts.map((p) => { if ('text' in p) { return { type: 'text', text: p.text }; } else if ('executableCode' in p) { return { type: 'executableCode', executableCode: p.executableCode }; } else if ('codeExecutionResult' in p) { return { type: 'codeExecutionResult', codeExecutionResult: p.codeExecutionResult }; } return p; }); } else { // no content returned - likely due to abnormal stop reason, e.g. malformed function call content = []; } let text = ''; if (typeof content === 'string') { text = content; } else if (Array.isArray(content) && content.length > 0) { const block = content.find((b) => 'text' in b); text = block?.text ?? text; } const generation = { text, message: new messages_1.AIMessage({ content: content ?? '', tool_calls: functionCalls?.map((fc) => { return { ...fc, type: 'tool_call', id: 'id' in fc && typeof fc.id === 'string' ? fc.id : (0, uuid_1.v4)() }; }), additional_kwargs: { ...generationInfo }, usage_metadata: extra?.usageMetadata }), generationInfo }; return { generations: [generation], llmOutput: { tokenUsage: { promptTokens: extra?.usageMetadata?.input_tokens, completionTokens: extra?.usageMetadata?.output_tokens, totalTokens: extra?.usageMetadata?.total_tokens } } }; } function convertResponseContentToChatGenerationChunk(response, extra) { if (!response.candidates || response.candidates.length === 0) { return null; } const functionCalls = response.functionCalls(); const [candidate] = response.candidates; const { content: candidateContent, ...generationInfo } = candidate; let content; // Checks if some parts do not have text. If false, it means that the content is a string. if (Array.isArray(candidateContent?.parts) && candidateContent.parts.every((p) => 'text' in p)) { content = candidateContent.parts.map((p) => p.text).join(''); } else if (Array.isArray(candidateContent?.parts)) { content = candidateContent.parts.map((p) => { if ('text' in p) { return { type: 'text', text: p.text }; } else if ('executableCode' in p) { return { type: 'executableCode', executableCode: p.executableCode }; } else if ('codeExecutionResult' in p) { return { type: 'codeExecutionResult', codeExecutionResult: p.codeExecutionResult }; } return p; }); } else { // no content returned - likely due to abnormal stop reason, e.g. malformed function call content = []; } let text = ''; if (content && typeof content === 'string') { text = content; } else if (Array.isArray(content)) { const block = content.find((b) => 'text' in b); text = block?.text ?? ''; } const toolCallChunks = []; if (functionCalls) { toolCallChunks.push(...functionCalls.map((fc) => ({ ...fc, args: JSON.stringify(fc.args), index: extra.index, type: 'tool_call_chunk', id: 'id' in fc && typeof fc.id === 'string' ? fc.id : (0, uuid_1.v4)() }))); } return new outputs_1.ChatGenerationChunk({ text, message: new messages_1.AIMessageChunk({ content: content || '', name: !candidateContent ? undefined : candidateContent.role, tool_call_chunks: toolCallChunks, // Each chunk can have unique "generationInfo", and merging strategy is unclear, // so leave blank for now. additional_kwargs: {}, usage_metadata: extra.usageMetadata }), generationInfo }); } function convertToGenerativeAITools(tools) { if (tools.every((tool) => 'functionDeclarations' in tool && Array.isArray(tool.functionDeclarations))) { return tools; } return [ { functionDeclarations: tools.map((tool) => { if ((0, function_calling_1.isLangChainTool)(tool)) { const jsonSchema = (0, zod_to_genai_parameters_js_1.schemaToGenerativeAIParameters)(tool.schema); if (jsonSchema.type === 'object' && 'properties' in jsonSchema && Object.keys(jsonSchema.properties).length === 0) { return { name: tool.name, description: tool.description }; } return { name: tool.name, description: tool.description, parameters: jsonSchema }; } if ((0, base_1.isOpenAITool)(tool)) { return { name: tool.function.name, description: tool.function.description ?? `A function available to call.`, parameters: (0, zod_to_genai_parameters_js_1.jsonSchemaToGeminiParameters)(tool.function.parameters) }; } return tool; }) } ]; } //# sourceMappingURL=common.js.map