mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
Feature/lang graph (#2319)
* add langgraph * datasource: initial commit * datasource: datasource details and chunks * datasource: Document Store Node * more changes * Document Store - Base functionality * Document Store Loader Component * Document Store Loader Component * before merging the modularity PR * after merging the modularity PR * preview mode * initial draft PR * fixes * minor updates and fixes * preview with loader and splitter * preview with credential * show stored chunks * preview update... * edit config * save, preview and other changes * save, preview and other changes * save, process and other changes * save, process and other changes * alpha1 - for internal testing * rerouting urls * bug fix on new leader create * pagination support for chunks * delete document store * Update pnpm-lock.yaml * doc store card view * Update store files to use updated storage functions, Document Store Table View and other changes * ui changes * add expanded chunk dialog, improve ui * change throw Error to InternalError * Bug Fixes and removal of subFolder, adding of view chunks for store * lint fixes * merge changes * DocumentStoreStatus component * ui changes for doc store * add remove metadata key field, add custom document loader * add chatflows used doc store chips * add types/interfaces to DocumentStore Services * document loader list dialog title bar color change * update interfaces * Whereused Chatflow Name and Added chunkNo to retain order of created chunks. * use typeorm order chunkNo, ui changes * update tabler icons react * cleanup agents * add pysandbox tool * add abort functionality, loading next agent * add empty view svg * update chatflow tool with chatId * rename to agentflows * update worker for prompt input values * update dashboard to agentflows, agentcanvas * fix marketplace use template * add agentflow templates * resolve merge conflict * update baseURL --------- Co-authored-by: vinodkiran <vinodkiran@usa.net> Co-authored-by: Vinod Paidimarry <vinodkiran@outlook.in>
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
import { flatten } from 'lodash'
|
||||
import { RunnableSequence, RunnablePassthrough, RunnableConfig } from '@langchain/core/runnables'
|
||||
import { ChatPromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate } from '@langchain/core/prompts'
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models'
|
||||
import { HumanMessage } from '@langchain/core/messages'
|
||||
import { formatToOpenAIToolMessages } from 'langchain/agents/format_scratchpad/openai_tools'
|
||||
import { type ToolsAgentStep } from 'langchain/agents/openai/output_parser'
|
||||
import { INode, INodeData, INodeParams, IMultiAgentNode, ITeamState, ICommonObject, MessageContentImageUrl } from '../../../src/Interface'
|
||||
import { ToolCallingAgentOutputParser, AgentExecutor } from '../../../src/agents'
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers'
|
||||
import { getInputVariables, handleEscapeCharacters } from '../../../src/utils'
|
||||
|
||||
const examplePrompt = 'You are a research assistant who can search for up-to-date info using search engine.'
|
||||
|
||||
class Worker_MultiAgents implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs?: INodeParams[]
|
||||
badge?: string
|
||||
|
||||
constructor() {
|
||||
this.label = 'Worker'
|
||||
this.name = 'worker'
|
||||
this.version = 1.0
|
||||
this.type = 'Worker'
|
||||
this.icon = 'worker.svg'
|
||||
this.category = 'Multi Agents'
|
||||
this.baseClasses = [this.type]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Worker Name',
|
||||
name: 'workerName',
|
||||
type: 'string',
|
||||
placeholder: 'Worker'
|
||||
},
|
||||
{
|
||||
label: 'Worker Prompt',
|
||||
name: 'workerPrompt',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
default: examplePrompt
|
||||
},
|
||||
{
|
||||
label: 'Tools',
|
||||
name: 'tools',
|
||||
type: 'Tool',
|
||||
list: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Supervisor',
|
||||
name: 'supervisor',
|
||||
type: 'Supervisor'
|
||||
},
|
||||
{
|
||||
label: 'Tool Calling Chat Model',
|
||||
name: 'model',
|
||||
type: 'BaseChatModel',
|
||||
optional: true,
|
||||
description: `Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, ChatVertexAI, GroqChat. If not specified, supervisor's model will be used`
|
||||
},
|
||||
{
|
||||
label: 'Format Prompt Values',
|
||||
name: 'promptValues',
|
||||
type: 'json',
|
||||
optional: true,
|
||||
acceptVariable: true,
|
||||
list: true
|
||||
},
|
||||
{
|
||||
label: 'Max Iterations',
|
||||
name: 'maxIterations',
|
||||
type: 'number',
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
let tools = nodeData.inputs?.tools
|
||||
tools = flatten(tools)
|
||||
let workerPrompt = nodeData.inputs?.workerPrompt as string
|
||||
const workerLabel = nodeData.inputs?.workerName as string
|
||||
const supervisor = nodeData.inputs?.supervisor as IMultiAgentNode
|
||||
const maxIterations = nodeData.inputs?.maxIterations as string
|
||||
const model = nodeData.inputs?.model as BaseChatModel
|
||||
const promptValuesStr = nodeData.inputs?.promptValues
|
||||
|
||||
if (!workerLabel) throw new Error('Worker name is required!')
|
||||
const workerName = workerLabel.toLowerCase().replace(/\s/g, '_').trim()
|
||||
|
||||
if (!workerPrompt) throw new Error('Worker prompt is required!')
|
||||
|
||||
let workerInputVariablesValues: ICommonObject = {}
|
||||
if (promptValuesStr) {
|
||||
try {
|
||||
workerInputVariablesValues = typeof promptValuesStr === 'object' ? promptValuesStr : JSON.parse(promptValuesStr)
|
||||
} catch (exception) {
|
||||
throw new Error("Invalid JSON in the Worker's Prompt Input Values: " + exception)
|
||||
}
|
||||
}
|
||||
workerInputVariablesValues = handleEscapeCharacters(workerInputVariablesValues, true)
|
||||
|
||||
const llm = model || (supervisor.llm as BaseChatModel)
|
||||
const multiModalMessageContent = supervisor?.multiModalMessageContent || []
|
||||
|
||||
const abortControllerSignal = options.signal as AbortController
|
||||
const workerInputVariables = getInputVariables(workerPrompt)
|
||||
|
||||
if (!workerInputVariables.every((element) => Object.keys(workerInputVariablesValues).includes(element))) {
|
||||
throw new Error('Worker input variables values are not provided!')
|
||||
}
|
||||
|
||||
const agent = await createAgent(
|
||||
llm,
|
||||
[...tools],
|
||||
workerPrompt,
|
||||
multiModalMessageContent,
|
||||
workerInputVariablesValues,
|
||||
maxIterations,
|
||||
{
|
||||
sessionId: options.sessionId,
|
||||
chatId: options.chatId,
|
||||
input
|
||||
}
|
||||
)
|
||||
|
||||
const workerNode = async (state: ITeamState, config: RunnableConfig) =>
|
||||
await agentNode(
|
||||
{
|
||||
state,
|
||||
agent: agent,
|
||||
name: workerName,
|
||||
abortControllerSignal
|
||||
},
|
||||
config
|
||||
)
|
||||
|
||||
const returnOutput: IMultiAgentNode = {
|
||||
node: workerNode,
|
||||
name: workerName,
|
||||
label: workerLabel,
|
||||
type: 'worker',
|
||||
workerPrompt,
|
||||
workerInputVariables,
|
||||
parentSupervisorName: supervisor.name ?? 'supervisor'
|
||||
}
|
||||
|
||||
return returnOutput
|
||||
}
|
||||
}
|
||||
|
||||
async function createAgent(
|
||||
llm: BaseChatModel,
|
||||
tools: any[],
|
||||
systemPrompt: string,
|
||||
multiModalMessageContent: MessageContentImageUrl[],
|
||||
workerInputVariablesValues: ICommonObject,
|
||||
maxIterations?: string,
|
||||
flowObj?: { sessionId?: string; chatId?: string; input?: string }
|
||||
): Promise<AgentExecutor | RunnableSequence> {
|
||||
if (tools.length) {
|
||||
const combinedPrompt =
|
||||
systemPrompt +
|
||||
'\nWork autonomously according to your specialty, using the tools available to you.' +
|
||||
' Do not ask for clarification.' +
|
||||
' Your other team members (and other teams) will collaborate with you with their own specialties.' +
|
||||
' You are chosen for a reason! You are one of the following team members: {team_members}.'
|
||||
|
||||
//const toolNames = tools.length ? tools.map((t) => t.name).join(', ') : ''
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', combinedPrompt],
|
||||
new MessagesPlaceholder('messages'),
|
||||
new MessagesPlaceholder('agent_scratchpad')
|
||||
/* Gettind rid of this for now because other LLMs dont support system message at later stage
|
||||
[
|
||||
'system',
|
||||
[
|
||||
'Supervisor instructions: {instructions}\n' + tools.length
|
||||
? `Remember, you individually can only use these tools: ${toolNames}`
|
||||
: '' + '\n\nEnd if you have already completed the requested task. Communicate the work completed.'
|
||||
].join('\n')
|
||||
]*/
|
||||
])
|
||||
|
||||
if (multiModalMessageContent.length) {
|
||||
const msg = HumanMessagePromptTemplate.fromTemplate([...multiModalMessageContent])
|
||||
prompt.promptMessages.splice(1, 0, msg)
|
||||
}
|
||||
|
||||
if (llm.bindTools === undefined) {
|
||||
throw new Error(`This agent only compatible with function calling models.`)
|
||||
}
|
||||
const modelWithTools = llm.bindTools(tools)
|
||||
|
||||
const agent = RunnableSequence.from([
|
||||
RunnablePassthrough.assign({
|
||||
//@ts-ignore
|
||||
agent_scratchpad: (input: { steps: ToolsAgentStep[] }) => formatToOpenAIToolMessages(input.steps)
|
||||
}),
|
||||
RunnablePassthrough.assign(transformObjectPropertyToFunction(workerInputVariablesValues)),
|
||||
prompt,
|
||||
modelWithTools,
|
||||
new ToolCallingAgentOutputParser()
|
||||
])
|
||||
|
||||
const executor = AgentExecutor.fromAgentAndTools({
|
||||
agent: agent,
|
||||
tools,
|
||||
sessionId: flowObj?.sessionId,
|
||||
chatId: flowObj?.chatId,
|
||||
input: flowObj?.input,
|
||||
verbose: process.env.DEBUG === 'true' ? true : false,
|
||||
maxIterations: maxIterations ? parseFloat(maxIterations) : undefined
|
||||
})
|
||||
return executor
|
||||
} else {
|
||||
const combinedPrompt =
|
||||
systemPrompt +
|
||||
'\nWork autonomously according to your specialty, using the tools available to you.' +
|
||||
' Do not ask for clarification.' +
|
||||
' Your other team members (and other teams) will collaborate with you with their own specialties.' +
|
||||
' You are chosen for a reason! You are one of the following team members: {team_members}.'
|
||||
|
||||
const prompt = ChatPromptTemplate.fromMessages([['system', combinedPrompt], new MessagesPlaceholder('messages')])
|
||||
if (multiModalMessageContent.length) {
|
||||
const msg = HumanMessagePromptTemplate.fromTemplate([...multiModalMessageContent])
|
||||
prompt.promptMessages.splice(1, 0, msg)
|
||||
}
|
||||
const conversationChain = RunnableSequence.from([
|
||||
RunnablePassthrough.assign(transformObjectPropertyToFunction(workerInputVariablesValues)),
|
||||
prompt,
|
||||
llm,
|
||||
new StringOutputParser()
|
||||
])
|
||||
return conversationChain
|
||||
}
|
||||
}
|
||||
|
||||
async function agentNode(
|
||||
{
|
||||
state,
|
||||
agent,
|
||||
name,
|
||||
abortControllerSignal
|
||||
}: { state: ITeamState; agent: AgentExecutor | RunnableSequence; name: string; abortControllerSignal: AbortController },
|
||||
config: RunnableConfig
|
||||
) {
|
||||
try {
|
||||
if (abortControllerSignal.signal.aborted) {
|
||||
throw new Error('Aborted!')
|
||||
}
|
||||
const result = await agent.invoke({ ...state, signal: abortControllerSignal.signal }, config)
|
||||
const additional_kwargs: ICommonObject = {}
|
||||
if (result.usedTools) {
|
||||
additional_kwargs.usedTools = result.usedTools
|
||||
}
|
||||
if (result.sourceDocuments) {
|
||||
additional_kwargs.sourceDocuments = result.sourceDocuments
|
||||
}
|
||||
return {
|
||||
messages: [
|
||||
new HumanMessage({
|
||||
content: typeof result === 'string' ? result : result.output,
|
||||
name,
|
||||
additional_kwargs: Object.keys(additional_kwargs).length ? additional_kwargs : undefined
|
||||
})
|
||||
]
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Aborted!')
|
||||
}
|
||||
}
|
||||
|
||||
const transformObjectPropertyToFunction = (obj: ICommonObject) => {
|
||||
const transformedObject: ICommonObject = {}
|
||||
|
||||
for (const key in obj) {
|
||||
transformedObject[key] = () => obj[key]
|
||||
}
|
||||
|
||||
return transformedObject
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Worker_MultiAgents }
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>
|
||||
|
After Width: | Height: | Size: 382 B |
Reference in New Issue
Block a user