{"record":{"id":"7eb24969261e0cca","repo":"amruthpillai/reactive-resume","slug":"internal-server-error","errorCode":"INTERNAL_SERVER_ERROR","errorMessage":"The AI response could not be parsed.","messagePattern":"The AI response could not be parsed\\.","errorType":"http","errorClass":"ORPCError","httpStatus":500,"severity":"error","filePath":"packages/api/src/features/applications/ai.ts","lineNumber":61,"sourceCode":"\t}\n\treturn getModel({\n\t\tprovider: provider.provider,\n\t\tmodel: provider.model,\n\t\tapiKey: provider.apiKey,\n\t\t...(provider.baseURL ? { baseURL: provider.baseURL } : {}),\n\t});\n}\n\n// generateText + tolerant JSON extraction + Zod validation. Mirrors the resume-analysis pattern\n// (the SDK's generateObject isn't wired for every provider here, so we parse defensively).\nasync function generateJson<T>(model: Awaited<ReturnType<typeof resolveModel>>, prompt: string, schema: z.ZodType<T>) {\n\tconst { text } = await generateText({ model, messages: [{ role: \"user\", content: prompt }] });\n\tconst fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/);\n\tconst candidate = fenced?.[1] ?? text;\n\tconst start = candidate.indexOf(\"{\");\n\tconst end = candidate.lastIndexOf(\"}\");\n\tif (start === -1 || end === -1 || end < start) {\n\t\tthrow new ORPCError(\"INTERNAL_SERVER_ERROR\", { message: \"The AI response could not be parsed.\" });\n\t}\n\treturn schema.parse(JSON.parse(candidate.slice(start, end + 1)));\n}\n\nasync function generatePlainText(model: Awaited<ReturnType<typeof resolveModel>>, prompt: string) {\n\tconst { text } = await generateText({ model, messages: [{ role: \"user\", content: prompt }] });\n\treturn text.trim();\n}\n\nfunction isPrivateIPv4(address: string) {\n\tconst parts = address.split(\".\").map((part) => Number(part));\n\tif (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;\n\tconst [a = 0, b = 0] = parts;\n\treturn (\n\t\ta === 0 ||\n\t\ta === 10 ||\n\t\ta === 127 ||\n\t\t(a === 100 && b >= 64 && b <= 127) ||","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/amruthpillai/reactive-resume/blob/3a5b12e2a40374a9571988701fcb75c5a1831c42/packages/api/src/features/applications/ai.ts#L43-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nprompt: 'Analyze this job.' // model returns prose\n\n// after\nprompt: 'Respond with ONLY raw JSON matching this schema, no markdown, no explanation:\\n' + schemaExample","handlingStrategy":"retry","validationCode":"function extractJsonObject(text) {\n  const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/);\n  const candidate = fenced?.[1] ?? text;\n  const start = candidate.indexOf('{');\n  const end = candidate.lastIndexOf('}');\n  if (start === -1 || end === -1 || end < start) return null;\n  try { return JSON.parse(candidate.slice(start, end + 1)); } catch { return null; }\n}","typeGuard":"function hasJsonObject(text) {\n  return extractJsonObject(text) !== null;\n}","tryCatchPattern":"try {\n  await applications.analyzeJobPosting({ url });\n} catch (e) {\n  if (e.code === 'INTERNAL_SERVER_ERROR' && /could not be parsed/i.test(e.message)) {\n    await backoffRetry(() => applications.analyzeJobPosting({ url }), { tries: 2 });\n  } else throw e;\n}","preventionTips":["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."],"tags":["applications","ai","json-parsing","model-behavior","internal-server-error"],"backgroundTag":null,"analyzedSha":"3a5b12e2a40374a9571988701fcb75c5a1831c42","analyzedAt":"2026-08-12T22:31:22.666Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}