Tool-Calling Patterns for Database Agents Using Function Calling APIs
Agents overwhelm schemas built for humans; programmatic calling fixes the structural limits.

Database tool schemas built for human analysts assume a caller who thinks before typing: one query, one answer, one decision about what to ask next. Agents don't work that way. They fan out across dozens of calls at once, chain steps together without pausing for a human to read the output, and retry when something comes back malformed, and that difference in behavior is what breaks the function-calling contract that has worked fine for years.
Why the function-calling contract breaks for agents
The standard agentic loop looks simple on paper. An LLM gets a prompt, decides it needs data it doesn't have, builds a structured JSON payload that matches a function signature, and hands it to an orchestration layer that runs the function and sends the result back. AWS Prescriptive Guidance frames this as the moment when an agent stops merely responding, because now it starts acting. That framing is accurate, but it hides a design assumption baked into most database tool schemas: that whoever is on the other end of the call is a person, moving one step at a time.
A human analyst works a database query in a deliberate rhythm. They form an intent, write the query, read what comes back, then decide what to ask next. One round trip, then a pause, then another round trip. The tool schemas built for that pattern tend to assume the same shape: a single clean request in, a single clean response out, low volume, low concurrency.
Agents don't move in that rhythm. A single agent task can spin off dozens of tool calls at once, check intermediate results mid-task to decide what to fetch next, and retry on a partial or malformed response, and it does this without asking anyone for permission. It is a different workload profile than a faster version of the same one.
What fails in production usually isn't the model's reasoning. A reasoning engine will work cleanly in a local prototype, selecting the right tool, building the right arguments, chaining steps correctly. The integration layer underneath it falls apart: schema drift between tools, auth routing that assumes one caller at a time, error messages shaped for a person to read rather than a program to parse, rate limits tuned for sequential human traffic. None of that infrastructure was built with an exploratory, parallel caller in mind, and an agent is exactly that.
Agent stress on a tool schema: fan-out, chaining, and context rot
Three distinct failure modes appear when agents push on a tool schema built for humans, and each one exposes a different weak point.
The first is fan-out collapse. PwC ran an evaluation on BFCL v4, and it found that native JSON tool calling simply drops tool calls once you cross a model-specific threshold for the number of parallel calls. For Claude Sonnet 5, that threshold is between 70 and 72 parallel calls. Past that point, the JSON paradigm hits a hard structural ceiling, something that is absent from a workload built around one human asking one question at a time. If database agents probe observability data, run several aggregations side by side, or explore different branches of a schema, they will often need to fan out well past that line.
The second is sequential chain degradation. The same BFCL v4 study found that JSON tool calling's early accuracy edge over programmatic calling flips once a chain of calls reaches 12 steps or more, and that opens an 18.8 percentage point gap in favor of programmatic calling. The cause is mechanical: every link in a JSON tool-calling chain costs an extra inference turn, and those turns pile up. Multi-step database workflows go through schema discovery, then query construction, then validation, then refinement, and they routinely run past 12 steps without anyone designing them to be especially long.
The third is context rot. Researchers call this context flooding, a condition meant to approximate a long agent session carrying many intermediate results, and under it JSON tool calling degrades on average, while programmatic tool calling holds steady. A comparison approach based on filesystem discovery degrades even more severely under the same load. Context rot isn't something wrong with the model. It comes from a schema design choice: when a schema forces the model to carry every intermediate result as a JSON object sitting in the context window, degradation follows from the weight of that accumulated state.
A survey of multi-tool LLM agents out of Harbin and Harvard put the shift in plain terms: early research asked whether a model could pick and execute one correct tool call. Now you have to orchestrate many tools over long trajectories, carry intermediate state, react to execution feedback, and adjust to an environment that keeps changing underneath the task. Fan-out, chaining, and context rot are three faces of that same shift, and they are the three things any database tool schema built for agents has to survive.
Programmatic tool calling as the structural fix for fan-out and chain length
One fix addresses fan-out and chain length at the structural level rather than through better prompting: stop asking the model to emit a fresh JSON payload for every call, and let it write code instead.
Programmatic tool calling, or PTC, exposes each tool as a typed Python stub compiled from the tool's function schema. Instead of emitting one JSON object per call, the model writes a short Python script that calls those stubs directly. An agent loop runs that script inside a shell subprocess, and the results come back without burning an extra inference turn for each step, just as PwC's BFCL v4 evaluation describes it. Chaining five steps or running ten calls in parallel becomes a matter of writing a few lines of code, not serializing five or ten separate JSON objects and holding all of them in the context window at once.
The results across model generations back this up. PTC matches or beats JSON tool calling in a strong majority of the 14 models PwC tested on BFCL v4, and the split falls along model generation, not model brand. All five Anthropic models tested and the three newest GPT generations matched or exceeded the JSON baseline; three older GPT models did not. If you narrow the test to parallel fan-out specifically, PTC matches or outperforms the baseline in 13 of those 14 models, and it holds full accuracy at N=100 calls, the exact zone where JSON tool calling had already collapsed. Earlier work on code-based agents, cited in PwC's paper, found the same pattern: agents that act through code complete tasks with fewer interaction turns than agents that act through JSON, and the advantage concentrates precisely in parallel and compositional tasks, which happens to be most of what a database agent spends its time doing.
For database tool schemas, the implication is concrete. A tool built for JSON function calling treats each call as an isolated object with no relationship to the others. Reconsidered as a typed Python stub, that same tool becomes something the model can compose: fetch five aggregations at once, then join the results, all inside one code block instead of five separate round trips through the context window. The stub's signature becomes the real contract between agent and database, so the types on those signatures need to be strict and specific enough that the model can use them correctly without guessing at field names.
Why parameter hallucination is an infrastructure problem
Every tool call an agent makes is a live request, and sometimes the model will get a parameter wrong. It is a predictable property of the loop that a better prompt cannot coach away. Treating it as something the schema has to absorb, rather than something the model has to be talked out of, is what separates a database integration that holds up under load from one that doesn't.
The job falls to the integration layer. It needs strict JSON schema validation on every call, and it needs to return errors the model can actually read and act on. Consider what happens when one API returns a field as properties.firstname and another returns the same kind of field as [FirstName](https://truto.one/blog/mapping-ai-agent-patterns-to-integration-platforms-2026-tutorial/). Faced with that inconsistency, the model either guesses at a mapping between the two or the call simply fails, and neither outcome is something a well-written prompt can prevent on its own.
The accuracy collapse that occurs when agents face very large schemas traces partly back to this problem. Databases that expose hundreds or thousands of columns to an agent see accuracy drop sharply, and more fields mean more surface area for a parameter to get confused with another one that looks similar. If a schema doesn't actively narrow that surface, it makes the confusion worse, not better. Fixing that means building field names that are explicit and consistent across every tool a database agent touches, so a query_timeseries tool and a query_aggregation tool never silently disagree on what a field is called. Error responses need to come back as structured, machine-readable objects, not human-readable sentences, so an agent's retry logic can read the failure and fix the call without burning a whole new reasoning pass on it. Idempotency has to live in the schema contract itself, not in application code off to the side, so a retried insert cannot produce a duplicate effect.
Designing the tool registry: what agents need to discover
Getting one tool's schema right solves only part of the problem. How an agent finds tools in the first place, and how many it can see at once, shapes the failure modes just as much as any individual schema does. Show an agent too many tools and hallucination risk climbs along with fan-out sprawl. Show it too few and it runs into dead ends it has no path around.
Production agents today tend to settle on one of five integration patterns: direct API calls, tool or function calling, an MCP gateway, a unified API, or agent-to-agent delegation. Each trades off control, maintenance work, and governance differently. A small, stable set of database tools is well served by direct function calling, which hands a team the most control but also makes that team responsible for auth routing, schema upkeep, and error handling, with nothing absorbed elsewhere. As the number of tools grows and governance requirements tighten, an MCP gateway starts to make more sense: it centralizes control and lets agents discover tools without rebuilding an integration for every new application. The general rule holds here: move from direct or tool calling toward MCP as the number of integrations and the governance burden both grow.
MCP's real contribution is that it splits tool and data definitions into standalone servers that each run independently. A single MCP server built for a given data source works with any MCP-compatible client, which turns what would otherwise be an N times M integration problem into an N plus M one. That reduction is part of why more than 2,000 open-source MCP servers now exist in the community registry. MCP's three primitives map onto what a database agent actually needs with little translation required: Resources for inspecting a schema without changing anything, Tools for running actual query operations, and Prompts for reusable templates that encode a query pattern known to be safe.
What a registry chooses to expose matters as much as how it's structured. A database agent registry should surface schema description tools, validated query templates, and aggregation or sampling tools scoped to read-only access. It should gate or hide raw DDL execution, any path that crosses tenant boundaries, and bulk export endpoints. Those are the operations where a hallucinated parameter does damage that a retry can't walk back. Rate limiting deserves the same care in how responsibility is split: reading rate-limit headers and deciding whether to retry, switch tasks, or back off belongs to the agent's orchestration framework, not the integration middleware. The registry's job is to surface that information clearly and hand it over, not to quietly absorb it somewhere the agent can't see.
Access control and execution isolation for agents that probe in parallel
An agent running several exploratory queries against the same database at once needs a kind of execution isolation that simply has no counterpart in the sequential, one-query-at-a-time model humans use. If one branch of exploration fails or produces a side effect, that failure can't be allowed to bleed into another branch running alongside it.
That starts with compute. Agents need isolated, read-only compute over shared data, because a shared connection pool assumes every caller behaves the way a careful human would. Isolation means a hallucinated parameter that triggers a runaway aggregation query burns its own resources and nothing more. It doesn't starve other branches of the same agent task, and it doesn't starve production traffic running at the same time. Sandboxed branches, where an agent explores against a consistent snapshot of real data without ever touching the live production table, let an iterative workflow (try a query, read the result, refine the query, try again) run its full iteration cycle without pushing that cost onto production.
Least privilege needs to be enforced at the level of each individual agent: an agent gets access only to the specific tools and data its current task requires, nothing broader. That's a schema design constraint as much as a security one, since a tool that exposes more data than a task needs also expands the surface where hallucination and runaway fan-out can do damage. Row-level security scoped to the MCP server's own connection role goes further than filtering in the application layer, because it makes it structurally impossible for the agent to construct a query that crosses a tenant boundary, where an application-layer filter is just a rule a hallucinated parameter might manage to slip past.
Coordinating concurrent workers rounds this out. A schema built for agents enforces idempotency through real constraints, logs operations separately from the business data those operations touch, and keeps enough history to recover cleanly after a timeout, rather than treating the model's own transcript as the record of what actually happened. None of this requires the database to understand what a prompt is or what the model was trying to do. It only needs to make unsafe state transitions and duplicate effects hard to produce by construction, which is the same design discipline that has always separated a system built to survive concurrency from one that merely tolerates it.


