@n8n/n8n-nodes-langchain
Version:
655 lines • 30.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LmChatAnthropic = void 0;
const anthropic_1 = require("@langchain/anthropic");
const ai_utilities_1 = require("@n8n/ai-utilities");
const n8n_workflow_1 = require("n8n-workflow");
const helpers_1 = require("../../../utils/helpers");
const searchModels_1 = require("./methods/searchModels");
const ANTHROPIC_MODEL_BUILDER_HINT = {
propertyHint: 'Default to claude-sonnet-5 (latest Sonnet); use claude-opus-5 when the user needs the most capable model. Do not fall back to an older generation (Claude Sonnet 4.6 or earlier, Claude 3.x, Claude 2, LEGACY options) unless the user asks for a specific model. Tell the user which model you picked, why, and that they can change it at any time. When extended thinking is needed, set Thinking Mode to Adaptive and choose an Effort level. The legacy Manual thinking mode is rejected by Opus 4.7.',
};
const ANTHROPIC_LEGACY_MODEL_BUILDER_HINT = {
propertyHint: 'This node version only offers superseded Claude models. Pick claude-3-5-sonnet-20241022 if the node has to stay on this version; otherwise rebuild it on the latest node version, where the current Claude generation is selectable.',
};
const modelField = {
displayName: 'Model',
name: 'model',
type: 'options',
options: [
{
name: 'Claude 3.5 Sonnet(20241022)',
value: 'claude-3-5-sonnet-20241022',
},
{
name: 'Claude 3 Opus(20240229)',
value: 'claude-3-opus-20240229',
},
{
name: 'Claude 3.5 Sonnet(20240620)',
value: 'claude-3-5-sonnet-20240620',
},
{
name: 'Claude 3 Sonnet(20240229)',
value: 'claude-3-sonnet-20240229',
},
{
name: 'Claude 3.5 Haiku(20241022)',
value: 'claude-3-5-haiku-20241022',
},
{
name: 'Claude 3 Haiku(20240307)',
value: 'claude-3-haiku-20240307',
},
{
name: 'LEGACY: Claude 2',
value: 'claude-2',
},
{
name: 'LEGACY: Claude 2.1',
value: 'claude-2.1',
},
{
name: 'LEGACY: Claude Instant 1.2',
value: 'claude-instant-1.2',
},
{
name: 'LEGACY: Claude Instant 1',
value: 'claude-instant-1',
},
],
description: 'The model which will generate the completion. <a href="https://docs.anthropic.com/claude/docs/models-overview">Learn more</a>.',
default: 'claude-2',
builderHint: ANTHROPIC_LEGACY_MODEL_BUILDER_HINT,
};
const MIN_THINKING_BUDGET = 1024;
const DEFAULT_MAX_TOKENS = 4096;
function modelSupportsSamplingParams(modelName) {
if (!modelName.startsWith('claude-')) {
return true;
}
if (modelName.startsWith('claude-2') ||
modelName.startsWith('claude-instant') ||
modelName.startsWith('claude-3')) {
return true;
}
if (modelName.startsWith('claude-sonnet-4') || modelName.startsWith('claude-haiku-4')) {
return true;
}
if (/^claude-opus-4(-[0-6])?(-\d{8})?$/.test(modelName)) {
return true;
}
return false;
}
class LmChatAnthropic {
constructor() {
this.methods = {
listSearch: {
searchModels: searchModels_1.searchModels,
},
};
this.description = {
displayName: 'Anthropic Chat Model',
name: 'lmChatAnthropic',
icon: 'file:anthropic.svg',
group: ['transform'],
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
defaultVersion: 1.6,
description: 'Language Model Anthropic',
defaults: {
name: 'Anthropic Chat Model',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Language Models', 'Root Nodes'],
'Language Models': ['Chat Models (Recommended)'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatanthropic/',
},
],
},
alias: ['claude', 'sonnet', 'opus'],
},
inputs: [],
outputs: [n8n_workflow_1.NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
name: 'anthropicApi',
required: true,
},
],
properties: [
(0, ai_utilities_1.getConnectionHintNoticeField)([n8n_workflow_1.NodeConnectionTypes.AiChain, n8n_workflow_1.NodeConnectionTypes.AiChain]),
{
...modelField,
displayOptions: {
show: {
'@version': [1],
},
},
},
{
...modelField,
default: 'claude-3-sonnet-20240229',
displayOptions: {
show: {
'@version': [1.1],
},
},
},
{
...modelField,
default: 'claude-3-5-sonnet-20240620',
options: (modelField.options ?? []).filter((o) => 'name' in o && !o.name.toString().startsWith('LEGACY')),
displayOptions: {
show: {
'@version': [{ _cnd: { lte: 1.2 } }],
},
},
},
{
displayName: 'Model',
name: 'model',
type: 'resourceLocator',
default: {
mode: 'list',
value: 'claude-sonnet-4-5-20250929',
cachedResultName: 'Claude Sonnet 4.5',
},
builderHint: ANTHROPIC_MODEL_BUILDER_HINT,
required: true,
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select a model...',
typeOptions: {
searchListMethod: 'searchModels',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'Claude Sonnet',
},
],
description: 'The model. Choose from the list, or specify an ID. <a href="https://docs.anthropic.com/claude/docs/models-overview">Learn more</a>.',
displayOptions: {
show: {
'@version': [1.3],
},
},
},
{
displayName: 'Model',
name: 'model',
type: 'resourceLocator',
default: {
mode: 'list',
value: 'claude-sonnet-4-6',
cachedResultName: 'Claude Sonnet 4.6',
},
builderHint: ANTHROPIC_MODEL_BUILDER_HINT,
required: true,
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select a model...',
typeOptions: {
searchListMethod: 'searchModels',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'Claude Sonnet',
},
],
description: 'The model. Choose from the list, or specify an ID. <a href="https://docs.anthropic.com/claude/docs/models-overview">Learn more</a>.',
displayOptions: {
show: {
'@version': [1.4],
},
},
},
{
displayName: 'Model',
name: 'model',
type: 'resourceLocator',
default: {
mode: 'list',
value: 'claude-sonnet-4-6',
cachedResultName: 'Claude Sonnet 4.6',
},
builderHint: ANTHROPIC_MODEL_BUILDER_HINT,
required: true,
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select a model...',
typeOptions: {
searchListMethod: 'searchModels',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'Claude Sonnet',
},
],
description: 'The model. Choose from the list, or specify an ID. <a href="https://docs.anthropic.com/claude/docs/models-overview">Learn more</a>.',
displayOptions: {
show: {
'@version': [1.5],
},
},
},
{
displayName: 'Model',
name: 'model',
type: 'resourceLocator',
default: {
mode: 'list',
value: 'claude-sonnet-5',
cachedResultName: 'Claude Sonnet 5',
},
builderHint: ANTHROPIC_MODEL_BUILDER_HINT,
required: true,
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select a model...',
typeOptions: {
searchListMethod: 'searchModels',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'Claude Sonnet',
},
],
description: 'The model. Choose from the list, or specify an ID. <a href="https://docs.anthropic.com/claude/docs/models-overview">Learn more</a>.',
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.6 } }],
},
},
},
{
displayName: 'Options',
name: 'options',
placeholder: 'Add Option',
description: 'Additional options to add',
type: 'collection',
default: {},
options: [
{
displayName: 'Maximum Number of Tokens',
name: 'maxTokensToSample',
default: DEFAULT_MAX_TOKENS,
description: 'The maximum number of tokens to generate in the completion',
type: 'number',
},
{
displayName: 'Sampling Temperature',
name: 'temperature',
default: 0.7,
typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 },
description: 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive. Not supported on newer Anthropic models (Claude Opus 4.7+, Claude Sonnet 5+) — ignored there.',
type: 'number',
displayOptions: {
hide: {
thinking: [true],
thinkingMode: ['adaptive', 'manual'],
},
},
},
{
displayName: 'Top K',
name: 'topK',
default: -1,
typeOptions: { maxValue: 1, minValue: -1, numberPrecision: 1 },
description: 'Used to remove "long tail" low probability responses. Defaults to -1, which disables it. Not supported on newer Anthropic models (Claude Opus 4.7+, Claude Sonnet 5+) — ignored there.',
type: 'number',
displayOptions: {
hide: {
thinking: [true],
thinkingMode: ['adaptive', 'manual'],
},
},
},
{
displayName: 'Top P',
name: 'topP',
default: 1,
typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 },
description: 'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. We generally recommend altering this or temperature but not both. Not supported on newer Anthropic models (Claude Opus 4.7+, Claude Sonnet 5+) — ignored there.',
type: 'number',
displayOptions: {
hide: {
thinking: [true],
thinkingMode: ['adaptive', 'manual'],
},
},
},
{
displayName: 'Enable Thinking',
name: 'thinking',
type: 'boolean',
default: false,
description: 'Whether to enable thinking mode for the model',
displayOptions: {
show: {
'@version': [{ _cnd: { lte: 1.4 } }],
},
},
},
{
displayName: 'Thinking Budget (Tokens)',
name: 'thinkingBudget',
type: 'number',
default: MIN_THINKING_BUDGET,
description: 'The maximum number of tokens to use for thinking',
displayOptions: {
show: {
'@version': [{ _cnd: { lte: 1.4 } }],
thinking: [true],
},
},
},
{
displayName: 'Thinking Mode',
name: 'thinkingMode',
type: 'options',
default: 'disabled',
description: 'How extended thinking is configured. Leave the option unset to use the model default.',
options: [
{
name: 'Disabled',
value: 'disabled',
description: 'No extended thinking',
},
{
name: 'Adaptive (Recommended)',
value: 'adaptive',
description: 'Claude decides how much to think; control with Effort',
},
{
name: 'Manual (Deprecated)',
value: 'manual',
description: 'Legacy fixed-budget mode; rejected by Opus 4.7+',
},
],
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.5 } }],
},
},
},
{
displayName: 'Effort',
name: 'effort',
type: 'options',
default: 'medium',
description: 'Effort level for adaptive thinking',
options: [
{ name: 'Low', value: 'low' },
{ name: 'Medium', value: 'medium' },
{ name: 'High', value: 'high' },
{ name: 'X-High', value: 'xhigh' },
{ name: 'Max', value: 'max' },
],
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.5 } }],
thinkingMode: ['adaptive'],
'/model.value': [{ _cnd: { includes: 'opus' } }],
},
},
},
{
displayName: 'Effort',
name: 'effort',
type: 'options',
default: 'medium',
description: 'Effort level for adaptive thinking',
options: [
{ name: 'Low', value: 'low' },
{ name: 'Medium', value: 'medium' },
{ name: 'High', value: 'high' },
],
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.5 } }],
thinkingMode: ['adaptive'],
'/model.value': [{ _cnd: { regex: '^(?!.*opus).*' } }],
},
},
},
{
displayName: 'Thinking Budget (Tokens)',
name: 'thinkingBudget',
type: 'number',
default: MIN_THINKING_BUDGET,
description: 'Maximum tokens used for thinking. Manual mode is rejected by Opus 4.7+.',
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.5 } }],
thinkingMode: ['manual'],
},
},
},
{
displayName: 'Stream Responses',
name: 'streaming',
type: 'boolean',
default: false,
description: 'Whether the model should stream its response over Server-Sent Events instead of returning a single non-streamed payload. Final output shape is unchanged.',
},
{
displayName: 'Prompt Caching',
name: 'promptCaching',
type: 'options',
default: 'disabled',
description: 'Whether to cache the system prompt, tool definitions, and conversation history between requests using <a href="https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching">Anthropic prompt caching</a>. The value sets how long cached content stays valid before it has to be written again.',
options: [
{ name: 'Disabled', value: 'disabled' },
{ name: '5 Minutes', value: '5m' },
{ name: '1 Hour', value: '1h' },
],
},
],
},
{
displayName: 'Cache reads and writes are billed at different rates than regular input tokens, so reported prompt/total tokens are only an approximation of actual billable usage',
name: 'promptCachingNotice',
type: 'notice',
default: '',
displayOptions: {
show: {
'/options.promptCaching': ['5m', '1h'],
},
},
},
],
};
}
async supplyData(itemIndex) {
const credentials = await this.getCredentials('anthropicApi');
const baseURL = credentials.url ?? 'https://api.anthropic.com';
const version = this.getNode().typeVersion;
const modelName = version >= 1.3
? this.getNodeParameter('model.value', itemIndex)
: this.getNodeParameter('model', itemIndex);
if (!modelName) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No model selected. Please choose a model.', {
itemIndex,
});
}
const options = this.getNodeParameter('options', itemIndex, {});
const isOpus47Model = modelName.startsWith('claude-opus-4-7');
const thinkingMode = version >= 1.5
? (options.thinkingMode ?? 'disabled')
: options.thinking
? 'manual'
: 'disabled';
const thinkingExplicitlyDisabled = version >= 1.5 ? options.thinkingMode === 'disabled' : options.thinking === false;
if (thinkingMode === 'manual' && isOpus47Model) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Manual thinking mode is not supported on "${modelName}". Use Thinking Mode = Adaptive (with Effort) instead.`, { itemIndex });
}
let invocationKwargs = {};
if (thinkingMode === 'adaptive') {
invocationKwargs = {
thinking: { type: 'adaptive' },
output_config: { effort: options.effort ?? 'medium' },
max_tokens: options.maxTokensToSample ?? DEFAULT_MAX_TOKENS,
top_k: undefined,
top_p: undefined,
temperature: undefined,
};
}
else if (thinkingMode === 'manual') {
invocationKwargs = {
thinking: {
type: 'enabled',
budget_tokens: options.thinkingBudget ?? MIN_THINKING_BUDGET,
},
max_tokens: options.maxTokensToSample ?? DEFAULT_MAX_TOKENS,
top_k: undefined,
top_p: undefined,
temperature: undefined,
};
}
else if (thinkingExplicitlyDisabled) {
invocationKwargs = { thinking: { type: 'disabled' } };
}
if (options.promptCaching && options.promptCaching !== 'disabled') {
invocationKwargs.cache_control = {
type: 'ephemeral',
ttl: options.promptCaching,
};
}
const tokensUsageParser = (result) => {
const usage = result?.llmOutput?.usage ?? {
input_tokens: 0,
output_tokens: 0,
};
const promptTokens = usage.input_tokens +
(usage.cache_creation_input_tokens ?? 0) +
(usage.cache_read_input_tokens ?? 0);
return {
completionTokens: usage.output_tokens,
promptTokens,
totalTokens: promptTokens + usage.output_tokens,
};
};
const clientOptions = {
fetchOptions: {
dispatcher: (0, ai_utilities_1.getProxyAgent)(baseURL),
},
};
const customHeader = (0, helpers_1.getCustomCredentialHeader)(credentials);
if (customHeader) {
clientOptions.defaultHeaders = {
[customHeader.name]: customHeader.value,
};
}
const isUsingGateway = baseURL !== 'https://api.anthropic.com';
const gatewayErrorHandler = isUsingGateway
? (error) => {
const message = error instanceof Error ? error.message : String(error);
const isModelError = /model.*not found|not found.*model|invalid model|does not exist/i.test(message);
if (isModelError) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The model "${modelName}" was not found at ${baseURL}. If you're using an AI gateway, select a model that your gateway supports.`, { itemIndex });
}
}
: undefined;
const deprecatedSamplingParamErrorHandler = (error) => {
const message = error instanceof Error ? error.message : String(error);
const isDeprecatedSamplingParamError = /(temperature|top_p|top_k).*(deprecated|not supported)/i.test(message);
if (isDeprecatedSamplingParamError) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The model "${modelName}" does not support the Sampling Temperature, Top K, or Top P options. Remove them from Options and try again.`, { itemIndex });
}
};
const thinkingErrorHandler = (error) => {
if (thinkingMode !== 'manual' && !thinkingExplicitlyDisabled)
return;
const message = error instanceof Error ? error.message : String(error);
const mentionsThinking = /thinking|budget_tokens/i.test(message);
const isRejection = /(?:not|n['’]?t) support|unsupported|not allowed|not permitted|deprecated|invalid|only supports/i.test(message);
if (mentionsThinking && isRejection) {
const guidance = version >= 1.5
? {
manual: 'Set Thinking Mode to Adaptive and choose an Effort level.',
disabled: 'Set Thinking Mode to Adaptive, or remove the Thinking Mode option to use the model default.',
}
: {
manual: 'Turn off Enable Thinking, or add a new Anthropic Chat Model node to use Adaptive thinking.',
disabled: 'Remove the Enable Thinking option, or add a new Anthropic Chat Model node to use Adaptive thinking.',
};
throw new n8n_workflow_1.NodeOperationError(this.getNode(), thinkingMode === 'manual'
? `The model "${modelName}" does not support the legacy Manual thinking mode. ${guidance.manual}`
: `The model "${modelName}" does not support disabling thinking. ${guidance.disabled}`, { itemIndex });
}
};
const failedAttemptHandler = (error) => {
gatewayErrorHandler?.(error);
deprecatedSamplingParamErrorHandler(error);
thinkingErrorHandler(error);
};
const chatAnthropicParams = {
anthropicApiKey: credentials.apiKey,
model: modelName,
anthropicApiUrl: baseURL,
maxTokens: options.maxTokensToSample,
callbacks: [
new ai_utilities_1.N8nLlmTracing(this, {
tokensUsageParser,
redactedHeaders: customHeader ? [customHeader.name] : [],
}),
],
onFailedAttempt: (0, ai_utilities_1.makeN8nLlmFailedAttemptHandler)(this, failedAttemptHandler),
invocationKwargs,
clientOptions,
streaming: options.streaming ?? false,
};
if (modelSupportsSamplingParams(modelName)) {
chatAnthropicParams.temperature = options.temperature;
chatAnthropicParams.topK = options.topK;
chatAnthropicParams.topP = options.topP;
}
const model = new anthropic_1.ChatAnthropic(chatAnthropicParams);
if (options.topP === undefined) {
delete model.topP;
}
if (options.topP !== undefined && options.temperature === undefined) {
delete model.temperature;
}
return {
response: model,
};
}
}
exports.LmChatAnthropic = LmChatAnthropic;
//# sourceMappingURL=LmChatAnthropic.node.js.map