Handle 413 context overflow errors gracefully
When the conversation context exceeds the model's limit, instead of throwing an opaque error or returning raw JSON, we now: 1. Detect context overflow errors (413, request_too_large, etc.) 2. Return a user-friendly message explaining the issue 3. Suggest using /new or /reset to start fresh This prevents the assistant from becoming completely unresponsive when context grows too large (e.g., from many screenshots or long tool outputs). Addresses issue #394
This commit is contained in:
committed by
Peter Steinberger
parent
42b637bbc8
commit
579828b2d5
@@ -1,6 +1,12 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { buildBootstrapContextFiles } from "./pi-embedded-helpers.js";
|
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildBootstrapContextFiles,
|
||||||
|
formatAssistantErrorText,
|
||||||
|
isContextOverflowError,
|
||||||
|
} from "./pi-embedded-helpers.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_AGENTS_FILENAME,
|
DEFAULT_AGENTS_FILENAME,
|
||||||
type WorkspaceBootstrapFile,
|
type WorkspaceBootstrapFile,
|
||||||
@@ -46,3 +52,35 @@ describe("buildBootstrapContextFiles", () => {
|
|||||||
expect(result?.content.endsWith(long.slice(-120))).toBe(true);
|
expect(result?.content.endsWith(long.slice(-120))).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isContextOverflowError", () => {
|
||||||
|
it("matches known overflow hints", () => {
|
||||||
|
const samples = [
|
||||||
|
"request_too_large",
|
||||||
|
"Request exceeds the maximum size",
|
||||||
|
"context length exceeded",
|
||||||
|
"Maximum context length",
|
||||||
|
"413 Request Entity Too Large",
|
||||||
|
];
|
||||||
|
for (const sample of samples) {
|
||||||
|
expect(isContextOverflowError(sample)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores unrelated errors", () => {
|
||||||
|
expect(isContextOverflowError("rate limit exceeded")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("formatAssistantErrorText", () => {
|
||||||
|
const makeAssistantError = (errorMessage: string): AssistantMessage =>
|
||||||
|
({
|
||||||
|
stopReason: "error",
|
||||||
|
errorMessage,
|
||||||
|
}) as AssistantMessage;
|
||||||
|
|
||||||
|
it("returns a friendly message for context overflow", () => {
|
||||||
|
const msg = makeAssistantError("request_too_large");
|
||||||
|
expect(formatAssistantErrorText(msg)).toContain("Context overflow");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -126,6 +126,18 @@ export function buildBootstrapContextFiles(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isContextOverflowError(errorMessage?: string): boolean {
|
||||||
|
if (!errorMessage) return false;
|
||||||
|
const lower = errorMessage.toLowerCase();
|
||||||
|
return (
|
||||||
|
lower.includes("request_too_large") ||
|
||||||
|
lower.includes("request exceeds the maximum size") ||
|
||||||
|
lower.includes("context length exceeded") ||
|
||||||
|
lower.includes("maximum context length") ||
|
||||||
|
(lower.includes("413") && lower.includes("too large"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function formatAssistantErrorText(
|
export function formatAssistantErrorText(
|
||||||
msg: AssistantMessage,
|
msg: AssistantMessage,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
@@ -133,6 +145,14 @@ export function formatAssistantErrorText(
|
|||||||
const raw = (msg.errorMessage ?? "").trim();
|
const raw = (msg.errorMessage ?? "").trim();
|
||||||
if (!raw) return "LLM request failed with an unknown error.";
|
if (!raw) return "LLM request failed with an unknown error.";
|
||||||
|
|
||||||
|
// Check for context overflow (413) errors
|
||||||
|
if (isContextOverflowError(raw)) {
|
||||||
|
return (
|
||||||
|
"Context overflow: the conversation history is too large. " +
|
||||||
|
"Use /new or /reset to start a fresh session."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const invalidRequest = raw.match(
|
const invalidRequest = raw.match(
|
||||||
/"type":"invalid_request_error".*?"message":"([^"]+)"/,
|
/"type":"invalid_request_error".*?"message":"([^"]+)"/,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import {
|
|||||||
formatAssistantErrorText,
|
formatAssistantErrorText,
|
||||||
isAuthAssistantError,
|
isAuthAssistantError,
|
||||||
isAuthErrorMessage,
|
isAuthErrorMessage,
|
||||||
|
isContextOverflowError,
|
||||||
isRateLimitAssistantError,
|
isRateLimitAssistantError,
|
||||||
isRateLimitErrorMessage,
|
isRateLimitErrorMessage,
|
||||||
pickFallbackThinkingLevel,
|
pickFallbackThinkingLevel,
|
||||||
@@ -1153,6 +1154,26 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
}
|
}
|
||||||
if (promptError && !aborted) {
|
if (promptError && !aborted) {
|
||||||
const errorText = describeUnknownError(promptError);
|
const errorText = describeUnknownError(promptError);
|
||||||
|
if (isContextOverflowError(errorText)) {
|
||||||
|
return {
|
||||||
|
payloads: [
|
||||||
|
{
|
||||||
|
text:
|
||||||
|
"Context overflow: the conversation history is too large for the model. " +
|
||||||
|
"Use /new or /reset to start a fresh session, or try a model with a larger context window.",
|
||||||
|
isError: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
durationMs: Date.now() - started,
|
||||||
|
agentMeta: {
|
||||||
|
sessionId: sessionIdUsed,
|
||||||
|
provider,
|
||||||
|
model: model.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
(isAuthErrorMessage(errorText) ||
|
(isAuthErrorMessage(errorText) ||
|
||||||
isRateLimitErrorMessage(errorText)) &&
|
isRateLimitErrorMessage(errorText)) &&
|
||||||
|
|||||||
Reference in New Issue
Block a user