{
"type": "function",
"function": {
"name": "accounting_ledger_proposeAllocatedTransaction",
"description": "Prepare a day-to-day accounting transaction proposal with required structured allocationLines for human review before booking. Use this for multi-line receipts or mixed account/VAT/private/payback treatment so the review card and approval use the line-level split instead of a single flat account. Opening balances must use the accounting onboarding opening-balance tools.",
"parameters": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable transaction description."
},
"amountNok": {
"type": "number",
"description": "Gross amount in NOK, not minor currency units."
},
"type": {
"type": "string",
"description": "Whether this is income or an expense.",
"enum": [
"income",
"expense"
]
},
"date": {
"type": "string",
"description": "Transaction date in YYYY-MM-DD format. Defaults to today."
},
"paymentMethod": {
"type": "string",
"description": "Optional payment method.",
"enum": [
"bank_transfer",
"company_card",
"private_card",
"cash",
"other"
]
},
"tredcoProjectId": {
"type": "string",
"description": "Optional canonical web project ID for allocation."
},
"clearProject": {
"type": "boolean",
"description": "Set true to record without a project, overriding the chat's conversation-default project. Do not combine with tredcoProjectId."
},
"allocationLines": {
"type": "array",
"items": {
"type": "object",
"properties": {
"lineId": {
"type": "string",
"description": "Stable source/review line ID."
},
"sourceLineIndex": {
"type": "number",
"description": "Zero-based source receipt/invoice line index when known."
},
"description": {
"type": "string",
"description": "Source-line description shown to the reviewer."
},
"accountNumber": {
"type": "string",
"description": "Four-digit NS 4102 account for this line."
},
"accountName": {
"type": "string",
"description": "Display name for accountNumber."
},
"grossAmountMinor": {
"type": "number",
"description": "Gross line amount in øre, including VAT. If a receipt line column is before VAT, put that amount in netAmountMinor and provide vatAmountMinor instead."
},
"grossAmountNok": {
"type": "number",
"description": "Gross line amount in NOK, including VAT; use only when minor units are not available."
},
"netAmountMinor": {
"type": "number",
"description": "Net/pre-VAT line amount in øre when VAT is split or the receipt lists line amounts before VAT."
},
"vatAmountMinor": {
"type": "number",
"description": "VAT amount in øre for this line; private/excluded lines may include VAT here for review even when it should not be posted to input VAT."
},
"vatRate": {
"type": "number",
"description": "VAT percentage for this line."
},
"debitAmountMinor": {
"type": "number",
"description": "Explicit debit amount in øre for special allocation lines."
},
"creditAmountMinor": {
"type": "number",
"description": "Explicit credit amount in øre for payback/refund/private offset lines."
},
"treatment": {
"type": "string",
"description": "Required review treatment for the line.",
"enum": [
"business_expense",
"employee_clearing",
"deposit_or_payback",
"private_exclusion",
"supplier_invoice_expense"
]
},
"status": {
"type": "string",
"description": "Whether this line is ready or needs clarification before approval.",
"enum": [
"ready",
"needs_clarification"
]
},
"confidence": {
"type": "number",
"description": "Assistant confidence on a 0-100 scale."
},
"rationale": {
"type": "string",
"description": "Line-level accounting rationale shown in the review card."
},
"tredcoProjectId": {
"type": "string",
"description": "Canonical web project ID for this line when a receipt is split across jobs; omit to use the proposal-level project."
}
},
"required": [
"accountNumber",
"treatment"
],
"additionalProperties": false
},
"description": "Required structured source/review lines for line-level booking. Structured source/review lines for multi-account booking. Use whenever the receipt or pending transaction has multiple source lines, mixed accounts, mixed VAT rates, employee clearing, deposit/payback, or private/excluded parts; do not put those splits only in rationale. Line amounts must balance to the gross payment total; Norwegian receipts often list source lines before VAT, so use netAmountMinor plus vatAmountMinor when the receipt has a separate MVA summary. Each line can carry account, VAT, treatment, status, confidence, and explicit debit/credit amounts for special cases."
},
"rationale": {
"type": "string",
"description": "Plain-language note, in the user's language, on why the split is booked this way and the one assumption the user should confirm."
},
"hasReceipt": {
"type": "boolean",
"description": "Whether the proposal is based on an attached receipt."
},
"receiptFileId": {
"type": "string",
"description": "Legacy file ID for the attached receipt. Prefer receiptFileRef."
},
"receiptFileRef": {
"type": "object",
"properties": {
"ownerService": {
"type": "string",
"description": "Service that originally owns the uploaded file metadata.",
"enum": [
"web",
"accounting",
"agent"
]
},
"fileId": {
"type": "string",
"description": "Stable file ID."
},
"tenantId": {
"type": "string",
"description": "Tenant that owns the file."
},
"name": {
"type": "string",
"description": "Original filename."
},
"contentType": {
"type": "string",
"description": "MIME content type."
},
"size": {
"type": "number",
"description": "File size in bytes."
},
"purpose": {
"type": "string",
"description": "File purpose.",
"enum": [
"chat_attachment",
"receipt",
"supplier_invoice",
"quote_source",
"generated_output",
"scratch"
]
}
},
"required": [
"ownerService",
"fileId"
],
"additionalProperties": false,
"description": "Owner-tagged file reference for the attached receipt."
},
"requestReceipt": {
"type": "boolean",
"description": "Show an upload affordance for the missing receipt on the proposal card. Set when the booking claims a VAT deduction or otherwise needs evidence and none is attached. Do not set for bank fees, interest, or entries that need no documentation."
}
},
"required": [
"description",
"amountNok",
"type",
"allocationLines"
],
"additionalProperties": false
}
}
}
Model:
x-ai/grok-4.5(upstreamx-ai/grok-4.5-20260708), provider xAI, viaPOST /api/v1/chat/completions(streaming and non-streaming both affected). Observed 2026-07-29 on our production account; all generation IDs below are from that account.temperature: 0.2,reasoning: {effort: "low"}unless noted (highbehaves the same). Control model on identical payloads:google/gemini-3.5-flash— always correct.Filing here because Discord is hard to attach a full repro to — happy to move/cross-post this if there is a better channel.
Summary
For one of our production tools (a bookkeeping proposal tool whose schema has 14 top-level properties, a nested object property, and a required array-of-objects property
allocationLineswith 17 item fields and two enums), grok-4.5 tool calling is broken in three observable ways:allocationLinesparameter is never emitted. Across 19+ calls (a production incident plus systematic repro runs) with the full schema, the model returns an otherwise-valid tool call that omits the required array — at low and high reasoning effort, with 1 tool or 19 tools in the request, regardless of prompting, even after a tool-result error message explicitly names the missing parameter. Billed completion tokens match the visible flat arguments (nothing hidden was generated in these cases)."tredcoProjectId": "employee_clearing"/"tredcoProjectId": "business_expense"/"tredcoProjectId": "treatment"(tredcoProjectIdis a string ID field;treatmentis the required enum). Identical corruption ateffort: high(1 600+ reasoning tokens), so it is not sampling noise.tool_choice. Withtool_choice: {"type":"function","function":{"name":...}}the API returnedfinish_reason: "stop"with an empty message and no tool_calls while billing 246–285 completion tokens (only ~24 of them reasoning) — 5/5 in one configuration with a ~2.8 KB system prompt. In other runs 363–924 completion tokens were billed for ~20–25 visible text tokens and no tool call. The visible text in those runs even narrates the allocation lines the tool call should have carried. With a one-line system prompt the same forced call sometimes succeeds, so this facet is intermittent/context-length-correlated.The model demonstrably knows the parameter: asked to recite the tool schema (no tool call), it lists
allocationLinesas required plus all 17 nested field names correctly. Small toy schemas of the same shape — required array-of-objects, including one with the same 5-value enum in items, and one with 17 item fields — work perfectly. The failure is specific to the full production schema, which suggests the server-side function-calling pipeline (schema-guided decoding / argument validation) mis-handles it above some complexity threshold.Generation IDs (our account, for server-side inspection)
gen-1785348939-S4oh46bpsVzuC01CbZJoallocationLinesgen-1785310378-gmBxA1Ro45RYI1PZ7joHgen-1785310355-9roddAlFvRrR4Gox2iBt,gen-1785310371-qomZaoZeYqASdNrkR5Pugen-1785348877-O3JzpTcfno8QrvkXjct4finish_reason:"stop", 284 completion tokens billed (23 reasoning), ~90 chars visible text, no tool callgen-1785348882-OR29DBOAT1VENSWH2Ybwfinish_reason:"stop", 285 completion tokens billed (24 reasoning), empty content, no tool callgen-1785348958-LPF3VPE9Zddt3u9jYyVL"tredcoProjectId": "employee_clearing", requiredtreatmentmissinggen-1785348953-584onbPh9VEwXWZq8o9ugen-1785348969-wy2trHyn6jOkfqf3zjYxallocationLinesReproduction
Two files, no dependencies (
OPENROUTER_API_KEY=... node standalone_repro.mjs). Scenario A (required param omitted) and C (transposed keys) reproduce deterministically for us; B is intermittent as described.standalone_repro.mjsalloc-tool.json— the failing tool schema (verbatim from production){ "type": "function", "function": { "name": "accounting_ledger_proposeAllocatedTransaction", "description": "Prepare a day-to-day accounting transaction proposal with required structured allocationLines for human review before booking. Use this for multi-line receipts or mixed account/VAT/private/payback treatment so the review card and approval use the line-level split instead of a single flat account. Opening balances must use the accounting onboarding opening-balance tools.", "parameters": { "type": "object", "properties": { "description": { "type": "string", "description": "Human-readable transaction description." }, "amountNok": { "type": "number", "description": "Gross amount in NOK, not minor currency units." }, "type": { "type": "string", "description": "Whether this is income or an expense.", "enum": [ "income", "expense" ] }, "date": { "type": "string", "description": "Transaction date in YYYY-MM-DD format. Defaults to today." }, "paymentMethod": { "type": "string", "description": "Optional payment method.", "enum": [ "bank_transfer", "company_card", "private_card", "cash", "other" ] }, "tredcoProjectId": { "type": "string", "description": "Optional canonical web project ID for allocation." }, "clearProject": { "type": "boolean", "description": "Set true to record without a project, overriding the chat's conversation-default project. Do not combine with tredcoProjectId." }, "allocationLines": { "type": "array", "items": { "type": "object", "properties": { "lineId": { "type": "string", "description": "Stable source/review line ID." }, "sourceLineIndex": { "type": "number", "description": "Zero-based source receipt/invoice line index when known." }, "description": { "type": "string", "description": "Source-line description shown to the reviewer." }, "accountNumber": { "type": "string", "description": "Four-digit NS 4102 account for this line." }, "accountName": { "type": "string", "description": "Display name for accountNumber." }, "grossAmountMinor": { "type": "number", "description": "Gross line amount in øre, including VAT. If a receipt line column is before VAT, put that amount in netAmountMinor and provide vatAmountMinor instead." }, "grossAmountNok": { "type": "number", "description": "Gross line amount in NOK, including VAT; use only when minor units are not available." }, "netAmountMinor": { "type": "number", "description": "Net/pre-VAT line amount in øre when VAT is split or the receipt lists line amounts before VAT." }, "vatAmountMinor": { "type": "number", "description": "VAT amount in øre for this line; private/excluded lines may include VAT here for review even when it should not be posted to input VAT." }, "vatRate": { "type": "number", "description": "VAT percentage for this line." }, "debitAmountMinor": { "type": "number", "description": "Explicit debit amount in øre for special allocation lines." }, "creditAmountMinor": { "type": "number", "description": "Explicit credit amount in øre for payback/refund/private offset lines." }, "treatment": { "type": "string", "description": "Required review treatment for the line.", "enum": [ "business_expense", "employee_clearing", "deposit_or_payback", "private_exclusion", "supplier_invoice_expense" ] }, "status": { "type": "string", "description": "Whether this line is ready or needs clarification before approval.", "enum": [ "ready", "needs_clarification" ] }, "confidence": { "type": "number", "description": "Assistant confidence on a 0-100 scale." }, "rationale": { "type": "string", "description": "Line-level accounting rationale shown in the review card." }, "tredcoProjectId": { "type": "string", "description": "Canonical web project ID for this line when a receipt is split across jobs; omit to use the proposal-level project." } }, "required": [ "accountNumber", "treatment" ], "additionalProperties": false }, "description": "Required structured source/review lines for line-level booking. Structured source/review lines for multi-account booking. Use whenever the receipt or pending transaction has multiple source lines, mixed accounts, mixed VAT rates, employee clearing, deposit/payback, or private/excluded parts; do not put those splits only in rationale. Line amounts must balance to the gross payment total; Norwegian receipts often list source lines before VAT, so use netAmountMinor plus vatAmountMinor when the receipt has a separate MVA summary. Each line can carry account, VAT, treatment, status, confidence, and explicit debit/credit amounts for special cases." }, "rationale": { "type": "string", "description": "Plain-language note, in the user's language, on why the split is booked this way and the one assumption the user should confirm." }, "hasReceipt": { "type": "boolean", "description": "Whether the proposal is based on an attached receipt." }, "receiptFileId": { "type": "string", "description": "Legacy file ID for the attached receipt. Prefer receiptFileRef." }, "receiptFileRef": { "type": "object", "properties": { "ownerService": { "type": "string", "description": "Service that originally owns the uploaded file metadata.", "enum": [ "web", "accounting", "agent" ] }, "fileId": { "type": "string", "description": "Stable file ID." }, "tenantId": { "type": "string", "description": "Tenant that owns the file." }, "name": { "type": "string", "description": "Original filename." }, "contentType": { "type": "string", "description": "MIME content type." }, "size": { "type": "number", "description": "File size in bytes." }, "purpose": { "type": "string", "description": "File purpose.", "enum": [ "chat_attachment", "receipt", "supplier_invoice", "quote_source", "generated_output", "scratch" ] } }, "required": [ "ownerService", "fileId" ], "additionalProperties": false, "description": "Owner-tagged file reference for the attached receipt." }, "requestReceipt": { "type": "boolean", "description": "Show an upload affordance for the missing receipt on the proposal card. Set when the booking claims a VAT deduction or otherwise needs evidence and none is attached. Do not set for bank fees, interest, or entries that need no documentation." } }, "required": [ "description", "amountNok", "type", "allocationLines" ], "additionalProperties": false } } }Full output of a verification run (2026-07-29)
Expected
requiredis emitted (or the request fails loudly).tool_choice: {"type":"function",...}never returns a text-only/empty message.Impact
In production this looped a customer-facing agent: the tool rejects the argument object missing its required parameter, the model retries (announcing, in text, the lines it never manages to emit), and the turn eventually died after repeated failures — while every retry was billed. The key-transposition facet is arguably worse: with a slimmer schema the call succeeds with silently wrong field assignments.
Happy to provide more generation IDs, run variants, or A/B anything else against our account.