feat(editor): Improve how we show default Agent prompt and Memory session parameters (#11491)

This commit is contained in:
oleg 2024-11-12 11:33:20 +01:00 committed by GitHub
parent e875bc5592
commit 565f8cd8c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 231 additions and 135 deletions

View file

@ -21,7 +21,7 @@ import { sqlAgentAgentProperties } from './agents/SqlAgent/description';
import { sqlAgentAgentExecute } from './agents/SqlAgent/execute'; import { sqlAgentAgentExecute } from './agents/SqlAgent/execute';
import { toolsAgentProperties } from './agents/ToolsAgent/description'; import { toolsAgentProperties } from './agents/ToolsAgent/description';
import { toolsAgentExecute } from './agents/ToolsAgent/execute'; 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 // Function used in the inputs expression to figure out which inputs to
// display based on the agent type // 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, ...textInput,
displayOptions: { displayOptions: {

View file

@ -1,6 +1,11 @@
import type { INodeProperties } from 'n8n-workflow'; import type { INodeProperties } from 'n8n-workflow';
import { promptTypeOptions, textInput } from '../../../../../utils/descriptions';
import { SQL_PREFIX, SQL_SUFFIX } from './other/prompts'; import { SQL_PREFIX, SQL_SUFFIX } from './other/prompts';
import {
promptTypeOptions,
textFromPreviousNode,
textInput,
} from '../../../../../utils/descriptions';
const dataSourceOptions: INodeProperties = { const dataSourceOptions: INodeProperties = {
displayName: 'Data Source', displayName: 'Data Source',
@ -114,6 +119,12 @@ export const sqlAgentAgentProperties: INodeProperties[] = [
}, },
}, },
}, },
{
...textFromPreviousNode,
displayOptions: {
show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.7 } }], agent: ['sqlAgent'] },
},
},
{ {
...textInput, ...textInput,
displayOptions: { displayOptions: {

View file

@ -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 { import {
type IExecuteFunctions, type IExecuteFunctions,
type INodeExecutionData, type INodeExecutionData,
@ -6,19 +12,12 @@ import {
type IDataObject, type IDataObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { SqlDatabase } from 'langchain/sql_db'; import { getMysqlDataSource } from './other/handlers/mysql';
import type { SqlCreatePromptArgs } from 'langchain/agents/toolkits/sql'; import { getPostgresDataSource } from './other/handlers/postgres';
import { SqlToolkit, createSqlAgent } from 'langchain/agents/toolkits/sql'; import { getSqliteDataSource } from './other/handlers/sqlite';
import type { BaseLanguageModel } from '@langchain/core/language_models/base'; import { SQL_PREFIX, SQL_SUFFIX } from './other/prompts';
import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
import type { DataSource } from '@n8n/typeorm';
import { getPromptInputByType, serializeChatHistory } from '../../../../../utils/helpers'; import { getPromptInputByType, serializeChatHistory } from '../../../../../utils/helpers';
import { getTracingConfig } from '../../../../../utils/tracing'; 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) => const parseTablesString = (tablesString: string) =>
tablesString tablesString

View file

@ -1,5 +1,5 @@
import { AgentExecutor } from 'langchain/agents'; 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 { OpenAIAssistantRunnable } from 'langchain/experimental/openai_assistant';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type { import type {
@ -8,10 +8,11 @@ import type {
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
} from 'n8n-workflow'; } 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 { getConnectedTools } from '../../../utils/helpers';
import { getTracingConfig } from '../../../utils/tracing'; import { getTracingConfig } from '../../../utils/tracing';
import { formatToOpenAIAssistantTool } from './utils';
export class OpenAiAssistant implements INodeType { export class OpenAiAssistant implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {

View file

@ -36,6 +36,7 @@ import {
getCustomErrorMessage as getCustomOpenAiErrorMessage, getCustomErrorMessage as getCustomOpenAiErrorMessage,
isOpenAiError, isOpenAiError,
} from '../../vendors/OpenAi/helpers/error-handling'; } from '../../vendors/OpenAi/helpers/error-handling';
import { promptTypeOptions, textFromPreviousNode } from '../../../utils/descriptions';
interface MessagesTemplate { interface MessagesTemplate {
type: string; type: string;
@ -253,7 +254,7 @@ export class ChainLlm implements INodeType {
name: 'chainLlm', name: 'chainLlm',
icon: 'fa:link', icon: 'fa:link',
group: ['transform'], 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', description: 'A simple chain to prompt a large language model',
defaults: { defaults: {
name: 'Basic LLM Chain', name: 'Basic LLM Chain',
@ -315,30 +316,16 @@ export class ChainLlm implements INodeType {
}, },
}, },
{ {
displayName: 'Prompt', ...promptTypeOptions,
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',
},
],
displayOptions: { displayOptions: {
hide: { hide: {
'@version': [1, 1.1, 1.2, 1.3], '@version': [1, 1.1, 1.2, 1.3],
}, },
}, },
default: 'auto', },
{
...textFromPreviousNode,
displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.5 } }] } },
}, },
{ {
displayName: 'Text', displayName: 'Text',

View file

@ -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 { import {
NodeConnectionType, NodeConnectionType,
type IExecuteFunctions, type IExecuteFunctions,
@ -7,20 +16,12 @@ import {
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { RetrievalQAChain } from 'langchain/chains'; import { promptTypeOptions, textFromPreviousNode } from '../../../utils/descriptions';
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 { getPromptInputByType, isChatInstance } from '../../../utils/helpers'; import { getPromptInputByType, isChatInstance } from '../../../utils/helpers';
import { getTemplateNoticeField } from '../../../utils/sharedFields';
import { getTracingConfig } from '../../../utils/tracing'; 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. If you don't know the answer, just say that you don't know, don't try to make up an answer.
---------------- ----------------
{context}`; {context}`;
@ -31,7 +32,7 @@ export class ChainRetrievalQa implements INodeType {
name: 'chainRetrievalQa', name: 'chainRetrievalQa',
icon: 'fa:link', icon: 'fa:link',
group: ['transform'], 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', description: 'Answer questions about retrieved documents',
defaults: { defaults: {
name: 'Question and Answer Chain', name: 'Question and Answer Chain',
@ -108,30 +109,16 @@ export class ChainRetrievalQa implements INodeType {
}, },
}, },
{ {
displayName: 'Prompt', ...promptTypeOptions,
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',
},
],
displayOptions: { displayOptions: {
hide: { hide: {
'@version': [{ _cnd: { lte: 1.2 } }], '@version': [{ _cnd: { lte: 1.2 } }],
}, },
}, },
default: 'auto', },
{
...textFromPreviousNode,
displayOptions: { show: { promptType: ['auto'], '@version': [{ _cnd: { gte: 1.4 } }] } },
}, },
{ {
displayName: 'Text', displayName: 'Text',

View file

@ -1,4 +1,6 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { BufferWindowMemoryInput } from 'langchain/memory';
import { BufferWindowMemory } from 'langchain/memory';
import { import {
NodeConnectionType, NodeConnectionType,
type INodeType, type INodeType,
@ -6,12 +8,16 @@ import {
type ISupplyDataFunctions, type ISupplyDataFunctions,
type SupplyData, type SupplyData,
} from 'n8n-workflow'; } 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 { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions'; import {
import { getSessionId } from '../../../utils/helpers'; sessionIdOption,
sessionKeyProperty,
contextWindowLengthProperty,
expressionSessionKeyProperty,
} from '../descriptions';
class MemoryChatBufferSingleton { class MemoryChatBufferSingleton {
private static instance: MemoryChatBufferSingleton; private static instance: MemoryChatBufferSingleton;
@ -72,7 +78,7 @@ export class MemoryBufferWindow implements INodeType {
name: 'memoryBufferWindow', name: 'memoryBufferWindow',
icon: 'fa:database', icon: 'fa:database',
group: ['transform'], 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', description: 'Stores in n8n memory, so no credentials required',
defaults: { defaults: {
name: 'Window Buffer Memory', name: 'Window Buffer Memory',
@ -129,6 +135,7 @@ export class MemoryBufferWindow implements INodeType {
}, },
}, },
}, },
expressionSessionKeyProperty(1.3),
sessionKeyProperty, sessionKeyProperty,
contextWindowLengthProperty, contextWindowLengthProperty,
], ],

View file

@ -1,4 +1,5 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { MotorheadMemory } from '@langchain/community/memory/motorhead_memory';
import { import {
NodeConnectionType, NodeConnectionType,
type INodeType, type INodeType,
@ -7,11 +8,10 @@ import {
type SupplyData, type SupplyData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { MotorheadMemory } from '@langchain/community/memory/motorhead_memory'; import { getSessionId } from '../../../utils/helpers';
import { logWrapper } from '../../../utils/logWrapper'; import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
import { sessionIdOption, sessionKeyProperty } from '../descriptions'; import { expressionSessionKeyProperty, sessionIdOption, sessionKeyProperty } from '../descriptions';
import { getSessionId } from '../../../utils/helpers';
export class MemoryMotorhead implements INodeType { export class MemoryMotorhead implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -19,7 +19,7 @@ export class MemoryMotorhead implements INodeType {
name: 'memoryMotorhead', name: 'memoryMotorhead',
icon: 'fa:file-export', icon: 'fa:file-export',
group: ['transform'], group: ['transform'],
version: [1, 1.1, 1.2], version: [1, 1.1, 1.2, 1.3],
description: 'Use Motorhead Memory', description: 'Use Motorhead Memory',
defaults: { defaults: {
name: 'Motorhead', name: 'Motorhead',
@ -82,6 +82,7 @@ export class MemoryMotorhead implements INodeType {
}, },
}, },
}, },
expressionSessionKeyProperty(1.3),
sessionKeyProperty, sessionKeyProperty,
], ],
}; };

View file

@ -1,4 +1,9 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ /* 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 { import type {
ISupplyDataFunctions, ISupplyDataFunctions,
INodeType, INodeType,
@ -6,16 +11,17 @@ import type {
SupplyData, SupplyData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeConnectionType } 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 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 { getSessionId } from '../../../utils/helpers';
import { postgresConnectionTest } from 'n8n-nodes-base/dist/nodes/Postgres/v2/methods/credentialTest';
import { logWrapper } from '../../../utils/logWrapper'; import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions'; import {
import { getSessionId } from '../../../utils/helpers'; sessionIdOption,
sessionKeyProperty,
contextWindowLengthProperty,
expressionSessionKeyProperty,
} from '../descriptions';
export class MemoryPostgresChat implements INodeType { export class MemoryPostgresChat implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -23,7 +29,7 @@ export class MemoryPostgresChat implements INodeType {
name: 'memoryPostgresChat', name: 'memoryPostgresChat',
icon: 'file:postgres.svg', icon: 'file:postgres.svg',
group: ['transform'], group: ['transform'],
version: [1, 1.1], version: [1, 1.1, 1.2, 1.3],
description: 'Stores the chat history in Postgres table.', description: 'Stores the chat history in Postgres table.',
defaults: { defaults: {
name: 'Postgres Chat Memory', name: 'Postgres Chat Memory',
@ -56,6 +62,7 @@ export class MemoryPostgresChat implements INodeType {
properties: [ properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]), getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
sessionIdOption, sessionIdOption,
expressionSessionKeyProperty(1.2),
sessionKeyProperty, sessionKeyProperty,
{ {
displayName: 'Table Name', displayName: 'Table Name',

View file

@ -1,4 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ /* 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 { import {
NodeOperationError, NodeOperationError,
type INodeType, type INodeType,
@ -7,15 +10,18 @@ import {
type SupplyData, type SupplyData,
NodeConnectionType, NodeConnectionType,
} from 'n8n-workflow'; } 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 type { RedisClientOptions } from 'redis';
import { createClient } from 'redis'; import { createClient } from 'redis';
import { getSessionId } from '../../../utils/helpers';
import { logWrapper } from '../../../utils/logWrapper'; import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions'; import {
import { getSessionId } from '../../../utils/helpers'; sessionIdOption,
sessionKeyProperty,
contextWindowLengthProperty,
expressionSessionKeyProperty,
} from '../descriptions';
export class MemoryRedisChat implements INodeType { export class MemoryRedisChat implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -23,7 +29,7 @@ export class MemoryRedisChat implements INodeType {
name: 'memoryRedisChat', name: 'memoryRedisChat',
icon: 'file:redis.svg', icon: 'file:redis.svg',
group: ['transform'], 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.', description: 'Stores the chat history in Redis.',
defaults: { defaults: {
name: 'Redis Chat Memory', name: 'Redis Chat Memory',
@ -86,6 +92,7 @@ export class MemoryRedisChat implements INodeType {
}, },
}, },
}, },
expressionSessionKeyProperty(1.4),
sessionKeyProperty, sessionKeyProperty,
{ {
displayName: 'Session Time To Live', displayName: 'Session Time To Live',

View file

@ -1,4 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ /* 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 { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type { import type {
ISupplyDataFunctions, ISupplyDataFunctions,
@ -6,13 +9,16 @@ import type {
INodeTypeDescription, INodeTypeDescription,
SupplyData, SupplyData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { XataChatMessageHistory } from '@langchain/community/stores/message/xata';
import { BufferMemory, BufferWindowMemory } from 'langchain/memory'; import { getSessionId } from '../../../utils/helpers';
import { BaseClient } from '@xata.io/client';
import { logWrapper } from '../../../utils/logWrapper'; import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
import { sessionIdOption, sessionKeyProperty, contextWindowLengthProperty } from '../descriptions'; import {
import { getSessionId } from '../../../utils/helpers'; sessionIdOption,
sessionKeyProperty,
contextWindowLengthProperty,
expressionSessionKeyProperty,
} from '../descriptions';
export class MemoryXata implements INodeType { export class MemoryXata implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -20,7 +26,7 @@ export class MemoryXata implements INodeType {
name: 'memoryXata', name: 'memoryXata',
icon: 'file:xata.svg', icon: 'file:xata.svg',
group: ['transform'], group: ['transform'],
version: [1, 1.1, 1.2, 1.3], version: [1, 1.1, 1.2, 1.3, 1.4],
description: 'Use Xata Memory', description: 'Use Xata Memory',
defaults: { defaults: {
name: 'Xata', name: 'Xata',
@ -86,6 +92,7 @@ export class MemoryXata implements INodeType {
}, },
}, },
sessionKeyProperty, sessionKeyProperty,
expressionSessionKeyProperty(1.4),
{ {
...contextWindowLengthProperty, ...contextWindowLengthProperty,
displayOptions: { hide: { '@version': [{ _cnd: { lt: 1.3 } }] } }, displayOptions: { hide: { '@version': [{ _cnd: { lt: 1.3 } }] } },

View file

@ -12,7 +12,7 @@ import { ZepCloudMemory } from '@langchain/community/memory/zep_cloud';
import { logWrapper } from '../../../utils/logWrapper'; import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
import { sessionIdOption, sessionKeyProperty } from '../descriptions'; import { expressionSessionKeyProperty, sessionIdOption, sessionKeyProperty } from '../descriptions';
import { getSessionId } from '../../../utils/helpers'; import { getSessionId } from '../../../utils/helpers';
import type { BaseChatMemory } from '@langchain/community/dist/memory/chat_memory'; import type { BaseChatMemory } from '@langchain/community/dist/memory/chat_memory';
import type { InputValues, MemoryVariables } from '@langchain/core/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 // eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
icon: 'file:zep.png', icon: 'file:zep.png',
group: ['transform'], group: ['transform'],
version: [1, 1.1, 1.2], version: [1, 1.1, 1.2, 1.3],
description: 'Use Zep Memory', description: 'Use Zep Memory',
defaults: { defaults: {
name: 'Zep', name: 'Zep',
@ -99,6 +99,7 @@ export class MemoryZep implements INodeType {
}, },
}, },
}, },
expressionSessionKeyProperty(1.3),
sessionKeyProperty, sessionKeyProperty,
], ],
}; };

View file

@ -21,6 +21,20 @@ export const sessionIdOption: INodeProperties = {
default: 'fromInput', 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 = { export const sessionKeyProperty: INodeProperties = {
displayName: 'Key', displayName: 'Key',
name: 'sessionKey', name: 'sessionKey',

View file

@ -18,6 +18,7 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { OpenAI as OpenAIClient } from 'openai'; import { OpenAI as OpenAIClient } from 'openai';
import { promptTypeOptions, textFromPreviousNode } from '../../../../../utils/descriptions';
import { getConnectedTools } from '../../../../../utils/helpers'; import { getConnectedTools } from '../../../../../utils/helpers';
import { getTracingConfig } from '../../../../../utils/tracing'; import { getTracingConfig } from '../../../../../utils/tracing';
import { formatToOpenAIAssistantTool } from '../../helpers/utils'; import { formatToOpenAIAssistantTool } from '../../helpers/utils';
@ -26,24 +27,18 @@ import { assistantRLC } from '../descriptions';
const properties: INodeProperties[] = [ const properties: INodeProperties[] = [
assistantRLC, assistantRLC,
{ {
displayName: 'Prompt', ...promptTypeOptions,
name: 'prompt', name: 'prompt',
type: 'options', },
options: [ {
{ ...textFromPreviousNode,
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased disabledOptions: { show: { prompt: ['auto'] } },
name: 'Take from previous node automatically', displayOptions: {
value: 'auto', show: {
description: 'Looks for an input field called chatInput', 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', displayName: 'Text',

View file

@ -77,7 +77,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'openAi', name: 'openAi',
icon: { light: 'file:openAi.svg', dark: 'file:openAi.dark.svg' }, icon: { light: 'file:openAi.svg', dark: 'file:openAi.dark.svg' },
group: ['transform'], 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)}}`, subtitle: `={{(${prettifyOperation})($parameter.resource, $parameter.operation)}}`,
description: 'Message an assistant or GPT, analyze images, generate audio, etc.', description: 'Message an assistant or GPT, analyze images, generate audio, etc.',
defaults: { defaults: {

View file

@ -66,7 +66,7 @@ export const inputSchemaField: INodeProperties = {
}; };
export const promptTypeOptions: INodeProperties = { export const promptTypeOptions: INodeProperties = {
displayName: 'Prompt', displayName: 'Prompt Source',
name: 'promptType', name: 'promptType',
type: 'options', type: 'options',
options: [ options: [
@ -97,3 +97,15 @@ export const textInput: INodeProperties = {
rows: 2, 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'] } },
};

View file

@ -74,7 +74,7 @@ const { segments, readEditorValue, editor, hasFocus, focus } = useExpressionEdit
editorRef: root, editorRef: root,
editorValue, editorValue,
extensions, extensions,
isReadOnly: props.isReadOnly, isReadOnly: computed(() => props.isReadOnly),
autocompleteTelemetry: { enabled: true, parameterPath: props.path }, autocompleteTelemetry: { enabled: true, parameterPath: props.path },
}); });
@ -110,7 +110,15 @@ defineExpose({ editor });
</template> </template>
<style lang="scss" module> <style lang="scss" module>
:global(.cm-content) { .editor {
border-radius: var(--border-radius-base); :global(.cm-content) {
border-radius: var(--border-radius-base);
&[aria-readonly='true'] {
--disabled-fill: var(--color-background-medium);
background-color: var(--disabled-fill, var(--color-background-light));
color: var(--disabled-color, var(--color-text-base));
cursor: not-allowed;
}
}
} }
</style> </style>

View file

@ -56,6 +56,7 @@ const extensions = computed(() => [
infoBoxTooltips(), infoBoxTooltips(),
]); ]);
const editorValue = ref<string>(removeExpressionPrefix(props.modelValue)); const editorValue = ref<string>(removeExpressionPrefix(props.modelValue));
const { const {
editor: editorRef, editor: editorRef,
segments, segments,
@ -68,7 +69,7 @@ const {
editorRef: root, editorRef: root,
editorValue, editorValue,
extensions, extensions,
isReadOnly: props.isReadOnly, isReadOnly: computed(() => props.isReadOnly),
autocompleteTelemetry: { enabled: true, parameterPath: props.path }, autocompleteTelemetry: { enabled: true, parameterPath: props.path },
additionalData: props.additionalData, additionalData: props.additionalData,
}); });
@ -133,6 +134,17 @@ defineExpose({
padding-left: 0; padding-left: 0;
} }
:deep(.cm-content) { :deep(.cm-content) {
--disabled-fill: var(--color-background-medium);
padding-left: var(--spacing-2xs); padding-left: var(--spacing-2xs);
&[aria-readonly='true'] {
background-color: var(--disabled-fill, var(--color-background-light));
border-color: var(--disabled-border, var(--border-color-base));
color: var(--disabled-color, var(--color-text-base));
cursor: not-allowed;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
} }
</style> </style>

View file

@ -16,12 +16,14 @@ interface InlineExpressionEditorOutputProps {
editorState?: EditorState; editorState?: EditorState;
selection?: SelectionRange; selection?: SelectionRange;
visible?: boolean; visible?: boolean;
isReadOnly?: boolean;
} }
withDefaults(defineProps<InlineExpressionEditorOutputProps>(), { withDefaults(defineProps<InlineExpressionEditorOutputProps>(), {
visible: false, visible: false,
editorState: undefined, editorState: undefined,
selection: undefined, selection: undefined,
isReadOnly: false,
unresolvedExpression: undefined, unresolvedExpression: undefined,
}); });
@ -51,7 +53,7 @@ onBeforeUnmount(() => {
> >
</ExpressionOutput> </ExpressionOutput>
</n8n-text> </n8n-text>
<div :class="$style.footer"> <div :class="$style.footer" v-if="!isReadOnly">
<InlineExpressionTip <InlineExpressionTip
:editor-state="editorState" :editor-state="editorState"
:selection="selection" :selection="selection"

View file

@ -983,7 +983,7 @@ watch(remoteParameterOptionsLoading, () => {
// Focus input field when changing from fixed value to expression // Focus input field when changing from fixed value to expression
watch(isModelValueExpression, async (isExpression, wasExpression) => { watch(isModelValueExpression, async (isExpression, wasExpression) => {
if (isExpression && !wasExpression) { if (!props.isReadOnly && isExpression && !wasExpression) {
await nextTick(); await nextTick();
inputField.value?.focus(); inputField.value?.focus();
} }
@ -1497,7 +1497,7 @@ onUpdated(async () => {
:disabled="isReadOnly" :disabled="isReadOnly"
@update:model-value="valueChanged" @update:model-value="valueChanged"
/> />
<div v-if="showDragnDropTip" :class="$style.tip"> <div v-if="!isReadOnly && showDragnDropTip" :class="$style.tip">
<InlineExpressionTip /> <InlineExpressionTip />
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue'; import { computed, ref, watch } from 'vue';
import type { IUpdateInformation } from '@/Interface'; import type { IUpdateInformation } from '@/Interface';
import DraggableTarget from '@/components/DraggableTarget.vue'; import DraggableTarget from '@/components/DraggableTarget.vue';
@ -189,6 +189,16 @@ function onDrop(newParamValue: string) {
forceShowExpression.value = false; forceShowExpression.value = false;
}, 200); }, 200);
} }
// When switching to read-only mode, reset the value to the default value
watch(
() => props.isReadOnly,
(isReadOnly) => {
if (isReadOnly) {
valueChanged({ name: props.path, value: props.parameter.default });
}
},
);
</script> </script>
<template> <template>

View file

@ -322,7 +322,10 @@ function mustHideDuringCustomApiCall(
return !MUST_REMAIN_VISIBLE.includes(parameter.name); return !MUST_REMAIN_VISIBLE.includes(parameter.name);
} }
function displayNodeParameter(parameter: INodeProperties): boolean { function displayNodeParameter(
parameter: INodeProperties,
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
): boolean {
if (parameter.type === 'hidden') { if (parameter.type === 'hidden') {
return false; return false;
} }
@ -343,7 +346,7 @@ function displayNodeParameter(parameter: INodeProperties): boolean {
return false; return false;
} }
if (parameter.displayOptions === undefined) { if (parameter[displayKey] === undefined) {
// If it is not defined no need to do a proper check // If it is not defined no need to do a proper check
return true; return true;
} }
@ -402,13 +405,19 @@ function displayNodeParameter(parameter: INodeProperties): boolean {
if (props.path) { if (props.path) {
rawValues = deepCopy(props.nodeValues); rawValues = deepCopy(props.nodeValues);
set(rawValues, props.path, nodeValues); set(rawValues, props.path, nodeValues);
return nodeHelpers.displayParameter(rawValues, parameter, props.path, node.value); return nodeHelpers.displayParameter(rawValues, parameter, props.path, node.value, displayKey);
} else { } else {
return nodeHelpers.displayParameter(nodeValues, parameter, '', node.value); return nodeHelpers.displayParameter(nodeValues, parameter, '', node.value, displayKey);
} }
} }
return nodeHelpers.displayParameter(props.nodeValues, parameter, props.path, node.value); return nodeHelpers.displayParameter(
props.nodeValues,
parameter,
props.path,
node.value,
displayKey,
);
} }
function valueChanged(parameterData: IUpdateInformation): void { function valueChanged(parameterData: IUpdateInformation): void {
@ -620,7 +629,10 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
:value="getParameterValue(parameter.name)" :value="getParameterValue(parameter.name)"
:display-options="shouldShowOptions(parameter)" :display-options="shouldShowOptions(parameter)"
:path="getPath(parameter.name)" :path="getPath(parameter.name)"
:is-read-only="isReadOnly" :is-read-only="
isReadOnly ||
(parameter.disabledOptions && displayNodeParameter(parameter, 'disabledOptions'))
"
:hide-label="false" :hide-label="false"
:node-values="nodeValues" :node-values="nodeValues"
@update="valueChanged" @update="valueChanged"

View file

@ -39,7 +39,7 @@ const isDefault = computed(() => props.parameter.default === props.value);
const isValueAnExpression = computed(() => isValueExpression(props.parameter, props.value)); const isValueAnExpression = computed(() => isValueExpression(props.parameter, props.value));
const isHtmlEditor = computed(() => getArgument('editor') === 'htmlEditor'); const isHtmlEditor = computed(() => getArgument('editor') === 'htmlEditor');
const shouldShowExpressionSelector = computed( const shouldShowExpressionSelector = computed(
() => !props.parameter.noDataExpression && props.showExpressionSelector, () => !props.parameter.noDataExpression && props.showExpressionSelector && !props.isReadOnly,
); );
const shouldShowOptions = computed(() => { const shouldShowOptions = computed(() => {
if (props.isReadOnly) { if (props.isReadOnly) {

View file

@ -120,8 +120,9 @@ export function useNodeHelpers() {
parameter: INodeProperties | INodeCredentialDescription, parameter: INodeProperties | INodeCredentialDescription,
path: string, path: string,
node: INodeUi | null, node: INodeUi | null,
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
) { ) {
return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node); return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node, displayKey);
} }
function refreshNodeIssues(): void { function refreshNodeIssues(): void {

View file

@ -1417,6 +1417,7 @@ export interface INodeProperties {
default: NodeParameterValueType; default: NodeParameterValueType;
description?: string; description?: string;
hint?: string; hint?: string;
disabledOptions?: IDisplayOptions;
displayOptions?: IDisplayOptions; displayOptions?: IDisplayOptions;
options?: Array<INodePropertyOptions | INodeProperties | INodePropertyCollection>; options?: Array<INodePropertyOptions | INodeProperties | INodePropertyCollection>;
placeholder?: string; placeholder?: string;
@ -1657,6 +1658,7 @@ export interface INodeCredentialDescription {
name: string; name: string;
required?: boolean; required?: boolean;
displayName?: string; displayName?: string;
disabledOptions?: ICredentialsDisplayOptions;
displayOptions?: ICredentialsDisplayOptions; displayOptions?: ICredentialsDisplayOptions;
testedBy?: ICredentialTestRequest | string; // Name of a function inside `loadOptions.credentialTest` testedBy?: ICredentialTestRequest | string; // Name of a function inside `loadOptions.credentialTest`
} }

View file

@ -639,12 +639,13 @@ export function displayParameter(
parameter: INodeProperties | INodeCredentialDescription, parameter: INodeProperties | INodeCredentialDescription,
node: Pick<INode, 'typeVersion'> | null, // Allow null as it does also get used by credentials and they do not have versioning yet node: Pick<INode, 'typeVersion'> | null, // Allow null as it does also get used by credentials and they do not have versioning yet
nodeValuesRoot?: INodeParameters, nodeValuesRoot?: INodeParameters,
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
) { ) {
if (!parameter.displayOptions) { if (!parameter[displayKey]) {
return true; return true;
} }
const { show, hide } = parameter.displayOptions; const { show, hide } = parameter[displayKey];
nodeValuesRoot = nodeValuesRoot || nodeValues; nodeValuesRoot = nodeValuesRoot || nodeValues;
@ -691,6 +692,7 @@ export function displayParameterPath(
parameter: INodeProperties | INodeCredentialDescription, parameter: INodeProperties | INodeCredentialDescription,
path: string, path: string,
node: Pick<INode, 'typeVersion'> | null, node: Pick<INode, 'typeVersion'> | null,
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
) { ) {
let resolvedNodeValues = nodeValues; let resolvedNodeValues = nodeValues;
if (path !== '') { if (path !== '') {
@ -703,7 +705,7 @@ export function displayParameterPath(
nodeValuesRoot = get(nodeValues, 'parameters') as INodeParameters; nodeValuesRoot = get(nodeValues, 'parameters') as INodeParameters;
} }
return displayParameter(resolvedNodeValues, parameter, node, nodeValuesRoot); return displayParameter(resolvedNodeValues, parameter, node, nodeValuesRoot, displayKey);
} }
/** /**