Trail content
9 minEnglish narration, Portuguese subtitlesPodcast (RSS)
Chapters
  1. 0:00Opening
  2. 1:06The idea
  3. 1:36Before and after
  4. 2:53Why it works
  5. 3:45A challenge
  6. 4:33Safe mechanics
  7. 6:11Risks
  8. 7:22Practice
  9. 8:11Wrap-up
Transcript

Opening

A software company's support bot started with one tool to get a user. Then came another to fetch a user by email. One to look up a customer by tax ID. One to search users by free text. Get account. Get account version two. And an export to spreadsheet that someone added, for the data team.

Seven names. Four ways to find the same person.

Now a simple request. What's Maria's plan? Here's her tax ID.

The model searches users for Maria, and gets forty rows back.

Then it gets the user with the first id in the list.

Then it asks for the account, with a user id where an account id belongs. Three calls. No plan. And the export tool? Zero calls in ninety days of traces.

In this lesson, you'll merge that list down to what the model can actually choose from, safely, and without changing a line of your backend.

The idea

The solution is simple to state. Merge tools that do the same thing into one, with clear parameters. And remove the ones your traces show nobody uses.

There's a telling detail from teams that build agents seriously. Some spend more time designing their tools than designing the prompt. A long list of overlapping names is the opposite. The model reads seven vague descriptions, and guesses.

Before and after

Before, seven tools, with four ways to find the same person, and two versions of the same account lookup.

After, the same request arrives, and the model sees a short list, with descriptions that say when to use each tool.

A single tool finds the customer, with the key as a parameter. By id, by email, or by tax ID.

And the next tool makes it clear that it depends on the previous result. It asks for a customer id, by name.

Here's the new list. Find customer. By id, email or tax ID. Use it before any question about plan, billing or account. Returns the id, name, plan and status.

And get account. Billing data. Needs the customer id returned by find customer.

So Maria's question becomes: find customer, by tax ID. It returns her plan. If the plan already answers the question, the model stops. If it needs invoices, it calls get account, with her customer id. No list of forty Marias. No account lookup with the wrong kind of id.

Why it works

The model picks tools by reading names and descriptions. When several options describe the same thing in different words, the choice becomes a guess. One tool per intent removes the guess. The decision stops being which of these four, and becomes which value goes in the by field. And the schema constrains that.

Removing unused tools has an extra effect. The prompt shrinks on every call. That export tool in the schema is noise you pay for in every conversation, including the ones that never export.

And there's a limit. If you merge synonyms, delete the ghosts, and still have forty tools with good descriptions, the next move isn't another merge. It's exposing a subset per intent, with a router, or by revealing tools progressively.

A challenge

Here's one for you. A colleague proposes a single customer tool, with a parameter called action. Its values are get, search, export, or delete. They call that consolidated. Is it?

No. Reading, searching, exporting and deleting are different intents. Now the model chooses among action values that are as vague as the seven old names. And a poorly chosen delete has a side effect. That's not consolidating. It's hiding the sprawl inside one function.

Consolidating merges what answers the same question. Who is this customer? Delete isn't a way to find a customer.

Safe mechanics

Step zero, measure. Run the agent's eval, and record the completion rate, the steps per task, and the rate of wrong calls. A tool that returned an argument error, or was followed by a similar one.

Gather real usage. From sixty to ninety days of traces, count how often each tool was called, and its error rate. Zero calls makes it a candidate for removal, not for a merge.

Group by intent. Tools that answer the same question go in the same group. And reads and writes never mix in the same group.

Design one tool per group. A choice parameter with a closed list of values, a description that says when to use it and what comes back, and a verb plus noun name. And build it on top of the old code. The new tool calls the functions that already existed. Nothing changes in the backend.

Remove the unused tools in a separate change, so you can revert one without the other.

Update the eval cases that referred to the old tools by name. Say after the merge, the completion rate drops, because the old eval still calls get account with a user id. Don't accept both parameter names, to keep the eval green. That brings the ambiguity right back. The eval is what's stale. Fix the dataset first, then compare.

And measure again. Completion the same or higher, fewer steps, fewer wrong calls, and fewer input tokens per call.

Risks

A few risks. A tool too big. Merging different things into one tool with ten optional parameters recreates the confusion inside it. If the parameters depend on each other, like if the action is refund, the amount is required, that's two tools.

A rare flow broken. The export tool had zero calls in ninety days. Then the data team says they use it at quarter close, which fell outside the window. Zero calls in the window doesn't prove it's unused. Confirm with the flow's owner, keep the removal reversible, and if it goes, take it out in its own change.

