From 3d845c40a779cb9652a692831c9c50c024d590c3 Mon Sep 17 00:00:00 2001 From: Vertirax <41989706+Vertirax@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:42:38 +0200 Subject: [PATCH] Fixed Decision Flow - the fixed decision flow is useful to define a predetermined flow - you add the tool and attach choices to it, which then gets matched to the AI's input once your tool is called - added FixedDecisionTool and FixedDecisionChoice with icons - added the returned text to the setMessages since the returned answer wasn't updated, probably because it wasn't sent through the websocket --- .../FixedDecision/FixedDecisionChoice.ts | 3 + .../tools/FixedDecision/FixedDecisionTool.ts | 99 +++++++++++++++++++ .../tools/FixedDecision/fixedDecisionTool.svg | 3 + .../FixedDecisionChoice.ts | 48 +++++++++ .../fixedDecisionChoice.svg | 4 + .../ui/src/views/chatmessage/ChatMessage.jsx | 2 + 6 files changed, 159 insertions(+) create mode 100644 packages/components/nodes/tools/FixedDecision/FixedDecisionChoice.ts create mode 100644 packages/components/nodes/tools/FixedDecision/FixedDecisionTool.ts create mode 100644 packages/components/nodes/tools/FixedDecision/fixedDecisionTool.svg create mode 100644 packages/components/nodes/tools/FixedDecisionChoice/FixedDecisionChoice.ts create mode 100644 packages/components/nodes/tools/FixedDecisionChoice/fixedDecisionChoice.svg diff --git a/packages/components/nodes/tools/FixedDecision/FixedDecisionChoice.ts b/packages/components/nodes/tools/FixedDecision/FixedDecisionChoice.ts new file mode 100644 index 00000000000..d5b7a4d8c27 --- /dev/null +++ b/packages/components/nodes/tools/FixedDecision/FixedDecisionChoice.ts @@ -0,0 +1,3 @@ +export class FixedDecisionChoice { + constructor(public input: string, public response: string) {} +} diff --git a/packages/components/nodes/tools/FixedDecision/FixedDecisionTool.ts b/packages/components/nodes/tools/FixedDecision/FixedDecisionTool.ts new file mode 100644 index 00000000000..2849cd4251c --- /dev/null +++ b/packages/components/nodes/tools/FixedDecision/FixedDecisionTool.ts @@ -0,0 +1,99 @@ +import { z } from 'zod' +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { FixedDecisionChoice } from './FixedDecisionChoice' +import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager' +import { DynamicStructuredTool } from '@langchain/core/tools' +import { getBaseClasses } from '../../../src' + +class FixedDecision_Tools implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Fixed Decision Tool' + this.name = 'fixedDecisionTool' + this.version = 1.0 + this.type = 'FixedDecisionTool' + this.icon = 'fixedDecisionTool.svg' + this.category = 'Tools' + this.description = 'Use as tool for agent that needs to run simple fixed defined flows' + this.baseClasses = [this.type, 'Tool', ...getBaseClasses(DynamicStructuredTool)] + this.inputs = [ + { + label: 'Tool Name', + name: 'name', + type: 'string', + placeholder: 'fixed_decision' + }, + { + label: 'Tool Description', + name: 'description', + type: 'string', + description: 'When the agent should use it for fixed decision(s)', + rows: 3, + placeholder: 'Returns the matching choice for the prompt' + }, + { + label: 'Choices', + name: 'choices', + type: 'FixedDecisionChoice', + list: true + }, + { + label: 'Return Direct', + name: 'returnDirect', + type: 'boolean', + description: 'Leave it switched on for exact response from the matched choice', + default: true, + optional: true + }, + { + label: 'Fallback Response', + name: 'fallbackResponse', + type: 'string', + description: 'This is returned when no matching input is found', + rows: 4, + default: 'No matching input found.', + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const name = nodeData.inputs?.name as string + const description = nodeData.inputs?.description as string + const choices = nodeData.inputs?.choices as FixedDecisionChoice[] + const returnDirect = nodeData.inputs?.returnDirect as boolean + const fallbackResponse = nodeData.inputs?.fallbackResponse as string + + const input = { + name, + description + } as any + + if (returnDirect) input.returnDirect = returnDirect + + const func = async ({ input }: { input: string }, _: CallbackManagerForToolRun) => { + // uses `includes` for more permissive matching + const choice = choices.filter((choice) => choice.input.toLowerCase().includes(input.toLowerCase())) + + return choice?.[0]?.response ?? fallbackResponse + } + + const schema = z.object({ + input: z.string().describe('query to look up in tool') + }) + + return new DynamicStructuredTool({ ...input, func, schema }) + } +} + +module.exports = { nodeClass: FixedDecision_Tools } diff --git a/packages/components/nodes/tools/FixedDecision/fixedDecisionTool.svg b/packages/components/nodes/tools/FixedDecision/fixedDecisionTool.svg new file mode 100644 index 00000000000..373c75d8836 --- /dev/null +++ b/packages/components/nodes/tools/FixedDecision/fixedDecisionTool.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/components/nodes/tools/FixedDecisionChoice/FixedDecisionChoice.ts b/packages/components/nodes/tools/FixedDecisionChoice/FixedDecisionChoice.ts new file mode 100644 index 00000000000..2a2429feb92 --- /dev/null +++ b/packages/components/nodes/tools/FixedDecisionChoice/FixedDecisionChoice.ts @@ -0,0 +1,48 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { FixedDecisionChoice } from '../FixedDecision/FixedDecisionChoice' + +class FixedDecisionChoiceNode implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Fixed Decision Choice' + this.name = 'fixedDecisionChoice' + this.version = 1.0 + this.type = 'FixedDecisionChoice' + this.icon = 'fixedDecisionChoice.svg' + this.category = 'Tools' + this.description = 'Use choice(s) with Fixed Decision Tool to created a fixed decision flow' + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Input', + name: 'input', + type: 'string', + rows: 5, + description: "This will be compared to the tool's input using an inclusive matching method", + placeholder: 'What is love?' + }, + { + label: 'Response', + name: 'response', + type: 'string', + rows: 5, + placeholder: "Baby don't hurt me." + } + ] + } + + async init(nodeData: INodeData): Promise { + return new FixedDecisionChoice(nodeData.inputs?.input, nodeData.inputs?.response) + } +} + +module.exports = { nodeClass: FixedDecisionChoiceNode } diff --git a/packages/components/nodes/tools/FixedDecisionChoice/fixedDecisionChoice.svg b/packages/components/nodes/tools/FixedDecisionChoice/fixedDecisionChoice.svg new file mode 100644 index 00000000000..73719d80cce --- /dev/null +++ b/packages/components/nodes/tools/FixedDecisionChoice/fixedDecisionChoice.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/views/chatmessage/ChatMessage.jsx b/packages/ui/src/views/chatmessage/ChatMessage.jsx index 4678f210316..0c391da1b52 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.jsx +++ b/packages/ui/src/views/chatmessage/ChatMessage.jsx @@ -425,6 +425,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews let allMessages = [...cloneDeep(prevMessages)] if (allMessages[allMessages.length - 1].type === 'apiMessage') { allMessages[allMessages.length - 1].id = data?.chatMessageId + + if (data.text) allMessages[allMessages.length - 1].message = data.text } return allMessages })