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

Model did not invoke #{TOOL_NAME}

Error message

Model did not invoke #{TOOL_NAME}

What it means

AutoMerchantDetector#extract_merchants scans the response for a :tool_use block; if none is present it raises "Model did not invoke report_merchants". The detector depends on Anthropic tool calling with the report_merchants tool forced, so a prose answer, an early truncation (stop_reason max_tokens), or a refusal leaves the expected tool block absent and the result cannot be extracted.

Source

Thrown at app/models/provider/anthropic/auto_merchant_detector.rb:135

    def user_message
      <<~MESSAGE.strip_heredoc
        User's known merchants:

        ```json
        #{user_merchants.to_json}
        ```

        Transactions to analyze:

        ```json
        #{transactions.to_json}
        ```
      MESSAGE
    end

    def extract_merchants(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)
      merchants = input.is_a?(Hash) ? (input["merchants"] || input[:merchants]) : nil

      raise Provider::Anthropic::Error, "Tool call missing merchants" unless merchants.is_a?(Array)
      merchants
    end

    def build_response(merchants)
      merchants.map do |m|
        AutoDetectedMerchant.new(
          transaction_id: m["transaction_id"] || m[:transaction_id],
          business_name: normalize_merchant_name(m["business_name"] || m[:business_name]),
          business_url: normalize_value(m["business_url"] || m[:business_url])
        )
      end
    end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Retry once — the usual remedy for a one-off non-tool response.
  2. Confirm the request forces tool_choice for report_merchants and inspect the Langfuse trace (anthropic.auto_detect_merchants) for the raw response.
  3. Move to a current strong default model and/or shrink the batch to leave room for the tool call within max_tokens.
  4. Validate that any custom base_url proxy forwards tool definitions and tool_choice untouched.

Example fix

# before
result = detector.auto_detect_merchants
rescue => e # generic, loses specificity

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

Strategy: retry

Try / catch

attempts = 0
begin
  result = provider.auto_detect_merchants(transactions: batch, family: family)
rescue Provider::Anthropic::Error => e
  attempts += 1
  retry if attempts < 2
  raise
end

Prevention

When it happens

Trigger: The model answers in text instead of calling the tool; output is truncated before the tool_use block on very long merchant-name inputs; a custom endpoint/proxy strips tool_choice; adversarial or messy transaction memo text pushes the model off-format.

Common situations: Weaker model selected via settings; large batches near the 25 cap consuming the output budget; OpenAI-compatible bridge dropping Anthropic tool parameters; occasional provider flakiness that succeeds on retry.

Related errors


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