AI Agents

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.

By Laxaar Engineering Team Aug 30, 2026 10 min read
Function Calling for Agents: Designing Tools That Work

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

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_data vs fetch_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_database function 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_report is weak. generate_monthly_usage_report is better.
  • api_call is useless. lookup_customer_by_email is unambiguous.
  • process tells the model nothing. convert_pdf_to_markdown tells 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:

  1. What does this tool do?
  2. When should the agent use it?
  3. 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:

Weak: "Queries order data from the database."
Strong: "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

PatternWeak versionStrong versionWhy it matters
Tool nameget_infoget_product_inventory_by_skuWeak name forces model to rely entirely on description
Description scope"Handles data queries"Includes explicit when-not-to-use guidanceWithout exclusions, tool is used on out-of-scope inputs
Parameter typesfilter: object (freeform)status: enum["active","archived"]Freeform objects produce invalid values under low-context
Required fieldsAll optionalCore identifiers marked requiredMissing required args produce silent failures
Parameter descriptionsNoneIncludes 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.

AI AgentsFunction CallingTool Design
Grow your business with us

Take your business to the next level.

Tell us what you're building. We'll come back inside one business day with a fixed scope, timeline, and team — or an honest “this isn't a fit”.

ENGINEERING PHILOSOPHY

Code is useless if it's not comprehensible to those who maintain it. We write code the next person can actually understand.