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

Tool call missing categorizations

Error message

Tool call missing categorizations

What it means

The model did invoke the report_categorizations tool, but block_input parsed to a Hash without a usable "categorizations" Array (string inputs are JSON.parse'd first, symbol and string keys are both accepted). This raises "Tool call missing categorizations" — the tool was called but its arguments have the wrong shape, e.g. a bare array, a differently-named key, or an empty payload.

Source

Thrown at app/models/provider/anthropic/auto_categorizer.rb:139

        ```

        Auto-categorize the following transactions:

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

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

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

    def build_response(categorizations)
      categorizations.map do |c|
        category_name = c["category_name"] || c[:category_name]
        AutoCategorization.new(
          transaction_id: c["transaction_id"] || c[:transaction_id],
          category_name: normalize_category(category_name)
        )
      end
    end

    def normalize_category(value)
      return nil if value.nil?
      str = value.to_s.strip
      return nil if str.empty? || str.casecmp("null").zero?

View on GitHub (pinned to e69894adb9)

Solutions

  1. Retry the request — isolated malformed tool args are usually transient.
  2. Tighten the tool's input schema for the categorizations field (type, required) so the model is steered to the exact key.
  3. Switch to a current, stronger model (claude-sonnet-4-6 class) known to follow the tool schema; lower batch size to avoid truncated arguments.
  4. Compare the raw tool arguments in the Langfuse trace against the expected schema to find the exact divergence.

Example fix

# tool definition (conceptual) — before
{ name: "report_categorizations", input_schema: { type: "object" } }
# model returns { "results": [...] } => "Tool call missing categorizations"

# after
{ name: "report_categorizations",
  input_schema: { type: "object", required: ["categorizations"],
    properties: { categorizations: { type: "array", items: { type: "object" } } } } }
Defensive patterns

Strategy: retry

Try / catch

attempts = 0
begin
  result = categorizer.auto_categorize
rescue Provider::Anthropic::Error => e
  attempts += 1
  retry if attempts < 2 && e.message.include?("missing categorizations")
  raise
end

Prevention

When it happens

Trigger: The model calls the tool with {"results": [...]} or a top-level array instead of {"categorizations": [...]}; a partially-truncated tool argument at the max_tokens boundary parses to an incomplete Hash; a proxy/gateway rewrites or mangles tool input JSON.

Common situations: Schema drift between the tool definition and what a newer/older model produces; forced tool_choice with an under-specified tool schema; flaky one-off malformed output that a retry fixes; gateways that re-serialize tool arguments and drop keys.

Related errors


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