mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
Feature/seq agents (#2798)
* update build functions * sequential agents * update langchain to 0.2, added sequential agent nodes * add marketplace templates * update howto wordings * Merge branch 'main' into feature/Seq-Agents # Conflicts: # pnpm-lock.yaml * update deprecated functions and add new sequential nodes * add marketplace templates * update marketplace templates, add structured output to llm node * add multi agents template * update llm node with bindmodels * update cypress version * update templates sticky note wordings * update tool node to include human in loop action * update structured outputs error from models * update cohere package to resolve google genai pipeThrough bug * update mistral package version, added message reconstruction before invoke seq agent * add HITL to agent * update state messages restructuring * update load and split methods for s3 directory
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"description": "Prompt engineering team working together to craft Worker Prompts for your AgentFlow.",
|
||||
"framework": ["Langchain"],
|
||||
"usecases": ["Engineering"],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "supervisor_0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "add_contact_hubspot",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Add new contact to Hubspot",
|
||||
"color": "linear-gradient(rgb(85,198,123), rgb(0,230,99))",
|
||||
"iconSrc": "https://cdn.worldvectorlogo.com/logos/hubspot-1.svg",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "add_airtable",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Add column1, column2 to Airtable",
|
||||
"color": "linear-gradient(rgb(125,71,222), rgb(128,102,23))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "todays_date_time",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Useful to get todays day, date and time.",
|
||||
"color": "linear-gradient(rgb(117,118,129), rgb(230,10,250))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/javascript.svg",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "get_stock_movers",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Get the stocks that has biggest price/volume moves, e.g. actives, gainers, losers, etc.",
|
||||
"iconSrc": "https://rapidapi.com/cdn/images?url=https://rapidapi-prod-apis.s3.amazonaws.com/9c/e743343bdd41edad39a3fdffd5b974/016c33699f51603ae6fe4420c439124b.png",
|
||||
"color": "linear-gradient(rgb(191,202,167), rgb(143,202,246))",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "make_webhook",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Useful when you need to send message to Discord",
|
||||
"color": "linear-gradient(rgb(19,94,2), rgb(19,124,59))",
|
||||
"iconSrc": "https://github.com/FlowiseAI/Flowise/assets/26460777/517fdab2-8a6e-4781-b3c8-fb92cc78aa0b",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "perplexity_ai_search",
|
||||
"framework": "Langchain",
|
||||
"description": "Useful when conducting research using Perplexity AI online model.",
|
||||
"color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/AsharibAli/project-images/main/perplexity-ai-icon.svg",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "send_message_to_discord_channel",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Send message to Discord channel",
|
||||
"color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/discord-icon.svg",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "send_message_to_slack_channel",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Send message to Slack channel",
|
||||
"color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/slack-icon.svg",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "send_message_to_teams_channel",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Send message to Teams channel",
|
||||
"color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/microsoft-teams.svg",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "sendgrid_email",
|
||||
"framework": ["Langchain"],
|
||||
"description": "Send email using SendGrid",
|
||||
"color": "linear-gradient(rgb(230,108,70), rgb(222,4,98))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/sendgrid-icon.svg",
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/sanitize-html": "^2.9.5",
|
||||
"concurrently": "^7.1.0",
|
||||
"cypress": "^13.7.1",
|
||||
"cypress": "^13.13.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"oclif": "^3",
|
||||
"rimraf": "^5.0.5",
|
||||
@@ -102,6 +102,6 @@
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"ts-node": "^10.7.0",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IAction } from 'flowise-components'
|
||||
import { ICommonObject, IFileUpload, INode, INodeData as INodeDataFromComponent, INodeParams } from 'flowise-components'
|
||||
|
||||
export type MessageType = 'apiMessage' | 'userMessage'
|
||||
@@ -48,6 +49,7 @@ export interface IChatMessage {
|
||||
sessionId?: string
|
||||
createdDate: Date
|
||||
leadEmail?: string
|
||||
action?: string | null
|
||||
}
|
||||
|
||||
export interface IChatMessageFeedback {
|
||||
@@ -218,6 +220,7 @@ export interface IncomingInput {
|
||||
uploads?: IFileUpload[]
|
||||
leadEmail?: string
|
||||
history?: IMessage[]
|
||||
action?: IAction
|
||||
}
|
||||
|
||||
export interface IActiveChatflows {
|
||||
|
||||
@@ -136,9 +136,6 @@ export default class Start extends Command {
|
||||
// Telemetry
|
||||
if (flags.DISABLE_FLOWISE_TELEMETRY) process.env.DISABLE_FLOWISE_TELEMETRY = flags.DISABLE_FLOWISE_TELEMETRY
|
||||
|
||||
// Disable langchain warnings
|
||||
process.env.LANGCHAIN_SUPPRESS_MIGRATION_WARNINGS = 'true'
|
||||
|
||||
// Model list config
|
||||
if (flags.MODEL_LIST_CONFIG_JSON) process.env.MODEL_LIST_CONFIG_JSON = flags.MODEL_LIST_CONFIG_JSON
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ export class ChatMessage implements IChatMessage {
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
fileUploads?: string
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
action?: string | null
|
||||
|
||||
@Column()
|
||||
chatType: string
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddActionToChatMessage1721078251523 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const columnExists = await queryRunner.hasColumn('chat_message', 'action')
|
||||
if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`action\` LONGTEXT;`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`chat_message\` DROP COLUMN \`action\`;`)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { AddLead1710832127079 } from './1710832127079-AddLead'
|
||||
import { AddLeadToChatMessage1711538023578 } from './1711538023578-AddLeadToChatMessage'
|
||||
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
|
||||
import { AddTypeToChatFlow1766759476232 } from './1766759476232-AddTypeToChatFlow'
|
||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
|
||||
export const mariadbMigrations = [
|
||||
Init1693840429259,
|
||||
@@ -43,5 +44,6 @@ export const mariadbMigrations = [
|
||||
AddLead1710832127079,
|
||||
AddLeadToChatMessage1711538023578,
|
||||
AddAgentReasoningToChatMessage1714679514451,
|
||||
AddTypeToChatFlow1766759476232
|
||||
AddTypeToChatFlow1766759476232,
|
||||
AddActionToChatMessage1721078251523
|
||||
]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddActionToChatMessage1721078251523 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const columnExists = await queryRunner.hasColumn('chat_message', 'action')
|
||||
if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`action\` LONGTEXT;`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`chat_message\` DROP COLUMN \`action\`;`)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { AddLead1710832127079 } from './1710832127079-AddLead'
|
||||
import { AddLeadToChatMessage1711538023578 } from './1711538023578-AddLeadToChatMessage'
|
||||
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
|
||||
import { AddTypeToChatFlow1766759476232 } from './1766759476232-AddTypeToChatFlow'
|
||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
|
||||
export const mysqlMigrations = [
|
||||
Init1693840429259,
|
||||
@@ -43,5 +44,6 @@ export const mysqlMigrations = [
|
||||
AddLead1710832127079,
|
||||
AddLeadToChatMessage1711538023578,
|
||||
AddAgentReasoningToChatMessage1714679514451,
|
||||
AddTypeToChatFlow1766759476232
|
||||
AddTypeToChatFlow1766759476232,
|
||||
AddActionToChatMessage1721078251523
|
||||
]
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddActionToChatMessage1721078251523 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" ADD COLUMN IF NOT EXISTS "action" TEXT;`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "action";`)
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import { AddLead1710832137905 } from './1710832137905-AddLead'
|
||||
import { AddLeadToChatMessage1711538016098 } from './1711538016098-AddLeadToChatMessage'
|
||||
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
|
||||
import { AddTypeToChatFlow1766759476232 } from './1766759476232-AddTypeToChatFlow'
|
||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
|
||||
export const postgresMigrations = [
|
||||
Init1693891895163,
|
||||
@@ -45,5 +46,6 @@ export const postgresMigrations = [
|
||||
AddLead1710832137905,
|
||||
AddLeadToChatMessage1711538016098,
|
||||
AddAgentReasoningToChatMessage1714679514451,
|
||||
AddTypeToChatFlow1766759476232
|
||||
AddTypeToChatFlow1766759476232,
|
||||
AddActionToChatMessage1721078251523
|
||||
]
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddActionToChatMessage1721078251523 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" ADD COLUMN "action" TEXT;`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "action";`)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { AddLead1710832117612 } from './1710832117612-AddLead'
|
||||
import { AddLeadToChatMessage1711537986113 } from './1711537986113-AddLeadToChatMessage'
|
||||
import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-AddAgentReasoningToChatMessage'
|
||||
import { AddTypeToChatFlow1766759476232 } from './1766759476232-AddTypeToChatFlow'
|
||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
|
||||
export const sqliteMigrations = [
|
||||
Init1693835579790,
|
||||
@@ -43,5 +44,6 @@ export const sqliteMigrations = [
|
||||
AddLead1710832117612,
|
||||
AddLeadToChatMessage1711537986113,
|
||||
AddAgentReasoningToChatMessage1714679514451,
|
||||
AddTypeToChatFlow1766759476232
|
||||
AddTypeToChatFlow1766759476232,
|
||||
AddActionToChatMessage1721078251523
|
||||
]
|
||||
|
||||
@@ -46,8 +46,8 @@ const checkIfChatflowIsValidForStreaming = async (chatflowId: string): Promise<a
|
||||
isStreaming = isFlowValidForStream(nodes, endingNodeData)
|
||||
}
|
||||
|
||||
// If it is a Multi Agents, always enable streaming
|
||||
if (endingNodes.filter((node) => node.data.category === 'Multi Agents').length > 0) {
|
||||
// If it is a Multi/Sequential Agents, always enable streaming
|
||||
if (endingNodes.filter((node) => node.data.category === 'Multi Agents' || node.data.category === 'Sequential Agents').length > 0) {
|
||||
return { isStreaming: true }
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,48 @@ import {
|
||||
ICommonObject,
|
||||
IMultiAgentNode,
|
||||
IAgentReasoning,
|
||||
IAction,
|
||||
ITeamState,
|
||||
ConsoleCallbackHandler,
|
||||
additionalCallbacks
|
||||
additionalCallbacks,
|
||||
ISeqAgentsState,
|
||||
ISeqAgentNode,
|
||||
IUsedTool,
|
||||
IDocument
|
||||
} from 'flowise-components'
|
||||
import { IChatFlow, IComponentNodes, IDepthQueue, IReactFlowNode, IReactFlowObject } from '../Interface'
|
||||
import { Server } from 'socket.io'
|
||||
import { buildFlow, getStartingNodes, getEndingNodes, constructGraphs, databaseEntities } from '../utils'
|
||||
import { getRunningExpressApp } from './getRunningExpressApp'
|
||||
import logger from './logger'
|
||||
import { StateGraph, END } from '@langchain/langgraph'
|
||||
import { BaseMessage, HumanMessage } from '@langchain/core/messages'
|
||||
import { cloneDeep, flatten } from 'lodash'
|
||||
import { replaceInputsWithConfig, resolveVariables } from '.'
|
||||
import { omit, cloneDeep, flatten, uniq } from 'lodash'
|
||||
import { StateGraph, END, START } from '@langchain/langgraph'
|
||||
import { Document } from '@langchain/core/documents'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { StructuredTool } from '@langchain/core/tools'
|
||||
import { BaseMessage, HumanMessage, AIMessage, AIMessageChunk, ToolMessage } from '@langchain/core/messages'
|
||||
import {
|
||||
IChatFlow,
|
||||
IComponentNodes,
|
||||
IDepthQueue,
|
||||
IReactFlowNode,
|
||||
IReactFlowObject,
|
||||
IReactFlowEdge,
|
||||
IMessage,
|
||||
IncomingInput
|
||||
} from '../Interface'
|
||||
import {
|
||||
buildFlow,
|
||||
getStartingNodes,
|
||||
getEndingNodes,
|
||||
constructGraphs,
|
||||
databaseEntities,
|
||||
getSessionChatHistory,
|
||||
getMemorySessionId,
|
||||
clearSessionMemory
|
||||
} from '../utils'
|
||||
import { getRunningExpressApp } from './getRunningExpressApp'
|
||||
import { replaceInputsWithConfig, resolveVariables } from '.'
|
||||
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../errors/utils'
|
||||
import logger from './logger'
|
||||
|
||||
/**
|
||||
* Build Agent Graph
|
||||
@@ -25,6 +51,7 @@ import { getErrorMessage } from '../errors/utils'
|
||||
* @param {string} chatId
|
||||
* @param {string} sessionId
|
||||
* @param {ICommonObject} incomingInput
|
||||
* @param {boolean} isInternal
|
||||
* @param {string} baseURL
|
||||
* @param {Server} socketIO
|
||||
*/
|
||||
@@ -32,7 +59,8 @@ export const buildAgentGraph = async (
|
||||
chatflow: IChatFlow,
|
||||
chatId: string,
|
||||
sessionId: string,
|
||||
incomingInput: ICommonObject,
|
||||
incomingInput: IncomingInput,
|
||||
isInternal: boolean,
|
||||
baseURL?: string,
|
||||
socketIO?: Server
|
||||
): Promise<any> => {
|
||||
@@ -65,27 +93,42 @@ export const buildAgentGraph = async (
|
||||
}
|
||||
startingNodeIds = [...new Set(startingNodeIds)]
|
||||
|
||||
/*** Get Memory Node for Chat History ***/
|
||||
let chatHistory: IMessage[] = []
|
||||
const memoryNode = nodes.find((node) => node.data.name === 'agentMemory')
|
||||
if (memoryNode) {
|
||||
chatHistory = await getSessionChatHistory(
|
||||
chatflowid,
|
||||
getMemorySessionId(memoryNode, incomingInput, chatId, isInternal),
|
||||
memoryNode,
|
||||
appServer.nodesPool.componentNodes,
|
||||
appServer.AppDataSource,
|
||||
databaseEntities,
|
||||
logger,
|
||||
incomingInput.history
|
||||
)
|
||||
}
|
||||
|
||||
// Initialize nodes like ChatModels, Tools, etc.
|
||||
const reactFlowNodes = await buildFlow(
|
||||
const reactFlowNodes: IReactFlowNode[] = await buildFlow({
|
||||
startingNodeIds,
|
||||
nodes,
|
||||
edges,
|
||||
reactFlowNodes: nodes,
|
||||
reactFlowEdges: edges,
|
||||
graph,
|
||||
depthQueue,
|
||||
appServer.nodesPool.componentNodes,
|
||||
incomingInput.question,
|
||||
[],
|
||||
componentNodes: appServer.nodesPool.componentNodes,
|
||||
question: incomingInput.question,
|
||||
chatHistory,
|
||||
chatId,
|
||||
sessionId,
|
||||
chatflowid,
|
||||
appServer.AppDataSource,
|
||||
incomingInput?.overrideConfig,
|
||||
appServer.cachePool,
|
||||
false,
|
||||
undefined,
|
||||
incomingInput.uploads,
|
||||
appDataSource: appServer.AppDataSource,
|
||||
overrideConfig: incomingInput?.overrideConfig,
|
||||
cachePool: appServer.cachePool,
|
||||
isUpsert: false,
|
||||
uploads: incomingInput.uploads,
|
||||
baseURL
|
||||
)
|
||||
})
|
||||
|
||||
const options = {
|
||||
chatId,
|
||||
@@ -103,92 +146,265 @@ export const buildAgentGraph = async (
|
||||
|
||||
let streamResults
|
||||
let finalResult = ''
|
||||
let finalSummarization = ''
|
||||
let agentReasoning: IAgentReasoning[] = []
|
||||
let isSequential = false
|
||||
let lastMessageRaw = {} as AIMessageChunk
|
||||
let finalAction: IAction = {}
|
||||
let totalSourceDocuments: IDocument[] = []
|
||||
let totalUsedTools: IUsedTool[] = []
|
||||
|
||||
const workerNodes: IReactFlowNode[] = reactFlowNodes.filter((node: IReactFlowNode) => node.data.name === 'worker')
|
||||
const supervisorNodes: IReactFlowNode[] = reactFlowNodes.filter((node: IReactFlowNode) => node.data.name === 'supervisor')
|
||||
const workerNodes = reactFlowNodes.filter((node) => node.data.name === 'worker')
|
||||
const supervisorNodes = reactFlowNodes.filter((node) => node.data.name === 'supervisor')
|
||||
const seqAgentNodes = reactFlowNodes.filter((node) => node.data.category === 'Sequential Agents')
|
||||
|
||||
const mapNameToLabel: Record<string, string> = {}
|
||||
const mapNameToLabel: Record<string, { label: string; nodeName: string }> = {}
|
||||
|
||||
for (const node of [...workerNodes, ...supervisorNodes]) {
|
||||
mapNameToLabel[node.data.instance.name] = node.data.instance.label
|
||||
for (const node of [...workerNodes, ...supervisorNodes, ...seqAgentNodes]) {
|
||||
if (!Object.prototype.hasOwnProperty.call(mapNameToLabel, node.data.instance.name)) {
|
||||
mapNameToLabel[node.data.instance.name] = {
|
||||
label: node.data.instance.label,
|
||||
nodeName: node.data.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
streamResults = await compileGraph(
|
||||
chatflow,
|
||||
mapNameToLabel,
|
||||
reactFlowNodes,
|
||||
endingNodeIds,
|
||||
appServer.nodesPool.componentNodes,
|
||||
options,
|
||||
startingNodeIds,
|
||||
incomingInput.question,
|
||||
incomingInput?.overrideConfig
|
||||
)
|
||||
if (!seqAgentNodes.length) {
|
||||
streamResults = await compileMultiAgentsGraph(
|
||||
chatflow,
|
||||
mapNameToLabel,
|
||||
reactFlowNodes,
|
||||
endingNodeIds,
|
||||
appServer.nodesPool.componentNodes,
|
||||
options,
|
||||
startingNodeIds,
|
||||
incomingInput.question,
|
||||
chatHistory,
|
||||
incomingInput?.overrideConfig,
|
||||
sessionId || chatId
|
||||
)
|
||||
} else {
|
||||
isSequential = true
|
||||
streamResults = await compileSeqAgentsGraph(
|
||||
depthQueue,
|
||||
chatflow,
|
||||
reactFlowNodes,
|
||||
edges,
|
||||
appServer.nodesPool.componentNodes,
|
||||
options,
|
||||
incomingInput.question,
|
||||
chatHistory,
|
||||
incomingInput?.overrideConfig,
|
||||
sessionId || chatId,
|
||||
incomingInput.action
|
||||
)
|
||||
}
|
||||
|
||||
if (streamResults) {
|
||||
let isStreamingStarted = false
|
||||
for await (const output of await streamResults) {
|
||||
if (!output?.__end__) {
|
||||
const agentName = Object.keys(output)[0]
|
||||
const usedTools = output[agentName]?.messages
|
||||
? output[agentName].messages.map((msg: any) => msg.additional_kwargs?.usedTools)
|
||||
: []
|
||||
const sourceDocuments = output[agentName]?.messages
|
||||
? output[agentName].messages.map((msg: any) => msg.additional_kwargs?.sourceDocuments)
|
||||
: []
|
||||
const messages = output[agentName]?.messages ? output[agentName].messages.map((msg: any) => msg.content) : []
|
||||
const reasoning = {
|
||||
agentName: mapNameToLabel[agentName],
|
||||
messages,
|
||||
next: output[agentName]?.next,
|
||||
instructions: output[agentName]?.instructions,
|
||||
usedTools: flatten(usedTools),
|
||||
sourceDocuments: flatten(sourceDocuments)
|
||||
}
|
||||
agentReasoning.push(reasoning)
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
if (!isStreamingStarted) {
|
||||
isStreamingStarted = true
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('start', JSON.stringify(agentReasoning))
|
||||
for (const agentName of Object.keys(output)) {
|
||||
if (!mapNameToLabel[agentName]) continue
|
||||
|
||||
const nodeId = output[agentName]?.messages
|
||||
? output[agentName].messages[output[agentName].messages.length - 1]?.additional_kwargs?.nodeId
|
||||
: ''
|
||||
const usedTools = output[agentName]?.messages
|
||||
? output[agentName].messages.map((msg: BaseMessage) => msg.additional_kwargs?.usedTools)
|
||||
: []
|
||||
const sourceDocuments = output[agentName]?.messages
|
||||
? output[agentName].messages.map((msg: BaseMessage) => msg.additional_kwargs?.sourceDocuments)
|
||||
: []
|
||||
const messages = output[agentName]?.messages
|
||||
? output[agentName].messages.map((msg: BaseMessage) => (typeof msg === 'string' ? msg : msg.content))
|
||||
: []
|
||||
lastMessageRaw = output[agentName]?.messages
|
||||
? output[agentName].messages[output[agentName].messages.length - 1]
|
||||
: {}
|
||||
|
||||
const state = omit(output[agentName], ['messages'])
|
||||
|
||||
if (usedTools && usedTools.length) {
|
||||
const cleanedTools = usedTools.filter((tool: IUsedTool) => tool)
|
||||
if (cleanedTools.length) totalUsedTools.push(...cleanedTools)
|
||||
}
|
||||
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('agentReasoning', JSON.stringify(agentReasoning))
|
||||
if (sourceDocuments && sourceDocuments.length) {
|
||||
const cleanedDocs = sourceDocuments.filter((documents: IDocument) => documents)
|
||||
if (cleanedDocs.length) totalSourceDocuments.push(...cleanedDocs)
|
||||
}
|
||||
|
||||
// Send loading next agent indicator
|
||||
if (reasoning.next && reasoning.next !== 'FINISH' && reasoning.next !== 'END') {
|
||||
socketIO
|
||||
.to(incomingInput.socketIOClientId)
|
||||
.emit('nextAgent', mapNameToLabel[reasoning.next] || reasoning.next)
|
||||
/*
|
||||
* Check if the next node is a condition node, if yes, then add the agent reasoning of the condition node
|
||||
*/
|
||||
if (isSequential) {
|
||||
const inputEdges = edges.filter(
|
||||
(edg) => edg.target === nodeId && edg.targetHandle.includes(`${nodeId}-input-sequentialNode`)
|
||||
)
|
||||
|
||||
inputEdges.forEach((edge) => {
|
||||
const parentNode = reactFlowNodes.find((nd) => nd.id === edge.source)
|
||||
if (parentNode) {
|
||||
if (parentNode.data.name.includes('seqCondition')) {
|
||||
const newMessages = messages.slice(0, -1)
|
||||
newMessages.push(mapNameToLabel[agentName].label)
|
||||
const reasoning = {
|
||||
agentName: parentNode.data.instance?.label || parentNode.data.type,
|
||||
messages: newMessages,
|
||||
nodeName: parentNode.data.name,
|
||||
nodeId: parentNode.data.id
|
||||
}
|
||||
agentReasoning.push(reasoning)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const reasoning = {
|
||||
agentName: mapNameToLabel[agentName].label,
|
||||
messages,
|
||||
next: output[agentName]?.next,
|
||||
instructions: output[agentName]?.instructions,
|
||||
usedTools: flatten(usedTools) as IUsedTool[],
|
||||
sourceDocuments: flatten(sourceDocuments) as Document[],
|
||||
state,
|
||||
nodeName: isSequential ? mapNameToLabel[agentName].nodeName : undefined,
|
||||
nodeId
|
||||
}
|
||||
agentReasoning.push(reasoning)
|
||||
|
||||
finalSummarization = output[agentName]?.summarization ?? ''
|
||||
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
if (!isStreamingStarted) {
|
||||
isStreamingStarted = true
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('start', agentReasoning)
|
||||
}
|
||||
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('agentReasoning', agentReasoning)
|
||||
|
||||
// Send loading next agent indicator
|
||||
if (reasoning.next && reasoning.next !== 'FINISH' && reasoning.next !== 'END') {
|
||||
socketIO
|
||||
.to(incomingInput.socketIOClientId)
|
||||
.emit('nextAgent', mapNameToLabel[reasoning.next].label || reasoning.next)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalResult = output.__end__.messages.length ? output.__end__.messages.pop()?.content : ''
|
||||
if (Array.isArray(finalResult)) finalResult = output.__end__.instructions
|
||||
|
||||
if (finalResult === incomingInput.question) {
|
||||
const supervisorNode = reactFlowNodes.find((node: IReactFlowNode) => node.data.name === 'supervisor')
|
||||
const llm = supervisorNode?.data?.instance?.llm
|
||||
if (llm) {
|
||||
const res = await llm.invoke(incomingInput.question)
|
||||
finalResult = res?.content
|
||||
}
|
||||
}
|
||||
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { finalResult, agentReasoning }
|
||||
/*
|
||||
* For multi agents mode, sometimes finalResult is empty
|
||||
* Provide summary as final result
|
||||
*/
|
||||
if (!isSequential && !finalResult && finalSummarization) {
|
||||
finalResult = finalSummarization
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For sequential mode, sometimes finalResult is empty
|
||||
* Use last agent message as final result
|
||||
*/
|
||||
if (isSequential && !finalResult && agentReasoning.length) {
|
||||
const lastMessages = agentReasoning[agentReasoning.length - 1].messages
|
||||
const lastAgentReasoningMessage = lastMessages[lastMessages.length - 1]
|
||||
|
||||
// If last message is an AI Message with tool calls, that means the last node was interrupted
|
||||
if (lastMessageRaw.tool_calls && lastMessageRaw.tool_calls.length > 0) {
|
||||
// The last node that got interrupted
|
||||
const node = reactFlowNodes.find((node) => node.id === lastMessageRaw.additional_kwargs.nodeId)
|
||||
|
||||
// Find the next tool node that is connected to the interrupted node, to get the approve/reject button text
|
||||
const tooNodeId = edges.find(
|
||||
(edge) =>
|
||||
edge.target.includes('seqToolNode') &&
|
||||
edge.source === (lastMessageRaw.additional_kwargs && lastMessageRaw.additional_kwargs.nodeId)
|
||||
)?.target
|
||||
const connectedToolNode = reactFlowNodes.find((node) => node.id === tooNodeId)
|
||||
|
||||
// Map raw tool calls to used tools, to be shown on interrupted message
|
||||
const mappedToolCalls = lastMessageRaw.tool_calls.map((toolCall) => {
|
||||
return { tool: toolCall.name, toolInput: toolCall.args, toolOutput: '' }
|
||||
})
|
||||
|
||||
// Emit the interrupt message to the client
|
||||
let approveButtonText = 'Yes'
|
||||
let rejectButtonText = 'No'
|
||||
|
||||
if (connectedToolNode || node) {
|
||||
if (connectedToolNode) {
|
||||
const result = await connectedToolNode.data.instance.node.seekPermissionMessage(mappedToolCalls)
|
||||
finalResult = result || 'Do you want to proceed?'
|
||||
approveButtonText = connectedToolNode.data.inputs?.approveButtonText || 'Yes'
|
||||
rejectButtonText = connectedToolNode.data.inputs?.rejectButtonText || 'No'
|
||||
} else if (node) {
|
||||
const result = await node.data.instance.agentInterruptToolNode.seekPermissionMessage(mappedToolCalls)
|
||||
finalResult = result || 'Do you want to proceed?'
|
||||
approveButtonText = node.data.inputs?.approveButtonText || 'Yes'
|
||||
rejectButtonText = node.data.inputs?.rejectButtonText || 'No'
|
||||
}
|
||||
finalAction = {
|
||||
id: uuidv4(),
|
||||
mapping: { approve: approveButtonText, reject: rejectButtonText, toolCalls: lastMessageRaw.tool_calls },
|
||||
elements: [
|
||||
{ type: 'approve-button', label: approveButtonText },
|
||||
{ type: 'reject-button', label: rejectButtonText }
|
||||
]
|
||||
}
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('action', finalAction)
|
||||
}
|
||||
}
|
||||
totalUsedTools.push(...mappedToolCalls)
|
||||
} else if (lastAgentReasoningMessage) {
|
||||
finalResult = lastAgentReasoningMessage
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalSourceDocuments = uniq(flatten(totalSourceDocuments))
|
||||
totalUsedTools = uniq(flatten(totalUsedTools))
|
||||
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('usedTools', totalUsedTools)
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('sourceDocuments', totalSourceDocuments)
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('end')
|
||||
}
|
||||
|
||||
return {
|
||||
finalResult,
|
||||
finalAction,
|
||||
sourceDocuments: totalSourceDocuments,
|
||||
usedTools: totalUsedTools,
|
||||
agentReasoning
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('abort')
|
||||
// clear agent memory because checkpoints were saved during runtime
|
||||
await clearSessionMemory(nodes, appServer.nodesPool.componentNodes, chatId, appServer.AppDataSource, sessionId)
|
||||
if (getErrorMessage(e).includes('Aborted')) {
|
||||
if (socketIO && incomingInput.socketIOClientId) {
|
||||
socketIO.to(incomingInput.socketIOClientId).emit('abort')
|
||||
}
|
||||
return { finalResult, agentReasoning }
|
||||
}
|
||||
return { finalResult, agentReasoning }
|
||||
throw new Error(getErrorMessage(e))
|
||||
}
|
||||
return streamResults
|
||||
} catch (e) {
|
||||
@@ -198,9 +414,9 @@ export const buildAgentGraph = async (
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Graph
|
||||
* Compile Multi Agents Graph
|
||||
* @param {IChatFlow} chatflow
|
||||
* @param {Record<string, string>} mapNameToLabel
|
||||
* @param {Record<string, {label: string, nodeName: string }>} mapNameToLabel
|
||||
* @param {IReactFlowNode[]} reactflowNodes
|
||||
* @param {string[]} workerNodeIds
|
||||
* @param {IComponentNodes} componentNodes
|
||||
@@ -208,17 +424,20 @@ export const buildAgentGraph = async (
|
||||
* @param {string[]} startingNodeIds
|
||||
* @param {string} question
|
||||
* @param {ICommonObject} overrideConfig
|
||||
* @param {string} threadId
|
||||
*/
|
||||
const compileGraph = async (
|
||||
const compileMultiAgentsGraph = async (
|
||||
chatflow: IChatFlow,
|
||||
mapNameToLabel: Record<string, string>,
|
||||
mapNameToLabel: Record<string, { label: string; nodeName: string }>,
|
||||
reactflowNodes: IReactFlowNode[] = [],
|
||||
workerNodeIds: string[],
|
||||
componentNodes: IComponentNodes,
|
||||
options: ICommonObject,
|
||||
startingNodeIds: string[],
|
||||
question: string,
|
||||
overrideConfig?: ICommonObject
|
||||
chatHistory: IMessage[] = [],
|
||||
overrideConfig?: ICommonObject,
|
||||
threadId?: string
|
||||
) => {
|
||||
const appServer = getRunningExpressApp()
|
||||
const channels: ITeamState = {
|
||||
@@ -228,7 +447,8 @@ const compileGraph = async (
|
||||
},
|
||||
next: 'initialState',
|
||||
instructions: "Solve the user's request.",
|
||||
team_members: []
|
||||
team_members: [],
|
||||
summarization: 'summarize'
|
||||
}
|
||||
|
||||
const workflowGraph = new StateGraph<ITeamState>({
|
||||
@@ -248,7 +468,7 @@ const compileGraph = async (
|
||||
|
||||
let flowNodeData = cloneDeep(workerNode.data)
|
||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
||||
flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, [])
|
||||
flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, chatHistory)
|
||||
|
||||
try {
|
||||
const workerResult: IMultiAgentNode = await newNodeInstance.init(flowNodeData, question, options)
|
||||
@@ -268,7 +488,7 @@ const compileGraph = async (
|
||||
|
||||
// Init supervisor nodes
|
||||
for (const supervisor in supervisorWorkers) {
|
||||
const supervisorInputLabel = mapNameToLabel[supervisor]
|
||||
const supervisorInputLabel = mapNameToLabel[supervisor].label
|
||||
const supervisorNode = reactflowNodes.find((node) => supervisorInputLabel === node.data.inputs?.supervisorName)
|
||||
if (!supervisorNode) continue
|
||||
|
||||
@@ -279,7 +499,7 @@ const compileGraph = async (
|
||||
let flowNodeData = cloneDeep(supervisorNode.data)
|
||||
|
||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
||||
flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, [])
|
||||
flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, chatHistory)
|
||||
|
||||
if (flowNodeData.inputs) flowNodeData.inputs.workerNodes = supervisorWorkers[supervisor]
|
||||
|
||||
@@ -300,6 +520,7 @@ const compileGraph = async (
|
||||
workflowGraph.addNode(supervisorResult.name, supervisorResult.node)
|
||||
|
||||
for (const worker of supervisorResult.workers) {
|
||||
//@ts-ignore
|
||||
workflowGraph.addEdge(worker, supervisorResult.name)
|
||||
}
|
||||
|
||||
@@ -308,12 +529,14 @@ const compileGraph = async (
|
||||
conditionalEdges[supervisorResult.workers[i]] = supervisorResult.workers[i]
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
workflowGraph.addConditionalEdges(supervisorResult.name, (x: ITeamState) => x.next, {
|
||||
...conditionalEdges,
|
||||
FINISH: END
|
||||
})
|
||||
|
||||
workflowGraph.setEntryPoint(supervisorResult.name)
|
||||
//@ts-ignore
|
||||
workflowGraph.addEdge(START, supervisorResult.name)
|
||||
|
||||
// Add agentflow to pool
|
||||
;(workflowGraph as any).signal = options.signal
|
||||
@@ -324,22 +547,443 @@ const compileGraph = async (
|
||||
overrideConfig
|
||||
)
|
||||
|
||||
// TODO: add persistence
|
||||
// const memory = new MemorySaver()
|
||||
const graph = workflowGraph.compile()
|
||||
// Get memory
|
||||
let memory = supervisorResult?.checkpointMemory
|
||||
|
||||
const graph = workflowGraph.compile({ checkpointer: memory })
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(logger)
|
||||
const callbacks = await additionalCallbacks(flowNodeData, options)
|
||||
const config = { configurable: { thread_id: threadId } }
|
||||
|
||||
// Return stream result as we should only have 1 supervisor
|
||||
return await graph.stream(
|
||||
{
|
||||
messages: [new HumanMessage({ content: question })]
|
||||
},
|
||||
{ recursionLimit: supervisorResult?.recursionLimit ?? 100, callbacks: [loggerHandler, ...callbacks] }
|
||||
{ recursionLimit: supervisorResult?.recursionLimit ?? 100, callbacks: [loggerHandler, ...callbacks], configurable: config }
|
||||
)
|
||||
} catch (e) {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error initialize supervisor nodes - ${getErrorMessage(e)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Seq Agents Graph
|
||||
* @param {IDepthQueue} depthQueue
|
||||
* @param {IChatFlow} chatflow
|
||||
* @param {IReactFlowNode[]} reactflowNodes
|
||||
* @param {IReactFlowEdge[]} reactflowEdges
|
||||
* @param {IComponentNodes} componentNodes
|
||||
* @param {ICommonObject} options
|
||||
* @param {string} question
|
||||
* @param {IMessage[]} chatHistory
|
||||
* @param {ICommonObject} overrideConfig
|
||||
* @param {string} threadId
|
||||
* @param {IAction} action
|
||||
*/
|
||||
const compileSeqAgentsGraph = async (
|
||||
depthQueue: IDepthQueue,
|
||||
chatflow: IChatFlow,
|
||||
reactflowNodes: IReactFlowNode[] = [],
|
||||
reactflowEdges: IReactFlowEdge[] = [],
|
||||
componentNodes: IComponentNodes,
|
||||
options: ICommonObject,
|
||||
question: string,
|
||||
chatHistory: IMessage[] = [],
|
||||
overrideConfig?: ICommonObject,
|
||||
threadId?: string,
|
||||
action?: IAction
|
||||
) => {
|
||||
const appServer = getRunningExpressApp()
|
||||
|
||||
let channels: ISeqAgentsState = {
|
||||
messages: {
|
||||
value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),
|
||||
default: () => []
|
||||
}
|
||||
}
|
||||
|
||||
// Get state
|
||||
const seqStateNode = reactflowNodes.find((node: IReactFlowNode) => node.data.name === 'seqState')
|
||||
if (seqStateNode) {
|
||||
channels = {
|
||||
...seqStateNode.data.instance.node,
|
||||
...channels
|
||||
}
|
||||
}
|
||||
|
||||
let seqGraph = new StateGraph<any>({
|
||||
//@ts-ignore
|
||||
channels
|
||||
})
|
||||
|
||||
/*** Validate Graph ***/
|
||||
const startAgentNodes: IReactFlowNode[] = reactflowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqStart')
|
||||
if (!startAgentNodes.length) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Start node not found')
|
||||
if (startAgentNodes.length > 1)
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Graph should have only one start node')
|
||||
|
||||
const endAgentNodes: IReactFlowNode[] = reactflowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqEnd')
|
||||
const loopNodes: IReactFlowNode[] = reactflowNodes.filter((node: IReactFlowNode) => node.data.name === 'seqLoop')
|
||||
if (!endAgentNodes.length && !loopNodes.length) {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Graph should have at least one End/Loop node')
|
||||
}
|
||||
/*** End of Validation ***/
|
||||
|
||||
let flowNodeData
|
||||
let conditionalEdges: Record<string, { nodes: Record<string, string>; func: any }> = {}
|
||||
let interruptedRouteMapping: Record<string, Record<string, string>> = {}
|
||||
let conditionalToolNodes: Record<string, { source: ISeqAgentNode; toolNodes: ISeqAgentNode[] }> = {}
|
||||
let bindModel: Record<string, any> = {}
|
||||
let interruptToolNodeNames = []
|
||||
|
||||
const initiateNode = async (node: IReactFlowNode) => {
|
||||
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
||||
const nodeModule = await import(nodeInstanceFilePath)
|
||||
const newNodeInstance = new nodeModule.nodeClass()
|
||||
|
||||
flowNodeData = cloneDeep(node.data)
|
||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
||||
flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, chatHistory)
|
||||
|
||||
const seqAgentNode: ISeqAgentNode = await newNodeInstance.init(flowNodeData, question, options)
|
||||
return seqAgentNode
|
||||
}
|
||||
|
||||
/*
|
||||
* Two objectives we want to achieve here:
|
||||
* 1.) Prepare the mapping of conditional outputs to next nodes. This mapping will ONLY be used to add conditional edges to the Interrupted Agent connected next to Condition/ConditionAgent Node.
|
||||
* For example, if the condition node has 2 outputs 'Yes' and 'No', and 'Yes' leads to 'agentName1' and 'No' leads to 'agentName2', then the mapping should be like:
|
||||
* {
|
||||
* <conditionNodeId>: { 'Yes': 'agentName1', 'No': 'agentName2' }
|
||||
* }
|
||||
* 2.) With the interruptedRouteMapping object, avoid adding conditional edges to the Interrupted Agent for the nodes that are already interrupted by tools. It will be separately added from the function - agentInterruptToolFunc
|
||||
*/
|
||||
const processInterruptedRouteMapping = (conditionNodeId: string) => {
|
||||
const conditionEdges = reactflowEdges.filter((edge) => edge.source === conditionNodeId) ?? []
|
||||
|
||||
for (const conditionEdge of conditionEdges) {
|
||||
const nextNodeId = conditionEdge.target
|
||||
const conditionNodeOutputAnchorId = conditionEdge.sourceHandle
|
||||
|
||||
const nextNode = reactflowNodes.find((node) => node.id === nextNodeId)
|
||||
if (!nextNode) continue
|
||||
|
||||
const conditionNode = reactflowNodes.find((node) => node.id === conditionNodeId)
|
||||
if (!conditionNode) continue
|
||||
|
||||
const outputAnchors = conditionNode?.data.outputAnchors
|
||||
if (!outputAnchors || !outputAnchors.length || !outputAnchors[0].options) continue
|
||||
|
||||
const conditionOutputAnchorLabel =
|
||||
outputAnchors[0].options.find((option: any) => option.id === conditionNodeOutputAnchorId)?.label ?? ''
|
||||
if (!conditionOutputAnchorLabel) continue
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(interruptedRouteMapping, conditionNodeId)) {
|
||||
interruptedRouteMapping[conditionNodeId] = {
|
||||
...interruptedRouteMapping[conditionNodeId],
|
||||
[conditionOutputAnchorLabel]: nextNode.data.instance.name
|
||||
}
|
||||
} else {
|
||||
interruptedRouteMapping[conditionNodeId] = {
|
||||
[conditionOutputAnchorLabel]: nextNode.data.instance.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare Conditional Edges
|
||||
* Example: {
|
||||
* 'seqCondition_1': { nodes: { 'Yes': 'agentName1', 'No': 'agentName2' }, func: <condition-function>, disabled: true },
|
||||
* 'seqCondition_2': { nodes: { 'Yes': 'agentName3', 'No': 'agentName4' }, func: <condition-function> }
|
||||
* }
|
||||
*/
|
||||
const prepareConditionalEdges = (nodeId: string, nodeInstance: ISeqAgentNode) => {
|
||||
const conditionEdges = reactflowEdges.filter((edge) => edge.target === nodeId && edge.source.includes('seqCondition')) ?? []
|
||||
|
||||
for (const conditionEdge of conditionEdges) {
|
||||
const conditionNodeId = conditionEdge.source
|
||||
const conditionNodeOutputAnchorId = conditionEdge.sourceHandle
|
||||
|
||||
const conditionNode = reactflowNodes.find((node) => node.id === conditionNodeId)
|
||||
const outputAnchors = conditionNode?.data.outputAnchors
|
||||
|
||||
if (!outputAnchors || !outputAnchors.length || !outputAnchors[0].options) continue
|
||||
|
||||
const conditionOutputAnchorLabel =
|
||||
outputAnchors[0].options.find((option: any) => option.id === conditionNodeOutputAnchorId)?.label ?? ''
|
||||
|
||||
if (!conditionOutputAnchorLabel) continue
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(conditionalEdges, conditionNodeId)) {
|
||||
conditionalEdges[conditionNodeId] = {
|
||||
...conditionalEdges[conditionNodeId],
|
||||
nodes: { ...conditionalEdges[conditionNodeId].nodes, [conditionOutputAnchorLabel]: nodeInstance.name }
|
||||
}
|
||||
} else {
|
||||
conditionalEdges[conditionNodeId] = {
|
||||
nodes: { [conditionOutputAnchorLabel]: nodeInstance.name },
|
||||
func: conditionNode.data.instance.node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare Conditional Tool Edges. This is just for LLMNode -> ToolNode
|
||||
* Example: {
|
||||
* 'agent_1': { source: agent, toolNodes: [node] }
|
||||
* }
|
||||
*/
|
||||
const prepareLLMToToolEdges = (predecessorAgent: ISeqAgentNode, toolNodeInstance: ISeqAgentNode) => {
|
||||
if (Object.prototype.hasOwnProperty.call(conditionalToolNodes, predecessorAgent.id)) {
|
||||
const toolNodes = conditionalToolNodes[predecessorAgent.id].toolNodes
|
||||
toolNodes.push(toolNodeInstance)
|
||||
conditionalToolNodes[predecessorAgent.id] = { source: predecessorAgent, toolNodes }
|
||||
} else {
|
||||
conditionalToolNodes[predecessorAgent.id] = {
|
||||
source: predecessorAgent,
|
||||
toolNodes: [toolNodeInstance]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*** This is to bind the tools to the model of LLMNode, when the LLMNode is predecessor/successor of ToolNode ***/
|
||||
const createBindModel = (agent: ISeqAgentNode, toolNodeInstance: ISeqAgentNode) => {
|
||||
const tools = flatten(toolNodeInstance.node?.tools)
|
||||
bindModel[agent.id] = agent.llm.bindTools(tools)
|
||||
}
|
||||
|
||||
/*** Start processing every Agent nodes ***/
|
||||
for (const agentNodeId of getSortedDepthNodes(depthQueue)) {
|
||||
const agentNode = reactflowNodes.find((node) => node.id === agentNodeId)
|
||||
if (!agentNode) continue
|
||||
|
||||
const eligibleSeqNodes = ['seqAgent', 'seqEnd', 'seqLoop', 'seqToolNode', 'seqLLMNode']
|
||||
const nodesToAdd = ['seqAgent', 'seqToolNode', 'seqLLMNode']
|
||||
|
||||
if (eligibleSeqNodes.includes(agentNode.data.name)) {
|
||||
try {
|
||||
const agentInstance: ISeqAgentNode = await initiateNode(agentNode)
|
||||
|
||||
if (nodesToAdd.includes(agentNode.data.name)) {
|
||||
// Add node to graph
|
||||
seqGraph.addNode(agentInstance.name, agentInstance.node)
|
||||
|
||||
/*
|
||||
* If it is an Interrupted Agent, we want to:
|
||||
* 1.) Add conditional edges to the Interrupted Agent via agentInterruptToolFunc
|
||||
* 2.) Add agent to the interruptToolNodeNames list
|
||||
*/
|
||||
if (agentInstance.type === 'agent' && agentNode.data.inputs?.interrupt) {
|
||||
interruptToolNodeNames.push(agentInstance.agentInterruptToolNode.name)
|
||||
|
||||
const nextNodeId = reactflowEdges.find((edge) => edge.source === agentNode.id)?.target
|
||||
const nextNode = reactflowNodes.find((node) => node.id === nextNodeId)
|
||||
|
||||
let nextNodeSeqAgentName = ''
|
||||
if (nextNodeId && nextNode) {
|
||||
nextNodeSeqAgentName = nextNode.data.instance.name
|
||||
|
||||
// If next node is Condition Node, process the interrupted route mapping, see more details from comments of processInterruptedRouteMapping
|
||||
if (nextNode.data.name.includes('seqCondition')) {
|
||||
const conditionNode = nextNodeId
|
||||
processInterruptedRouteMapping(conditionNode)
|
||||
seqGraph = await agentInstance.agentInterruptToolFunc(
|
||||
seqGraph,
|
||||
undefined,
|
||||
nextNode.data.instance.node,
|
||||
interruptedRouteMapping[conditionNode]
|
||||
)
|
||||
} else {
|
||||
seqGraph = await agentInstance.agentInterruptToolFunc(seqGraph, nextNodeSeqAgentName)
|
||||
}
|
||||
} else {
|
||||
seqGraph = await agentInstance.agentInterruptToolFunc(seqGraph, nextNodeSeqAgentName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (agentInstance.predecessorAgents) {
|
||||
const predecessorAgents: ISeqAgentNode[] = agentInstance.predecessorAgents
|
||||
|
||||
const edges = []
|
||||
for (const predecessorAgent of predecessorAgents) {
|
||||
// Add start edge and set entry point
|
||||
if (predecessorAgent.name === START) {
|
||||
if (agentInstance.moderations && agentInstance.moderations.length > 0) {
|
||||
try {
|
||||
for (const moderation of agentInstance.moderations) {
|
||||
question = await moderation.checkForViolations(question)
|
||||
}
|
||||
} catch (e) {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, getErrorMessage(e))
|
||||
}
|
||||
}
|
||||
//@ts-ignore
|
||||
seqGraph.addEdge(START, agentInstance.name)
|
||||
} else if (predecessorAgent.type === 'condition') {
|
||||
/*
|
||||
* If current node is Condition Node, AND predecessor is an Interrupted Agent
|
||||
* Don't add conditional edges to the Interrupted Agent, as it will be added separately from the function - agentInterruptToolFunc
|
||||
*/
|
||||
if (!Object.prototype.hasOwnProperty.call(interruptedRouteMapping, predecessorAgent.id)) {
|
||||
prepareConditionalEdges(agentNode.data.id, agentInstance)
|
||||
}
|
||||
} else if (agentNode.data.name === 'seqToolNode') {
|
||||
// Prepare the conditional edges for LLMNode -> ToolNode AND bind the tools to LLMNode
|
||||
prepareLLMToToolEdges(predecessorAgent, agentInstance)
|
||||
createBindModel(predecessorAgent, agentInstance)
|
||||
|
||||
// If current ToolNode has interrupt turned on, add the ToolNode name to interruptToolNodeNames
|
||||
if (agentInstance.node.interrupt) {
|
||||
interruptToolNodeNames.push(agentInstance.name)
|
||||
}
|
||||
} else if (predecessorAgent.name) {
|
||||
// In the scenario when ToolNode -> LLMNode, bind the tools to LLMNode
|
||||
if (agentInstance.type === 'llm' && predecessorAgent.type === 'tool') {
|
||||
createBindModel(agentInstance, predecessorAgent)
|
||||
}
|
||||
|
||||
// Add edge to graph ONLY when predecessor is not an Interrupted Agent
|
||||
if (!predecessorAgent.agentInterruptToolNode) {
|
||||
edges.push(predecessorAgent.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edges can be multiple, in the case of parallel node executions
|
||||
if (edges.length > 1) {
|
||||
//@ts-ignore
|
||||
seqGraph.addEdge(edges, agentInstance.name)
|
||||
} else if (edges.length === 1) {
|
||||
//@ts-ignore
|
||||
seqGraph.addEdge(...edges, agentInstance.name)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error initialize agent nodes - ${getErrorMessage(e)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*** Add conditional edges to graph for condition nodes ***/
|
||||
for (const conditionNodeId in conditionalEdges) {
|
||||
const startConditionEdges = reactflowEdges.filter((edge) => edge.target === conditionNodeId)
|
||||
if (!startConditionEdges.length) continue
|
||||
|
||||
for (const startConditionEdge of startConditionEdges) {
|
||||
const startConditionNode = reactflowNodes.find((node) => node.id === startConditionEdge.source)
|
||||
if (!startConditionNode) continue
|
||||
seqGraph.addConditionalEdges(
|
||||
startConditionNode.data.instance.name,
|
||||
conditionalEdges[conditionNodeId].func,
|
||||
//@ts-ignore
|
||||
conditionalEdges[conditionNodeId].nodes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*** Add conditional edges to graph for LLMNode -> ToolNode ***/
|
||||
for (const llmSourceNodeId in conditionalToolNodes) {
|
||||
const connectedToolNodes = conditionalToolNodes[llmSourceNodeId].toolNodes
|
||||
const sourceNode = conditionalToolNodes[llmSourceNodeId].source
|
||||
|
||||
const routeMessage = (state: ISeqAgentsState) => {
|
||||
const messages = state.messages as unknown as BaseMessage[]
|
||||
const lastMessage = messages[messages.length - 1] as AIMessage
|
||||
|
||||
if (!lastMessage.tool_calls?.length) {
|
||||
return END
|
||||
}
|
||||
|
||||
for (const toolCall of lastMessage.tool_calls) {
|
||||
for (const toolNode of connectedToolNodes) {
|
||||
const tools = (toolNode.node?.tools as StructuredTool[]) || ((toolNode as any).tools as StructuredTool[])
|
||||
if (tools.some((tool) => tool.name === toolCall.name)) {
|
||||
return toolNode.name
|
||||
}
|
||||
}
|
||||
}
|
||||
return END
|
||||
}
|
||||
|
||||
seqGraph.addConditionalEdges(
|
||||
//@ts-ignore
|
||||
sourceNode.name,
|
||||
routeMessage
|
||||
)
|
||||
}
|
||||
|
||||
/*** Add agentflow to pool ***/
|
||||
;(seqGraph as any).signal = options.signal
|
||||
appServer.chatflowPool.add(
|
||||
`${chatflow.id}_${options.chatId}`,
|
||||
seqGraph as any,
|
||||
reactflowNodes.filter((node) => startAgentNodes.map((nd) => nd.id).includes(node.id)),
|
||||
overrideConfig
|
||||
)
|
||||
|
||||
/*** Get memory ***/
|
||||
const startNode = reactflowNodes.find((node: IReactFlowNode) => node.data.name === 'seqStart')
|
||||
let memory = startNode?.data.instance?.checkpointMemory
|
||||
|
||||
try {
|
||||
const graph = seqGraph.compile({ checkpointer: memory, interruptBefore: interruptToolNodeNames as any })
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(logger)
|
||||
const callbacks = await additionalCallbacks(flowNodeData as any, options)
|
||||
const config = { configurable: { thread_id: threadId }, bindModel }
|
||||
|
||||
let humanMsg: { messages: HumanMessage[] | ToolMessage[] } | null = {
|
||||
messages: [new HumanMessage({ content: question })]
|
||||
}
|
||||
|
||||
if (action && action.mapping && question === action.mapping.approve) {
|
||||
humanMsg = null
|
||||
} else if (action && action.mapping && question === action.mapping.reject) {
|
||||
humanMsg = {
|
||||
messages: action.mapping.toolCalls.map((toolCall) => {
|
||||
return new ToolMessage({
|
||||
name: toolCall.name,
|
||||
content: `Tool ${toolCall.name} call denied by user. Acknowledge that, and DONT perform further actions. Only ask if user have other questions`,
|
||||
tool_call_id: toolCall.id!,
|
||||
additional_kwargs: { toolCallsDenied: true }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
return await graph.stream(humanMsg, { callbacks: [loggerHandler, ...callbacks], configurable: config })
|
||||
} catch (e) {
|
||||
logger.error('Error compile graph', e)
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error compile graph - ${getErrorMessage(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
const getSortedDepthNodes = (depthQueue: IDepthQueue) => {
|
||||
// Step 1: Convert the object into an array of [key, value] pairs and sort them by the value
|
||||
const sortedEntries = Object.entries(depthQueue).sort((a, b) => a[1] - b[1])
|
||||
|
||||
// Step 2: Group keys by their depth values
|
||||
const groupedByDepth: Record<number, string[]> = {}
|
||||
sortedEntries.forEach(([key, value]) => {
|
||||
if (!groupedByDepth[value]) {
|
||||
groupedByDepth[value] = []
|
||||
}
|
||||
groupedByDepth[value].push(key)
|
||||
})
|
||||
|
||||
// Step 3: Create the final sorted array with grouped keys
|
||||
const sortedArray: (string | string[])[] = []
|
||||
Object.keys(groupedByDepth)
|
||||
.sort((a, b) => parseInt(a) - parseInt(b))
|
||||
.forEach((depth) => {
|
||||
const items = groupedByDepth[parseInt(depth)]
|
||||
sortedArray.push(...items)
|
||||
})
|
||||
|
||||
return sortedArray.flat()
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ import logger from './logger'
|
||||
import { utilAddChatMessage } from './addChatMesage'
|
||||
import { buildAgentGraph } from './buildAgentGraph'
|
||||
import { getErrorMessage } from '../errors/utils'
|
||||
import { ChatMessage } from '../database/entities/ChatMessage'
|
||||
import { IAction } from 'flowise-components'
|
||||
|
||||
/**
|
||||
* Build Chatflow
|
||||
@@ -174,7 +176,7 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
||||
const endingNodes = getEndingNodes(nodeDependencies, directedGraph, nodes)
|
||||
|
||||
/*** If the graph is an agent graph, build the agent response ***/
|
||||
if (endingNodes.filter((node) => node.data.category === 'Multi Agents').length) {
|
||||
if (endingNodes.filter((node) => node.data.category === 'Multi Agents' || node.data.category === 'Sequential Agents').length) {
|
||||
return await utilBuildAgentResponse(
|
||||
chatflow,
|
||||
isInternal,
|
||||
@@ -292,28 +294,27 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
||||
|
||||
logger.debug(`[server]: Start building chatflow ${chatflowid}`)
|
||||
/*** BFS to traverse from Starting Nodes to Ending Node ***/
|
||||
const reactFlowNodes = await buildFlow(
|
||||
const reactFlowNodes = await buildFlow({
|
||||
startingNodeIds,
|
||||
nodes,
|
||||
edges,
|
||||
reactFlowNodes: nodes,
|
||||
reactFlowEdges: edges,
|
||||
graph,
|
||||
depthQueue,
|
||||
appServer.nodesPool.componentNodes,
|
||||
incomingInput.question,
|
||||
componentNodes: appServer.nodesPool.componentNodes,
|
||||
question: incomingInput.question,
|
||||
chatHistory,
|
||||
chatId,
|
||||
sessionId ?? '',
|
||||
sessionId: sessionId ?? '',
|
||||
chatflowid,
|
||||
appServer.AppDataSource,
|
||||
incomingInput?.overrideConfig,
|
||||
appServer.cachePool,
|
||||
false,
|
||||
undefined,
|
||||
incomingInput.uploads,
|
||||
appDataSource: appServer.AppDataSource,
|
||||
overrideConfig: incomingInput?.overrideConfig,
|
||||
cachePool: appServer.cachePool,
|
||||
isUpsert: false,
|
||||
uploads: incomingInput.uploads,
|
||||
baseURL,
|
||||
socketIO,
|
||||
incomingInput.socketIOClientId
|
||||
)
|
||||
socketIOClientId: incomingInput.socketIOClientId
|
||||
})
|
||||
|
||||
const nodeToExecute =
|
||||
endingNodeIds.length === 1
|
||||
@@ -432,14 +433,14 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
||||
}
|
||||
|
||||
const utilBuildAgentResponse = async (
|
||||
chatflow: IChatFlow,
|
||||
agentflow: IChatFlow,
|
||||
isInternal: boolean,
|
||||
chatId: string,
|
||||
memoryType: string,
|
||||
sessionId: string,
|
||||
userMessageDateTime: Date,
|
||||
fileUploads: IFileUpload[],
|
||||
incomingInput: ICommonObject,
|
||||
incomingInput: IncomingInput,
|
||||
nodes: IReactFlowNode[],
|
||||
edges: IReactFlowEdge[],
|
||||
socketIO?: Server,
|
||||
@@ -447,13 +448,13 @@ const utilBuildAgentResponse = async (
|
||||
) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const streamResults = await buildAgentGraph(chatflow, chatId, sessionId, incomingInput, baseURL, socketIO)
|
||||
const streamResults = await buildAgentGraph(agentflow, chatId, sessionId, incomingInput, isInternal, baseURL, socketIO)
|
||||
if (streamResults) {
|
||||
const { finalResult, agentReasoning } = streamResults
|
||||
const { finalResult, finalAction, sourceDocuments, usedTools, agentReasoning } = streamResults
|
||||
const userMessage: Omit<IChatMessage, 'id'> = {
|
||||
role: 'userMessage',
|
||||
content: incomingInput.question,
|
||||
chatflowid: chatflow.id,
|
||||
chatflowid: agentflow.id,
|
||||
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
||||
chatId,
|
||||
memoryType,
|
||||
@@ -467,23 +468,54 @@ const utilBuildAgentResponse = async (
|
||||
const apiMessage: Omit<IChatMessage, 'id' | 'createdDate'> = {
|
||||
role: 'apiMessage',
|
||||
content: finalResult,
|
||||
chatflowid: chatflow.id,
|
||||
chatflowid: agentflow.id,
|
||||
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
||||
chatId,
|
||||
memoryType,
|
||||
sessionId
|
||||
}
|
||||
if (sourceDocuments.length) apiMessage.sourceDocuments = JSON.stringify(sourceDocuments)
|
||||
if (usedTools.length) apiMessage.usedTools = JSON.stringify(usedTools)
|
||||
if (agentReasoning.length) apiMessage.agentReasoning = JSON.stringify(agentReasoning)
|
||||
if (Object.keys(finalAction).length) apiMessage.action = JSON.stringify(finalAction)
|
||||
const chatMessage = await utilAddChatMessage(apiMessage)
|
||||
|
||||
await appServer.telemetry.sendTelemetry('prediction_sent', {
|
||||
await appServer.telemetry.sendTelemetry('agentflow_prediction_sent', {
|
||||
version: await getAppVersion(),
|
||||
chatlowId: chatflow.id,
|
||||
agentflowId: agentflow.id,
|
||||
chatId,
|
||||
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
})
|
||||
|
||||
// Find the previous chat message with the same action id and remove the action
|
||||
if (incomingInput.action && Object.keys(incomingInput.action).length) {
|
||||
let query = await appServer.AppDataSource.getRepository(ChatMessage)
|
||||
.createQueryBuilder('chat_message')
|
||||
.where('chat_message.chatId = :chatId', { chatId })
|
||||
.orWhere('chat_message.sessionId = :sessionId', { sessionId })
|
||||
.orderBy('chat_message.createdDate', 'DESC')
|
||||
.getMany()
|
||||
|
||||
for (const result of query) {
|
||||
if (result.action) {
|
||||
try {
|
||||
const action: IAction = JSON.parse(result.action)
|
||||
if (action.id === incomingInput.action.id) {
|
||||
const newChatMessage = new ChatMessage()
|
||||
Object.assign(newChatMessage, result)
|
||||
newChatMessage.action = null
|
||||
const cm = await appServer.AppDataSource.getRepository(ChatMessage).create(newChatMessage)
|
||||
await appServer.AppDataSource.getRepository(ChatMessage).save(cm)
|
||||
break
|
||||
}
|
||||
} catch (e) {
|
||||
// error converting action to JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare response
|
||||
let result: ICommonObject = {}
|
||||
result.text = finalResult
|
||||
@@ -493,13 +525,7 @@ const utilBuildAgentResponse = async (
|
||||
if (sessionId) result.sessionId = sessionId
|
||||
if (memoryType) result.memoryType = memoryType
|
||||
if (agentReasoning.length) result.agentReasoning = agentReasoning
|
||||
|
||||
await appServer.telemetry.sendTelemetry('graph_compiled', {
|
||||
version: await getAppVersion(),
|
||||
graphId: chatflow.id,
|
||||
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
})
|
||||
if (Object.keys(finalAction).length) result.action = finalAction
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -18,7 +18,15 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<any> =>
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowid} not found`)
|
||||
}
|
||||
|
||||
const uploadAllowedNodes = ['llmChain', 'conversationChain', 'reactAgentChat', 'conversationalAgent', 'toolAgent', 'supervisor']
|
||||
const uploadAllowedNodes = [
|
||||
'llmChain',
|
||||
'conversationChain',
|
||||
'reactAgentChat',
|
||||
'conversationalAgent',
|
||||
'toolAgent',
|
||||
'supervisor',
|
||||
'seqStart'
|
||||
]
|
||||
const uploadProcessingNodes = ['chatOpenAI', 'chatAnthropic', 'awsChatBedrock', 'azureChatOpenAI', 'chatGoogleGenerativeAI']
|
||||
|
||||
const flowObj = JSON.parse(chatflow.flowData)
|
||||
|
||||
@@ -269,7 +269,8 @@ export const getEndingNodes = (
|
||||
endingNodeData.category !== 'Chains' &&
|
||||
endingNodeData.category !== 'Agents' &&
|
||||
endingNodeData.category !== 'Engine' &&
|
||||
endingNodeData.category !== 'Multi Agents'
|
||||
endingNodeData.category !== 'Multi Agents' &&
|
||||
endingNodeData.category !== 'Sequential Agents'
|
||||
) {
|
||||
error = new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Ending node must be either a Chain or Agent or Engine`)
|
||||
continue
|
||||
@@ -416,42 +417,55 @@ const checkIfDocLoaderShouldBeIgnored = (
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Build langchain from start to end
|
||||
* @param {string[]} startingNodeIds
|
||||
* @param {IReactFlowNode[]} reactFlowNodes
|
||||
* @param {INodeDirectedGraph} graph
|
||||
* @param {IDepthQueue} depthQueue
|
||||
* @param {IComponentNodes} componentNodes
|
||||
* @param {string} question
|
||||
* @param {string} chatId
|
||||
* @param {string} chatflowid
|
||||
* @param {DataSource} appDataSource
|
||||
* @param {ICommonObject} overrideConfig
|
||||
* @param {CachePool} cachePool
|
||||
*/
|
||||
export const buildFlow = async (
|
||||
startingNodeIds: string[],
|
||||
reactFlowNodes: IReactFlowNode[],
|
||||
reactFlowEdges: IReactFlowEdge[],
|
||||
graph: INodeDirectedGraph,
|
||||
depthQueue: IDepthQueue,
|
||||
componentNodes: IComponentNodes,
|
||||
question: string,
|
||||
chatHistory: IMessage[],
|
||||
chatId: string,
|
||||
sessionId: string,
|
||||
chatflowid: string,
|
||||
appDataSource: DataSource,
|
||||
overrideConfig?: ICommonObject,
|
||||
cachePool?: CachePool,
|
||||
isUpsert?: boolean,
|
||||
stopNodeId?: string,
|
||||
uploads?: IFileUpload[],
|
||||
baseURL?: string,
|
||||
socketIO?: Server,
|
||||
type BuildFlowParams = {
|
||||
startingNodeIds: string[]
|
||||
reactFlowNodes: IReactFlowNode[]
|
||||
reactFlowEdges: IReactFlowEdge[]
|
||||
graph: INodeDirectedGraph
|
||||
depthQueue: IDepthQueue
|
||||
componentNodes: IComponentNodes
|
||||
question: string
|
||||
chatHistory: IMessage[]
|
||||
chatId: string
|
||||
sessionId: string
|
||||
chatflowid: string
|
||||
appDataSource: DataSource
|
||||
overrideConfig?: ICommonObject
|
||||
cachePool?: CachePool
|
||||
isUpsert?: boolean
|
||||
stopNodeId?: string
|
||||
uploads?: IFileUpload[]
|
||||
baseURL?: string
|
||||
socketIO?: Server
|
||||
socketIOClientId?: string
|
||||
) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build flow from start to end
|
||||
* @param {BuildFlowParams} params
|
||||
*/
|
||||
export const buildFlow = async ({
|
||||
startingNodeIds,
|
||||
reactFlowNodes,
|
||||
reactFlowEdges,
|
||||
graph,
|
||||
depthQueue,
|
||||
componentNodes,
|
||||
question,
|
||||
chatHistory,
|
||||
chatId,
|
||||
sessionId,
|
||||
chatflowid,
|
||||
appDataSource,
|
||||
overrideConfig,
|
||||
cachePool,
|
||||
isUpsert,
|
||||
stopNodeId,
|
||||
uploads,
|
||||
baseURL,
|
||||
socketIO,
|
||||
socketIOClientId
|
||||
}: BuildFlowParams) => {
|
||||
const flowNodes = cloneDeep(reactFlowNodes)
|
||||
|
||||
let upsertHistory: Record<string, any> = {}
|
||||
@@ -779,9 +793,15 @@ export const getVariableValue = (
|
||||
const variablePaths = Object.keys(variableDict)
|
||||
variablePaths.sort() // Sort by length of variable path because longer path could possibly contains nested variable
|
||||
variablePaths.forEach((path) => {
|
||||
const variableValue = variableDict[path]
|
||||
let variableValue: object | string = variableDict[path]
|
||||
// Replace all occurrence
|
||||
if (typeof variableValue === 'object') {
|
||||
// Just get the id of variableValue object if it is agentflow node, to avoid circular JSON error
|
||||
if (Object.prototype.hasOwnProperty.call(variableValue, 'predecessorAgents')) {
|
||||
const nodeId = variableValue['id']
|
||||
variableValue = { id: nodeId }
|
||||
}
|
||||
|
||||
const stringifiedValue = JSON.stringify(JSON.stringify(variableValue))
|
||||
if (stringifiedValue.startsWith('"') && stringifiedValue.endsWith('"')) {
|
||||
// get rid of the double quotes
|
||||
|
||||
@@ -117,24 +117,24 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||
|
||||
const { startingNodeIds, depthQueue } = getStartingNodes(filteredGraph, stopNodeId)
|
||||
|
||||
const upsertedResult = await buildFlow(
|
||||
const upsertedResult = await buildFlow({
|
||||
startingNodeIds,
|
||||
nodes,
|
||||
edges,
|
||||
filteredGraph,
|
||||
reactFlowNodes: nodes,
|
||||
reactFlowEdges: edges,
|
||||
graph: filteredGraph,
|
||||
depthQueue,
|
||||
appServer.nodesPool.componentNodes,
|
||||
incomingInput.question,
|
||||
componentNodes: appServer.nodesPool.componentNodes,
|
||||
question: incomingInput.question,
|
||||
chatHistory,
|
||||
chatId,
|
||||
sessionId ?? '',
|
||||
sessionId: sessionId ?? '',
|
||||
chatflowid,
|
||||
appServer.AppDataSource,
|
||||
incomingInput?.overrideConfig,
|
||||
appServer.cachePool,
|
||||
appDataSource: appServer.AppDataSource,
|
||||
overrideConfig: incomingInput?.overrideConfig,
|
||||
cachePool: appServer.cachePool,
|
||||
isUpsert,
|
||||
stopNodeId
|
||||
)
|
||||
})
|
||||
|
||||
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.data.id))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user