Changed semantics. If version two returned fields version one didn't, the merged tool must return them. Otherwise you simplified by deleting data.

And mixed permissions. Never merge a read tool with a write tool, even when they share a key. Getting an invoice and issuing a refund both take an order id. Merging them leaves the model one guess away from sending money, when it only wanted to see the invoice.

Practice

Let's practice on an HR agent. Four tools. Get vacation balance. Vacation days left. Check paid time off. And request vacation. One of them shouldn't be merged with the others. Which one?

The first three answer the same question, and become one read tool. Get the vacation balance, by employee id or by email. Use it before suggesting dates.

And request vacation has a side effect. It stays separate, ideally behind a confirmation step from the employee. Its description says it only creates a request for the manager's approval, only after the employee confirms the dates, and approves nothing on its own.

Wrap-up

So here's the idea to take with you. Count what's really used, merge what answers the same question, and keep side effects in tools of their own. Build on the code you already have, remove in separate steps, and let the eval prove the model now picks right the first time.

In the next lesson, we'll handle what's left when the list is still long, because the agent really covers many topics: the tool router. See you there.

RefactoringSimplifying tools

Consolidate Tools

Consolidar Ferramentas

Consolidate Tools is an AI refactoring for LLM systems, in the Simplifying tools group. Merge similar tools and remove the ones nobody uses.

Element
20/35
Level
Intermediate
Read
4 min
Updated
On this page 8
  1. 01Problem
  2. 02Solution
  3. 03Before and after
  4. 04Why it works
  5. 05Safe mechanics
  6. 06Risks
  7. 07Challenge
  8. 08Check your understanding
  9. ··Quick answers

Problem

The agent has too many tools, similar to each other or unused, and often picks the wrong one. These are the Tool Sprawl and Unused Tools smells.

The SaaS support bot started with get_user. Then came fetch_user by email, lookup_customer by CPF (Brazil’s personal tax id), search_users by free text, get_account, get_account_v2, and an export_users_csv someone added “for the data team”. Seven names, four ways to find the same person.

The trace for a simple request (“what is Maria’s plan, CPF 123.456.789-00?”) shows search_users("Maria") returning 40 rows, then get_user with the first id, then get_account with a user id instead of an account id. Three calls, no plan. export_users_csv does not appear in 90 days of traces.

In the post “Building effective agents” (Dec 2024), Anthropic writes that on their SWE-bench agent they spent more time designing tools than the prompt. A long list of overlapping names is the opposite: the model reads seven vague descriptions and guesses.

Solution

Merge tools that do the same thing into one, with clear parameters, and remove the ones traces show nobody uses.

Before and after

 TOOLS = [
-  tool("get_user",         "Gets a user.",                {"id": str}),
-  tool("fetch_user",       "Gets a user.",                {"email": str}),
-  tool("lookup_customer",  "Looks up a customer.",        {"document": str}),
-  tool("search_users",     "Searches users.",             {"query": str}),
-  tool("get_account",      "Gets an account.",            {"id": str}),
-  tool("get_account_v2",   "Gets an account (new).",      {"id": str}),
-  tool("export_users_csv", "Exports users.",              {}),          # 0 calls in 90 days
+  tool("find_customer",
+       "Finds a customer by id, email, or CPF. Use it before any question "
+       "about plan, billing, or account. Returns id, name, plan, and status.",
+       {"by": "id" | "email" | "cpf", "value": str}),
+  tool("get_account",
+       "Billing data for an account: plan, due date, open invoices. "
+       "Needs the customer id returned by find_customer.",
+       {"customer_id": str}),
 ]

Before: seven tools, four ways to find the same person.

flowchart LR
  P(["1. Request"]):::io --> M(["2. Model"]):::llm
  M --> A["get_user"]:::tool
  M --> B["fetch_user"]:::tool
  M --> C["lookup_customer"]:::tool
  M --> D["search_users"]:::tool
  M --> E["get_account / v2"]:::tool

After: two tools, each with one role.

flowchart LR
  P(["1. Request"]):::io --> M(["2. Model"]):::llm
  M --> A["3. find_customer by=cpf"]:::tool
  A --> M
  M --> E["4. get_account"]:::tool
  1. The same request arrives.
  2. The model sees a short list, with descriptions that say when to use each tool.
  3. A single tool finds the customer, with the key as a parameter.
  4. The next tool makes it clear that it depends on the previous result.

The same request, after

“What is Maria’s plan, CPF 123.456.789-00?” becomes:

  1. find_customer(by="cpf", value="12345678900") returns {id, name, plan, status}.
  2. If plan already answers the question, the model stops. If it needs invoices, it calls get_account(customer_id=...).

