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

Model did not invoke #{TOOL_NAME}

Error message

Model did not invoke #{TOOL_NAME}

What it means

PdfProcessor#extract_tool_input scans the response for a :tool_use block and raises "Model did not invoke report_document_analysis" when none is found. The processor forces the report_document_analysis tool to get structured summary/document_type/extracted_data JSON back; a plain-text analysis, a refusal ('return null if unclear' over-applied), or a max_tokens truncation before the tool block makes extraction impossible.

Source

Thrown at app/models/provider/anthropic/pdf_processor.rb:146

        Classification options:
          - bank_statement: bank account statements (incl. mobile money / digital wallets)
          - credit_card_statement: credit card statements
          - investment_statement: brokerage / investment statements
          - financial_document: tax forms, receipts, invoices, financial reports
          - contract: legal agreements, loans, terms of service
          - other: anything else

        Rules:
          - Be factual; only report what is clearly visible
          - If a field is unclear/redacted, return null for it
          - Do not invent figures or names you cannot read
          - For statements with many transactions, return the count rather than enumerating them
      INSTRUCTIONS
    end

    def extract_tool_input(response)
      tool_use = Array(response.content).find { |block| block_type(block) == :tool_use }
      raise Provider::Anthropic::Error, "Model did not invoke #{TOOL_NAME}" unless tool_use

      input = block_input(tool_use)
      input = JSON.parse(input) if input.is_a?(String)
      input
    end

    def build_result(parsed)
      PdfProcessingResult.new(
        summary: parsed["summary"] || parsed[:summary],
        document_type: normalize_document_type(parsed["document_type"] || parsed[:document_type]),
        extracted_data: parsed["extracted_data"] || parsed[:extracted_data] || {}
      )
    end

    def normalize_document_type(doc_type)
      return "other" if doc_type.blank?

      normalized = doc_type.to_s.strip.downcase.gsub(/\s+/, "_")

View on GitHub (pinned to e69894adb9)

Solutions

  1. Retry once — the cheapest fix for intermittent tool-less responses.
  2. Inspect the process_pdf_api_call Langfuse span for stop_reason; if max_tokens, raise the cap or send fewer pages per request.
  3. Default to a current strong model (claude-sonnet-4-6) and confirm supports_pdf_processing? passed for it.
  4. Ensure any custom base_url proxy preserves tool definitions and tool_choice.

Example fix

# before
result = processor.process

# after
begin
  result = processor.process
rescue Provider::Anthropic::Error => e
  retry if e.message.include?("did not invoke") && (tries += 1) < 2
  raise
end
Defensive patterns

Strategy: retry

Try / catch

attempts = 0
begin
  result = processor.process
rescue Provider::Anthropic::Error => e
  attempts += 1
  retry if attempts < 2 && e.message.include?("did not invoke")
  raise
end

Prevention

When it happens

Trigger: The model describes the document in prose instead of calling the tool; the tool arguments for a dense statement exceed max_tokens so the block never completes; a custom proxy silently drops tool_choice; image-only or garbled PDFs cause the model to hedge in text.

Common situations: Long statements producing huge tool payloads; weak model chosen via settings; gateway translating Anthropic tool calls imperfectly; sporadic provider behavior that a single retry clears.

Related errors


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