{"record":{"id":"65b725dcbca8db4d","repo":"we-promise/sure","slug":"could-not-parse-json-from-pdf-processing-response","errorCode":null,"errorMessage":"Could not parse JSON from PDF processing response: #{raw.truncate(200)}","messagePattern":"Could not parse JSON from PDF processing response: #(.+?)","errorType":"exception","errorClass":"Provider::Openai::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/openai/pdf_processor.rb","lineNumber":264,"sourceCode":"      # Try to extract JSON from markdown code blocks\n      if raw =~ /```(?:json)?\\s*(\\{[\\s\\S]*?\\})\\s*```/m\n        begin\n          return JSON.parse($1)\n        rescue JSON::ParserError\n          # Continue to next strategy\n        end\n      end\n\n      # Try to find any JSON object\n      if raw =~ /(\\{[\\s\\S]*\\})/m\n        begin\n          return JSON.parse($1)\n        rescue JSON::ParserError\n          # Fall through to error\n        end\n      end\n\n      raise Provider::Openai::Error, \"Could not parse JSON from PDF processing response: #{raw.truncate(200)}\"\n    end\nend\n","sourceCodeStart":246,"sourceCodeEnd":267,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/openai/pdf_processor.rb#L246-L267","documentation":"Raised by Provider::Openai::PdfProcessor#parse_json_flexibly when the model's PDF-analysis reply defeats every recovery strategy: direct JSON.parse, fence stripping, fragment unwrapping, and a final regex grab of the outermost {...}. Crucially, the vision path (process_with_vision) cannot use response_format json_object — the code comments note response_format is incompatible with vision — so it only prompts for JSON; models that answer with markdown fences plus prose, or get truncated by max_tokens, land here. The message embeds the first 200 chars of raw output for diagnosis.","triggerScenarios":"Vision model wrapping its answer in ```json fences with commentary that defeats the regex (e.g. multiple fenced blocks, or braces inside prose); truncation at max_tokens leaving unclosed JSON; model answering in plain prose ignoring the JSON-only instruction; extracted-JSON regex matching an outer {...} that is itself invalid (nested unbalanced braces).","commonSituations":"First 5 pages of dense statements consuming the budget; gpt-4o/gpt-4.1 vision replies with preamble text; custom vision-capable models with weaker instruction following; max_response_tokens configured low to cut cost.","solutions":["Inspect the truncated raw in the message: unclosed JSON means raise max_tokens or process fewer pages per call.","Strengthen the prompt suffix already present ('IMPORTANT: Respond with valid JSON only') — also demand 'no markdown fences, first character must be {'.","Retry once: ask a follow-up 'Return only the JSON object' with the previous output as context, or re-run the same chunk (nondeterministic fence usage often clears).","Improve the last-resort extraction: try the first balanced brace sequence or JSON.parse with truncation repair before giving up."],"exampleFix":"# before\nresponse = client.chat(parameters: params)\nparse_response_generic(response)\n\n# after\nparams[:max_tokens] = [ params[:max_tokens].to_i, 4096 ].max # avoid truncated JSON\nresponse = client.chat(parameters: params)\nparse_response_generic(response)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  result = processor.process\nrescue Provider::Openai::Error => e\n  raise unless e.message.include?(\"Could not parse JSON\")\n  result = processor.process # one retry: fence-wrapped vision answers are nondeterministic\nend","preventionTips":["Budget max_tokens for the full multi-page vision answer; truncation is the top cause of unparseable JSON.","Prompt 'first character must be { — no markdown fences' since response_format is unusable on the vision path.","Cap pages per request (the code already limits to 5) and split larger documents into multiple calls."],"tags":["openai","json-parsing","llm-output","vision","pdf","truncation"],"backgroundTag":"llm-invalid-json","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}