diff --git a/packages/components/nodes/agents/XMLAgent/XMLAgent.ts b/packages/components/nodes/agents/XMLAgent/XMLAgent.ts
new file mode 100644
index 00000000..49109947
--- /dev/null
+++ b/packages/components/nodes/agents/XMLAgent/XMLAgent.ts
@@ -0,0 +1,203 @@
+import { flatten } from 'lodash'
+import { ChainValues } from '@langchain/core/utils/types'
+import { AgentStep } from '@langchain/core/agents'
+import { RunnableSequence } from '@langchain/core/runnables'
+import { ChatOpenAI } from '@langchain/openai'
+import { Tool } from '@langchain/core/tools'
+import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts'
+import { XMLAgentOutputParser } from 'langchain/agents/xml/output_parser'
+import { formatLogToMessage } from 'langchain/agents/format_scratchpad/log_to_message'
+import { getBaseClasses } from '../../../src/utils'
+import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
+import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
+import { AgentExecutor } from '../../../src/agents'
+//import { AgentExecutor } from "langchain/agents";
+
+const defaultSystemMessage = `You are a helpful assistant. Help the user answer any questions.
+
+You have access to the following tools:
+
+{tools}
+
+In order to use a tool, you can use and tags. You will then get back a response in the form
+For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond:
+
+searchweather in SF
+64 degrees
+
+When you are done, respond with a final answer between . For example:
+
+The weather in SF is 64 degrees
+
+Begin!
+
+Previous Conversation:
+{chat_history}
+
+Question: {input}
+{agent_scratchpad}`
+
+class XMLAgent_Agents implements INode {
+ label: string
+ name: string
+ version: number
+ description: string
+ type: string
+ icon: string
+ category: string
+ baseClasses: string[]
+ inputs: INodeParams[]
+ sessionId?: string
+
+ constructor(fields?: { sessionId?: string }) {
+ this.label = 'XML Agent'
+ this.name = 'xmlAgent'
+ this.version = 1.0
+ this.type = 'XMLAgent'
+ this.category = 'Agents'
+ this.icon = 'xmlagent.svg'
+ this.description = `Agent that is designed for LLMs that are good for reasoning/writing XML (e.g: Anthropic Claude)`
+ this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)]
+ this.inputs = [
+ {
+ label: 'Tools',
+ name: 'tools',
+ type: 'Tool',
+ list: true
+ },
+ {
+ label: 'Memory',
+ name: 'memory',
+ type: 'BaseChatMemory'
+ },
+ {
+ label: 'Chat Model',
+ name: 'model',
+ type: 'BaseChatModel'
+ },
+ {
+ label: 'System Message',
+ name: 'systemMessage',
+ type: 'string',
+ warning: 'Prompt must include input variables: {tools}, {chat_history}, {input} and {agent_scratchpad}',
+ rows: 4,
+ default: defaultSystemMessage,
+ additionalParams: true
+ }
+ ]
+ this.sessionId = fields?.sessionId
+ }
+
+ async init(): Promise {
+ return null
+ }
+
+ async run(nodeData: INodeData, input: string, options: ICommonObject): Promise {
+ const memory = nodeData.inputs?.memory as FlowiseMemory
+ const executor = await prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
+
+ const loggerHandler = new ConsoleCallbackHandler(options.logger)
+ const callbacks = await additionalCallbacks(nodeData, options)
+
+ let res: ChainValues = {}
+ let sourceDocuments: ICommonObject[] = []
+
+ if (options.socketIO && options.socketIOClientId) {
+ const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
+ res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] })
+ if (res.sourceDocuments) {
+ options.socketIO.to(options.socketIOClientId).emit('sourceDocuments', flatten(res.sourceDocuments))
+ sourceDocuments = res.sourceDocuments
+ }
+ } else {
+ res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] })
+ if (res.sourceDocuments) {
+ sourceDocuments = res.sourceDocuments
+ }
+ }
+
+ await memory.addChatMessages(
+ [
+ {
+ text: input,
+ type: 'userMessage'
+ },
+ {
+ text: res?.output,
+ type: 'apiMessage'
+ }
+ ],
+ this.sessionId
+ )
+
+ return sourceDocuments.length ? { text: res?.output, sourceDocuments: flatten(sourceDocuments) } : res?.output
+ }
+}
+
+const prepareAgent = async (
+ nodeData: INodeData,
+ flowObj: { sessionId?: string; chatId?: string; input?: string },
+ chatHistory: IMessage[] = []
+) => {
+ const model = nodeData.inputs?.model as ChatOpenAI
+ const memory = nodeData.inputs?.memory as FlowiseMemory
+ const systemMessage = nodeData.inputs?.systemMessage as string
+ let tools = nodeData.inputs?.tools
+ tools = flatten(tools)
+ const inputKey = memory.inputKey ? memory.inputKey : 'input'
+ const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history'
+
+ let promptMessage = systemMessage ? systemMessage : defaultSystemMessage
+ if (memory.memoryKey) promptMessage = promptMessage.replaceAll('{chat_history}', `{${memory.memoryKey}}`)
+ if (memory.inputKey) promptMessage = promptMessage.replaceAll('{input}', `{${memory.inputKey}}`)
+
+ const prompt = ChatPromptTemplate.fromMessages([
+ HumanMessagePromptTemplate.fromTemplate(promptMessage),
+ new MessagesPlaceholder('agent_scratchpad')
+ ])
+
+ const missingVariables = ['tools', 'agent_scratchpad'].filter((v) => !prompt.inputVariables.includes(v))
+
+ if (missingVariables.length > 0) {
+ throw new Error(`Provided prompt is missing required input variables: ${JSON.stringify(missingVariables)}`)
+ }
+
+ const llmWithStop = model.bind({ stop: ['', ''] })
+
+ const messages = (await memory.getChatMessages(flowObj.sessionId, false, chatHistory)) as IMessage[]
+ let chatHistoryMsgTxt = ''
+ for (const message of messages) {
+ if (message.type === 'apiMessage') {
+ chatHistoryMsgTxt += `\\nAI:${message.message}`
+ } else if (message.type === 'userMessage') {
+ chatHistoryMsgTxt += `\\nHuman:${message.message}`
+ }
+ }
+
+ const runnableAgent = RunnableSequence.from([
+ {
+ [inputKey]: (i: { input: string; tools: Tool[]; steps: AgentStep[] }) => i.input,
+ agent_scratchpad: (i: { input: string; tools: Tool[]; steps: AgentStep[] }) => formatLogToMessage(i.steps),
+ tools: (_: { input: string; tools: Tool[]; steps: AgentStep[] }) =>
+ tools.map((tool: Tool) => `${tool.name}: ${tool.description}`),
+ [memoryKey]: (_: { input: string; tools: Tool[]; steps: AgentStep[] }) => chatHistoryMsgTxt
+ },
+ prompt,
+ llmWithStop,
+ new XMLAgentOutputParser()
+ ])
+
+ const executor = AgentExecutor.fromAgentAndTools({
+ agent: runnableAgent,
+ tools,
+ sessionId: flowObj?.sessionId,
+ chatId: flowObj?.chatId,
+ input: flowObj?.input,
+ isXML: true,
+ verbose: process.env.DEBUG === 'true' ? true : false
+ })
+
+ return executor
+}
+
+module.exports = { nodeClass: XMLAgent_Agents }
diff --git a/packages/components/nodes/agents/XMLAgent/xmlagent.svg b/packages/components/nodes/agents/XMLAgent/xmlagent.svg
new file mode 100644
index 00000000..d1b5f708
--- /dev/null
+++ b/packages/components/nodes/agents/XMLAgent/xmlagent.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts
index 85586d14..251bd24a 100644
--- a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts
+++ b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts
@@ -95,6 +95,8 @@ class AWSChatBedrock_ChatModels implements INode {
name: 'model',
type: 'options',
options: [
+ { label: 'anthropic.claude-3-sonnet', name: 'anthropic.claude-3-sonnet-20240229-v1:0' },
+ { label: 'anthropic.claude-instant-v1', name: 'anthropic.claude-instant-v1' },
{ label: 'anthropic.claude-instant-v1', name: 'anthropic.claude-instant-v1' },
{ label: 'anthropic.claude-v1', name: 'anthropic.claude-v1' },
{ label: 'anthropic.claude-v2', name: 'anthropic.claude-v2' },
diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts
index 8b4f7c0e..844e7d25 100644
--- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts
+++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts
@@ -43,6 +43,16 @@ class ChatAnthropic_ChatModels implements INode {
name: 'modelName',
type: 'options',
options: [
+ {
+ label: 'claude-3-opus',
+ name: 'claude-3-opus-20240229',
+ description: 'Most powerful model for highly complex tasks'
+ },
+ {
+ label: 'claude-3-sonnet',
+ name: 'claude-3-sonnet-20240229',
+ description: 'Ideal balance of intelligence and speed for enterprise workloads'
+ },
{
label: 'claude-2',
name: 'claude-2',
diff --git a/packages/components/src/agents.ts b/packages/components/src/agents.ts
index 5e4bd9c8..41bc076d 100644
--- a/packages/components/src/agents.ts
+++ b/packages/components/src/agents.ts
@@ -257,6 +257,8 @@ export class AgentExecutor extends BaseChain {
input?: string
+ isXML?: boolean
+
/**
* How to handle errors raised by the agent's output parser.
Defaults to `False`, which raises the error.
@@ -277,7 +279,7 @@ export class AgentExecutor extends BaseChain {
return this.agent.returnValues
}
- constructor(input: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }) {
+ constructor(input: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string; isXML?: boolean }) {
let agent: BaseSingleActionAgent | BaseMultiActionAgent
if (Runnable.isRunnable(input.agent)) {
agent = new RunnableAgent({ runnable: input.agent })
@@ -305,13 +307,17 @@ export class AgentExecutor extends BaseChain {
this.sessionId = input.sessionId
this.chatId = input.chatId
this.input = input.input
+ this.isXML = input.isXML
}
- static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }): AgentExecutor {
+ static fromAgentAndTools(
+ fields: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string; isXML?: boolean }
+ ): AgentExecutor {
const newInstance = new AgentExecutor(fields)
if (fields.sessionId) newInstance.sessionId = fields.sessionId
if (fields.chatId) newInstance.chatId = fields.chatId
if (fields.input) newInstance.input = fields.input
+ if (fields.isXML) newInstance.isXML = fields.isXML
return newInstance
}
@@ -405,12 +411,16 @@ export class AgentExecutor extends BaseChain {
* - flowConfig?: { sessionId?: string, chatId?: string, input?: string }
*/
observation = tool
- ? // @ts-ignore
- await tool.call(action.toolInput, runManager?.getChild(), undefined, {
- sessionId: this.sessionId,
- chatId: this.chatId,
- input: this.input
- })
+ ? await (tool as any).call(
+ this.isXML && typeof action.toolInput === 'string' ? { input: action.toolInput } : action.toolInput,
+ runManager?.getChild(),
+ undefined,
+ {
+ sessionId: this.sessionId,
+ chatId: this.chatId,
+ input: this.input
+ }
+ )
: `${action.tool} is not a valid tool, try another one.`
} catch (e) {
if (e instanceof ToolInputParsingException) {
@@ -526,12 +536,16 @@ export class AgentExecutor extends BaseChain {
* - tags?: string[]
* - flowConfig?: { sessionId?: string, chatId?: string, input?: string }
*/
- // @ts-ignore
- observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, {
- sessionId: this.sessionId,
- chatId: this.chatId,
- input: this.input
- })
+ observation = await (tool as any).call(
+ this.isXML && typeof agentAction.toolInput === 'string' ? { input: agentAction.toolInput } : agentAction.toolInput,
+ runManager?.getChild(),
+ undefined,
+ {
+ sessionId: this.sessionId,
+ chatId: this.chatId,
+ input: this.input
+ }
+ )
if (observation?.includes(SOURCE_DOCUMENTS_PREFIX)) {
const observationArray = observation.split(SOURCE_DOCUMENTS_PREFIX)
observation = observationArray[0]
diff --git a/packages/server/marketplaces/chatflows/Claude LLM.json b/packages/server/marketplaces/chatflows/Claude LLM.json
index 48be286d..fdce533e 100644
--- a/packages/server/marketplaces/chatflows/Claude LLM.json
+++ b/packages/server/marketplaces/chatflows/Claude LLM.json
@@ -179,6 +179,16 @@
"name": "modelName",
"type": "options",
"options": [
+ {
+ "label": "claude-3-opus",
+ "name": "claude-3-opus-20240229",
+ "description": "Most powerful model for highly complex tasks"
+ },
+ {
+ "label": "claude-3-sonnet",
+ "name": "claude-3-sonnet-20240229",
+ "description": "Ideal balance of intelligence and speed for enterprise workloads"
+ },
{
"label": "claude-2",
"name": "claude-2",