{"slug": "ai-agent-tool-calling-designing-tools-the-model-will-actually-use-correctly", "title": "AI agent tool calling: designing tools the model will actually use correctly", "summary": "A developer outlines a practical approach to designing tool interfaces for AI agents, arguing that most tool-calling failures stem from vague names, overlapping descriptions and loose input schemas rather than model limitations. The guidance recommends descriptive verb_noun tool names, explicit \"when not to use\" boundaries, enums and constraints in the schema, and consolidating related endpoints into fewer, clearer tools.", "body_md": "You give your agent a handful of tools, write a decent system prompt and try a few requests. It works. Then real users show up. The agent calls `search` when it should have called `get_order`. It passes a customer name where an ID was expected. It retries the same failing call five times in a row. It dumps a 40,000 token API response into its own context and forgets what the user asked.\n\nThe usual reaction is to blame the model or pile more instructions into the system prompt. Most of the time the real problem is the tools themselves. The model only knows what you tell it: a name, a description and a schema. If those are vague, overlapping or noisy, the model guesses, and it guesses with confidence.\n\nThis article is about designing tools as an interface for a model, the same way you would design an API for another engineer who can't ask you questions.\n\nWhatever framework you use, a tool reaches the model as three things:\n\n```\n{\n  \"name\": \"get_order\",\n  \"description\": \"Fetch a single order by its order ID...\",\n  \"input_schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"order_id\": { \"type\": \"string\", \"description\": \"...\" }\n    },\n    \"required\": [\"order_id\"]\n  }\n}\n```\n\nThat is the whole contract. The model never sees your implementation, your database or the comment you left in the code. Every decision it makes about when to call this tool and what to pass comes from those few lines. Treat them as the most important documentation in your project, because for the agent they are.\n\nA good tool description answers four questions: what it does, when to use it, when not to use it, and what comes back.\n\nCompare these two:\n\n```\nsearch: Searches for things.\nsearch_help_articles: Full text search over the public help center.\nUse it for general \"how do I\" questions about product features.\nDo not use it to look up a specific customer, order or invoice;\nuse get_order or get_customer for those.\nReturns up to 5 articles with title, url and a short snippet.\n```\n\nThe second one is longer, and that is fine. Descriptions are cheap compared to a wrong call. The \"do not use it for\" line is the part most people skip, and it is the one that prevents the most mistakes, because it tells the model where the boundary sits between two similar tools.\n\nNames matter too. `search` and `lookup` side by side are a coin flip. `search_help_articles` and `get_order_by_id` are not. Use a consistent pattern like `verb_noun` and avoid two names that could reasonably mean the same thing.\n\nEvery tool you add makes the choice harder. An agent with 40 tools has to scan 40 descriptions on every step, and the chance that two of them overlap goes up fast.\n\nA few rules that help:\n\n`get_open_orders`, `get_closed_orders` and `get_cancelled_orders`, have one `list_orders` with a `status` enum.`find_customer` that accepts an email or a name, than with five low level endpoints they have to chain correctly.`manage_user` tool with an `action` field that can be `read`, `update` or `delete` hides a destructive operation inside a harmless sounding name. Make `delete_user` its own tool, so you can describe it clearly and require approval for it.\nIf you find yourself writing a long paragraph in the system prompt about which tool to use when, that is a sign the tools themselves need clearer boundaries.\n\nThe input schema is your best defense against garbage arguments. Every constraint you put in the schema is one less thing the model has to remember from prose.\n\n```\n{\n  \"name\": \"list_orders\",\n  \"description\": \"List a customer's orders, newest first. Use get_order if you already have an order ID.\",\n  \"input_schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"customer_id\": {\n        \"type\": \"string\",\n        \"description\": \"Internal customer ID, format cus_ followed by 14 characters. Get it from find_customer, never guess it.\"\n      },\n      \"status\": {\n        \"type\": \"string\",\n        \"enum\": [\"open\", \"shipped\", \"delivered\", \"cancelled\"],\n        \"description\": \"Optional filter. Omit to return all statuses.\"\n      },\n      \"limit\": {\n        \"type\": \"integer\",\n        \"minimum\": 1,\n        \"maximum\": 20,\n        \"description\": \"How many orders to return. Default 5.\"\n      }\n    },\n    \"required\": [\"customer_id\"]\n  }\n}\n```\n\nWhat this gets right:\n\n`maximum` on `limit` keeps one call from returning a thousand rows.\nThen validate on your side anyway. The schema guides the model, it doesn't guarantee anything. Check arguments in the tool implementation and reject bad ones with a clear message.\n\nTool outputs go straight into the model's context. A tool that returns a full API response, with nested metadata, internal IDs and 200 fields per record, burns tokens and buries the useful part.\n\nShape the output for the next decision the model has to make:\n\n``` php\ndef get_order(order_id: str) -> dict:\n    order = orders_api.get(order_id)\n    return {\n        \"order_id\": order[\"id\"],\n        \"status\": order[\"status\"],\n        \"placed_at\": order[\"created_at\"][:10],\n        \"items\": [f'{i[\"qty\"]} x {i[\"name\"]}' for i in order[\"line_items\"]],\n        \"total\": f'{order[\"total\"] / 100:.2f} {order[\"currency\"]}',\n        \"tracking_url\": order.get(\"shipment\", {}).get(\"tracking_url\"),\n    }\n```\n\nHuman readable values like \"2 x Blue T shirt\" and \"49.90 EUR\" are easier for the model to reason about and to repeat back to a user than raw cents and nested arrays. For list tools, return a short summary per item plus a way to fetch details, and say in the output when results were truncated, for example `\"note\": \"showing 5 of 132, narrow with status\"`. Otherwise the model assumes it has seen everything.\n\nWhen a tool fails, the error message is the only thing the model has to decide what to do next. A stack trace or a bare `400 Bad Request` gives it nothing to work with, so it retries the same call or gives up.\n\nWrite errors the way you would write them for a junior engineer:\n\n``` php\ndef get_order(order_id: str) -> dict:\n    if not order_id.startswith(\"ord_\"):\n        return {\n            \"error\": \"invalid_order_id\",\n            \"message\": f\"'{order_id}' is not an order ID. Order IDs start with ord_. \"\n                       \"If you only have the customer's email, call find_customer, \"\n                       \"then list_orders.\",\n        }\n    order = orders_api.get(order_id)\n    if order is None:\n        return {\n            \"error\": \"not_found\",\n            \"message\": f\"No order {order_id}. Ask the user to confirm the ID. Do not retry.\",\n        }\n    ...\n```\n\nTwo things make this work. The message says what went wrong in plain words, and it says what to do instead. \"Do not retry\" alone cuts out a whole class of loops where the agent hammers a tool that will never succeed.\n\nReturn these as normal tool results the model can read, not as exceptions that crash your agent loop. Save real exceptions for things the model can't fix, like your database being down.\n\nEven with great tools, the model will sometimes call the wrong one or call the right one twice. Plan for it:\n\n`create_refund` so a repeated call doesn't refund twice.\nYou can't tell if a tool is well designed by reading it. You find out by watching the agent use it.\n\nBuild a small set of realistic requests, 20 to 50 is enough to start, each with the tool calls you expect. Run the agent on them and log every tool call with its arguments. Then look for patterns:\n\nChange one tool at a time and rerun the set. It is the same loop as any other test suite, and it is the fastest way to turn \"the agent is flaky\" into specific fixes.\n\nThe model can only be as precise as the tools you hand it. Clear names, descriptions that say when not to use a tool, schemas with enums and real constraints, compact outputs and errors that explain the next step will fix more agent bugs than any amount of prompt tweaking. Design your tools like an API for someone who can't ask you questions, because that is exactly who is using them.\n\nI'm Amit Shukla, a software engineer working across DevOps, cloud and applied AI. Behind the Stack is where I write up the things I learn the hard way, with working code and honest tradeoffs.\n\nIf this saved you some time, a reaction or a comment helps more people find it.", "url": "https://wpnews.pro/news/ai-agent-tool-calling-designing-tools-the-model-will-actually-use-correctly", "canonical_source": "https://dev.to/amitshuklabag/ai-agent-tool-calling-designing-tools-the-model-will-actually-use-correctly-h4f", "published_at": "2026-10-15 13:15:26+00:00", "updated_at": "2026-10-15 13:20:03.450919+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models"], "entities": [], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/ai-agent-tool-calling-designing-tools-the-model-will-actually-use-correctly", "markdown": "https://wpnews.pro/news/ai-agent-tool-calling-designing-tools-the-model-will-actually-use-correctly.md", "text": "https://wpnews.pro/news/ai-agent-tool-calling-designing-tools-the-model-will-actually-use-correctly.txt", "jsonld": "https://wpnews.pro/news/ai-agent-tool-calling-designing-tools-the-model-will-actually-use-correctly.jsonld"}}