webui: preserve partial response on streaming error (#23090)
This commit is contained in:
@@ -4,9 +4,6 @@ export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/;
|
|||||||
|
|
||||||
export const NEWLINE_SEPARATOR = '\n';
|
export const NEWLINE_SEPARATOR = '\n';
|
||||||
|
|
||||||
export const LLM_ERROR_BLOCK_START = '\n\n```\nUpstream LLM error:\n';
|
|
||||||
export const LLM_ERROR_BLOCK_END = '\n```\n';
|
|
||||||
|
|
||||||
export const DEFAULT_AGENTIC_CONFIG: AgenticConfig = {
|
export const DEFAULT_AGENTIC_CONFIG: AgenticConfig = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
maxTurns: 100,
|
maxTurns: 100,
|
||||||
|
|||||||
@@ -30,12 +30,7 @@ import { ToolSource, ToolPermissionDecision } from '$lib/enums';
|
|||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
import { ToolsService } from '$lib/services/tools.service';
|
import { ToolsService } from '$lib/services/tools.service';
|
||||||
import { isAbortError } from '$lib/utils';
|
import { isAbortError } from '$lib/utils';
|
||||||
import {
|
import { DEFAULT_AGENTIC_CONFIG, NEWLINE_SEPARATOR } from '$lib/constants';
|
||||||
DEFAULT_AGENTIC_CONFIG,
|
|
||||||
NEWLINE_SEPARATOR,
|
|
||||||
LLM_ERROR_BLOCK_START,
|
|
||||||
LLM_ERROR_BLOCK_END
|
|
||||||
} from '$lib/constants';
|
|
||||||
import {
|
import {
|
||||||
IMAGE_MIME_TO_EXTENSION,
|
IMAGE_MIME_TO_EXTENSION,
|
||||||
DATA_URI_BASE64_REGEX,
|
DATA_URI_BASE64_REGEX,
|
||||||
@@ -640,10 +635,9 @@ class AgenticStore {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const normalizedError = error instanceof Error ? error : new Error('LLM stream error');
|
const normalizedError = error instanceof Error ? error : new Error('LLM stream error');
|
||||||
// Save error as content in the current turn
|
// preserve partial output as is, the outer error dialog informs the user separately
|
||||||
onChunk?.(`${LLM_ERROR_BLOCK_START}${normalizedError.message}${LLM_ERROR_BLOCK_END}`);
|
|
||||||
await onAssistantTurnComplete?.(
|
await onAssistantTurnComplete?.(
|
||||||
turnContent + `${LLM_ERROR_BLOCK_START}${normalizedError.message}${LLM_ERROR_BLOCK_END}`,
|
turnContent,
|
||||||
turnReasoningContent || undefined,
|
turnReasoningContent || undefined,
|
||||||
this.buildFinalTimings(capturedTimings, agenticTimings),
|
this.buildFinalTimings(capturedTimings, agenticTimings),
|
||||||
undefined
|
undefined
|
||||||
|
|||||||
@@ -814,7 +814,7 @@ class ChatStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: async (error: Error) => {
|
||||||
this.setStreamingActive(false);
|
this.setStreamingActive(false);
|
||||||
if (isAbortError(error)) {
|
if (isAbortError(error)) {
|
||||||
cleanupStreamingState();
|
cleanupStreamingState();
|
||||||
@@ -826,13 +826,10 @@ class ChatStore {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error('Streaming error:', error);
|
console.error('Streaming error:', error);
|
||||||
|
// keep whatever was streamed so far, the message stays in memory and in DB
|
||||||
|
await this.savePartialResponseIfNeeded(convId);
|
||||||
cleanupStreamingState();
|
cleanupStreamingState();
|
||||||
this.clearPendingMessage(convId);
|
this.clearPendingMessage(convId);
|
||||||
const idx = conversationsStore.findMessageIndex(assistantMessage.id);
|
|
||||||
if (idx !== -1) {
|
|
||||||
const failedMessage = conversationsStore.removeMessageAtIndex(idx);
|
|
||||||
if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error);
|
|
||||||
}
|
|
||||||
const contextInfo = (
|
const contextInfo = (
|
||||||
error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }
|
error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }
|
||||||
).contextInfo;
|
).contextInfo;
|
||||||
@@ -1389,9 +1386,17 @@ class ChatStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.error('Continue generation error:', error);
|
console.error('Continue generation error:', error);
|
||||||
conversationsStore.updateMessageAtIndex(idx, { content: originalContent });
|
// keep whatever was appended so far, the message stays in memory and in DB
|
||||||
|
await DatabaseService.updateMessage(msg.id, {
|
||||||
await DatabaseService.updateMessage(msg.id, { content: originalContent });
|
content: originalContent + appendedContent,
|
||||||
|
reasoningContent: originalReasoning + appendedReasoning || undefined,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
conversationsStore.updateMessageAtIndex(idx, {
|
||||||
|
content: originalContent + appendedContent,
|
||||||
|
reasoningContent: originalReasoning + appendedReasoning || undefined,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
this.setChatLoading(msg.convId, false);
|
this.setChatLoading(msg.convId, false);
|
||||||
this.clearChatStreaming(msg.convId);
|
this.clearChatStreaming(msg.convId);
|
||||||
|
|||||||
Reference in New Issue
Block a user