Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(AI Agent Node): Implement Tool calling agent #9339

Merged
merged 16 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 87 additions & 37 deletions packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
INodeExecutionData,
INodeType,
INodeTypeDescription,
INodeProperties,
} from 'n8n-workflow';
import { getTemplateNoticeField } from '../../../utils/sharedFields';
import { promptTypeOptions, textInput } from '../../../utils/descriptions';
Expand All @@ -20,11 +21,13 @@ import { reActAgentAgentProperties } from './agents/ReActAgent/description';
import { reActAgentAgentExecute } from './agents/ReActAgent/execute';
import { sqlAgentAgentProperties } from './agents/SqlAgent/description';
import { sqlAgentAgentExecute } from './agents/SqlAgent/execute';
import { toolsAgentProperties } from './agents/ToolsAgent/description';
import { toolsAgentExecute } from './agents/ToolsAgent/execute';

// Function used in the inputs expression to figure out which inputs to
// display based on the agent type
function getInputs(
agent: 'conversationalAgent' | 'openAiFunctionsAgent' | 'reActAgent' | 'sqlAgent',
agent: 'toolsAgent' | 'conversationalAgent' | 'openAiFunctionsAgent' | 'reActAgent' | 'sqlAgent',
hasOutputParser?: boolean,
): Array<ConnectionTypes | INodeInputConfiguration> {
interface SpecialInput {
Expand Down Expand Up @@ -92,6 +95,31 @@ function getInputs(
type: NodeConnectionType.AiOutputParser,
},
];
} else if (agent === 'toolsAgent') {
specialInputs = [
{
type: NodeConnectionType.AiLanguageModel,
filter: {
nodes: [
'@n8n/n8n-nodes-langchain.lmChatAnthropic',
'@n8n/n8n-nodes-langchain.lmChatAzureOpenAi',
'@n8n/n8n-nodes-langchain.lmChatMistralCloud',
'@n8n/n8n-nodes-langchain.lmChatOpenAi',
'@n8n/n8n-nodes-langchain.lmChatGroq',
],
},
},
{
type: NodeConnectionType.AiMemory,
},
{
type: NodeConnectionType.AiTool,
required: true,
},
{
type: NodeConnectionType.AiOutputParser,
},
];
} else if (agent === 'openAiFunctionsAgent') {
specialInputs = [
{
Expand Down Expand Up @@ -157,16 +185,60 @@ function getInputs(
return [NodeConnectionType.Main, ...getInputData(specialInputs)];
}

const agentTypeProperty: INodeProperties = {
displayName: 'Agent',
name: 'agent',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Conversational Agent',
value: 'conversationalAgent',
description:
'Selects tools to accomplish its task and uses memory to recall previous conversations',
},
{
name: 'OpenAI Functions Agent',
value: 'openAiFunctionsAgent',
description:
"Utilizes OpenAI's Function Calling feature to select the appropriate tool and arguments for execution",
},
{
name: 'Plan and Execute Agent',
value: 'planAndExecuteAgent',
description:
'Plan and execute agents accomplish an objective by first planning what to do, then executing the sub tasks',
},
{
name: 'ReAct Agent',
value: 'reActAgent',
description: 'Strategically select tools to accomplish a given task',
},
{
name: 'SQL Agent',
value: 'sqlAgent',
description: 'Answers questions about data in an SQL database',
},
{
name: 'Tools Agent',
value: 'toolsAgent',
description:
'Utilized unified Tool calling interface to select the appropriate tools and argument for execution',
},
],
default: '',
};

export class Agent implements INodeType {
description: INodeTypeDescription = {
displayName: 'AI Agent',
name: 'agent',
icon: 'fa:robot',
group: ['transform'],
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5],
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
description: 'Generates an action plan and executes it. Can use external tools.',
subtitle:
"={{ { conversationalAgent: 'Conversational Agent', openAiFunctionsAgent: 'OpenAI Functions Agent', reActAgent: 'ReAct Agent', sqlAgent: 'SQL Agent', planAndExecuteAgent: 'Plan and Execute Agent' }[$parameter.agent] }}",
"={{ { toolsAgent: 'Tools Agent', conversationalAgent: 'Conversational Agent', openAiFunctionsAgent: 'OpenAI Functions Agent', reActAgent: 'ReAct Agent', sqlAgent: 'SQL Agent', planAndExecuteAgent: 'Plan and Execute Agent' }[$parameter.agent] }}",
defaults: {
name: 'AI Agent',
color: '#404040',
Expand Down Expand Up @@ -225,43 +297,18 @@ export class Agent implements INodeType {
},
},
},
// Make Conversational Agent the default agent for versions 1.5 and below
{
displayName: 'Agent',
name: 'agent',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Conversational Agent',
value: 'conversationalAgent',
description:
'Selects tools to accomplish its task and uses memory to recall previous conversations',
},
{
name: 'OpenAI Functions Agent',
value: 'openAiFunctionsAgent',
description:
"Utilizes OpenAI's Function Calling feature to select the appropriate tool and arguments for execution",
},
{
name: 'Plan and Execute Agent',
value: 'planAndExecuteAgent',
description:
'Plan and execute agents accomplish an objective by first planning what to do, then executing the sub tasks',
},
{
name: 'ReAct Agent',
value: 'reActAgent',
description: 'Strategically select tools to accomplish a given task',
},
{
name: 'SQL Agent',
value: 'sqlAgent',
description: 'Answers questions about data in an SQL database',
},
],
...agentTypeProperty,
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.5 } }] } },
default: 'conversationalAgent',
},
// Make Tools Agent the default agent for versions 1.6 and above
{
...agentTypeProperty,
displayOptions: { show: { '@version': [{ _cnd: { gte: 1.6 } }] } },
default: 'toolsAgent',
},
{
...promptTypeOptions,
displayOptions: {
Expand Down Expand Up @@ -307,6 +354,7 @@ export class Agent implements INodeType {
},
},

...toolsAgentProperties,
...conversationalAgentProperties,
...openAiFunctionsAgentProperties,
...reActAgentAgentProperties,
Expand All @@ -321,6 +369,8 @@ export class Agent implements INodeType {

if (agentType === 'conversationalAgent') {
return await conversationalAgentExecute.call(this, nodeVersion);
} else if (agentType === 'toolsAgent') {
return await toolsAgentExecute.call(this, nodeVersion);
} else if (agentType === 'openAiFunctionsAgent') {
return await openAiFunctionsAgentExecute.call(this, nodeVersion);
} else if (agentType === 'reActAgent') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { INodeProperties } from 'n8n-workflow';
import { SYSTEM_MESSAGE } from './prompt';

export const toolsAgentProperties: INodeProperties[] = [
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
agent: ['toolsAgent'],
},
},
default: {},
placeholder: 'Add Option',
options: [
{
displayName: 'System Message',
name: 'systemMessage',
type: 'string',
default: SYSTEM_MESSAGE,
description: 'The message that will be sent to the agent before the conversation starts',
typeOptions: {
rows: 6,
},
},
{
displayName: 'Max Iterations',
name: 'maxIterations',
type: 'number',
default: 10,
description: 'The maximum number of iterations the agent will run before stopping',
},
{
displayName: 'Return Intermediate Steps',
name: 'returnIntermediateSteps',
type: 'boolean',
default: false,
description: 'Whether or not the output should include intermediate steps the agent took',
},
],
},
];
Loading
Loading