From 0c1f2ad82db42769b7ce4b0b3fa710d6f96eab22 Mon Sep 17 00:00:00 2001 From: oleg Date: Fri, 3 Oct 2025 13:33:05 +0200 Subject: [PATCH] feat: Use Sonnet 4.5 for AI Workflow Builder (no-changelog) (#20351) --- .../evaluations/core/environment.ts | 4 ++-- .../evaluations/utils/evaluation-helpers.ts | 4 ++-- .../src/ai-workflow-builder-agent.service.ts | 4 ++-- .../ai-workflow-builder.ee/src/llm-config.ts | 11 ++++++++--- .../ai-workflow-builder-agent.service.test.ts | 16 ++++++++-------- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts b/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts index 2a54b08b7ea..309d2b6d360 100644 --- a/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts +++ b/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts @@ -4,7 +4,7 @@ import { MemorySaver } from '@langchain/langgraph'; import { Client } from 'langsmith/client'; import type { INodeTypeDescription } from 'n8n-workflow'; -import { anthropicClaudeSonnet4 } from '../../src/llm-config.js'; +import { anthropicClaudeSonnet45 } from '../../src/llm-config.js'; import { WorkflowBuilderAgent } from '../../src/workflow-builder-agent.js'; import { loadNodesFromFile } from '../load-nodes.js'; @@ -25,7 +25,7 @@ export async function setupLLM(): Promise { if (!apiKey) { throw new Error('N8N_AI_ANTHROPIC_KEY environment variable is required'); } - return await anthropicClaudeSonnet4({ apiKey }); + return await anthropicClaudeSonnet45({ apiKey }); } /** diff --git a/packages/@n8n/ai-workflow-builder.ee/evaluations/utils/evaluation-helpers.ts b/packages/@n8n/ai-workflow-builder.ee/evaluations/utils/evaluation-helpers.ts index a4e754e3562..bffc2fce77d 100644 --- a/packages/@n8n/ai-workflow-builder.ee/evaluations/utils/evaluation-helpers.ts +++ b/packages/@n8n/ai-workflow-builder.ee/evaluations/utils/evaluation-helpers.ts @@ -7,7 +7,7 @@ import type { INodeTypeDescription } from 'n8n-workflow'; import { join } from 'path'; import pc from 'picocolors'; -import { anthropicClaudeSonnet4 } from '../../src/llm-config'; +import { anthropicClaudeSonnet45 } from '../../src/llm-config'; import type { ChatPayload } from '../../src/workflow-builder-agent'; import { WorkflowBuilderAgent } from '../../src/workflow-builder-agent'; import type { Violation } from '../types/evaluation'; @@ -23,7 +23,7 @@ export async function setupLLM(): Promise { if (!apiKey) { throw new Error('N8N_AI_ANTHROPIC_KEY environment variable is required'); } - return await anthropicClaudeSonnet4({ apiKey }); + return await anthropicClaudeSonnet45({ apiKey }); } /** diff --git a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts index d8082e6bb97..dba42c7f803 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts @@ -9,7 +9,7 @@ import { INodeTypes } from 'n8n-workflow'; import type { IUser, INodeTypeDescription } from 'n8n-workflow'; import { LLMServiceError } from '@/errors'; -import { anthropicClaudeSonnet4 } from '@/llm-config'; +import { anthropicClaudeSonnet45 } from '@/llm-config'; import { SessionManagerService } from '@/session-manager.service'; import { WorkflowBuilderAgent, type ChatPayload } from '@/workflow-builder-agent'; @@ -41,7 +41,7 @@ export class AiWorkflowBuilderService { authHeaders?: Record; apiKey?: string; } = {}): Promise { - return await anthropicClaudeSonnet4({ + return await anthropicClaudeSonnet45({ baseUrl, apiKey, headers: { diff --git a/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts b/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts index 883d7c36e5d..ed4e6bc3de4 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts @@ -47,10 +47,10 @@ export const gpt41 = async (config: LLMProviderConfig) => { }); }; -export const anthropicClaudeSonnet4 = async (config: LLMProviderConfig) => { +export const anthropicClaudeSonnet45 = async (config: LLMProviderConfig) => { const { ChatAnthropic } = await import('@langchain/anthropic'); - return new ChatAnthropic({ - model: 'claude-sonnet-4-20250514', + const model = new ChatAnthropic({ + model: 'claude-sonnet-4-5', apiKey: config.apiKey, temperature: 0, maxTokens: MAX_OUTPUT_TOKENS, @@ -59,4 +59,9 @@ export const anthropicClaudeSonnet4 = async (config: LLMProviderConfig) => { defaultHeaders: config.headers, }, }); + + // Remove Langchain default topP parameter since Sonnet 4.5 doesn't allow setting both temperature and topP + delete model.topP; + + return model; }; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts index 35fe579b0b3..5f0ce349997 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts @@ -8,7 +8,7 @@ import type { IUser, INodeTypes, INodeTypeDescription } from 'n8n-workflow'; import { AiWorkflowBuilderService } from '@/ai-workflow-builder-agent.service'; import { LLMServiceError } from '@/errors'; -import { anthropicClaudeSonnet4 } from '@/llm-config'; +import { anthropicClaudeSonnet45 } from '@/llm-config'; import { SessionManagerService } from '@/session-manager.service'; import { formatMessages } from '@/utils/stream-processor'; import { WorkflowBuilderAgent, type ChatPayload } from '@/workflow-builder-agent'; @@ -20,7 +20,7 @@ jest.mock('langsmith'); jest.mock('@/workflow-builder-agent'); jest.mock('@/session-manager.service'); jest.mock('@/llm-config', () => ({ - anthropicClaudeSonnet4: jest.fn(), + anthropicClaudeSonnet45: jest.fn(), })); jest.mock('@/utils/stream-processor', () => ({ formatMessages: jest.fn(), @@ -36,8 +36,8 @@ const MockedSessionManagerService = SessionManagerService as jest.MockedClass< typeof SessionManagerService >; -const anthropicClaudeSonnet4Mock = anthropicClaudeSonnet4 as jest.MockedFunction< - typeof anthropicClaudeSonnet4 +const anthropicClaudeSonnet45Mock = anthropicClaudeSonnet45 as jest.MockedFunction< + typeof anthropicClaudeSonnet45 >; const formatMessagesMock = formatMessages as jest.MockedFunction; @@ -157,7 +157,7 @@ describe('AiWorkflowBuilderService', () => { return mockAgent; }); - anthropicClaudeSonnet4Mock.mockResolvedValue(mockChatAnthropic); + anthropicClaudeSonnet45Mock.mockResolvedValue(mockChatAnthropic); // Mock onCreditsUpdated callback mockOnCreditsUpdated = jest.fn(); @@ -441,7 +441,7 @@ describe('AiWorkflowBuilderService', () => { it('should throw LLMServiceError when model setup fails', async () => { const testError = new Error('Model setup failed'); - anthropicClaudeSonnet4Mock.mockRejectedValue(testError); + anthropicClaudeSonnet45Mock.mockRejectedValue(testError); const generator = service.chat(mockPayload, mockUser); @@ -450,7 +450,7 @@ describe('AiWorkflowBuilderService', () => { it('should include error details in LLMServiceError', async () => { const testError = new Error('Specific error message'); - anthropicClaudeSonnet4Mock.mockRejectedValue(testError); + anthropicClaudeSonnet45Mock.mockRejectedValue(testError); const generator = service.chat(mockPayload, mockUser); @@ -473,7 +473,7 @@ describe('AiWorkflowBuilderService', () => { const generator = serviceWithoutClient.chat(mockPayload, mockUser); await generator.next(); - expect(anthropicClaudeSonnet4Mock).toHaveBeenCalledWith({ + expect(anthropicClaudeSonnet45Mock).toHaveBeenCalledWith({ baseUrl: undefined, apiKey: 'test-env-key', headers: {