Skip to content
All insights AI architecture for finance

Making function calling reliable in finance workflows

A model that calls the wrong tool with the wrong arguments is a production incident. Here is how we make function calling dependable in finance systems.

5 min read #function-calling#reliability#fintech
Financial services professionals working through an AI initiative

A model that calls the wrong tool with the wrong arguments is a production incident, not a bad answer. You make function calling reliable in finance by treating each tool as a typed contract, validating arguments against a schema and your business rules before the call runs, keeping the exposed tool set small, and scoring call accuracy on a fixed eval set the way you would score a model.

Most teams get the demo working and assume the hard part is done. The demo works because the input was clean and the tool list was short. The incident happens three weeks later, at quarter-end, when a reconciliation agent calls post_adjustment instead of stage_adjustment because both descriptions mention “adjustment” and the model guessed. Nobody wrote a wrong sentence. A journal entry hit the ledger that should have waited for review.

The failure modes are boring and that is the point

Function calling in a finance workflow fails in a small number of predictable ways, and none of them look like the hallucination stories people worry about.

  • Right tool, wrong argument. The model calls get_positions but passes as_of as today’s date when the workflow needed the prior business day. The call succeeds. The number is off. This is a point-in-time correctness bug wearing the costume of a successful API response.
  • Wrong tool, plausible name. Two tools with overlapping descriptions, and the model picks the one that reads closest to the prompt. Naming collisions cause more misfires than reasoning failures do.
  • Well-formed, out of policy. The arguments validate against the JSON schema and still violate a rule the schema does not encode: a transfer above a threshold, a counterparty on a restricted list, a date outside the open period.
  • Silent coercion. The model emits "1,250.00" and something downstream parses it as 1 or 1250 depending on locale. String-to-number coercion at a tool boundary is where money quietly changes by three orders of magnitude.

The reason to enumerate these is that each one has a specific control. You do not fix them with a better prompt. You fix them with structure around the call.

Treat every tool as a typed contract, then validate twice

Start by making the argument surface as narrow as the task allows. If a tool retrieves a valuation, its parameters should be an instrument identifier and a resolved point-in-time timestamp, not a free-text query the model has to compose. Every degree of freedom you hand the model is a degree of freedom it can get wrong. Enumerations beat free strings. A closed set of report_period values the model chooses from cannot drift the way a date string it constructs will.

Then validate in two layers, because they catch different things.

  • Structural validation. The arguments must match the JSON schema: types, required fields, formats, enum membership. This is cheap, deterministic, and rejects the malformed calls before they reach a system that costs something. Reject and return the error to the model so it can retry with the correction in context.
  • Semantic validation. The arguments must satisfy business rules the schema cannot express. Is the as_of date inside an open accounting period? Is the entity resolved to a single, unambiguous counterparty rather than a fuzzy match across three spellings? Is the amount within the authority of whoever initiated the request? These checks are where entity resolution and point-in-time correctness actually get enforced.

A well-formed argument that fails semantic validation is the dangerous case, because the schema said yes. Keep those two layers separate in your code so the semantic layer stays readable and reviewable by someone from the controls side who does not read your type definitions.

One more rule we hold to: mutating tools and reading tools live in different tiers. A read that returns the wrong number is a bug you catch downstream. A write that posts, transfers, or approves leaves a mark. Anything that changes state should require an explicit, validated confirmation step and should never be reachable in the same unguarded call path as a lookup.

Keep the tool set small, and disambiguate the survivors

Model accuracy on tool selection degrades as the tool list grows and as descriptions start to overlap. The instinct to expose fifty capabilities at one call site is the instinct that produces wrong-tool misfires.

  • Route first, then present a handful. Classify the request into a workflow, and expose only the tools that workflow needs. A reconciliation agent does not need to see the onboarding tools.
  • Write descriptions that draw the contrast between tools. If you have stage_adjustment and post_adjustment, the description of each should say what the other does and when not to reach for it. The model disambiguates on the text you give it.
  • Prefer one parameterized tool over five near-duplicates when the difference is an argument, not a capability. Five tools that differ only by report type invite selection errors; one tool with a report_type enum does not.

Measure call accuracy like you measure a model

You would not ship a classifier without an eval set. A function-calling system is a classifier over your tool space plus an argument generator, and it deserves the same discipline.

Build a fixed set of cases that map an input to the exact tool and arguments that should result. Include the adversarial ones: the ambiguous entity, the closed period, the amount one cent over the threshold, the request that should call no tool at all. Score exact tool match and argument correctness separately, because a system that picks the right tool with a wrong date is failing differently from one that picks the wrong tool.

Run this on every model change, every prompt change, and every tool addition. Watch for drift when a provider updates a model underneath you, because tool-selection behavior shifts in ways that pass a smoke test and fail your closed-period case. Keep a false-positive budget for the mutating tools and hold the line on it. The whole point of straight-through processing is that the exceptions get routed to a human, and you cannot know your exception rate without measuring the calls that produced it.

The audit trail falls out of this naturally when you do it right. Every call carries its resolved arguments, the validation results from both layers, and the outcome. When someone asks in six months why an adjustment posted on a Tuesday, the lineage is already there to read.

FAQ

Should the model construct SQL and API payloads directly, or call typed functions?

Have it call typed functions with narrow, validated parameters. Free-form SQL or raw payloads move the failure surface into a place your schema cannot check, and they make the audit trail much harder to read after the fact.

How many tools can a model handle before accuracy drops?

In our testing accuracy starts sliding once a single call site exposes more than a dozen or so similar tools. Split them across scoped sub-agents or route by intent first, then present only the relevant handful.

Do JSON schema constraints alone make function calling safe?

No. Schema catches malformed arguments, not wrong-but-well-formed ones. A valid ISO date can still be the wrong reporting period, so you need business-rule validation and an eval set on top of structural checks.

Working on something similar?

Tell us about your data and the workflow around it, and we will give you a straight read.

Book a 30-min intro call