dtamind-components
Version:
Apps integration for Dtamind. Contain Nodes and Credentials.
384 lines • 16.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkMessageHistory = exports.RunnableCallable = exports.ExtractTool = exports.restructureMessages = exports.convertStructuredSchemaToZod = exports.customGet = exports.getVM = exports.processImageMessage = exports.transformObjectPropertyToFunction = exports.checkCondition = void 0;
exports.filterConversationHistory = filterConversationHistory;
const lodash_1 = require("lodash");
const zod_1 = require("zod");
const nodevm_1 = require("@dtamindai/nodevm");
const tools_1 = require("@langchain/core/tools");
const mistralai_1 = require("@langchain/mistralai");
const anthropic_1 = require("@langchain/anthropic");
const runnables_1 = require("@langchain/core/runnables");
const messages_1 = require("@langchain/core/messages");
const multiModalUtils_1 = require("../../src/multiModalUtils");
const utils_1 = require("../../src/utils");
const prompts_1 = require("@langchain/core/prompts");
const checkCondition = (input, condition, value = '') => {
if (!input && condition === 'Is Empty')
return true;
else if (!input)
return false;
// Function to check if a string is a valid number
const isNumericString = (str) => /^-?\d*\.?\d+$/.test(str);
// Function to convert input to number if possible
const toNumber = (val) => {
if (typeof val === 'number')
return val;
return isNumericString(val) ? parseFloat(val) : NaN;
};
// Convert input and value to numbers
const numInput = toNumber(input);
const numValue = toNumber(value);
// Helper function for numeric comparisons
const numericCompare = (comp) => {
if (isNaN(numInput) || isNaN(numValue))
return false;
return comp(numInput, numValue);
};
// Helper function for string operations
const stringCompare = (strInput, strValue, op) => {
return op(String(strInput), String(strValue));
};
switch (condition) {
// String conditions
case 'Contains':
return stringCompare(input, value, (a, b) => a.includes(b));
case 'Not Contains':
return stringCompare(input, value, (a, b) => !a.includes(b));
case 'Start With':
return stringCompare(input, value, (a, b) => a.startsWith(b));
case 'End With':
return stringCompare(input, value, (a, b) => a.endsWith(b));
case 'Is':
return String(input) === String(value);
case 'Is Not':
return String(input) !== String(value);
case 'Is Empty':
return String(input).trim().length === 0;
case 'Is Not Empty':
return String(input).trim().length > 0;
// Numeric conditions
case 'Greater Than':
return numericCompare((a, b) => a > b);
case 'Less Than':
return numericCompare((a, b) => a < b);
case 'Equal To':
return numericCompare((a, b) => a === b);
case 'Not Equal To':
return numericCompare((a, b) => a !== b);
case 'Greater Than or Equal To':
return numericCompare((a, b) => a >= b);
case 'Less Than or Equal To':
return numericCompare((a, b) => a <= b);
default:
return false;
}
};
exports.checkCondition = checkCondition;
const transformObjectPropertyToFunction = (obj, state) => {
const transformedObject = {};
for (const key in obj) {
let value = obj[key];
// get message from agent
try {
const parsedValue = JSON.parse(value);
if (typeof parsedValue === 'object' && parsedValue.id) {
const messageOutputs = (state.messages ?? []).filter((message) => message.additional_kwargs && message.additional_kwargs?.nodeId === parsedValue.id);
const messageOutput = messageOutputs[messageOutputs.length - 1];
if (messageOutput) {
// if messageOutput.content is a string, set value to the content
if (typeof messageOutput.content === 'string')
value = messageOutput.content;
// if messageOutput.content is an array
else if (Array.isArray(messageOutput.content)) {
if (messageOutput.content.length === 0) {
throw new Error(`Message output content is an empty array for node ${parsedValue.id}`);
}
// Get the first element of the array
const messageOutputContentFirstElement = messageOutput.content[0];
if (typeof messageOutputContentFirstElement === 'string')
value = messageOutputContentFirstElement;
// If messageOutputContentFirstElement is an object and has a text property, set value to the text property
else if (typeof messageOutputContentFirstElement === 'object' && messageOutputContentFirstElement.text)
value = messageOutputContentFirstElement.text;
// Otherwise, stringify the messageOutputContentFirstElement
else
value = JSON.stringify(messageOutputContentFirstElement);
}
}
}
}
catch (e) {
// do nothing
}
// get state value
if (value.startsWith('$flow.state')) {
value = (0, exports.customGet)(state, value.replace('$flow.state.', ''));
if (typeof value === 'object')
value = JSON.stringify(value);
}
transformedObject[key] = () => value;
}
return transformedObject;
};
exports.transformObjectPropertyToFunction = transformObjectPropertyToFunction;
const processImageMessage = async (llm, nodeData, options) => {
let multiModalMessageContent = [];
if ((0, multiModalUtils_1.llmSupportsVision)(llm)) {
const visionChatModel = llm;
multiModalMessageContent = await (0, multiModalUtils_1.addImagesToMessages)(nodeData, options, llm.multiModalOption);
if (multiModalMessageContent?.length) {
visionChatModel.setVisionModel();
}
else {
visionChatModel.revertToOriginalModel();
}
}
return multiModalMessageContent;
};
exports.processImageMessage = processImageMessage;
const getVM = async (appDataSource, databaseEntities, nodeData, options, flow) => {
const variables = await (0, utils_1.getVars)(appDataSource, databaseEntities, nodeData, options);
let sandbox = {
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
};
sandbox['$vars'] = (0, utils_1.prepareSandboxVars)(variables);
sandbox['$flow'] = flow;
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? utils_1.defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: utils_1.defaultAllowBuiltInDep;
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : [];
const deps = utils_1.availableDependencies.concat(externalDeps);
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
};
return new nodevm_1.NodeVM(nodeVMOptions);
};
exports.getVM = getVM;
const customGet = (obj, path) => {
if (path.includes('[-1]')) {
const parts = path.split('.');
let result = obj;
for (let part of parts) {
if (part.includes('[') && part.includes(']')) {
const [name, indexPart] = part.split('[');
const index = parseInt(indexPart.replace(']', ''));
result = result[name];
if (Array.isArray(result)) {
if (index < 0) {
result = result[result.length + index];
}
else {
result = result[index];
}
}
else {
return undefined;
}
}
else {
result = (0, lodash_1.get)(result, part);
}
if (result === undefined) {
return undefined;
}
}
return result;
}
else {
return (0, lodash_1.get)(obj, path);
}
};
exports.customGet = customGet;
const convertStructuredSchemaToZod = (schema) => {
try {
const parsedSchema = typeof schema === 'string' ? JSON.parse(schema) : schema;
const zodObj = {};
for (const sch of parsedSchema) {
if (sch.type === 'String') {
zodObj[sch.key] = zod_1.z.string().describe(sch.description);
}
else if (sch.type === 'String Array') {
zodObj[sch.key] = zod_1.z.array(zod_1.z.string()).describe(sch.description);
}
else if (sch.type === 'Number') {
zodObj[sch.key] = zod_1.z.number().describe(sch.description);
}
else if (sch.type === 'Boolean') {
zodObj[sch.key] = zod_1.z.boolean().describe(sch.description);
}
else if (sch.type === 'Enum') {
zodObj[sch.key] = zod_1.z.enum(sch.enumValues.split(',').map((item) => item.trim())).describe(sch.description);
}
}
return zodObj;
}
catch (e) {
throw new Error(e);
}
};
exports.convertStructuredSchemaToZod = convertStructuredSchemaToZod;
/**
* Filter the conversation history based on the selected option.
*
* @param historySelection - The selected history option.
* @param input - The user input.
* @param state - The current state of the sequential llm or agent node.
*/
function filterConversationHistory(historySelection, input, state) {
switch (historySelection) {
case 'user_question':
return [new messages_1.HumanMessage(input)];
case 'last_message':
// @ts-ignore
return state.messages?.length ? [state.messages[state.messages.length - 1]] : [];
case 'empty':
return [];
case 'all_messages':
// @ts-ignore
return state.messages ?? [];
default:
throw new Error(`Unhandled conversationHistorySelection: ${historySelection}`);
}
}
const restructureMessages = (llm, state) => {
const messages = [];
for (const message of state.messages) {
// Sometimes Anthropic can return a message with content types of array, ignore that EXECEPT when tool calls are present
if (message.tool_calls?.length && message.content !== '') {
message.content = JSON.stringify(message.content);
}
if (typeof message.content === 'string') {
messages.push(message);
}
}
const isToolMessage = (message) => message instanceof messages_1.ToolMessage || message.constructor.name === 'ToolMessageChunk';
const isAIMessage = (message) => message instanceof messages_1.AIMessage || message.constructor.name === 'AIMessageChunk';
const isHumanMessage = (message) => message instanceof messages_1.HumanMessage || message.constructor.name === 'HumanMessageChunk';
/*
* MistralAI does not support:
* 1.) Last message as AI Message or Tool Message
* 2.) Tool Message followed by Human Message
*/
if (llm instanceof mistralai_1.ChatMistralAI) {
if (messages.length > 1) {
for (let i = 0; i < messages.length; i++) {
const message = messages[i];
// If last message is denied Tool Message, add a new Human Message
if (isToolMessage(message) && i === messages.length - 1 && message.additional_kwargs?.toolCallsDenied) {
messages.push(new messages_1.AIMessage({ content: `Tool calls got denied. Do you have other questions?` }));
}
else if (i + 1 < messages.length) {
const nextMessage = messages[i + 1];
const currentMessage = message;
// If current message is Tool Message and next message is Human Message, add AI Message between Tool and Human Message
if (isToolMessage(currentMessage) && isHumanMessage(nextMessage)) {
messages.splice(i + 1, 0, new messages_1.AIMessage({ content: 'Tool calls executed' }));
}
// If last message is AI Message or Tool Message, add Human Message
if (i + 1 === messages.length - 1 && (isAIMessage(nextMessage) || isToolMessage(nextMessage))) {
messages.push(new messages_1.HumanMessage({ content: nextMessage.content || 'Given the user question, answer user query' }));
}
}
}
}
}
else if (llm instanceof anthropic_1.ChatAnthropic) {
/*
* Anthropic does not support first message as AI Message
*/
if (messages.length) {
const firstMessage = messages[0];
if (isAIMessage(firstMessage)) {
messages.shift();
messages.unshift(new messages_1.HumanMessage({ ...firstMessage }));
}
}
}
return messages;
};
exports.restructureMessages = restructureMessages;
class ExtractTool extends tools_1.StructuredTool {
constructor(fields) {
super();
this.name = 'extract';
this.description = 'Extract structured data from the output';
this.schema = fields.schema;
}
async _call(input) {
return JSON.stringify(input);
}
}
exports.ExtractTool = ExtractTool;
class RunnableCallable extends runnables_1.Runnable {
constructor(fields) {
super();
this.lc_namespace = ['langgraph'];
this.trace = true;
this.recurse = true;
this.name = fields.name ?? fields.func.name;
this.func = fields.func;
this.config = fields.tags ? { tags: fields.tags } : undefined;
this.trace = fields.trace ?? this.trace;
this.recurse = fields.recurse ?? this.recurse;
if (fields.metadata) {
this.config = { ...this.config, metadata: { ...this.config, ...fields.metadata } };
}
}
async invoke(input, options) {
if (this.func === undefined) {
return this.invoke(input, options);
}
let returnValue;
if (this.trace) {
returnValue = await this._callWithConfig(this.func, input, (0, runnables_1.mergeConfigs)(this.config, options));
}
else {
returnValue = await this.func(input, (0, runnables_1.mergeConfigs)(this.config, options));
}
if (returnValue instanceof runnables_1.Runnable && this.recurse) {
return await returnValue.invoke(input, options);
}
return returnValue;
}
}
exports.RunnableCallable = RunnableCallable;
const checkMessageHistory = async (nodeData, options, prompt, promptArrays, sysPrompt) => {
const messageHistory = nodeData.inputs?.messageHistory;
if (messageHistory) {
const appDataSource = options.appDataSource;
const databaseEntities = options.databaseEntities;
const vm = await (0, exports.getVM)(appDataSource, databaseEntities, nodeData, options, {});
try {
const response = await vm.run(`module.exports = async function() {${messageHistory}}()`, __dirname);
if (!Array.isArray(response))
throw new Error('Returned message history must be an array');
if (sysPrompt) {
// insert at index 1
promptArrays.splice(1, 0, ...response);
}
else {
promptArrays.unshift(...response);
}
prompt = prompts_1.ChatPromptTemplate.fromMessages(promptArrays);
}
catch (e) {
throw new Error(e);
}
}
return prompt;
};
exports.checkMessageHistory = checkMessageHistory;
//# sourceMappingURL=commonUtils.js.map