{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"tools-and-function-calling","title":"A tool call is a request, not an action","url":"https://addaly.com/learn/agents-and-automation/tools-and-function-calling","position":10,"of":72,"minutes":7,"answer":"A tool call is a request your code fulfils, and the result string is the model's only feedback.","keyIdea":"A tool call is a request your code fulfils, and the result string is the model's only feedback."},"body":{"format":"markdown","text":"## The model does not do anything\n\nThis is the single most useful thing to internalise, and it is not obvious from the marketing. When you \"give a model a tool,\" the model gains no new powers. It gains the ability to *ask*.\n\nYou send the model a list of tool definitions. The model replies with a structured block that names one and supplies arguments. **Your code** then runs whatever that name maps to (an HTTP call, a SQL query, a shell command) and sends the result back as another message. The model never reaches the network. It only ever produces text and reads text.\n\nA tool definition looks like this. The exact JSON differs slightly between providers; the shape does not.\n\n```json\n{\n  \"name\": \"get_order\",\n  \"description\": \"Look up one order by its ID. Returns status, items, amount paid and refund eligibility. Use only when you have an order ID like ORD-48213. If you only have a customer email, call find_orders_by_email first.\",\n  \"input_schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"order_id\": {\n        \"type\": \"string\",\n        \"description\": \"Format ORD- followed by five digits. Not an email, not an invoice number.\"\n      }\n    },\n    \"required\": [\"order_id\"]\n  }\n}\n```\n\n## The description is the interface\n\nEngineers treat the schema as the important part and the description as a comment. It is the other way round. The schema constrains the shape of the arguments. The description is the only thing that tells the model *when* to reach for this and when not to.\n\nCompare:\n\n- Weak: `\"Gets an order.\"`\n- Strong: the version above, which says what comes back, what identifier it needs, and what to do instead when you have the wrong identifier.\n\nTool descriptions are prompt. They are read on every single call. Writing a good one is the cheapest quality improvement available to you, and it takes ten minutes.\n\n## Errors are feedback, and they must be readable\n\nWhen a tool fails, the model sees exactly one thing: whatever string you put in the result. That string is its entire understanding of what went wrong.\n\n```python\n# Useless. The model learns nothing and will try again.\nreturn \"Error: 500\"\nreturn \"[]\"\n\n# Useful. The model can act on this.\nreturn \"No order found with id 'arjun@example.com'. That looks like an \\\n        email address. Order IDs look like ORD-48213. Try find_orders_by_email.\"\nreturn \"No rows matched status='pending'. 412 orders exist with other statuses.\"\n```\n\nNever let an exception propagate as an opaque failure, and never return an empty result that is indistinguishable from a successful empty result. An agent stuck in a retry loop is almost always an agent being handed uninformative errors.\n\n## Fewer tools, wider tools\n\nEvery tool definition sits in the context window on every request. Twelve well-chosen tools beat forty narrow ones, for two reasons: cost, and selection accuracy. Past roughly twenty tools in one context, teams routinely watch the model start picking plausible-but-wrong tools, and the failure is quiet: it calls something reasonable and returns a confident, incorrect answer.\n\nSo design coarse. `search_orders(query, status=None, since=None)` is better than `list_orders`, `filter_orders_by_status`, `filter_orders_by_date` and `sort_orders` as four separate entries. You are designing an interface for a reader with limited attention, not an API for a compiler.\n\nOne last practical note: make tools that are safe to call twice. Models retry. If `send_invoice` is not idempotent, a retry becomes a duplicate invoice, and duplicate invoices become a phone call from a supplier in Karachi asking why he was billed twice."},"check":{"question":"A support agent has a `refund_order` tool. When it is called on an order that was already refunded, the underlying code raises, and the harness returns HTTP 500 with an empty body as the tool result. The agent then calls `refund_order` with identical arguments four more times. What most directly explains the repetition?","options":[{"text":"The result carried no information about what went wrong, so nothing in the model's context distinguished retrying from any other next move.","correct":true},{"text":"Temperature is too high, so the model is not selecting its next action deterministically.","correct":false,"tempting":"Attributes a reasoning failure to sampling randomness; at temperature 0 the model would repeat the same wrong call just as happily."},{"text":"The tool description is missing an instruction not to retry failed refunds.","correct":false,"tempting":"Assumes a prompt instruction can substitute for observable state, when the model still cannot see which failure it hit."},{"text":"No retry budget was configured, so the loop defaulted to retrying without limit.","correct":false,"tempting":"Confuses the missing stopping rule (a real but separate problem) with the reason the model chose an identical call again."}]},"prev":{"slug":"choosing-code-or-no-code","title":"n8n, Zapier, Make, or a file of Python","url":"https://addaly.com/learn/agents-and-automation/choosing-code-or-no-code"},"next":{"slug":"structured-output-and-schemas","title":"A schema constrains the shape, not the truth","url":"https://addaly.com/learn/agents-and-automation/structured-output-and-schemas"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'A tool call is a request, not an action', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/tools-and-function-calling (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"A tool call is a request, not an action","courseTitle":"Agents and Automation","module":{"index":2,"slug":"how-the-loop-runs","title":"The machinery: transcript, tools, loop"},"answer":null,"keyIdea":"A tool call is a request your code fulfils, and the result string is the model’s only feedback.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-model-does-not-do-anything","runs":[{"text":"The model does not do anything"}]},{"t":"p","runs":[{"text":"This is the single most useful thing to internalise, and it is not obvious from the marketing. When you “give a model a tool,” the model gains no new powers. It gains the ability to "},{"text":"ask","i":true},{"text":"."}]},{"t":"p","runs":[{"text":"You send the model a list of tool definitions. The model replies with a structured block that names one and supplies arguments. "},{"text":"Your code","b":true},{"text":" then runs whatever that name maps to (an HTTP call, a SQL query, a shell command) and sends the result back as another message. The model never reaches the network. It only ever produces text and reads text."}]},{"t":"p","runs":[{"text":"A tool definition looks like this. The exact JSON differs slightly between providers; the shape does not."}]},{"t":"code","lang":"json","label":"JSON","text":"{\n  \"name\": \"get_order\",\n  \"description\": \"Look up one order by its ID. Returns status, items, amount paid and refund eligibility. Use only when you have an order ID like ORD-48213. If you only have a customer email, call find_orders_by_email first.\",\n  \"input_schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"order_id\": {\n        \"type\": \"string\",\n        \"description\": \"Format ORD- followed by five digits. Not an email, not an invoice number.\"\n      }\n    },\n    \"required\": [\"order_id\"]\n  }\n}"},{"t":"h2","id":"the-description-is-the-interface","runs":[{"text":"The description is the interface"}]},{"t":"p","runs":[{"text":"Engineers treat the schema as the important part and the description as a comment. It is the other way round. The schema constrains the shape of the arguments. The description is the only thing that tells the model "},{"text":"when","i":true},{"text":" to reach for this and when not to."}]},{"t":"p","runs":[{"text":"Compare:"}]},{"t":"ul","items":[[{"text":"Weak: "},{"text":"\"Gets an order.\"","code":true}],[{"text":"Strong: the version above, which says what comes back, what identifier it needs, and what to do instead when you have the wrong identifier."}]]},{"t":"p","runs":[{"text":"Tool descriptions are prompt. They are read on every single call. Writing a good one is the cheapest quality improvement available to you, and it takes ten minutes."}]},{"t":"h2","id":"errors-are-feedback-and-they-must-be-readable","runs":[{"text":"Errors are feedback, and they must be readable"}]},{"t":"p","runs":[{"text":"When a tool fails, the model sees exactly one thing: whatever string you put in the result. That string is its entire understanding of what went wrong."}]},{"t":"code","lang":"python","label":"Python","text":"# Useless. The model learns nothing and will try again.\nreturn \"Error: 500\"\nreturn \"[]\"\n\n# Useful. The model can act on this.\nreturn \"No order found with id 'arjun@example.com'. That looks like an \\\n        email address. Order IDs look like ORD-48213. Try find_orders_by_email.\"\nreturn \"No rows matched status='pending'. 412 orders exist with other statuses.\""},{"t":"p","runs":[{"text":"Never let an exception propagate as an opaque failure, and never return an empty result that is indistinguishable from a successful empty result. An agent stuck in a retry loop is almost always an agent being handed uninformative errors."}]},{"t":"h2","id":"fewer-tools-wider-tools","runs":[{"text":"Fewer tools, wider tools"}]},{"t":"p","runs":[{"text":"Every tool definition sits in the context window on every request. Twelve well-chosen tools beat forty narrow ones, for two reasons: cost, and selection accuracy. Past roughly twenty tools in one context, teams routinely watch the model start picking plausible-but-wrong tools, and the failure is quiet: it calls something reasonable and returns a confident, incorrect answer."}]},{"t":"p","runs":[{"text":"So design coarse. "},{"text":"search_orders(query, status=None, since=None)","code":true},{"text":" is better than "},{"text":"list_orders","code":true},{"text":", "},{"text":"filter_orders_by_status","code":true},{"text":", "},{"text":"filter_orders_by_date","code":true},{"text":" and "},{"text":"sort_orders","code":true},{"text":" as four separate entries. You are designing an interface for a reader with limited attention, not an API for a compiler."}]},{"t":"p","runs":[{"text":"One last practical note: make tools that are safe to call twice. Models retry. If "},{"text":"send_invoice","code":true},{"text":" is not idempotent, a retry becomes a duplicate invoice, and duplicate invoices become a phone call from a supplier in Karachi asking why he was billed twice."}]}],"check":{"question":[{"text":"A support agent has a "},{"text":"refund_order","code":true},{"text":" tool. When it is called on an order that was already refunded, the underlying code raises, and the harness returns HTTP 500 with an empty body as the tool result. The agent then calls "},{"text":"refund_order","code":true},{"text":" with identical arguments four more times. What most directly explains the repetition?"}],"options":[{"text":[{"text":"The result carried no information about what went wrong, so nothing in the model’s context distinguished retrying from any other next move."}],"correct":true,"tempting":null},{"text":[{"text":"Temperature is too high, so the model is not selecting its next action deterministically."}],"correct":false,"tempting":[{"text":"Attributes a reasoning failure to sampling randomness; at temperature 0 the model would repeat the same wrong call just as happily."}]},{"text":[{"text":"The tool description is missing an instruction not to retry failed refunds."}],"correct":false,"tempting":[{"text":"Assumes a prompt instruction can substitute for observable state, when the model still cannot see which failure it hit."}]},{"text":[{"text":"No retry budget was configured, so the loop defaulted to retrying without limit."}],"correct":false,"tempting":[{"text":"Confuses the missing stopping rule (a real but separate problem) with the reason the model chose an identical call again."}]}],"order":[1,0,3,2],"why":null},"prev":{"slug":"choosing-code-or-no-code","title":"n8n, Zapier, Make, or a file of Python","minutes":8},"next":{"slug":"structured-output-and-schemas","title":"A schema constrains the shape, not the truth","minutes":9}}}