we-promise/sure · error · Provider::Openai::Error

Could not parse JSON from PDF processing response: #{raw.tru

Error message

Could not parse JSON from PDF processing response: #{raw.truncate(200)}

What it means

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.

Source

Thrown at app/models/provider/openai/pdf_processor.rb:264

      # Try to extract JSON from markdown code blocks
      if raw =~ /```(?:json)?\s*(\{[\s\S]*?\})\s*```/m
        begin
          return JSON.parse($1)
        rescue JSON::ParserError
          # Continue to next strategy
        end
      end

      # Try to find any JSON object
      if raw =~ /(\{[\s\S]*\})/m
        begin
          return JSON.parse($1)
        rescue JSON::ParserError
          # Fall through to error
        end
      end

      raise Provider::Openai::Error, "Could not parse JSON from PDF processing response: #{raw.truncate(200)}"
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Inspect the truncated raw in the message: unclosed JSON means raise max_tokens or process fewer pages per call.
  2. Strengthen the prompt suffix already present ('IMPORTANT: Respond with valid JSON only') — also demand 'no markdown fences, first character must be {'.
  3. 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).
  4. Improve the last-resort extraction: try the first balanced brace sequence or JSON.parse with truncation repair before giving up.

Example fix

# before
response = client.chat(parameters: params)
parse_response_generic(response)

# after
params[:max_tokens] = [ params[:max_tokens].to_i, 4096 ].max # avoid truncated JSON
response = client.chat(parameters: params)
parse_response_generic(response)
Defensive patterns

Strategy: retry

Try / catch

begin
  result = processor.process
rescue Provider::Openai::Error => e
  raise unless e.message.include?("Could not parse JSON")
  result = processor.process # one retry: fence-wrapped vision answers are nondeterministic
end

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/65b725dcbca8db4d. Report an issue: GitHub.