mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 18:41:14 +00:00
fix: Handle AI errors better in builder (no-changelog) (#18406)
This commit is contained in:
@@ -1453,6 +1453,244 @@ describe('useBuilderMessages', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('error message handling with retry', () => {
|
||||
it('should pass retry function to error messages from processAssistantMessages', () => {
|
||||
const retryFn = vi.fn(async () => {});
|
||||
const currentMessages: ChatUI.AssistantMessage[] = [];
|
||||
const newMessages: ChatRequest.MessageResponse[] = [
|
||||
{
|
||||
type: 'error',
|
||||
role: 'assistant',
|
||||
content: 'Something went wrong',
|
||||
},
|
||||
];
|
||||
|
||||
const result = builderMessages.processAssistantMessages(
|
||||
currentMessages,
|
||||
newMessages,
|
||||
'test-id',
|
||||
retryFn,
|
||||
);
|
||||
|
||||
expect(result.messages).toHaveLength(1);
|
||||
const errorMessage = result.messages[0] as ChatUI.ErrorMessage;
|
||||
expect(errorMessage).toMatchObject({
|
||||
id: 'test-id-0',
|
||||
role: 'assistant',
|
||||
type: 'error',
|
||||
content: 'Something went wrong',
|
||||
read: false,
|
||||
});
|
||||
expect(errorMessage.retry).toBe(retryFn);
|
||||
});
|
||||
|
||||
it('should not pass retry function to non-error messages', () => {
|
||||
const retryFn = vi.fn(async () => {});
|
||||
const currentMessages: ChatUI.AssistantMessage[] = [];
|
||||
const newMessages: ChatRequest.MessageResponse[] = [
|
||||
{
|
||||
type: 'message',
|
||||
role: 'assistant',
|
||||
text: 'This is a normal text message',
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
type: 'tool',
|
||||
toolName: 'add_nodes',
|
||||
toolCallId: 'call-1',
|
||||
status: 'running',
|
||||
updates: [],
|
||||
},
|
||||
];
|
||||
|
||||
const result = builderMessages.processAssistantMessages(
|
||||
currentMessages,
|
||||
newMessages,
|
||||
'test-id',
|
||||
retryFn,
|
||||
);
|
||||
|
||||
expect(result.messages).toHaveLength(2);
|
||||
|
||||
const textMessage = result.messages[0] as ChatUI.TextMessage;
|
||||
expect(textMessage.type).toBe('text');
|
||||
expect('retry' in textMessage).toBe(false);
|
||||
|
||||
const toolMessage = result.messages[1] as ChatUI.ToolMessage;
|
||||
expect(toolMessage.type).toBe('tool');
|
||||
expect('retry' in toolMessage).toBe(false);
|
||||
});
|
||||
|
||||
it('should clear retry from previous error messages when processing new messages', () => {
|
||||
const oldRetryFn = vi.fn(async () => {});
|
||||
const newRetryFn = vi.fn(async () => {});
|
||||
|
||||
const currentMessages: ChatUI.AssistantMessage[] = [
|
||||
{
|
||||
id: 'error-1',
|
||||
role: 'assistant',
|
||||
type: 'error',
|
||||
content: 'First error',
|
||||
retry: oldRetryFn,
|
||||
read: false,
|
||||
} as ChatUI.ErrorMessage,
|
||||
{
|
||||
id: 'error-2',
|
||||
role: 'assistant',
|
||||
type: 'error',
|
||||
content: 'Second error',
|
||||
retry: oldRetryFn,
|
||||
read: false,
|
||||
} as ChatUI.ErrorMessage,
|
||||
];
|
||||
|
||||
const newMessages: ChatRequest.MessageResponse[] = [
|
||||
{
|
||||
type: 'error',
|
||||
role: 'assistant',
|
||||
content: 'New error',
|
||||
},
|
||||
];
|
||||
|
||||
const result = builderMessages.processAssistantMessages(
|
||||
currentMessages,
|
||||
newMessages,
|
||||
'test-id',
|
||||
newRetryFn,
|
||||
);
|
||||
|
||||
expect(result.messages).toHaveLength(3);
|
||||
|
||||
// First error should have retry removed
|
||||
const firstError = result.messages[0] as ChatUI.ErrorMessage;
|
||||
expect(firstError.content).toBe('First error');
|
||||
expect('retry' in firstError).toBe(false);
|
||||
|
||||
// Second error should have retry removed
|
||||
const secondError = result.messages[1] as ChatUI.ErrorMessage;
|
||||
expect(secondError.content).toBe('Second error');
|
||||
expect('retry' in secondError).toBe(false);
|
||||
|
||||
// New error should have the new retry function
|
||||
const newError = result.messages[2] as ChatUI.ErrorMessage;
|
||||
expect(newError.content).toBe('New error');
|
||||
expect(newError.retry).toBe(newRetryFn);
|
||||
});
|
||||
|
||||
it('should only keep retry on the last error message when multiple errors exist', () => {
|
||||
const retryFn = vi.fn(async () => {});
|
||||
const currentMessages: ChatUI.AssistantMessage[] = [];
|
||||
const newMessages: ChatRequest.MessageResponse[] = [
|
||||
{
|
||||
type: 'error',
|
||||
role: 'assistant',
|
||||
content: 'First error in batch',
|
||||
},
|
||||
{
|
||||
type: 'error',
|
||||
role: 'assistant',
|
||||
content: 'Second error in batch',
|
||||
},
|
||||
{
|
||||
type: 'error',
|
||||
role: 'assistant',
|
||||
content: 'Third error in batch',
|
||||
},
|
||||
];
|
||||
|
||||
const result = builderMessages.processAssistantMessages(
|
||||
currentMessages,
|
||||
newMessages,
|
||||
'test-id',
|
||||
retryFn,
|
||||
);
|
||||
|
||||
expect(result.messages).toHaveLength(3);
|
||||
|
||||
// First error should not have retry
|
||||
const firstError = result.messages[0] as ChatUI.ErrorMessage;
|
||||
expect(firstError.content).toBe('First error in batch');
|
||||
expect('retry' in firstError).toBe(false);
|
||||
|
||||
// Second error should not have retry
|
||||
const secondError = result.messages[1] as ChatUI.ErrorMessage;
|
||||
expect(secondError.content).toBe('Second error in batch');
|
||||
expect('retry' in secondError).toBe(false);
|
||||
|
||||
// Only the last error should have retry
|
||||
const lastError = result.messages[2] as ChatUI.ErrorMessage;
|
||||
expect(lastError.content).toBe('Third error in batch');
|
||||
expect(lastError.retry).toBe(retryFn);
|
||||
});
|
||||
|
||||
it('should handle mixed message types and only affect error messages with retry logic', () => {
|
||||
const retryFn = vi.fn(async () => {});
|
||||
const currentMessages: ChatUI.AssistantMessage[] = [
|
||||
{
|
||||
id: 'msg-1',
|
||||
role: 'assistant',
|
||||
type: 'text',
|
||||
content: 'Normal message',
|
||||
read: false,
|
||||
},
|
||||
{
|
||||
id: 'error-1',
|
||||
role: 'assistant',
|
||||
type: 'error',
|
||||
content: 'Old error',
|
||||
retry: retryFn,
|
||||
read: false,
|
||||
} as ChatUI.ErrorMessage,
|
||||
];
|
||||
|
||||
const newMessages: ChatRequest.MessageResponse[] = [
|
||||
{
|
||||
type: 'message',
|
||||
role: 'assistant',
|
||||
text: 'New text message',
|
||||
},
|
||||
{
|
||||
type: 'error',
|
||||
role: 'assistant',
|
||||
content: 'New error message',
|
||||
},
|
||||
];
|
||||
|
||||
const result = builderMessages.processAssistantMessages(
|
||||
currentMessages,
|
||||
newMessages,
|
||||
'test-id',
|
||||
retryFn,
|
||||
);
|
||||
|
||||
expect(result.messages).toHaveLength(4);
|
||||
|
||||
// Normal text message should be unchanged
|
||||
expect(result.messages[0]).toMatchObject({
|
||||
type: 'text',
|
||||
content: 'Normal message',
|
||||
});
|
||||
expect('retry' in result.messages[0]).toBe(false);
|
||||
|
||||
// Old error should have retry removed
|
||||
const oldError = result.messages[1] as ChatUI.ErrorMessage;
|
||||
expect(oldError.content).toBe('Old error');
|
||||
expect('retry' in oldError).toBe(false);
|
||||
|
||||
// New text message should not have retry
|
||||
expect(result.messages[2]).toMatchObject({
|
||||
type: 'text',
|
||||
content: 'New text message',
|
||||
});
|
||||
expect('retry' in result.messages[2]).toBe(false);
|
||||
|
||||
// Only the new error should have retry
|
||||
const newError = result.messages[3] as ChatUI.ErrorMessage;
|
||||
expect(newError.content).toBe('New error message');
|
||||
expect(newError.retry).toBe(retryFn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyRatingLogic', () => {
|
||||
it('should apply rating to the last assistant text message after workflow-updated when no tools are running', () => {
|
||||
const messages: ChatUI.AssistantMessage[] = [
|
||||
|
||||
@@ -97,6 +97,7 @@ export function useBuilderMessages() {
|
||||
messages: ChatUI.AssistantMessage[],
|
||||
msg: ChatRequest.MessageResponse,
|
||||
messageId: string,
|
||||
retry?: () => Promise<void>,
|
||||
): boolean {
|
||||
let shouldClearThinking = false;
|
||||
|
||||
@@ -127,6 +128,7 @@ export function useBuilderMessages() {
|
||||
type: 'error',
|
||||
content: msg.content,
|
||||
read: false,
|
||||
retry,
|
||||
});
|
||||
shouldClearThinking = true;
|
||||
}
|
||||
@@ -248,6 +250,7 @@ export function useBuilderMessages() {
|
||||
currentMessages: ChatUI.AssistantMessage[],
|
||||
newMessages: ChatRequest.MessageResponse[],
|
||||
baseId: string,
|
||||
retry?: () => Promise<void>,
|
||||
): MessageProcessingResult {
|
||||
const mutableMessages = [...currentMessages];
|
||||
let shouldClearThinking = false;
|
||||
@@ -255,22 +258,36 @@ export function useBuilderMessages() {
|
||||
newMessages.forEach((msg, index) => {
|
||||
// Generate unique ID for each message in the batch
|
||||
const messageId = `${baseId}-${index}`;
|
||||
const clearThinking = processSingleMessage(mutableMessages, msg, messageId);
|
||||
const clearThinking = processSingleMessage(mutableMessages, msg, messageId, retry);
|
||||
shouldClearThinking = shouldClearThinking || clearThinking;
|
||||
});
|
||||
|
||||
const thinkingMessage = determineThinkingMessage(mutableMessages);
|
||||
|
||||
// Apply rating logic only to messages after workflow-updated
|
||||
const finalMessages = applyRatingLogic(mutableMessages);
|
||||
const messagesWithRatingLogic = applyRatingLogic(mutableMessages);
|
||||
|
||||
// Remove retry from all error messages except the last one
|
||||
const messagesWithRetryLogic = removeRetryFromOldErrorMessages(messagesWithRatingLogic);
|
||||
|
||||
return {
|
||||
messages: finalMessages,
|
||||
messages: messagesWithRetryLogic,
|
||||
thinkingMessage,
|
||||
shouldClearThinking: shouldClearThinking && mutableMessages.length > currentMessages.length,
|
||||
};
|
||||
}
|
||||
|
||||
function removeRetryFromOldErrorMessages(messages: ChatUI.AssistantMessage[]) {
|
||||
// Remove retry from all error messages except the last one
|
||||
return messages.map((message, index) => {
|
||||
if (message.type === 'error' && message.retry && index !== messages.length - 1) {
|
||||
const { retry, ...messageWithoutRetry } = message;
|
||||
return messageWithoutRetry;
|
||||
}
|
||||
return message;
|
||||
});
|
||||
}
|
||||
|
||||
function createUserMessage(content: string, id: string): ChatUI.AssistantMessage {
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -286,6 +286,7 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
||||
chatMessages.value,
|
||||
response.messages,
|
||||
generateMessageId(),
|
||||
retry,
|
||||
);
|
||||
chatMessages.value = result.messages;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user