refactor(Basic LLM Chain Node): Refactor Basic LLM Chain & add tests (#13850)

This commit is contained in:
oleg
2025-03-14 11:38:22 +01:00
committed by GitHub
parent 311553926a
commit 1bfd128717
24 changed files with 1754 additions and 609 deletions

View File

@@ -65,9 +65,11 @@ function getInputs(
type,
displayName,
required: isModelType,
maxConnections: [NodeConnectionType.AiLanguageModel, NodeConnectionType.AiMemory].includes(
type as NodeConnectionType,
)
maxConnections: [
NodeConnectionType.AiLanguageModel,
NodeConnectionType.AiMemory,
NodeConnectionType.AiOutputParser,
].includes(type as NodeConnectionType)
? 1
: undefined,
};

View File

@@ -1,13 +1,11 @@
import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
import type { BaseOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';
import { initializeAgentExecutorWithOptions } from 'langchain/agents';
import { CombiningOutputParser } from 'langchain/output_parsers';
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { isChatInstance, getPromptInputByType, getConnectedTools } from '@utils/helpers';
import { getOptionalOutputParsers } from '@utils/output_parsers/N8nOutputParser';
import { getOptionalOutputParser } from '@utils/output_parsers/N8nOutputParser';
import { throwIfToolSchema } from '@utils/schemaParsing';
import { getTracingConfig } from '@utils/tracing';
@@ -29,7 +27,7 @@ export async function conversationalAgentExecute(
| undefined;
const tools = await getConnectedTools(this, nodeVersion >= 1.5, true, true);
const outputParsers = await getOptionalOutputParsers(this);
const outputParser = await getOptionalOutputParser(this);
await checkForStructuredTools(tools, this.getNode(), 'Conversational Agent');
@@ -58,24 +56,15 @@ export async function conversationalAgentExecute(
const returnData: INodeExecutionData[] = [];
let outputParser: BaseOutputParser | undefined;
let prompt: PromptTemplate | undefined;
if (outputParsers.length) {
if (outputParsers.length === 1) {
outputParser = outputParsers[0];
} else {
outputParser = new CombiningOutputParser(...outputParsers);
}
if (outputParser) {
const formatInstructions = outputParser.getFormatInstructions();
if (outputParser) {
const formatInstructions = outputParser.getFormatInstructions();
prompt = new PromptTemplate({
template: '{input}\n{formatInstructions}',
inputVariables: ['input'],
partialVariables: { formatInstructions },
});
}
prompt = new PromptTemplate({
template: '{input}\n{formatInstructions}',
inputVariables: ['input'],
partialVariables: { formatInstructions },
});
}
const items = this.getInputData();
@@ -104,7 +93,7 @@ export async function conversationalAgentExecute(
const response = await agentExecutor
.withConfig(getTracingConfig(this))
.invoke({ input, outputParsers });
.invoke({ input, outputParser });
if (outputParser) {
response.output = await extractParsedOutput(this, outputParser, response.output as string);

View File

@@ -1,10 +1,8 @@
import type { BaseOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';
import { ChatOpenAI } from '@langchain/openai';
import type { AgentExecutorInput } from 'langchain/agents';
import { AgentExecutor, OpenAIAgent } from 'langchain/agents';
import { BufferMemory, type BaseChatMemory } from 'langchain/memory';
import { CombiningOutputParser } from 'langchain/output_parsers';
import {
type IExecuteFunctions,
type INodeExecutionData,
@@ -13,7 +11,7 @@ import {
} from 'n8n-workflow';
import { getConnectedTools, getPromptInputByType } from '@utils/helpers';
import { getOptionalOutputParsers } from '@utils/output_parsers/N8nOutputParser';
import { getOptionalOutputParser } from '@utils/output_parsers/N8nOutputParser';
import { getTracingConfig } from '@utils/tracing';
import { extractParsedOutput } from '../utils';
@@ -38,7 +36,7 @@ export async function openAiFunctionsAgentExecute(
| BaseChatMemory
| undefined;
const tools = await getConnectedTools(this, nodeVersion >= 1.5, false);
const outputParsers = await getOptionalOutputParsers(this);
const outputParser = await getOptionalOutputParser(this);
const options = this.getNodeParameter('options', 0, {}) as {
systemMessage?: string;
maxIterations?: number;
@@ -67,12 +65,8 @@ export async function openAiFunctionsAgentExecute(
const returnData: INodeExecutionData[] = [];
let outputParser: BaseOutputParser | undefined;
let prompt: PromptTemplate | undefined;
if (outputParsers.length) {
outputParser =
outputParsers.length === 1 ? outputParsers[0] : new CombiningOutputParser(...outputParsers);
if (outputParser) {
const formatInstructions = outputParser.getFormatInstructions();
prompt = new PromptTemplate({
@@ -107,7 +101,7 @@ export async function openAiFunctionsAgentExecute(
const response = await agentExecutor
.withConfig(getTracingConfig(this))
.invoke({ input, outputParsers });
.invoke({ input, outputParser });
if (outputParser) {
response.output = await extractParsedOutput(this, outputParser, response.output as string);

View File

@@ -1,8 +1,6 @@
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
import type { BaseOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';
import { PlanAndExecuteAgentExecutor } from 'langchain/experimental/plan_and_execute';
import { CombiningOutputParser } from 'langchain/output_parsers';
import {
type IExecuteFunctions,
type INodeExecutionData,
@@ -11,7 +9,7 @@ import {
} from 'n8n-workflow';
import { getConnectedTools, getPromptInputByType } from '@utils/helpers';
import { getOptionalOutputParsers } from '@utils/output_parsers/N8nOutputParser';
import { getOptionalOutputParser } from '@utils/output_parsers/N8nOutputParser';
import { throwIfToolSchema } from '@utils/schemaParsing';
import { getTracingConfig } from '@utils/tracing';
@@ -30,7 +28,7 @@ export async function planAndExecuteAgentExecute(
const tools = await getConnectedTools(this, nodeVersion >= 1.5, true, true);
await checkForStructuredTools(tools, this.getNode(), 'Plan & Execute Agent');
const outputParsers = await getOptionalOutputParsers(this);
const outputParser = await getOptionalOutputParser(this);
const options = this.getNodeParameter('options', 0, {}) as {
humanMessageTemplate?: string;
@@ -44,12 +42,8 @@ export async function planAndExecuteAgentExecute(
const returnData: INodeExecutionData[] = [];
let outputParser: BaseOutputParser | undefined;
let prompt: PromptTemplate | undefined;
if (outputParsers.length) {
outputParser =
outputParsers.length === 1 ? outputParsers[0] : new CombiningOutputParser(...outputParsers);
if (outputParser) {
const formatInstructions = outputParser.getFormatInstructions();
prompt = new PromptTemplate({
@@ -84,7 +78,7 @@ export async function planAndExecuteAgentExecute(
const response = await agentExecutor
.withConfig(getTracingConfig(this))
.invoke({ input, outputParsers });
.invoke({ input, outputParser });
if (outputParser) {
response.output = await extractParsedOutput(this, outputParser, response.output as string);

View File

@@ -1,9 +1,7 @@
import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
import type { BaseOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';
import { AgentExecutor, ChatAgent, ZeroShotAgent } from 'langchain/agents';
import { CombiningOutputParser } from 'langchain/output_parsers';
import {
type IExecuteFunctions,
type INodeExecutionData,
@@ -12,7 +10,7 @@ import {
} from 'n8n-workflow';
import { getConnectedTools, getPromptInputByType, isChatInstance } from '@utils/helpers';
import { getOptionalOutputParsers } from '@utils/output_parsers/N8nOutputParser';
import { getOptionalOutputParser } from '@utils/output_parsers/N8nOutputParser';
import { throwIfToolSchema } from '@utils/schemaParsing';
import { getTracingConfig } from '@utils/tracing';
@@ -32,7 +30,7 @@ export async function reActAgentAgentExecute(
await checkForStructuredTools(tools, this.getNode(), 'ReAct Agent');
const outputParsers = await getOptionalOutputParsers(this);
const outputParser = await getOptionalOutputParser(this);
const options = this.getNodeParameter('options', 0, {}) as {
prefix?: string;
@@ -66,12 +64,8 @@ export async function reActAgentAgentExecute(
const returnData: INodeExecutionData[] = [];
let outputParser: BaseOutputParser | undefined;
let prompt: PromptTemplate | undefined;
if (outputParsers.length) {
outputParser =
outputParsers.length === 1 ? outputParsers[0] : new CombiningOutputParser(...outputParsers);
if (outputParser) {
const formatInstructions = outputParser.getFormatInstructions();
prompt = new PromptTemplate({
@@ -107,7 +101,7 @@ export async function reActAgentAgentExecute(
const response = await agentExecutor
.withConfig(getTracingConfig(this))
.invoke({ input, outputParsers });
.invoke({ input, outputParser });
if (outputParser) {
response.output = await extractParsedOutput(this, outputParser, response.output as string);

View File

@@ -18,7 +18,7 @@ import { z } from 'zod';
import { isChatInstance, getPromptInputByType, getConnectedTools } from '@utils/helpers';
import {
getOptionalOutputParsers,
getOptionalOutputParser,
type N8nOutputParser,
} from '@utils/output_parsers/N8nOutputParser';
@@ -392,8 +392,7 @@ export async function toolsAgentExecute(this: IExecuteFunctions): Promise<INodeE
const returnData: INodeExecutionData[] = [];
const items = this.getInputData();
const outputParsers = await getOptionalOutputParsers(this);
const outputParser = outputParsers?.[0];
const outputParser = await getOptionalOutputParser(this);
const tools = await getTools(this, outputParser);
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {