Teaching an LLM to Use My Tools
Notes from wiring real function-calling into a product. What actually makes tool use reliable, and the mistakes I made first.
I spent the last few weeks moving an LLM feature from a clever demo to something I would actually put in front of users. The gap between those two states is almost entirely about tool use, so this is what I learned getting it to behave.
Start with the contract, not the prompt
My first instinct was to write a long system prompt describing what the model should do. That was the wrong lever. The thing that moved reliability the most was tightening the tool schema: clear names, required fields, and descriptions written for the model rather than for me.
{
name: "search_flights",
description: "Search bookable flights. Use when the user names an origin and destination.",
input_schema: {
type: "object",
properties: {
origin: { type: "string", description: "IATA code, e.g. DEL" },
destination: { type: "string", description: "IATA code, e.g. BOM" },
},
required: ["origin", "destination"],
},
}
Validate everything coming back
The model will occasionally hand you arguments that are almost right. I validate every tool call with Zod before it touches real code, and feed validation errors back to the model so it can correct itself instead of failing silently.
Treat the model like an untrusted client. It is usually right, and you still verify every time.
- Keep tools small and single-purpose, the way you would keep functions small.
- Return structured results, not prose, so the next turn has clean inputs.
- Log every tool call and its arguments. It is the only way to debug behavior.
Building something in this space?
I take on select builds when the work is worth doing right.