Function Calling for Agents: Designing Tools That Work
Learn how to design tool schemas for tool-using agents that actually work — naming, descriptions, and parameter shapes that reduce wrong-tool calls in production.

Most teams debugging a broken agent assume the model is the problem. The tool call returns something unexpected, the agent loops, and someone opens a ticket asking for a better base model. Nine times out of ten, the fix is rewriting the tool's description field.
Tool-using agents decide which function to invoke by reading your schema — the name, the description, and the shape of the parameters. If those read like internal API documentation written for engineers who already know the system, the model will misfire. Not because it's incapable, but because you handed it ambiguous instructions and expected it to guess right.
At Laxaar, we've built and debugged enough agentic systems to see this pattern repeat. The agents that work in production aren't running smarter models; they're using better-described tools.
What you'll learn
- Why tool selection fails and what's actually causing it
- How function calling works under the hood
- Writing tool names the agent can reason about
- Crafting descriptions that steer selection correctly
- Designing parameter schemas that prevent malformed calls
- Comparison: weak vs strong tool schema patterns
- Testing and iterating on tool schemas
- Frequently Asked Questions
Why tool selection fails and what's actually causing it
The model doesn't see your code. It sees the JSON schema you registered and the conversation so far. When it picks the wrong tool, it's usually because two tools look similar from that limited view, or because the description implies a broader scope than you intended.
Common failure modes we see:
- Ambiguous names:
get_datavsfetch_records. The model can't distinguish them without reading descriptions carefully, and it doesn't always. - Descriptions written for developers: phrases like "calls the v2 reporting endpoint" mean nothing to a model reasoning about a user's intent.
- Over-broad tools: a single
query_databasefunction that accepts any SQL string forces the model to construct safe queries itself, which it will get wrong under pressure. - Missing edge-case guidance: no mention of when not to use a tool means the model will try it on cases it shouldn't handle.
The fix isn't switching models. It's treating tool schema design as a first-class engineering discipline.
How function calling works under the hood
Function calling (also called tool use) is the mechanism by which a language model signals that it wants to invoke an external function rather than generate a plain text reply. The model outputs a structured object containing the tool name and arguments; your application code executes the actual function and returns the result.
Here's the basic shape of a tool definition in OpenAI's format, which most frameworks follow:
{
"type": "function",
"function": {
"name": "search_knowledge_base",
"description": "Search the company knowledge base for articles matching a user query. Use this when the user asks a question that might be answered by internal documentation, FAQs, or policy guides. Do not use for real-time data like order status.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The user's question or search phrase, written as a natural language query"
},
"max_results": {
"type": "integer",
"description": "Number of results to return. Defaults to 5. Use 1-3 for concise answers, up to 10 for exploratory queries.",
"default": 5
}
},
"required": ["query"]
}
}
}
The model reads the name and description at selection time, then fills the parameters object. Both layers need to be clear.
Writing tool names the agent can reason about
A tool name is a signal, not just an identifier. The model uses it alongside the description to build an internal sense of what the tool does and when it applies.
Good names are verb-noun pairs that describe the action and the subject. Bad names describe the implementation.
get_reportis weak.generate_monthly_usage_reportis better.api_callis useless.lookup_customer_by_emailis unambiguous.processtells the model nothing.convert_pdf_to_markdowntells it everything.
Short is good. Precise is better. When you have to choose between brevity and clarity, pick clarity. The model handles long names fine. It struggles with vague ones.
One practical heuristic: read the name aloud and finish the sentence "Use this to ___." If you can't complete that sentence naturally from the name alone, rename the tool.
Crafting descriptions that steer selection correctly
The description field does more work than most engineers give it credit for. It's the model's primary signal for when to use the tool, and equally for when not to.
A strong description answers three questions:
- What does this tool do?
- When should the agent use it?
- What cases should it not use it for?
That third point is the one teams consistently skip. Without negative examples, the model will attempt to use a broadly described tool for cases it can't handle, then fail or return garbage.
Here's a weak description versus a strong one for the same tool:
"Queries order data from the database.""Retrieve the status, items, and shipping details for a specific order by order ID. Use this when the user asks about a particular order they've placed. Do not use this to search all orders or browse order history; use search_orders for that."The strong version is longer. That's fine. Description length has no meaningful impact on latency, and the cost of an extra hundred tokens in a system prompt is negligible compared to a failed tool call.
You can also use the description to guide argument construction. If a parameter value needs to be transformed before passing (for instance, converting a date from "last Tuesday" into ISO 8601), say so in the description. The model will do it.
Designing parameter schemas that prevent malformed calls
The parameters schema shapes what arguments the model generates. A poorly designed schema produces malformed calls even when the tool selection was correct.
Rules we follow at Laxaar:
Use enum for categorical values. If a parameter can only be "asc" or "desc", define it as an enum. Don't describe the allowed values in prose and hope the model picks the right string.
"sort_order": {
"type": "string",
"enum": ["asc", "desc"],
"description": "Sort direction for results"
}
Write per-parameter descriptions. The top-level description tells the model when to call the tool. Parameter descriptions tell it how to construct the call. Each parameter should explain what it expects, with an example if the format is non-obvious.
Mark required vs optional explicitly. Put non-negotiable parameters in required. Give optional ones sensible defaults and describe what happens when they're omitted.
Avoid compound parameters. A parameter called filter that accepts a freeform object is asking the model to invent a query language. Split it into explicit fields. More parameters with clear types beats fewer parameters with freeform values.
Keep parameters flat where possible. Deeply nested objects increase the surface area for malformed calls. If you need complex input, flatten it or accept multiple simpler calls instead of one compound one.
Comparison: weak vs strong tool schema patterns
| Pattern | Weak version | Strong version | Why it matters |
|---|---|---|---|
| Tool name | get_info | get_product_inventory_by_sku | Weak name forces model to rely entirely on description |
| Description scope | "Handles data queries" | Includes explicit when-not-to-use guidance | Without exclusions, tool is used on out-of-scope inputs |
| Parameter types | filter: object (freeform) | status: enum["active","archived"] | Freeform objects produce invalid values under low-context |
| Required fields | All optional | Core identifiers marked required | Missing required args produce silent failures |
| Parameter descriptions | None | Includes format example (e.g. "ISO 8601 date") | Model constructs valid values from examples |
The weak patterns all feel harmless in development, where you're feeding the agent clean, controlled inputs. They break in production when user phrasing diverges from what you tested.
Testing and iterating on tool schemas
Schema design isn't done when you write it. It's done when it stops failing in production. That requires a test loop most teams skip.
Log every tool call with its arguments. Not just whether the call succeeded, but what arguments the model generated and whether they matched your intent. A call that returns results isn't necessarily correct; the model may have gotten lucky with a malformed query.
Build a golden set of tool-selection examples. Take 20-30 real user messages and manually label which tool each should invoke and what arguments it should produce. Run your schema against them. Any mismatch is a schema problem.
Test adversarial inputs. Ask the agent questions where the wrong tool selection is tempting: cases that sound like they belong to tool A but actually need tool B. If the model consistently picks wrong, the two tools' descriptions overlap too much. Sharpen them.
Iterate descriptions before adding tools. When reliability is low, resist the urge to add a new, more specific tool. Often the right fix is tightening the existing description. More tools mean more selection decisions, which means more surface area for mistakes.
Our AI agent development practice runs schema reviews as part of every agentic system build. The tool layer is where most reliability work happens, not the prompt layer.
For teams evaluating what kinds of agentic systems are worth building, our AI agents service overview covers the production patterns we use.
If you're choosing between frameworks that handle tool registration differently (OpenAI's function calling format, Anthropic's tool use API, or abstractions like LangGraph or CrewAI), the design principles here apply everywhere. The schema is the contract regardless of which layer wraps it.
Want to see how this plays out in a real system? Our portfolio includes agent builds where tool design was the central engineering challenge.
Frequently Asked Questions
How many tools should an agent have at once?
There's no hard limit, but reliability degrades as tool count grows. The model has to make correct selection decisions across a larger set, and mistakes compound. We generally keep any single agent under 15-20 tools. If you need more, route to specialized sub-agents rather than loading one agent with a large toolset. More tools also means more tokens consumed by the schema in every call, which affects cost and latency.
Does the quality of tool descriptions actually matter if the model is strong enough?
Yes, even with the best current models. Tool descriptions are decision inputs, not hints. A frontier model choosing between two tools with identical ambiguous descriptions won't magically pick the right one; it'll guess based on whichever aligns better with the conversation so far. Good descriptions collapse that ambiguity. We've measured 30-40% reductions in wrong-tool calls just from description rewrites, with no model change.
Should tool descriptions be written in natural language or technical language?
Natural language, written from the perspective of a task the user is trying to accomplish, not the API the tool wraps. If the tool queries an order_items table in PostgreSQL, the description shouldn't say that. It should say "retrieves the list of items in a customer's order." The model reasons about user intent, not database schemas.
What's the difference between function calling and an agent that just uses code?
Function calling is the structured mechanism for a model to request that your application run a specific function and return the result. The model doesn't execute code itself; it emits a structured call object that your application handles. This keeps tool execution inside your infrastructure, under your access controls, with your error handling. It's architecturally different from code-execution sandboxes, where the model generates and runs arbitrary code in an isolated runtime.
How do we handle tool errors so the agent recovers gracefully?
Return structured error responses in the tool result, not exceptions. Include an error field with a human-readable message explaining what went wrong and, where possible, what the agent should try instead. Models handle descriptive error messages well: they'll adjust their next action based on what you tell them failed. Unhandled exceptions or empty responses usually cause the agent to retry the same call or get stuck.
Can we add tools dynamically based on user context?
Yes, and it's often a good pattern. Rather than registering all possible tools upfront, you can inject only the tools relevant to the current session or user role. This keeps the tool namespace small, which improves selection accuracy and reduces schema token overhead. The trade-off is that dynamic registration adds infrastructure complexity and requires careful logic to decide which tools are relevant when.
If your agent is misfiring in production, audit the schemas before you upgrade models, add retry logic, or restructure prompts. Nine times out of ten the fix is there. The Laxaar team builds production agentic systems with tool-layer discipline baked in from the start. If you're launching a new agent project or debugging an existing one, get in touch with us and we'll start with the tools.
Working on something like this?
Get a fixed scope, timeline, and price within one business day — no obligation.


