feat: Use Sonnet 4.5 for AI Workflow Builder (no-changelog) (#20351)

This commit is contained in:
oleg
2025-10-03 13:33:05 +02:00
committed by GitHub
parent 5ae84c1c0b
commit 0c1f2ad82d
5 changed files with 22 additions and 17 deletions
@@ -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<BaseChatModel> {
if (!apiKey) {
throw new Error('N8N_AI_ANTHROPIC_KEY environment variable is required');
}
return await anthropicClaudeSonnet4({ apiKey });
return await anthropicClaudeSonnet45({ apiKey });
}
/**
@@ -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<BaseChatModel> {
if (!apiKey) {
throw new Error('N8N_AI_ANTHROPIC_KEY environment variable is required');
}
return await anthropicClaudeSonnet4({ apiKey });
return await anthropicClaudeSonnet45({ apiKey });
}
/**
@@ -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<string, string>;
apiKey?: string;
} = {}): Promise<ChatAnthropic> {
return await anthropicClaudeSonnet4({
return await anthropicClaudeSonnet45({
baseUrl,
apiKey,
headers: {
@@ -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;
};
@@ -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<typeof formatMessages>;
@@ -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: {