How to Structure Tools/Functions So the Model Uses Them Well

By Carlos Montiel | Enterprise AI Specialist
Leer en español →
Published: 2026-07-28 | By: Carlos Montiel | Reading time: ~5 minutes

The most common cause of an agent "not using tools well" isn't the model -- it's that the tool is poorly described, poorly named, or the tool set is too large for the model to pick precisely.

The name and description are the real interface

The model doesn't see your code -- it sees the tool's name, description, and schema. Everything it knows about when and how to use it comes from that text. A generic name like `process` or a one-line description that only says what the function does, without saying *when* to use it, leaves the model guessing.

# Bad: describes WHAT it does, not WHEN to use it { "name": "search", "description": "Searches for information.", ... } # Good: specific name + description with an activation criterion { "name": "search_product_price", "description": ( "Looks up the current price of a specific product in the " "catalog. Use it when the user asks about prices, costs, " "or availability of a product by name or SKU. " "Don't use it for general questions about product " "categories -- use list_category for that." ), ... }

On recent models, which tend to be more conservative about invoking tools, being explicit about the activation criterion in the description (not just in the general system prompt) gives a measurable improvement in correct invocation rate.

The schema should reflect real constraints, not just types

A schema that only says `"type": "string"` for a field that actually only accepts 5 possible values leaves too much room for the model to invent variants. Use `enum` whenever the set of valid values is finite and known.

{ "name": "update_ticket_status", "description": "Updates the status of an existing support ticket.", "input_schema": { "type": "object", "properties": { "ticket_id": { "type": "string", "description": "Ticket ID, format TICK-XXXXX", }, "new_status": { "type": "string", "enum": ["open", "in_progress", "waiting_on_customer", "resolved", "closed"], }, "reason": { "type": "string", "description": "Brief explanation of the status change", }, }, "required": ["ticket_id", "new_status", "reason"], "additionalProperties": False, }, }

Marking `required` precisely matters as much as `enum`: if a field is optional but you mark it as required, the model will invent a value when it doesn't have one, instead of correctly omitting it.

Use strict mode when exact validation matters

For tools where a malformed parameter has real consequences (a transaction, a configuration change), strict mode guarantees the model's `input` validates exactly against your schema -- no extra fields, no wrong types.

{ "name": "execute_payment", "description": "Executes a payment to a registered vendor.", "strict": True, "input_schema": { "type": "object", "properties": { "vendor_id": {"type": "string"}, "amount_cents": {"type": "integer"}, "currency": {"type": "string", "enum": ["GTQ", "USD"]}, }, "required": ["vendor_id", "amount_cents", "currency"], "additionalProperties": False, }, }

Strict mode (`strict: true` in the tool definition, not in `tool_choice`) requires `additionalProperties: false` and an explicit `required` list, but in exchange guarantees you'll never receive a malformed input that your execution code has to defensively re-validate.

Don't overload the tool set

A set of 30+ tools available on every call degrades selection accuracy -- the model has to discriminate among too many similar options, and the odds of picking the wrong tool (or none, when it should use one) go up. If your agent needs a large tool catalog, the solution isn't always loading all of them -- it's using a dynamic discovery mechanism that only loads the definitions relevant to the task's current context, deferring the rest until they're actually needed.

As a practical rule: if an agent has more than 15-20 active tools at once and you're seeing selection errors, the first intervention isn't rewriting the descriptions -- it's reducing how many are available at a time.

Handle tool errors as part of the contract, not as an exception

When a tool's execution fails (a downed API, a timeout, failed validation in your backend), the result must go back to the model explicitly marked as an error, not as if it were a successful result with odd content. This lets the model reasonably decide whether to retry, inform the user, or try an alternate path -- instead of interpreting an error message as valid data.

tool_result = { "type": "tool_result", "tool_use_id": tool_use_id, "content": "Error: vendor with ID 'VEN-9981' does not exist in the system.", "is_error": True, }

A pattern that frequently breaks agents: returning the error as plain text with no `is_error: true`. The model then treats the error message as if it were the operation's legitimate result, and may report to the user that the action completed when it actually failed.

Multiple parallel calls: bundle all results into a single turn

When the model requests several tools in a single response (default behavior on most current models), execute all of them and return all the `tool_result` blocks together in one message -- don't split them across multiple user messages. Splitting them implicitly trains the model to stop requesting parallel calls, reintroducing the sequential latency that parallelism was supposed to avoid.

# Correct: all tool_results in a single user message tool_results = [ {"type": "tool_result", "tool_use_id": tu.id, "content": execute(tu.name, tu.input)} for tu in tool_use_blocks ] messages.append({"role": "user", "content": tool_results})

Give correct-usage examples directly in the definition when the schema is complex

For tools with nested parameters or non-obvious formats (dates in a specific format, complex filter structures), an example of a correct invocation inside the description reduces format errors more than any amount of detail in the raw JSON schema.

{ "name": "filter_transactions", "description": ( "Filters transactions by date range and category. " "Example of correct use: for 'March transactions in " "the travel category,' the input would be " '{"start_date": "2026-03-01", "end_date": "2026-03-31", ' '"category": "travel"}. Dates always in ISO 8601 ' "format (YYYY-MM-DD)." ), ... }

Designing the tool set well isn't a secondary detail of an agent system -- it's, along with context engineering, the part of the architecture that most determines whether the agent is reliable in production or ends up generating erratic calls that need patching case by case.

Carlos Montiel
Enterprise AI Solutions Architect
Specialist in LLMs, Agents, and Orchestration
guatemalia.com/en/#contact · info@guatemalia.com

Need to implement AI at your company?

Carlos Montiel is an enterprise AI solutions architect. He implements LLMs, Agents, RAG, and orchestrators for companies across Guatemala and Latin America. Reach out for a consultation.

Contact Carlos Montiel

info@guatemalia.com