amruthpillai/reactive-resume · error · ORPCError
INTERNAL_SERVER_ERROR
INTERNAL_SERVER_ERROR
Error message
The AI response could not be parsed.
What it means
INTERNAL_SERVER_ERROR thrown by generateJson when the model's text response contains no parseable JSON object — specifically when there is no '{' ... '}' pair with end >= start after extracting a fenced ```json block or falling back to the raw text. It means the AI returned prose or empty content rather than JSON. Note the choice of INTERNAL_SERVER_ERROR (not BAD_REQUEST) because the input was fine; the model/parse pipeline failed.
Source
Thrown at packages/api/src/features/applications/ai.ts:61
}
return getModel({
provider: provider.provider,
model: provider.model,
apiKey: provider.apiKey,
...(provider.baseURL ? { baseURL: provider.baseURL } : {}),
});
}
// generateText + tolerant JSON extraction + Zod validation. Mirrors the resume-analysis pattern
// (the SDK's generateObject isn't wired for every provider here, so we parse defensively).
async function generateJson<T>(model: Awaited<ReturnType<typeof resolveModel>>, prompt: string, schema: z.ZodType<T>) {
const { text } = await generateText({ model, messages: [{ role: "user", content: prompt }] });
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
const candidate = fenced?.[1] ?? text;
const start = candidate.indexOf("{");
const end = candidate.lastIndexOf("}");
if (start === -1 || end === -1 || end < start) {
throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "The AI response could not be parsed." });
}
return schema.parse(JSON.parse(candidate.slice(start, end + 1)));
}
async function generatePlainText(model: Awaited<ReturnType<typeof resolveModel>>, prompt: string) {
const { text } = await generateText({ model, messages: [{ role: "user", content: prompt }] });
return text.trim();
}
function isPrivateIPv4(address: string) {
const parts = address.split(".").map((part) => Number(part));
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
const [a = 0, b = 0] = parts;
return (
a === 0 ||
a === 10 ||
a === 127 ||
(a === 100 && b >= 64 && b <= 127) ||View on GitHub (pinned to 3a5b12e2a4)
Solutions
- Retry the request — transient model behavior often resolves on the next call.
- Strengthen the prompt to demand pure JSON with no surrounding prose.
- Switch to a more capable/instruction-following model for the applications feature.
- If using a gateway, verify it forwards the model response verbatim and does not inject HTML error pages.
- Handle this 500 in the UI with a friendly 'AI response was malformed, please try again' message.
Example fix
// before prompt: 'Analyze this job.' // model returns prose // after prompt: 'Respond with ONLY raw JSON matching this schema, no markdown, no explanation:\n' + schemaExample
Defensive patterns
Strategy: retry
Validate before calling
function extractJsonObject(text) {
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
const candidate = fenced?.[1] ?? text;
const start = candidate.indexOf('{');
const end = candidate.lastIndexOf('}');
if (start === -1 || end === -1 || end < start) return null;
try { return JSON.parse(candidate.slice(start, end + 1)); } catch { return null; }
} Type guard
function hasJsonObject(text) {
return extractJsonObject(text) !== null;
} Try / catch
try {
await applications.analyzeJobPosting({ url });
} catch (e) {
if (e.code === 'INTERNAL_SERVER_ERROR' && /could not be parsed/i.test(e.message)) {
await backoffRetry(() => applications.analyzeJobPosting({ url }), { tries: 2 });
} else throw e;
} Prevention
- Prompt the model to return ONLY raw JSON with no prose or markdown.
- Use a capable, instruction-following model for JSON-producing features.
- Show a user-friendly 'try again' message on this 500.
When it happens
Trigger: An applications AI feature calls generateJson; the model returns non-JSON text (apology, markdown without braces, empty string, or a refused response) so the brace-finding heuristic finds no candidate object.
Common situations: Model refuses due to content policy and returns prose; model is overloaded and returns empty; a provider gateway returns an error page as text; weak model ignoring the JSON instruction; prompt too long causing truncation before JSON.
Related errors
- The model returned too much text during the provider test.
- BAD_REQUEST
- An unknown error occurred while validating the merged resume
- BAD_GATEWAY
- BAD_REQUEST
AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12).
Data as JSON: /api/errors/7eb24969261e0cca.
Report an issue: GitHub.