mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-28 12:44:28 +00:00
feat(client): render context budget errors with readable copy
empty_messages and final_context_overflow are structured errors thrown by the agents SDK, but they were missing from the typed-error dispatch table and surfaced as raw JSON inside the generic failure text. Map both to localized copy: empty_messages keeps its token budget breakdown in a details block, final_context_overflow interpolates the projected and available token counts.
This commit is contained in:
parent
6fdf2c8c63
commit
aefd3c9d14
4 changed files with 123 additions and 2 deletions
|
|
@ -37,6 +37,22 @@ type TGenericError = {
|
|||
info: string;
|
||||
};
|
||||
|
||||
type TContextOverflow = {
|
||||
info?: string;
|
||||
provider?: string;
|
||||
projectedMessageTokens?: number;
|
||||
availableMessageTokens?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* SDK boilerplate already covered by the localized headline; whatever remains
|
||||
* (specific guidance and the token budget breakdown) renders as details.
|
||||
*/
|
||||
const emptyMessagesBoilerplate = [
|
||||
'Message pruning removed all messages as none fit in the context window.',
|
||||
'Please increase the context window size or make your message shorter.',
|
||||
];
|
||||
|
||||
const errorMessages = {
|
||||
[ErrorTypes.MODERATION]: 'com_error_moderation',
|
||||
[ErrorTypes.NO_USER_KEY]: 'com_error_no_user_key',
|
||||
|
|
@ -79,6 +95,39 @@ const errorMessages = {
|
|||
[ErrorTypes.GOOGLE_VIDEO_UNPROCESSABLE]: 'com_error_google_video_unprocessable',
|
||||
[ErrorTypes.RESOURCE_RECOVERY_REQUIRED]: 'com_error_resource_recovery_required',
|
||||
[ErrorTypes.STREAM_EXPIRED]: 'com_error_stream_expired',
|
||||
[ErrorTypes.EMPTY_MESSAGES]: (json: TGenericError, localize: LocalizeFunction) => {
|
||||
const detail = emptyMessagesBoilerplate
|
||||
.reduce((info, sentence) => info.replace(sentence, ''), json.info ?? '')
|
||||
.trim();
|
||||
return (
|
||||
<>
|
||||
{localize('com_error_empty_messages')}
|
||||
{detail && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
<CodeBlock
|
||||
lang={localize('com_ui_details')}
|
||||
error={true}
|
||||
allowExecution={false}
|
||||
codeChildren={detail}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
[ErrorTypes.FINAL_CONTEXT_OVERFLOW]: (json: TContextOverflow, localize: LocalizeFunction) => {
|
||||
const { projectedMessageTokens: projected, availableMessageTokens: available } = json;
|
||||
const message = localize('com_error_final_context_overflow');
|
||||
if (typeof projected !== 'number' || typeof available !== 'number') {
|
||||
return message;
|
||||
}
|
||||
return `${message} ${localize('com_error_context_tokens_detail', {
|
||||
0: projected,
|
||||
1: available,
|
||||
})}`;
|
||||
},
|
||||
[ViolationTypes.BAN]:
|
||||
'Your account has been temporarily banned due to violations of our service.',
|
||||
[ViolationTypes.ILLEGAL_MODEL_REQUEST]: (json: TGenericError, localize: LocalizeFunction) => {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,16 @@ import Error from '../Error';
|
|||
jest.mock('~/hooks', () => ({
|
||||
useLocalize:
|
||||
() =>
|
||||
(key: string): string =>
|
||||
(jest.requireActual('~/locales/en/translation.json') as Record<string, string>)[key] ?? key,
|
||||
(key: string, values?: Record<string, unknown>): string => {
|
||||
const template =
|
||||
(jest.requireActual('~/locales/en/translation.json') as Record<string, string>)[key] ?? key;
|
||||
if (!values) {
|
||||
return template;
|
||||
}
|
||||
return template.replace(/\{\{(\w+)\}\}/g, (match, name) =>
|
||||
values[name] != null ? String(values[name]) : match,
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
const catalog = translation as Record<string, string>;
|
||||
|
|
@ -49,3 +57,56 @@ describe('Error — typed provider errors', () => {
|
|||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error — agent context budget errors', () => {
|
||||
beforeAll(() => {
|
||||
/** CodeBlock observes its code bar; jsdom ships no IntersectionObserver. */
|
||||
(global as { IntersectionObserver?: unknown }).IntersectionObserver = class {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
};
|
||||
});
|
||||
|
||||
it('renders localized copy and the token breakdown for empty_messages', () => {
|
||||
/** Mirrors the SDK payload as embedded by the agents controller's error prefix. */
|
||||
const info =
|
||||
'Message pruning removed all messages as none fit in the context window. Please increase the context window size or make your message shorter.\nToken budget breakdown:\n maxContextTokens: 10\n messageTokens: 32 (4 messages)\n availableForMessages: 9';
|
||||
const payload = `An error occurred while processing the request: ${JSON.stringify({
|
||||
type: ErrorTypes.EMPTY_MESSAGES,
|
||||
info,
|
||||
})}`;
|
||||
render(<Error text={payload} />);
|
||||
|
||||
expect(screen.getByText(catalog.com_error_empty_messages)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Token budget breakdown/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Something went wrong/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders localized copy with token counts for final_context_overflow', () => {
|
||||
const payload = `An error occurred while processing the request: ${JSON.stringify({
|
||||
type: ErrorTypes.FINAL_CONTEXT_OVERFLOW,
|
||||
info: 'Provider message formatting exceeded the context budget and no safe synthetic-context compaction could make it fit.',
|
||||
provider: 'openAI',
|
||||
projectedMessageTokens: 3,
|
||||
availableMessageTokens: 0,
|
||||
})}`;
|
||||
render(<Error text={payload} />);
|
||||
|
||||
expect(
|
||||
screen.getByText(new RegExp(catalog.com_error_final_context_overflow.slice(0, 40))),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/need 3 tokens, but only 0 are available/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Something went wrong/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('falls back to the plain sentence when overflow token counts are absent', () => {
|
||||
const payload = JSON.stringify({
|
||||
type: ErrorTypes.FINAL_CONTEXT_OVERFLOW,
|
||||
info: 'Fallback provider message formatting exceeded the context budget before invocation.',
|
||||
});
|
||||
render(<Error text={payload} />);
|
||||
|
||||
expect(screen.getByText(catalog.com_error_final_context_overflow)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -380,12 +380,15 @@
|
|||
"com_error_expired_user_key": "Provided key for {{0}} expired at {{1}}. Please provide a new key and try again.",
|
||||
"com_error_files_dupe": "Duplicate file detected.",
|
||||
"com_error_files_empty": "Empty files are not allowed.",
|
||||
"com_error_context_tokens_detail": "Your messages need {{0}} tokens, but only {{1}} are available.",
|
||||
"com_error_empty_messages": "Your messages do not fit in the current context window, so there was nothing left to send to the model. Increase the max context size from the conversation parameters, shorten your message, or start a new conversation.",
|
||||
"com_error_files_process": "An error occurred while processing the file.",
|
||||
"com_error_files_unsupported": "This file type can't be attached here.",
|
||||
"com_error_files_upload": "An error occurred while uploading the file.",
|
||||
"com_error_files_upload_canceled": "The file upload request was canceled. Note: the file upload may still be processing and will need to be manually deleted.",
|
||||
"com_error_files_upload_too_large": "The file is too large. Please upload a file smaller than {{0}} MB",
|
||||
"com_error_files_validation": "An error occurred while validating the file.",
|
||||
"com_error_final_context_overflow": "The request exceeds the current context window and could not be reduced to fit. Increase the max context size from the conversation parameters or shorten your message.",
|
||||
"com_error_google_tool_conflict": "Usage of built-in Google tools are not supported with external tools. Please disable either the built-in tools or the external tools.",
|
||||
"com_error_google_video_unprocessable": "The linked video could not be processed. It is most likely too long for this model, but it may also be unavailable in this region or restricted. Try a shorter video, or describe the relevant parts in your message instead.",
|
||||
"com_error_heic_conversion": "Failed to convert HEIC image to JPEG. Please try converting the image manually or use a different format.",
|
||||
|
|
|
|||
|
|
@ -2950,6 +2950,14 @@ export enum ErrorTypes {
|
|||
* SSE stream 404 — job completed, expired, or was deleted before the subscriber connected
|
||||
*/
|
||||
STREAM_EXPIRED = 'stream_expired',
|
||||
/**
|
||||
* Context pruning removed every message; nothing fits the configured context window
|
||||
*/
|
||||
EMPTY_MESSAGES = 'empty_messages',
|
||||
/**
|
||||
* Formatted provider payload exceeded the context budget before invocation
|
||||
*/
|
||||
FINAL_CONTEXT_OVERFLOW = 'final_context_overflow',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue