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

Could not extract text from PDF

Error message

Could not extract text from PDF

What it means

Raised by Provider::Openai::PdfProcessor#process_with_text_extraction when extract_text_from_pdf returns nil/empty. The helper constructs a PDF::Reader over the bytes, joins each page's text with page markers, and rescues every exception to nil (logging 'Failed to extract text from PDF'). Blank therefore means one of: pdf_content blank, reader raised (encrypted/corrupt/unsupported PDF), or every page.text came back empty — the classic scanned-PDF signature. Text over ~100k chars is truncated afterward, so length is not a trigger.

Source

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

          "opening_balance": number or null,
          "closing_balance": number or null,
          "currency": "USD/EUR/etc or null",
          "account_holder": "Name or null"
        }
      }
    INSTRUCTIONS
  end

  private

    PdfProcessingResult = Provider::LlmConcept::PdfProcessingResult

    def process_with_text_extraction
      effective_model = model.presence || Provider::Openai::DEFAULT_MODEL

      # Extract text from PDF using pdf-reader gem
      pdf_text = extract_text_from_pdf
      raise Provider::Openai::Error, "Could not extract text from PDF" if pdf_text.blank?

      # Truncate if too long (max ~100k chars to stay within token limits)
      pdf_text = pdf_text.truncate(100_000) if pdf_text.length > 100_000

      params = {
        model: effective_model,
        messages: [
          { role: "system", content: instructions },
          {
            role: "user",
            content: "Please analyze the following document text and provide a structured summary:\n\n#{pdf_text}"
          }
        ],
        response_format: { type: "json_object" }
      }

      response = client.chat(parameters: params)

View on GitHub (pinned to e69894adb9)

Solutions

  1. Check the error log for the swallowed exception text to distinguish encrypted (password required) from corrupt (malformed PDF) from empty pages.
  2. Route scanned PDFs to process_with_vision, which rasterizes pages via pdftoppm instead of reading text.
  3. Pre-validate uploads: %PDF- header, non-trivial size, and at least one page with extractable text before choosing the text path.
  4. For encrypted files, collect the password and decrypt first (e.g. with hexapdf/qpdf) before extraction.

Example fix

# before
pdf_text = extract_text_from_pdf
raise Provider::Openai::Error, "Could not extract text from PDF" if pdf_text.blank?

# after
pdf_text = extract_text_from_pdf
if pdf_text.blank?
  Rails.logger.info("PDF has no text layer; falling back to vision processing")
  return process_with_vision
end
Defensive patterns

Strategy: fallback

Try / catch

begin
  processor.process
rescue Provider::Openai::Error => e
  raise unless e.message.include?("Could not extract text")
  processor.process_with_vision # same processor, image path handles scanned pages
end

Prevention

When it happens

Trigger: Image-only scanned PDF fed to the text path; password-protected PDF throwing inside PDF::Reader; corrupt/truncated upload bytes; malformed xref tables from certain PDF generators; nil pdf_content passed through from an upload handler.

Common situations: Users uploading phone-scanned documents; e-statements exported with DRM/encryption flags; PDFs produced by niche tools with quirky internal structure; the vision fallback not selected because the routing logic assumed text would exist.

Related errors


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