{"record":{"id":"376d88be71aa17e7","repo":"we-promise/sure","slug":"model-did-not-invoke-tool-name-376d88","errorCode":null,"errorMessage":"Model did not invoke #{TOOL_NAME}","messagePattern":"Model did not invoke #(.+?)","errorType":"exception","errorClass":"Provider::Anthropic::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/anthropic/bank_statement_extractor.rb","lineNumber":151,"sourceCode":"      <<~INSTRUCTIONS\n        Extract bank statement data from the attached PDF and return the result via the report_bank_statement tool.\n\n        Rules:\n          - Extract EVERY transaction in document order\n          - Negative amounts for debits / expenses, positive for credits / deposits\n          - Dates in YYYY-MM-DD\n          - Use null for any field you cannot read; do not invent values\n      INSTRUCTIONS\n    end\n\n    def stop_reason(response)\n      raw = response.respond_to?(:stop_reason) ? response.stop_reason : nil\n      raw.to_s.to_sym if raw\n    end\n\n    def extract_tool_input(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      input\n    end\n\n    def build_result(parsed)\n      # Intentionally NOT deduplicated, unlike Provider::Openai's extractor. That\n      # one chunks the PDF text with overlap and must drop transactions repeated\n      # across adjacent chunks. We send the whole PDF as a single native document\n      # block — no chunk artifacts — so deduping here would wrongly merge\n      # legitimate same-day, same-amount rows (e.g. two identical purchases).\n      # Preserve every transaction the model returns.\n      transactions = Array(parsed[\"transactions\"] || parsed[:transactions]).map { |t| normalize_transaction(t) }.compact\n\n      {\n        transactions: transactions,\n        period: {","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/anthropic/bank_statement_extractor.rb#L133-L169","documentation":"BankStatementExtractor#extract_tool_input scans the Messages response content for a :tool_use block; if none exists it raises \"Model did not invoke report_bank_statement\". The extractor forces the report_bank_statement tool so the statement structure comes back as JSON; a prose answer, a refusal ('do not invent values' pushed too far), or a response truncated by max_tokens before the tool call leaves the block missing.","triggerScenarios":"The model narrates the statement in text instead of calling the tool; max_tokens cuts the response (large statements produce large tool arguments); a custom proxy drops tool_choice; degraded image-only PDFs make the model hedge with prose.","commonSituations":"Huge statements whose tool payload exceeds the output cap; weak/legacy model behind a custom endpoint; proxy translation layer losing tool parameters; transient model behavior fixed by a retry.","solutions":["Retry the extraction once — tool-less responses are frequently one-off.","Check the Langfuse span (extract_bank_statement_api_call) for stop_reason: if max_tokens, raise max_tokens or split the PDF into smaller documents.","Use a current strong model (claude-sonnet-4-6 class) that reliably honors forced tool_choice.","Verify custom-endpoint proxies forward tool definitions and tool_choice."],"exampleFix":"# before\nresult = extractor.extract\n\n# after\nbegin\n  result = extractor.extract\nrescue Provider::Anthropic::Error => e\n  retry if (e.message.include?(\"did not invoke\") && (attempts += 1) < 2)\n  raise\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"attempts = 0\nbegin\n  result = extractor.extract\nrescue Provider::Anthropic::Error => e\n  attempts += 1\n  retry if attempts < 2 && e.message.include?(\"did not invoke\")\n  raise\nend","preventionTips":["Watch stop_reason in the Langfuse span; max_tokens truncation needs a bigger cap or a smaller PDF, not retries.","Split large statements so tool arguments fit the output budget.","Use a current strong model for forced tool calls."],"tags":["anthropic","llm","tool-use","pdf","bank-statement","response-parsing"],"backgroundTag":"llm-tool-call-failure","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}