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.

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.

Appendix: 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)