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,17 +16,9 @@ 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.
@ -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);
} }
/** /**