There is no list of 40 Marias. There is no get_account with the wrong kind of id: the parameter is named customer_id.

Why it works

The model picks tools by reading name and description. When several options describe the same thing in different words, the choice becomes a guess. One tool per intent removes the guess: the decision stops being “which of these four” and becomes “which value goes in the by field”, which the schema constrains.

Removing unused tools has an extra effect: the remaining definitions sit closer together in the context, and the prompt shrinks on every call. export_users_csv in the schema is noise you pay for in every conversation, including the ones that never export.

If the list is still huge after merging, the next step is not another merge: it is a Tool Router or Progressive Disclosure, so the model only sees the subset for the current intent.

Check +20 XP on first tryThe trace shows search_users('Maria'), then get_user on the first id in the list, then get_account with that user id. The plan never comes back. What is this refactoring's first cut?

Safe mechanics

  1. Measure. Run the agent’s eval and record completion rate, steps per task, and the rate of wrong calls (a tool that returned an argument error or was followed by a similar one).
  2. Gather real usage. From the last 60 to 90 days of traces, count how often each tool was called and its error rate. Zero calls is a candidate for removal, not for a merge.
  3. Group by intent. Tools that answer the same question (“who is this customer?”) go in the same group. Reads and writes do not mix in the same group.
  4. Design one tool per group. A choice parameter with an enum, a description that says when to use it and what comes back, and a verb-plus-noun name.
  5. Build on top of the old code. The new tool calls the functions that already existed. Nothing changes in the backend. find_customer(by="cpf") internally calls what used to be lookup_customer.
  6. Remove unused tools in a separate change, so you can revert one without the other.
  7. Update eval cases that referred to old tools by name.
  8. Measure again. Completion rate equal or higher, fewer steps, fewer wrong calls, and fewer input tokens per call.

Risks

  • Tool too big. Merging different things into one tool with ten optional parameters recreates the confusion inside it. If the parameters depend on each other (“if action=refund, amount is required”), it is two tools.
  • Rare flow broken. A rarely used tool may be essential in a rare case (quarterly export, audit). Check with the flow’s owner before removing, and keep the removal reversible.
  • Changed semantics. If get_account_v2 returned fields that get_account did not, the consolidated version must return the new fields. Otherwise you “simplified” by deleting data.
  • Mixed permissions. Never merge a read tool with a write tool. Reading and side effects stay apart. Confirming a Pix payment (Brazil’s instant transfer) cannot live in the same schema as checking a balance.

Common mistake: consolidating the name, not the intent

Someone creates customer_tool(action, **kwargs) with action equal to get, search, export, delete. The model now chooses among action values as vague as the seven old names, and a poorly chosen delete has a side effect. That is not consolidating: it is hiding sprawl inside one function.

Check +20 XP on first tryA colleague proposes a single customer_tool with action equal to get, search, export, or delete. Why is that not this refactoring?

Challenge

Consolidate these tools from an HR agent. One of them should not be merged with the others.

get_vacation_balance(employee_id)          Vacation balance.
vacation_days_left(email)                  Remaining vacation days.
check_pto(employee_id)                     Checks PTO.
request_vacation(employee_id, start, end)  Requests vacation.
Show solution

The first three answer the same question and become one read tool. request_vacation has a side effect and stays separate, ideally behind confirmation.

get_vacation_balance(by: "employee_id" | "email", value)
    Vacation balance and days already booked. Use it before suggesting dates.

request_vacation(employee_id, start, end)
    Creates a vacation request for manager approval. Only use it after the
    employee confirms the dates. It does not approve anything on its own.

Check your understanding

Check +20 XP on first tryexport_users_csv had zero calls in 90 days. The data team says they use it at quarter close, which fell outside the window. What do you do?

Check +20 XP on first tryAfter the merge, get_account requires customer_id, but the old eval still calls get_account(id=user_id). Completion rate dropped. What is the fix?

Check +20 XP on first tryThe agent still has 40 tools after merging synonyms and deleting ghosts. The descriptions are good. What is the next move?

Check +20 XP on first tryYou want to merge get_invoice (read) and issue_refund (side effect) because both take order_id. Should you?

Quick answers

What is Consolidate Tools?

Consolidate Tools is an AI refactoring for LLM systems, in the Simplifying tools group. Merge similar tools and remove the ones nobody uses.

What problem does Consolidate Tools solve?

It treats Tool Sprawl and Unused Tools.

Which pattern does Consolidate Tools lead to?

The Tool Router and Progressive Disclosure design pattern.

Go further

Relations

Smells it treats 2
Part of the tracks 1
Search