diff --git a/packages/@n8n/chat/src/css/markdown.scss b/packages/@n8n/chat/src/css/markdown.scss
index 070e6d6a5fe..6bcf13ba740 100644
--- a/packages/@n8n/chat/src/css/markdown.scss
+++ b/packages/@n8n/chat/src/css/markdown.scss
@@ -37,7 +37,7 @@ body {
4. Prevent font size adjustment after orientation changes (IE, iOS)
5. Prevent overflow from long words (all)
*/
- font-size: 125%; /* 2 */
+ font-size: 110%; /* 2 */
line-height: 1.6; /* 3 */
-webkit-text-size-adjust: 100%; /* 4 */
word-break: break-word; /* 5 */
@@ -596,7 +596,7 @@ body {
pre code {
display: block;
- padding: 0.3em 0.7em;
+ padding: 0 0 0.5rem 0.5rem;
word-break: normal;
overflow-x: auto;
}
diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts
index 0b27456db6c..80e5da9cfac 100644
--- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts
@@ -21,7 +21,7 @@ import { sqlAgentAgentProperties } from './agents/SqlAgent/description';
import { sqlAgentAgentExecute } from './agents/SqlAgent/execute';
import { toolsAgentProperties } from './agents/ToolsAgent/description';
import { toolsAgentExecute } from './agents/ToolsAgent/execute';
-import { promptTypeOptions, textInput } from '../../../utils/descriptions';
+import { promptTypeOptions, textFromPreviousNode, textInput } from '../../../utils/descriptions';
// Function used in the inputs expression to figure out which inputs to
// display based on the agent type
@@ -341,6 +341,17 @@ export class Agent implements INodeType {
},
},
},
+ {
+ ...textFromPreviousNode,
+ displayOptions: {
+ show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.7 } }] },
+ // SQL Agent has data source and credentials parameters so we need to include this input there manually
+ // to preserve the order
+ hide: {
+ agent: ['sqlAgent'],
+ },
+ },
+ },
{
...textInput,
displayOptions: {
diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/description.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/description.ts
index c7f888a3349..bed547ba6da 100644
--- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/description.ts
+++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/description.ts
@@ -1,6 +1,11 @@
import type { INodeProperties } from 'n8n-workflow';
-import { promptTypeOptions, textInput } from '../../../../../utils/descriptions';
+
import { SQL_PREFIX, SQL_SUFFIX } from './other/prompts';
+import {
+ promptTypeOptions,
+ textFromPreviousNode,
+ textInput,
+} from '../../../../../utils/descriptions';
const dataSourceOptions: INodeProperties = {
displayName: 'Data Source',
@@ -114,6 +119,12 @@ export const sqlAgentAgentProperties: INodeProperties[] = [
},
},
},
+ {
+ ...textFromPreviousNode,
+ displayOptions: {
+ show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.7 } }], agent: ['sqlAgent'] },
+ },
+ },
{
...textInput,
displayOptions: {
diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/execute.ts
index 4b31491120a..b9c0f3db8ee 100644
--- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/execute.ts
+++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/SqlAgent/execute.ts
@@ -1,3 +1,9 @@
+import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
+import type { BaseLanguageModel } from '@langchain/core/language_models/base';
+import type { DataSource } from '@n8n/typeorm';
+import type { SqlCreatePromptArgs } from 'langchain/agents/toolkits/sql';
+import { SqlToolkit, createSqlAgent } from 'langchain/agents/toolkits/sql';
+import { SqlDatabase } from 'langchain/sql_db';
import {
type IExecuteFunctions,
type INodeExecutionData,
@@ -6,19 +12,12 @@ import {
type IDataObject,
} from 'n8n-workflow';
-import { SqlDatabase } from 'langchain/sql_db';
-import type { SqlCreatePromptArgs } from 'langchain/agents/toolkits/sql';
-import { SqlToolkit, createSqlAgent } from 'langchain/agents/toolkits/sql';
-import type { BaseLanguageModel } from '@langchain/core/language_models/base';
-import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
-import type { DataSource } from '@n8n/typeorm';
-
+import { getMysqlDataSource } from './other/handlers/mysql';
+import { getPostgresDataSource } from './other/handlers/postgres';
+import { getSqliteDataSource } from './other/handlers/sqlite';
+import { SQL_PREFIX, SQL_SUFFIX } from './other/prompts';
import { getPromptInputByType, serializeChatHistory } from '../../../../../utils/helpers';
import { getTracingConfig } from '../../../../../utils/tracing';
-import { getSqliteDataSource } from './other/handlers/sqlite';
-import { getPostgresDataSource } from './other/handlers/postgres';
-import { SQL_PREFIX, SQL_SUFFIX } from './other/prompts';
-import { getMysqlDataSource } from './other/handlers/mysql';
const parseTablesString = (tablesString: string) =>
tablesString
diff --git a/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts b/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts
index d8e6f8a775d..ca42323fc33 100644
--- a/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts
@@ -1,5 +1,5 @@
import { AgentExecutor } from 'langchain/agents';
-import { OpenAI as OpenAIClient } from 'openai';
+import type { OpenAIToolType } from 'langchain/dist/experimental/openai_assistant/schema';
import { OpenAIAssistantRunnable } from 'langchain/experimental/openai_assistant';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type {
@@ -8,10 +8,11 @@ import type {
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
-import type { OpenAIToolType } from 'langchain/dist/experimental/openai_assistant/schema';
+import { OpenAI as OpenAIClient } from 'openai';
+
+import { formatToOpenAIAssistantTool } from './utils';
import { getConnectedTools } from '../../../utils/helpers';
import { getTracingConfig } from '../../../utils/tracing';
-import { formatToOpenAIAssistantTool } from './utils';
export class OpenAiAssistant implements INodeType {
description: INodeTypeDescription = {
diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts
index a080862c0fd..32d70f2d320 100644
--- a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts
@@ -36,6 +36,7 @@ import {
getCustomErrorMessage as getCustomOpenAiErrorMessage,
isOpenAiError,
} from '../../vendors/OpenAi/helpers/error-handling';
+import { promptTypeOptions, textFromPreviousNode } from '../../../utils/descriptions';
interface MessagesTemplate {
type: string;
@@ -253,7 +254,7 @@ export class ChainLlm implements INodeType {
name: 'chainLlm',
icon: 'fa:link',
group: ['transform'],
- version: [1, 1.1, 1.2, 1.3, 1.4],
+ version: [1, 1.1, 1.2, 1.3, 1.4, 1.5],
description: 'A simple chain to prompt a large language model',
defaults: {
name: 'Basic LLM Chain',
@@ -315,30 +316,16 @@ export class ChainLlm implements INodeType {
},
},
{
- displayName: 'Prompt',
- name: 'promptType',
- type: 'options',
- options: [
- {
- // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
- name: 'Take from previous node automatically',
- value: 'auto',
- description: 'Looks for an input field called chatInput',
- },
- {
- // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
- name: 'Define below',
- value: 'define',
- description:
- 'Use an expression to reference data in previous nodes or enter static text',
- },
- ],
+ ...promptTypeOptions,
displayOptions: {
hide: {
'@version': [1, 1.1, 1.2, 1.3],
},
},
- default: 'auto',
+ },
+ {
+ ...textFromPreviousNode,
+ displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.5 } }] } },
},
{
displayName: 'Text',
diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts
index dfbdb3e9d19..75f74584384 100644
--- a/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts
@@ -1,3 +1,12 @@
+import type { BaseLanguageModel } from '@langchain/core/language_models/base';
+import {
+ ChatPromptTemplate,
+ SystemMessagePromptTemplate,
+ HumanMessagePromptTemplate,
+ PromptTemplate,
+} from '@langchain/core/prompts';
+import type { BaseRetriever } from '@langchain/core/retrievers';
+import { RetrievalQAChain } from 'langchain/chains';
import {
NodeConnectionType,
type IExecuteFunctions,
@@ -7,20 +16,12 @@ import {
NodeOperationError,
} from 'n8n-workflow';
-import { RetrievalQAChain } from 'langchain/chains';
-import type { BaseLanguageModel } from '@langchain/core/language_models/base';
-import type { BaseRetriever } from '@langchain/core/retrievers';
-import {
- ChatPromptTemplate,
- SystemMessagePromptTemplate,
- HumanMessagePromptTemplate,
- PromptTemplate,
-} from '@langchain/core/prompts';
-import { getTemplateNoticeField } from '../../../utils/sharedFields';
+import { promptTypeOptions, textFromPreviousNode } from '../../../utils/descriptions';
import { getPromptInputByType, isChatInstance } from '../../../utils/helpers';
+import { getTemplateNoticeField } from '../../../utils/sharedFields';
import { getTracingConfig } from '../../../utils/tracing';
-const SYSTEM_PROMPT_TEMPLATE = `Use the following pieces of context to answer the users question.
+const SYSTEM_PROMPT_TEMPLATE = `Use the following pieces of context to answer the users question.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
----------------
{context}`;
@@ -31,7 +32,7 @@ export class ChainRetrievalQa implements INodeType {
name: 'chainRetrievalQa',
icon: 'fa:link',
group: ['transform'],
- version: [1, 1.1, 1.2, 1.3],
+ version: [1, 1.1, 1.2, 1.3, 1.4],
description: 'Answer questions about retrieved documents',
defaults: {
name: 'Question and Answer Chain',
@@ -108,30 +109,16 @@ export class ChainRetrievalQa implements INodeType {
},
},
{
- displayName: 'Prompt',
- name: 'promptType',
- type: 'options',
- options: [
- {
- // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
- name: 'Take from previous node automatically',
- value: 'auto',
- description: 'Looks for an input field called chatInput',
- },
- {
- // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
- name: 'Define below',
- value: 'define',
- description:
- 'Use an expression to reference data in previous nodes or enter static text',
- },
- ],
+ ...promptTypeOptions,
displayOptions: {
hide: {
'@version': [{ _cnd: { lte: 1.2 } }],
},
},
- default: 'auto',
+ },
+ {
+ ...textFromPreviousNode,
+ displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.4 } }] } },
},
{
displayName: 'Text',
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryBufferWindow/MemoryBufferWindow.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryBufferWindow/MemoryBufferWindow.node.ts
index fae6927c250..28025f28845 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryBufferWindow/MemoryBufferWindow.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryBufferWindow/MemoryBufferWindow.node.ts
@@ -1,4 +1,6 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
+import type { BufferWindowMemoryInput } from 'langchain/memory';
+import { BufferWindowMemory } from 'langchain/memory';
import {
NodeConnectionType,
type INodeType,
@@ -6,12 +8,16 @@ import {
type ISupplyDataFunctions,
type SupplyData,
} from 'n8n-workflow';
-import type { BufferWindowMemoryInput } from 'langchain/memory';
-import { BufferWindowMemory } from 'langchain/memory';
+
+import { getSessionId } from '../../../utils/helpers';
import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
-import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions';
-import { getSessionId } from '../../../utils/helpers';
+import {
+ sessionIdOption,
+ sessionKeyProperty,
+ contextWindowLengthProperty,
+ expressionSessionKeyProperty,
+} from '../descriptions';
class MemoryChatBufferSingleton {
private static instance: MemoryChatBufferSingleton;
@@ -72,7 +78,7 @@ export class MemoryBufferWindow implements INodeType {
name: 'memoryBufferWindow',
icon: 'fa:database',
group: ['transform'],
- version: [1, 1.1, 1.2],
+ version: [1, 1.1, 1.2, 1.3],
description: 'Stores in n8n memory, so no credentials required',
defaults: {
name: 'Window Buffer Memory',
@@ -129,6 +135,7 @@ export class MemoryBufferWindow implements INodeType {
},
},
},
+ expressionSessionKeyProperty(1.3),
sessionKeyProperty,
contextWindowLengthProperty,
],
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryMotorhead/MemoryMotorhead.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryMotorhead/MemoryMotorhead.node.ts
index 7f8d88fbaf7..a326f4c1bea 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryMotorhead/MemoryMotorhead.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryMotorhead/MemoryMotorhead.node.ts
@@ -1,4 +1,5 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
+import { MotorheadMemory } from '@langchain/community/memory/motorhead_memory';
import {
NodeConnectionType,
type INodeType,
@@ -7,11 +8,10 @@ import {
type SupplyData,
} from 'n8n-workflow';
-import { MotorheadMemory } from '@langchain/community/memory/motorhead_memory';
+import { getSessionId } from '../../../utils/helpers';
import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
-import { sessionIdOption, sessionKeyProperty } from '../descriptions';
-import { getSessionId } from '../../../utils/helpers';
+import { expressionSessionKeyProperty, sessionIdOption, sessionKeyProperty } from '../descriptions';
export class MemoryMotorhead implements INodeType {
description: INodeTypeDescription = {
@@ -19,7 +19,7 @@ export class MemoryMotorhead implements INodeType {
name: 'memoryMotorhead',
icon: 'fa:file-export',
group: ['transform'],
- version: [1, 1.1, 1.2],
+ version: [1, 1.1, 1.2, 1.3],
description: 'Use Motorhead Memory',
defaults: {
name: 'Motorhead',
@@ -82,6 +82,7 @@ export class MemoryMotorhead implements INodeType {
},
},
},
+ expressionSessionKeyProperty(1.3),
sessionKeyProperty,
],
};
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryPostgresChat/MemoryPostgresChat.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryPostgresChat/MemoryPostgresChat.node.ts
index f51b76fb183..b3d5f2f4091 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryPostgresChat/MemoryPostgresChat.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryPostgresChat/MemoryPostgresChat.node.ts
@@ -1,4 +1,9 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
+import { PostgresChatMessageHistory } from '@langchain/community/stores/message/postgres';
+import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
+import type { PostgresNodeCredentials } from 'n8n-nodes-base/dist/nodes/Postgres/v2/helpers/interfaces';
+import { postgresConnectionTest } from 'n8n-nodes-base/dist/nodes/Postgres/v2/methods/credentialTest';
+import { configurePostgres } from 'n8n-nodes-base/dist/nodes/Postgres/v2/transport';
import type {
ISupplyDataFunctions,
INodeType,
@@ -6,16 +11,17 @@ import type {
SupplyData,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
-import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
-import { PostgresChatMessageHistory } from '@langchain/community/stores/message/postgres';
import type pg from 'pg';
-import { configurePostgres } from 'n8n-nodes-base/dist/nodes/Postgres/v2/transport';
-import type { PostgresNodeCredentials } from 'n8n-nodes-base/dist/nodes/Postgres/v2/helpers/interfaces';
-import { postgresConnectionTest } from 'n8n-nodes-base/dist/nodes/Postgres/v2/methods/credentialTest';
+
+import { getSessionId } from '../../../utils/helpers';
import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
-import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions';
-import { getSessionId } from '../../../utils/helpers';
+import {
+ sessionIdOption,
+ sessionKeyProperty,
+ contextWindowLengthProperty,
+ expressionSessionKeyProperty,
+} from '../descriptions';
export class MemoryPostgresChat implements INodeType {
description: INodeTypeDescription = {
@@ -23,7 +29,7 @@ export class MemoryPostgresChat implements INodeType {
name: 'memoryPostgresChat',
icon: 'file:postgres.svg',
group: ['transform'],
- version: [1, 1.1],
+ version: [1, 1.1, 1.2, 1.3],
description: 'Stores the chat history in Postgres table.',
defaults: {
name: 'Postgres Chat Memory',
@@ -56,6 +62,7 @@ export class MemoryPostgresChat implements INodeType {
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
sessionIdOption,
+ expressionSessionKeyProperty(1.2),
sessionKeyProperty,
{
displayName: 'Table Name',
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts
index 01a31458de3..09208d96f7f 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts
@@ -1,4 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
+import type { RedisChatMessageHistoryInput } from '@langchain/redis';
+import { RedisChatMessageHistory } from '@langchain/redis';
+import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
import {
NodeOperationError,
type INodeType,
@@ -7,15 +10,18 @@ import {
type SupplyData,
NodeConnectionType,
} from 'n8n-workflow';
-import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
-import type { RedisChatMessageHistoryInput } from '@langchain/redis';
-import { RedisChatMessageHistory } from '@langchain/redis';
import type { RedisClientOptions } from 'redis';
import { createClient } from 'redis';
+
+import { getSessionId } from '../../../utils/helpers';
import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
-import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions';
-import { getSessionId } from '../../../utils/helpers';
+import {
+ sessionIdOption,
+ sessionKeyProperty,
+ contextWindowLengthProperty,
+ expressionSessionKeyProperty,
+} from '../descriptions';
export class MemoryRedisChat implements INodeType {
description: INodeTypeDescription = {
@@ -23,7 +29,7 @@ export class MemoryRedisChat implements INodeType {
name: 'memoryRedisChat',
icon: 'file:redis.svg',
group: ['transform'],
- version: [1, 1.1, 1.2, 1.3],
+ version: [1, 1.1, 1.2, 1.3, 1.4],
description: 'Stores the chat history in Redis.',
defaults: {
name: 'Redis Chat Memory',
@@ -86,6 +92,7 @@ export class MemoryRedisChat implements INodeType {
},
},
},
+ expressionSessionKeyProperty(1.4),
sessionKeyProperty,
{
displayName: 'Session Time To Live',
@@ -120,6 +127,7 @@ export class MemoryRedisChat implements INodeType {
socket: {
host: credentials.host as string,
port: credentials.port as number,
+ tls: credentials.ssl === true,
},
database: credentials.database as number,
};
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryXata/MemoryXata.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryXata/MemoryXata.node.ts
index be431b9b3c5..c48f32976ba 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryXata/MemoryXata.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryXata/MemoryXata.node.ts
@@ -1,4 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
+import { XataChatMessageHistory } from '@langchain/community/stores/message/xata';
+import { BaseClient } from '@xata.io/client';
+import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type {
ISupplyDataFunctions,
@@ -6,13 +9,16 @@ import type {
INodeTypeDescription,
SupplyData,
} from 'n8n-workflow';
-import { XataChatMessageHistory } from '@langchain/community/stores/message/xata';
-import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
-import { BaseClient } from '@xata.io/client';
+
+import { getSessionId } from '../../../utils/helpers';
import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
-import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions';
-import { getSessionId } from '../../../utils/helpers';
+import {
+ sessionIdOption,
+ sessionKeyProperty,
+ contextWindowLengthProperty,
+ expressionSessionKeyProperty,
+} from '../descriptions';
export class MemoryXata implements INodeType {
description: INodeTypeDescription = {
@@ -20,7 +26,7 @@ export class MemoryXata implements INodeType {
name: 'memoryXata',
icon: 'file:xata.svg',
group: ['transform'],
- version: [1, 1.1, 1.2, 1.3],
+ version: [1, 1.1, 1.2, 1.3, 1.4],
description: 'Use Xata Memory',
defaults: {
name: 'Xata',
@@ -86,6 +92,7 @@ export class MemoryXata implements INodeType {
},
},
sessionKeyProperty,
+ expressionSessionKeyProperty(1.4),
{
...contextWindowLengthProperty,
displayOptions: { hide: { '@version': [{ _cnd: { lt: 1.3 } }] } },
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryZep/MemoryZep.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryZep/MemoryZep.node.ts
index 20e70fd9203..42ae7244b42 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryZep/MemoryZep.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryZep/MemoryZep.node.ts
@@ -12,7 +12,7 @@ import { ZepCloudMemory } from '@langchain/community/memory/zep_cloud';
import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
-import { sessionIdOption, sessionKeyProperty } from '../descriptions';
+import { expressionSessionKeyProperty, sessionIdOption, sessionKeyProperty } from '../descriptions';
import { getSessionId } from '../../../utils/helpers';
import type { BaseChatMemory } from '@langchain/community/dist/memory/chat_memory';
import type { InputValues, MemoryVariables } from '@langchain/core/memory';
@@ -36,7 +36,7 @@ export class MemoryZep implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
icon: 'file:zep.png',
group: ['transform'],
- version: [1, 1.1, 1.2],
+ version: [1, 1.1, 1.2, 1.3],
description: 'Use Zep Memory',
defaults: {
name: 'Zep',
@@ -99,6 +99,7 @@ export class MemoryZep implements INodeType {
},
},
},
+ expressionSessionKeyProperty(1.3),
sessionKeyProperty,
],
};
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/descriptions.ts b/packages/@n8n/nodes-langchain/nodes/memory/descriptions.ts
index 354d134fb7c..4627671a9b5 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/descriptions.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/descriptions.ts
@@ -21,6 +21,20 @@ export const sessionIdOption: INodeProperties = {
default: 'fromInput',
};
+export const expressionSessionKeyProperty = (fromVersion: number): INodeProperties => ({
+ displayName: 'Session Key From Previous Node',
+ name: 'sessionKey',
+ type: 'string',
+ default: '={{ $json.sessionId }}',
+ disabledOptions: { show: { sessionIdType: ['fromInput'] } },
+ displayOptions: {
+ show: {
+ sessionIdType: ['fromInput'],
+ '@version': [{ _cnd: { gte: fromVersion } }],
+ },
+ },
+});
+
export const sessionKeyProperty: INodeProperties = {
displayName: 'Key',
name: 'sessionKey',
diff --git a/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserItemList/test/OutputParserItemList.node.test.ts b/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserItemList/test/OutputParserItemList.node.test.ts
index 31e96077c4f..c8ac869169e 100644
--- a/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserItemList/test/OutputParserItemList.node.test.ts
+++ b/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserItemList/test/OutputParserItemList.node.test.ts
@@ -35,6 +35,7 @@ describe('OutputParserItemList', () => {
const { response } = await outputParser.supplyData.call(thisArg, 0);
expect(response).toBeInstanceOf(N8nItemListOutputParser);
+ expect((response as any).numberOfItems).toBe(3);
});
it('should create a parser with custom number of items', async () => {
@@ -50,6 +51,20 @@ describe('OutputParserItemList', () => {
expect((response as any).numberOfItems).toBe(5);
});
+ it('should create a parser with unlimited number of items', async () => {
+ thisArg.getNodeParameter.mockImplementation((parameterName) => {
+ if (parameterName === 'options') {
+ return { numberOfItems: -1 };
+ }
+
+ throw new ApplicationError('Not implemented');
+ });
+
+ const { response } = await outputParser.supplyData.call(thisArg, 0);
+ expect(response).toBeInstanceOf(N8nItemListOutputParser);
+ expect((response as any).numberOfItems).toBeUndefined();
+ });
+
it('should create a parser with custom separator', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreInMemory/VectorStoreInMemory.node.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreInMemory/VectorStoreInMemory.node.ts
index cbd2cef75ad..5508f957f8f 100644
--- a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreInMemory/VectorStoreInMemory.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreInMemory/VectorStoreInMemory.node.ts
@@ -54,6 +54,6 @@ export class VectorStoreInMemory extends createVectorStoreNode({
const workflowId = context.getWorkflow().id;
const vectorStoreInstance = MemoryVectorStoreManager.getInstance(embeddings);
- void vectorStoreInstance.addDocuments(`${workflowId}__${memoryKey}`, documents, clearStore);
+ await vectorStoreInstance.addDocuments(`${workflowId}__${memoryKey}`, documents, clearStore);
},
}) {}
diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/shared/MemoryVectorStoreManager.test.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/MemoryVectorStoreManager.test.ts
new file mode 100644
index 00000000000..6229868f320
--- /dev/null
+++ b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/MemoryVectorStoreManager.test.ts
@@ -0,0 +1,44 @@
+import type { OpenAIEmbeddings } from '@langchain/openai';
+
+import { MemoryVectorStoreManager } from './MemoryVectorStoreManager';
+import { mock } from 'jest-mock-extended';
+
+describe('MemoryVectorStoreManager', () => {
+ it('should create an instance of MemoryVectorStoreManager', () => {
+ const embeddings = mock
();
+
+ const instance = MemoryVectorStoreManager.getInstance(embeddings);
+ expect(instance).toBeInstanceOf(MemoryVectorStoreManager);
+ });
+
+ it('should return existing instance', () => {
+ const embeddings = mock();
+
+ const instance1 = MemoryVectorStoreManager.getInstance(embeddings);
+ const instance2 = MemoryVectorStoreManager.getInstance(embeddings);
+ expect(instance1).toBe(instance2);
+ });
+
+ it('should update embeddings in existing instance', () => {
+ const embeddings1 = mock();
+ const embeddings2 = mock();
+
+ const instance = MemoryVectorStoreManager.getInstance(embeddings1);
+ MemoryVectorStoreManager.getInstance(embeddings2);
+
+ expect((instance as any).embeddings).toBe(embeddings2);
+ });
+
+ it('should update embeddings in existing vector store instances', async () => {
+ const embeddings1 = mock();
+ const embeddings2 = mock();
+
+ const instance1 = MemoryVectorStoreManager.getInstance(embeddings1);
+ await instance1.getVectorStore('test');
+
+ const instance2 = MemoryVectorStoreManager.getInstance(embeddings2);
+ const vectorStoreInstance2 = await instance2.getVectorStore('test');
+
+ expect((vectorStoreInstance2 as any).embeddings).toBe(embeddings2);
+ });
+});
diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/shared/MemoryVectorStoreManager.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/MemoryVectorStoreManager.ts
index 1076fb93baf..5c507a5196f 100644
--- a/packages/@n8n/nodes-langchain/nodes/vector_store/shared/MemoryVectorStoreManager.ts
+++ b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/MemoryVectorStoreManager.ts
@@ -14,7 +14,16 @@ export class MemoryVectorStoreManager {
public static getInstance(embeddings: Embeddings): MemoryVectorStoreManager {
if (!MemoryVectorStoreManager.instance) {
MemoryVectorStoreManager.instance = new MemoryVectorStoreManager(embeddings);
+ } else {
+ // We need to update the embeddings in the existing instance.
+ // This is important as embeddings instance is wrapped in a logWrapper,
+ // which relies on supplyDataFunctions context which changes on each workflow run
+ MemoryVectorStoreManager.instance.embeddings = embeddings;
+ MemoryVectorStoreManager.instance.vectorStoreBuffer.forEach((vectorStoreInstance) => {
+ vectorStoreInstance.embeddings = embeddings;
+ });
}
+
return MemoryVectorStoreManager.instance;
}
diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/message.operation.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/message.operation.ts
index cf770e40576..9af94a2121c 100644
--- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/message.operation.ts
+++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/message.operation.ts
@@ -18,6 +18,7 @@ import {
} from 'n8n-workflow';
import { OpenAI as OpenAIClient } from 'openai';
+import { promptTypeOptions, textFromPreviousNode } from '../../../../../utils/descriptions';
import { getConnectedTools } from '../../../../../utils/helpers';
import { getTracingConfig } from '../../../../../utils/tracing';
import { formatToOpenAIAssistantTool } from '../../helpers/utils';
@@ -26,24 +27,18 @@ import { assistantRLC } from '../descriptions';
const properties: INodeProperties[] = [
assistantRLC,
{
- displayName: 'Prompt',
+ ...promptTypeOptions,
name: 'prompt',
- type: 'options',
- options: [
- {
- // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
- name: 'Take from previous node automatically',
- value: 'auto',
- description: 'Looks for an input field called chatInput',
+ },
+ {
+ ...textFromPreviousNode,
+ disabledOptions: { show: { prompt: ['auto'] } },
+ displayOptions: {
+ show: {
+ prompt: ['auto'],
+ '@version': [{ _cnd: { gte: 1.7 } }],
},
- {
- // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
- name: 'Define below',
- value: 'define',
- description: 'Use an expression to reference data in previous nodes or enter static text',
- },
- ],
- default: 'auto',
+ },
},
{
displayName: 'Text',
diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts
index 20d7bd471ed..8beabcf0a4c 100644
--- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts
+++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts
@@ -77,7 +77,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'openAi',
icon: { light: 'file:openAi.svg', dark: 'file:openAi.dark.svg' },
group: ['transform'],
- version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
+ version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7],
subtitle: `={{(${prettifyOperation})($parameter.resource, $parameter.operation)}}`,
description: 'Message an assistant or GPT, analyze images, generate audio, etc.',
defaults: {
diff --git a/packages/@n8n/nodes-langchain/utils/descriptions.ts b/packages/@n8n/nodes-langchain/utils/descriptions.ts
index b779df1be4a..4a22e368942 100644
--- a/packages/@n8n/nodes-langchain/utils/descriptions.ts
+++ b/packages/@n8n/nodes-langchain/utils/descriptions.ts
@@ -66,7 +66,7 @@ export const inputSchemaField: INodeProperties = {
};
export const promptTypeOptions: INodeProperties = {
- displayName: 'Prompt',
+ displayName: 'Prompt Source',
name: 'promptType',
type: 'options',
options: [
@@ -97,3 +97,15 @@ export const textInput: INodeProperties = {
rows: 2,
},
};
+
+export const textFromPreviousNode: INodeProperties = {
+ displayName: 'Text From Previous Node',
+ name: 'text',
+ type: 'string',
+ required: true,
+ default: '={{ $json.chatInput }}',
+ typeOptions: {
+ rows: 2,
+ },
+ disabledOptions: { show: { promptType: ['auto'] } },
+};
diff --git a/packages/@n8n/nodes-langchain/utils/output_parsers/N8nItemListOutputParser.ts b/packages/@n8n/nodes-langchain/utils/output_parsers/N8nItemListOutputParser.ts
index f24238690b6..d12f24bf0a5 100644
--- a/packages/@n8n/nodes-langchain/utils/output_parsers/N8nItemListOutputParser.ts
+++ b/packages/@n8n/nodes-langchain/utils/output_parsers/N8nItemListOutputParser.ts
@@ -3,16 +3,21 @@ import { BaseOutputParser, OutputParserException } from '@langchain/core/output_
export class N8nItemListOutputParser extends BaseOutputParser {
lc_namespace = ['n8n-nodes-langchain', 'output_parsers', 'list_items'];
- private numberOfItems: number = 3;
+ private numberOfItems: number | undefined;
private separator: string;
constructor(options: { numberOfItems?: number; separator?: string }) {
super();
- if (options.numberOfItems && options.numberOfItems > 0) {
- this.numberOfItems = options.numberOfItems;
+
+ const { numberOfItems = 3, separator = '\n' } = options;
+
+ if (numberOfItems && numberOfItems > 0) {
+ this.numberOfItems = numberOfItems;
}
- this.separator = options.separator ?? '\\n';
+
+ this.separator = separator;
+
if (this.separator === '\\n') {
this.separator = '\n';
}
@@ -39,7 +44,7 @@ export class N8nItemListOutputParser extends BaseOutputParser {
this.numberOfItems ? this.numberOfItems + ' ' : ''
}items separated by`;
- const numberOfExamples = this.numberOfItems;
+ const numberOfExamples = this.numberOfItems ?? 3; // Default number of examples in case numberOfItems is not set
const examples: string[] = [];
for (let i = 1; i <= numberOfExamples; i++) {
diff --git a/packages/cli/src/response-helper.ts b/packages/cli/src/response-helper.ts
index 0dff1625cb4..2b993c266c4 100644
--- a/packages/cli/src/response-helper.ts
+++ b/packages/cli/src/response-helper.ts
@@ -103,6 +103,14 @@ export function sendErrorResponse(res: Response, error: Error) {
}
}
+ if (error.errorCode === 409 && originalUrl && originalUrl.includes('form-waiting')) {
+ //codes other than 200 breaks redirection to form-waiting page from form trigger
+ //render form page instead of json
+ return res.render('form-trigger-409', {
+ message: error.message,
+ });
+ }
+
httpStatusCode = error.httpStatusCode;
if (error.errorCode) {
diff --git a/packages/cli/templates/form-trigger-409.handlebars b/packages/cli/templates/form-trigger-409.handlebars
new file mode 100644
index 00000000000..6f34e5080df
--- /dev/null
+++ b/packages/cli/templates/form-trigger-409.handlebars
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+ Problem loading form
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue
index 47d2f1268bf..c9d23632de9 100644
--- a/packages/editor-ui/src/App.vue
+++ b/packages/editor-ui/src/App.vue
@@ -33,15 +33,11 @@ const loading = ref(true);
const defaultLocale = computed(() => rootStore.defaultLocale);
const isDemoMode = computed(() => route.name === VIEWS.DEMO);
const showAssistantButton = computed(() => assistantStore.canShowAssistantButtonsOnCanvas);
-
+const hasContentFooter = ref(false);
const appGrid = ref(null);
const assistantSidebarWidth = computed(() => assistantStore.chatWidth);
-watch(defaultLocale, (newLocale) => {
- void loadLanguage(newLocale);
-});
-
onMounted(async () => {
setAppZIndexes();
logHiringBanner();
@@ -54,11 +50,6 @@ onBeforeUnmount(() => {
window.removeEventListener('resize', updateGridWidth);
});
-// As assistant sidebar width changes, recalculate the total width regularly
-watch(assistantSidebarWidth, async () => {
- await updateGridWidth();
-});
-
const logHiringBanner = () => {
if (settingsStore.isHiringBannerEnabled && !isDemoMode.value) {
console.log(HIRING_BANNER);
@@ -71,6 +62,21 @@ const updateGridWidth = async () => {
uiStore.appGridWidth = appGrid.value.clientWidth;
}
};
+
+// As assistant sidebar width changes, recalculate the total width regularly
+watch(assistantSidebarWidth, async () => {
+ await updateGridWidth();
+});
+
+watch(route, (r) => {
+ hasContentFooter.value = r.matched.some(
+ (matchedRoute) => matchedRoute.components?.footer !== undefined,
+ );
+});
+
+watch(defaultLocale, (newLocale) => {
+ void loadLanguage(newLocale);
+});
@@ -94,12 +100,17 @@ const updateGridWidth = async () => {