Tool Descriptions Are Contracts, Not Wishes: A 3-Step Rewrite That Stopped Surprise Refunds

Most teams write a tool description like a friendly job ad, and a small model treats it like one. That’s the lesson from a recent r/PromptEngineering post. It’s a good one for anyone building agents on local models, because the fix is faster than switching to a bigger model.

The key idea

The author tested a customer support bot on Ollama (llama3.2) with LangChain/LangGraph. It had two tools: search_order, a read-only lookup, and resolve_order, which issues a full refund.

The refund tool’s description read: “Handle customer order problems such as late delivery, delays, missing order, or complaints. Use this to fix customer issues.”

Then came the test message: “Where is my order? It was supposed to arrive yesterday.”

The model called resolve_order and refunded the whole order. Across a 3-query test batch, it handed out ₹3,640 nobody asked for. The model wasn’t broken. The description told it that refunds fix delay complaints, so it followed that. Look at the overlap: the user message mentions a delay, and the tool description mentions delays. A small model matches those words and acts. It has no way to know that “fix” was meant to be a last resort.

Old way vs new way

The old way is to describe the goal of the tool. “Fix customer issues” is a goal, and a vague one. A bigger model might read between the lines and guess that a refund is too heavy for a status question. A small 8B-class model takes the words at face value. Most of us write descriptions the way we’d brief a smart coworker, who fills gaps with common sense. A small model fills gaps with whatever matches the input best.

The new way is to write the description as an API contract. A contract says what the tool does, what it costs, when to call it, and what to call instead. Think of how a payment API doc reads. It never says “helps with money stuff.” It says what happens, what’s irreversible, and what the preconditions are.

Here’s the rewrite from the post:

Issues an irreversible full refund. Call ONLY when the user explicitly requests a refund AND states the item is damaged or lost. For delays or status questions, use search_order instead.

Same model, same parameters, same system prompt, same test inputs. This time the bot sent query 1 to search_order and kept refunds for explicit damage complaints. The agent code didn’t change at all. The model only sees the tool name and the description string, so that string is the whole interface. Nothing else you wrote in your codebase reaches it, not your comments, not your README, not the intent in your head.

How to rewrite your own tool descriptions

  1. 🔧 Lead with the effect. Say what the tool actually does, in plain words. “Issues an irreversible full refund” beats “handles problems.” If the action is destructive or costs money, say so in the first sentence. Words like irreversible, permanent, and sends an email to the customer carry real weight for a small model.
  2. 🚦 State the trigger conditions. Use words like ONLY, explicitly, and AND to set a hard gate. In the example, two conditions have to be true before the refund tool fires. Write the conditions as things you can check in the user’s message, not as feelings like “when the customer seems upset.”
  3. 🧭 Name the alternative. Tell the model where to go instead. “For delays or status questions, use search_order” gives it a safe exit so it doesn’t have to guess. Use the exact tool name, spelled the way it appears in your tool list.

Then test it with the messages that sit next to the dangerous case. Use status questions, vague complaints, and angry-but-not-asking-for-money messages. A good starter set is “Where is my order?”, “This is the third time my delivery is late,” and “I’m so annoyed with this service.” If a read-only question can trigger a write action, your description isn’t done. Run each message a few times too, since small models can flip between runs, and a single clean pass doesn’t prove much.

Why this matters

A wrong tool call on a read-only tool costs you nothing. A wrong call on a refund tool costs real money. The more irreversible the action, the more precise the description needs to be. Sorting your tools by blast radius is a quick way to decide where to spend your editing time. Lookups can stay short. Anything that moves money, deletes data, or messages a customer deserves the full contract treatment.

The post also asks the question I’d like to see answered: do you rely only on descriptions, enforce strict JSON schemas, or add a confirmation step before risky tools? The post doesn’t test those, but a description is only a request to the model. A confirmation step for refunds is cheap insurance on top of it. Even a simple “are you sure?” gate in your own code, checked outside the model, means a bad guess becomes a pending action instead of a payout.

Try it yourself

The author put together a video walkthrough (about 7 minutes, running locally on Ollama) at https://youtu.be/pT2jEki52_w and the code at https://github.com/AI-Literacy-Lab/AgentDemo.

Open your agent’s tool list today and read each description as if you were the model with nothing else to go on. Find the one that could cost you money, rewrite it as a contract, and run your three nastiest test messages through it. Then tell me what your bot did, because I’d like to hear it!

Captain YAR

Why a single word in your LLM tool description can accidentally issue refunds (and how to fix it)
by u/Pristine_Post6875 in PromptEngineering

Scroll to Top