{"record":{"id":"25fc273fdfe23fe8","repo":"we-promise/sure","slug":"tool-call-missing-merchants","errorCode":null,"errorMessage":"Tool call missing merchants","messagePattern":"Tool call missing merchants","errorType":"exception","errorClass":"Provider::Anthropic::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/anthropic/auto_merchant_detector.rb","lineNumber":141,"sourceCode":"        ```\n\n        Transactions to analyze:\n\n        ```json\n        #{transactions.to_json}\n        ```\n      MESSAGE\n    end\n\n    def extract_merchants(response)\n      tool_use = Array(response.content).find { |block| block_type(block) == :tool_use }\n      raise Provider::Anthropic::Error, \"Model did not invoke #{TOOL_NAME}\" unless tool_use\n\n      input = block_input(tool_use)\n      input = JSON.parse(input) if input.is_a?(String)\n      merchants = input.is_a?(Hash) ? (input[\"merchants\"] || input[:merchants]) : nil\n\n      raise Provider::Anthropic::Error, \"Tool call missing merchants\" unless merchants.is_a?(Array)\n      merchants\n    end\n\n    def build_response(merchants)\n      merchants.map do |m|\n        AutoDetectedMerchant.new(\n          transaction_id: m[\"transaction_id\"] || m[:transaction_id],\n          business_name: normalize_merchant_name(m[\"business_name\"] || m[:business_name]),\n          business_url: normalize_value(m[\"business_url\"] || m[:business_url])\n        )\n      end\n    end\n\n    def normalize_value(value)\n      return nil if value.nil?\n      str = value.to_s.strip\n      return nil if str.empty? || str.casecmp(\"null\").zero?\n      str","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/anthropic/auto_merchant_detector.rb#L123-L159","documentation":"The model invoked report_merchants, but its parsed input is not a Hash containing a \"merchants\" Array (string inputs are JSON.parse'd; string and symbol keys both accepted). \"Tool call missing merchants\" means the tool arguments came back with the wrong shape — a bare array, a renamed key, or truncated JSON — so no merchant list can be built.","triggerScenarios":"Model returns {\"detected_merchants\": [...]} or a top-level array instead of {\"merchants\": [...]}; tool arguments get cut at max_tokens and JSON.parse yields a partial Hash; a proxy rewrites tool input keys.","commonSituations":"Loose tool schema letting the model pick its own key name; older/smaller models deviating from schema; one-off malformed responses; gateway middleware normalizing JSON keys (e.g. camelize).","solutions":["Retry — isolated malformed args are typically transient.","Make the tool schema strict: require the \"merchants\" property with item types so the model must use the exact key.","Upgrade to a current model (claude-sonnet-4-6 class) and reduce batch size to prevent truncated arguments.","Inspect the tool input in the Langfuse trace to see the exact shape the model sent."],"exampleFix":"# tool schema — before\n{ type: \"object\", properties: { merchants: { type: \"array\" } } } # not required\n# model omits \"merchants\" => \"Tool call missing merchants\"\n\n# after\n{ type: \"object\", required: [\"merchants\"],\n  properties: { merchants: { type: \"array\", items: { type: \"object\",\n    properties: { transaction_id: { type: \"string\" }, business_name: { type: \"string\" }, business_url: { type: \"string\" } } } } } }","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"attempts = 0\nbegin\n  result = detector.auto_detect_merchants\nrescue Provider::Anthropic::Error => e\n  attempts += 1\n  retry if attempts < 2 && e.message.include?(\"missing merchants\")\n  raise\nend","preventionTips":["Mark \"merchants\" required in the tool input schema with typed items.","Smaller batches reduce truncated tool arguments.","Use Langfuse traces to catch systematic schema drift early."],"tags":["anthropic","llm","tool-use","schema-mismatch","merchant-detection"],"backgroundTag":"llm-malformed-tool-response","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}