import { StatusCodes } from 'http-status-codes' import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { getErrorMessage } from '../../errors/utils' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import path from 'path' import * as fs from 'fs' import { generateAgentflowv2 as generateAgentflowv2_json } from 'flowise-components' import { z } from 'zod' import { sysPrompt } from './prompt' import { databaseEntities } from '../../utils' import logger from '../../utils/logger' import { MODE } from '../../Interface' // Define the Zod schema for Agentflowv2 data structure const NodeType = z.object({ id: z.string(), type: z.string(), position: z.object({ x: z.number(), y: z.number() }), width: z.number(), height: z.number(), selected: z.boolean().optional(), positionAbsolute: z .object({ x: z.number(), y: z.number() }) .optional(), dragging: z.boolean().optional(), data: z.any().optional(), parentNode: z.string().optional() }) const EdgeType = z.object({ source: z.string(), sourceHandle: z.string(), target: z.string(), targetHandle: z.string(), data: z .object({ sourceColor: z.string().optional(), targetColor: z.string().optional(), edgeLabel: z.string().optional(), isHumanInput: z.boolean().optional() }) .optional(), type: z.string().optional(), id: z.string() }) const AgentFlowV2Type = z .object({ description: z.string().optional(), usecases: z.array(z.string()).optional(), nodes: z.array(NodeType), edges: z.array(EdgeType) }) .describe('Generate Agentflowv2 nodes and edges') // Type for the templates array type AgentFlowV2Template = z.infer const getAllAgentFlow2Nodes = async () => { const appServer = getRunningExpressApp() const nodes = appServer.nodesPool.componentNodes const agentFlow2Nodes = [] for (const node in nodes) { if (nodes[node].category === 'Agent Flows') { agentFlow2Nodes.push({ name: nodes[node].name, label: nodes[node].label, description: nodes[node].description }) } } return JSON.stringify(agentFlow2Nodes, null, 2) } const getAllToolNodes = async () => { const appServer = getRunningExpressApp() const nodes = appServer.nodesPool.componentNodes const toolNodes = [] const disabled_nodes = process.env.DISABLED_NODES ? process.env.DISABLED_NODES.split(',') : [] const removeTools = ['chainTool', 'retrieverTool', 'webBrowser', ...disabled_nodes] for (const node in nodes) { if (nodes[node].category.includes('Tools')) { if (removeTools.includes(nodes[node].name)) { continue } toolNodes.push({ name: nodes[node].name, description: nodes[node].description }) } } return JSON.stringify(toolNodes, null, 2) } const getAllAgentflowv2Marketplaces = async () => { const templates: AgentFlowV2Template[] = [] let marketplaceDir = path.join(__dirname, '..', '..', '..', 'marketplaces', 'agentflowsv2') let jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json') jsonsInDir.forEach((file) => { try { const filePath = path.join(__dirname, '..', '..', '..', 'marketplaces', 'agentflowsv2', file) const fileData = fs.readFileSync(filePath) const fileDataObj = JSON.parse(fileData.toString()) // get rid of the node.data, remain all other properties const filteredNodes = fileDataObj.nodes.map((node: any) => { return { ...node, data: undefined } }) const template = { title: file.split('.json')[0], description: fileDataObj.description || `Template from ${file}`, usecases: fileDataObj.usecases || [], nodes: filteredNodes, edges: fileDataObj.edges } // Validate template against schema const validatedTemplate = AgentFlowV2Type.parse(template) templates.push(validatedTemplate) } catch (error) { console.error(`Error processing template file ${file}:`, error) // Continue with next file instead of failing completely } }) // Format templates into the requested string format let formattedTemplates = '' templates.forEach((template: AgentFlowV2Template, index: number) => { formattedTemplates += `Example ${index + 1}: <<${(template as any).title}>> - ${template.description}\n` formattedTemplates += `"nodes": [\n` // Format nodes with proper indentation const nodesJson = JSON.stringify(template.nodes, null, 3) // Split by newlines and add 3 spaces to the beginning of each line except the first and last const nodesLines = nodesJson.split('\n') if (nodesLines.length > 2) { formattedTemplates += ` ${nodesLines[0]}\n` for (let i = 1; i < nodesLines.length - 1; i++) { formattedTemplates += ` ${nodesLines[i]}\n` } formattedTemplates += ` ${nodesLines[nodesLines.length - 1]}\n` } else { formattedTemplates += ` ${nodesJson}\n` } formattedTemplates += `]\n` formattedTemplates += `"edges": [\n` // Format edges with proper indentation const edgesJson = JSON.stringify(template.edges, null, 3) // Split by newlines and add tab to the beginning of each line except the first and last const edgesLines = edgesJson.split('\n') if (edgesLines.length > 2) { formattedTemplates += `\t${edgesLines[0]}\n` for (let i = 1; i < edgesLines.length - 1; i++) { formattedTemplates += `\t${edgesLines[i]}\n` } formattedTemplates += `\t${edgesLines[edgesLines.length - 1]}\n` } else { formattedTemplates += `\t${edgesJson}\n` } formattedTemplates += `]\n\n` }) return formattedTemplates } const generateAgentflowv2 = async (question: string, selectedChatModel: Record) => { try { const agentFlow2Nodes = await getAllAgentFlow2Nodes() const toolNodes = await getAllToolNodes() const marketplaceTemplates = await getAllAgentflowv2Marketplaces() const prompt = sysPrompt .replace('{agentFlow2Nodes}', agentFlow2Nodes) .replace('{marketplaceTemplates}', marketplaceTemplates) .replace('{userRequest}', question) const options: Record = { appDataSource: getRunningExpressApp().AppDataSource, databaseEntities: databaseEntities, logger: logger } let response if (process.env.MODE === MODE.QUEUE) { const predictionQueue = getRunningExpressApp().queueManager.getQueue('prediction') const job = await predictionQueue.addJob({ prompt, question, toolNodes, selectedChatModel, isAgentFlowGenerator: true }) logger.debug(`[server]: Generated Agentflowv2 Job added to queue: ${job.id}`) const queueEvents = predictionQueue.getQueueEvents() response = await job.waitUntilFinished(queueEvents) } else { response = await generateAgentflowv2_json( { prompt, componentNodes: getRunningExpressApp().nodesPool.componentNodes, toolNodes, selectedChatModel }, question, options ) } try { // Try to parse and validate the response if it's a string if (typeof response === 'string') { const parsedResponse = JSON.parse(response) const validatedResponse = AgentFlowV2Type.parse(parsedResponse) return validatedResponse } // If response is already an object else if (typeof response === 'object') { const validatedResponse = AgentFlowV2Type.parse(response) return validatedResponse } // Unexpected response type else { throw new Error(`Unexpected response type: ${typeof response}`) } } catch (parseError) { console.error('Failed to parse or validate response:', parseError) // If parsing fails, return an error object return { error: 'Failed to validate response format', rawResponse: response } as any // Type assertion to avoid type errors } } catch (error) { throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: generateAgentflowv2 - ${getErrorMessage(error)}`) } } export default { generateAgentflowv2 }