@n8n/n8n-nodes-langchain
Version:

529 lines • 19.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.description = void 0;
exports.execute = execute;
const n8n_workflow_1 = require("n8n-workflow");
const zod_to_json_schema_1 = __importDefault(require("zod-to-json-schema"));
const helpers_1 = require("../../../../../utils/helpers");
const utils_1 = require("../../helpers/utils");
const transport_1 = require("../../transport");
const descriptions_1 = require("../descriptions");
const properties = [
descriptions_1.modelRLC,
{
displayName: 'Messages',
name: 'messages',
type: 'fixedCollection',
typeOptions: {
sortable: true,
multipleValues: true,
},
placeholder: 'Add Message',
default: { values: [{ content: '', role: 'user' }] },
options: [
{
displayName: 'Values',
name: 'values',
values: [
{
displayName: 'Prompt',
name: 'content',
type: 'string',
description: 'The content of the message to be sent',
default: '',
placeholder: 'e.g. Hello, how can you help me?',
typeOptions: {
rows: 2,
},
},
{
displayName: 'Role',
name: 'role',
type: 'options',
description: "Role in shaping the model's response, it tells the model how it should behave and interact with the user",
options: [
{
name: 'User',
value: 'user',
description: 'Send a message as a user and get a response from the model',
},
{
name: 'Assistant',
value: 'assistant',
description: 'Tell the model to adopt a specific tone or personality',
},
],
default: 'user',
},
],
},
],
},
{
displayName: 'Add Attachments',
name: 'addAttachments',
type: 'boolean',
default: false,
description: 'Whether to add attachments to the message',
},
{
displayName: 'Attachments Input Type',
name: 'attachmentsInputType',
type: 'options',
default: 'url',
description: 'The type of input to use for the attachments',
options: [
{
name: 'URL(s)',
value: 'url',
},
{
name: 'Binary File(s)',
value: 'binary',
},
],
displayOptions: {
show: {
addAttachments: [true],
},
},
},
{
displayName: 'Attachment URL(s)',
name: 'attachmentsUrls',
type: 'string',
default: '',
placeholder: 'e.g. https://example.com/image.png',
description: 'URL(s) of the file(s) to attach, multiple URLs can be added separated by comma',
displayOptions: {
show: {
addAttachments: [true],
attachmentsInputType: ['url'],
},
},
},
{
displayName: 'Attachment Input Data Field Name(s)',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
placeholder: 'e.g. data',
description: 'Name of the binary field(s) which contains the file(s) to attach, multiple field names can be added separated by comma',
displayOptions: {
show: {
addAttachments: [true],
attachmentsInputType: ['binary'],
},
},
},
{
displayName: 'Simplify Output',
name: 'simplify',
type: 'boolean',
default: true,
description: 'Whether to return a simplified version of the response instead of the raw data',
},
{
displayName: 'Options',
name: 'options',
placeholder: 'Add Option',
type: 'collection',
default: {},
options: [
{
displayName: 'Include Merged Response',
name: 'includeMergedResponse',
type: 'boolean',
default: false,
description: 'Whether to include a single output string merging all text parts of the response',
},
{
displayName: 'System Message',
name: 'system',
type: 'string',
default: '',
placeholder: 'e.g. You are a helpful assistant',
},
{
displayName: 'Code Execution',
name: 'codeExecution',
type: 'boolean',
default: false,
description: 'Whether to enable code execution. Not supported by all models.',
},
{
displayName: 'Web Search',
name: 'webSearch',
type: 'boolean',
default: false,
description: 'Whether to enable web search',
},
{
displayName: 'Web Search Max Uses',
name: 'maxUses',
type: 'number',
default: 5,
description: 'The maximum number of web search uses per request',
typeOptions: {
minValue: 0,
numberPrecision: 0,
},
},
{
displayName: 'Web Search Allowed Domains',
name: 'allowedDomains',
type: 'string',
default: '',
description: 'Comma-separated list of domains to search. Only domains in this list will be searched. Conflicts with "Web Search Blocked Domains".',
placeholder: 'e.g. google.com, wikipedia.org',
},
{
displayName: 'Web Search Blocked Domains',
name: 'blockedDomains',
type: 'string',
default: '',
description: 'Comma-separated list of domains to block from search. Conflicts with "Web Search Allowed Domains".',
placeholder: 'e.g. google.com, wikipedia.org',
},
{
displayName: 'Maximum Number of Tokens',
name: 'maxTokens',
default: 1024,
description: 'The maximum number of tokens to generate in the completion',
type: 'number',
typeOptions: {
minValue: 1,
numberPrecision: 0,
},
},
{
displayName: 'Output Randomness (Temperature)',
name: 'temperature',
default: 1,
description: 'Controls the randomness of the output. Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive',
type: 'number',
typeOptions: {
minValue: 0,
maxValue: 1,
numberPrecision: 1,
},
},
{
displayName: 'Output Randomness (Top P)',
name: 'topP',
default: 0.7,
description: 'The maximum cumulative probability of tokens to consider when sampling',
type: 'number',
typeOptions: {
minValue: 0,
maxValue: 1,
numberPrecision: 1,
},
},
{
displayName: 'Output Randomness (Top K)',
name: 'topK',
default: 5,
description: 'The maximum number of tokens to consider when sampling',
type: 'number',
typeOptions: {
minValue: 0,
numberPrecision: 0,
},
},
{
displayName: 'Max Tool Calls Iterations',
name: 'maxToolsIterations',
type: 'number',
default: 15,
description: 'The maximum number of tool iteration cycles the LLM will run before stopping. A single iteration can contain multiple tool calls. Set to 0 for no limit',
typeOptions: {
minValue: 0,
numberPrecision: 0,
},
},
],
},
];
const displayOptions = {
show: {
operation: ['message'],
resource: ['text'],
},
};
exports.description = (0, n8n_workflow_1.updateDisplayOptions)(displayOptions, properties);
function getFileTypeOrThrow(mimeType) {
if (mimeType?.startsWith('image/')) {
return 'image';
}
if (mimeType === 'application/pdf') {
return 'document';
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported file type: ${mimeType}. Only images and PDFs are supported.`);
}
async function execute(i) {
const model = this.getNodeParameter('modelId', i, '', { extractValue: true });
const messages = this.getNodeParameter('messages.values', i, []);
const addAttachments = this.getNodeParameter('addAttachments', i, false);
const simplify = this.getNodeParameter('simplify', i, true);
const options = this.getNodeParameter('options', i, {});
const { tools, connectedTools } = await getTools.call(this, options);
if (addAttachments) {
if (options.codeExecution) {
await addCodeAttachmentsToMessages.call(this, i, messages);
}
else {
await addRegularAttachmentsToMessages.call(this, i, messages);
}
}
const body = {
model,
messages,
tools,
max_tokens: options.maxTokens ?? 1024,
system: options.system,
temperature: options.temperature,
top_p: options.topP,
top_k: options.topK,
};
let response = (await transport_1.apiRequest.call(this, 'POST', '/v1/messages', {
body,
enableAnthropicBetas: { codeExecution: options.codeExecution },
}));
const captureUsage = () => {
const usage = response.usage;
if (usage) {
(0, n8n_workflow_1.accumulateTokenUsage)(this, usage.input_tokens, usage.output_tokens);
}
};
captureUsage();
const maxToolsIterations = this.getNodeParameter('options.maxToolsIterations', i, 15);
const abortSignal = this.getExecutionCancelSignal();
let currentIteration = 0;
let pauseTurns = 0;
while (true) {
if (abortSignal?.aborted) {
break;
}
if (response.stop_reason === 'tool_use') {
if (maxToolsIterations > 0 && currentIteration >= maxToolsIterations) {
break;
}
messages.push({
role: 'assistant',
content: response.content,
});
await handleToolUse.call(this, response, messages, connectedTools);
currentIteration++;
}
else if (response.stop_reason === 'pause_turn') {
if (pauseTurns >= 3) {
break;
}
messages.push({
role: 'assistant',
content: response.content,
});
pauseTurns++;
}
else {
break;
}
response = (await transport_1.apiRequest.call(this, 'POST', '/v1/messages', {
body,
enableAnthropicBetas: { codeExecution: options.codeExecution },
}));
captureUsage();
}
const mergedResponse = options.includeMergedResponse
? response.content
.filter((c) => c.type === 'text')
.map((c) => c.text)
.join('')
: undefined;
if (simplify) {
return [
{
json: {
content: response.content,
merged_response: mergedResponse,
},
pairedItem: { item: i },
},
];
}
return [
{
json: { ...response, merged_response: mergedResponse },
pairedItem: { item: i },
},
];
}
async function getTools(options) {
let connectedTools = [];
const nodeInputs = this.getNodeInputs();
if (nodeInputs.some((i) => i.type === 'ai_tool')) {
connectedTools = await (0, helpers_1.getConnectedTools)(this, true);
}
const tools = connectedTools.map((t) => ({
type: 'custom',
name: t.name,
input_schema: (0, zod_to_json_schema_1.default)(t.schema),
description: t.description,
}));
if (options.codeExecution) {
tools.push({
type: 'code_execution_20250522',
name: 'code_execution',
});
}
if (options.webSearch) {
const allowedDomains = options.allowedDomains
? (0, utils_1.splitByComma)(options.allowedDomains)
: undefined;
const blockedDomains = options.blockedDomains
? (0, utils_1.splitByComma)(options.blockedDomains)
: undefined;
tools.push({
type: 'web_search_20250305',
name: 'web_search',
max_uses: options.maxUses,
allowed_domains: allowedDomains,
blocked_domains: blockedDomains,
});
}
return { tools, connectedTools };
}
async function addCodeAttachmentsToMessages(i, messages) {
const inputType = this.getNodeParameter('attachmentsInputType', i, 'url');
const baseUrl = await utils_1.getBaseUrl.call(this);
const fileUrlPrefix = `${baseUrl}/v1/files/`;
let content;
if (inputType === 'url') {
const urls = this.getNodeParameter('attachmentsUrls', i, '');
const promises = (0, utils_1.splitByComma)(urls).map(async (url) => {
if (url.startsWith(fileUrlPrefix)) {
return url.replace(fileUrlPrefix, '');
}
else {
const { fileContent, mimeType } = await utils_1.downloadFile.call(this, url);
const response = await utils_1.uploadFile.call(this, fileContent, mimeType);
return response.id;
}
});
const fileIds = await Promise.all(promises);
content = fileIds.map((fileId) => ({
type: 'container_upload',
file_id: fileId,
}));
}
else {
const binaryPropertyNames = this.getNodeParameter('binaryPropertyName', i, 'data');
const promises = (0, utils_1.splitByComma)(binaryPropertyNames).map(async (binaryPropertyName) => {
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
const response = await utils_1.uploadFile.call(this, buffer, binaryData.mimeType);
return response.id;
});
const fileIds = await Promise.all(promises);
content = fileIds.map((fileId) => ({
type: 'container_upload',
file_id: fileId,
}));
}
messages.push({
role: 'user',
content,
});
}
async function addRegularAttachmentsToMessages(i, messages) {
const inputType = this.getNodeParameter('attachmentsInputType', i, 'url');
const baseUrl = await utils_1.getBaseUrl.call(this);
const fileUrlPrefix = `${baseUrl}/v1/files/`;
let content;
if (inputType === 'url') {
const urls = this.getNodeParameter('attachmentsUrls', i, '');
const promises = (0, utils_1.splitByComma)(urls).map(async (url) => {
if (url.startsWith(fileUrlPrefix)) {
const response = (await transport_1.apiRequest.call(this, 'GET', '', {
option: { url },
}));
const type = getFileTypeOrThrow.call(this, response.mime_type);
return {
type,
source: {
type: 'file',
file_id: url.replace(fileUrlPrefix, ''),
},
};
}
else {
const response = (await this.helpers.httpRequest.call(this, {
url,
method: 'HEAD',
returnFullResponse: true,
}));
const mimeType = (0, utils_1.getMimeType)(response.headers['content-type']);
const type = getFileTypeOrThrow.call(this, mimeType);
return {
type,
source: {
type: 'url',
url,
},
};
}
});
content = await Promise.all(promises);
}
else {
const binaryPropertyNames = this.getNodeParameter('binaryPropertyName', i, 'data');
const promises = (0, utils_1.splitByComma)(binaryPropertyNames).map(async (binaryPropertyName) => {
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
const type = getFileTypeOrThrow.call(this, binaryData.mimeType);
const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
const fileBase64 = buffer.toString('base64');
return {
type,
source: {
type: 'base64',
media_type: binaryData.mimeType,
data: fileBase64,
},
};
});
content = await Promise.all(promises);
}
messages.push({
role: 'user',
content,
});
}
async function handleToolUse(response, messages, connectedTools) {
const toolCalls = response.content.filter((c) => c.type === 'tool_use');
if (!toolCalls.length) {
return;
}
const toolResults = {
role: 'user',
content: [],
};
for (const toolCall of toolCalls) {
let toolResponse;
for (const connectedTool of connectedTools) {
if (connectedTool.name === toolCall.name) {
toolResponse = (await connectedTool.invoke(toolCall.input));
}
}
toolResults.content.push({
type: 'tool_result',
tool_use_id: toolCall.id,
content: typeof toolResponse === 'object' ? JSON.stringify(toolResponse) : (toolResponse ?? ''),
});
}
messages.push(toolResults);
}
//# sourceMappingURL=message.operation.js.map