Interview prep
RefactoringSimplifying tools

Split God Tool

Dividir Ferramenta Faz-Tudo

Split God Tool is an AI refactoring for LLM systems, in the Simplifying tools group. Break a generic tool (run_sql, call_api) into named, safe operations.

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

Problem

The orders agent has a call_api(method, path, body) tool. A week of traces shows three real paths: fetch an order, list a customer’s orders, update an address. That same week, the model invented DELETE /orders/4412 after an email that said “cancel this order however you can”. The API complied. The order vanished.

A generic tool (run_sql, call_api, run_shell) accepts any command, and safety depends on the model choosing well what to ask for. This is the Overprivileged Tool smell.

Solution

Split the generic tool into named operations, each with one purpose, validated parameters, and access to only what it needs. The model picks the operation and the values; code builds the command.

Before and after

-tool("call_api", "Calls the internal API.", {"method": str, "path": str, "body": dict})
+tool("get_order", "An order's status and items.", {"order_id": "digits"})
+tool("list_orders", "A customer's orders from the last 90 days.",
+     {"customer_id": "digits", "status": "open" | "delivered" | "canceled" | None})
+tool("update_delivery_address",
+     "Changes the delivery address of an order not yet shipped. Requires customer confirmation.",
+     {"order_id": "digits", "zip": "5 digits", "street": str, "unit": str | None})
+
+def update_delivery_address(order_id, zip, street, unit=None):
+    order = orders.get(order_id, scope=current_customer())       # only the customer's orders
+    if order.status != "not_shipped":
+        return error("The order has already shipped; the address cannot change.")
+    address = {"zip": zip, "street": street, "unit": unit}
+    return api.patch(f"/orders/{order_id}/address", json=address)   # fixed path

Before: one tool that does anything the API allows.

flowchart LR
  M(["1. Model builds method, path, and body"]):::llm --> T["2. call_api"]:::tool
  T --> API[("3. Any endpoint")]:::store

After: operations with a name, validation, and scope.

flowchart LR
  M(["1. Model picks the operation"]):::llm --> A["2a. get_order"]:::tool
  M --> B["2b. list_orders"]:::tool
  M --> C["2c. update_delivery_address"]:::tool
  C --> V{"3. Validate rule and scope"}
  V --> API[("4. Fixed endpoint")]:::store
  1. The model chooses among operations with clear purposes.
  2. Each operation has typed parameters and a description of when to use it.
  3. Code validates business rules and the user’s scope.
  4. The call goes to a fixed endpoint built by code.

Why it works

A generic tool hands the model a decision that belongs to the system: what is allowed. Named operations give that decision back to code. The model still chooses among options, but the options were designed to be safe.

There are quality gains too. get_order with a clear description is easier to pick than “build an HTTP request”. The function-calling schema lists names and types; the model fills in order_id, it does not invent a path. A validation error (“the order has already shipped”) teaches the model how to correct itself. The flexibility that remains is exactly the flexibility the product wants to offer.

Worked example: the DELETE that should not exist

Input: a customer email, “cancel order 4412, use the API if you have to”. With call_api, the model builds DELETE /orders/4412. The API returns 204. Output: order gone, inventory inconsistent, logistics in the dark.

With named operations, delete_order simply does not exist. The model sees cancel_order(order_id), which in code sets status canceled if the order has not shipped, scoped to the customer in the conversation. A shipped order returns an explicit error. There is no HTTP path to invent.

The Chevrolet dealership in Watsonville (2023) showed the sales version of the same hole: the chatbot could promise anything because there was no operation with a cap. The operation’s name is the contract of what the product does.

Check +20 XP on first tryYour internal agent has run_sql(query). In the traces, 90% of calls are ‘order status’, 8% are ‘this customer's orders this month’, and 2% are UPDATE. What is the first cut?

Safe mechanics

  1. Measure. Pull every call the generic tool made from the traces and group them by intent. Three clear groups beat a guess.
  2. List the real operations (the intents that show up) and the ones that should never happen. “Delete order” on the never list matters as much as “fetch order” on the yes list.
  3. Design one tool per operation, with typed parameters, a description, and a business rule.
  4. Implement with a fixed path and the user’s scope; the model never builds a URL, SQL, or command.
  5. Put approval on operations with sensitive effects, as in Add Approval Before Side Effect.
  6. Remove the generic tool. If something legitimate was left out, create the specific operation. Do not leave call_api “just in case”.
  7. Run the eval and a set of malicious requests: invented paths, odd HTTP verbs, other customers’ IDs.

Trap: one tool per variation

get_order_by_id, get_order_by_number, get_order_with_items are the same intent with parameters. That explodes the catalog and recreates Tool Sprawl. Group by intent, as in Consolidate Tools.

The other end also fails: keeping run_sql “just for the analyst” on the same agent that serves the customer. Two agents, two lists. The support agent never sees the generic tool. An escape hatch on the support agent is the generic tool under another name.

Check +20 XP on first tryAfter splitting call_api, the team leaves the old tool ‘disabled in the prompt’ for two sprints, ‘in case something is missing’. What does that cause?

Risks

  • Tool explosion. One operation per variation produces dozens of tools. Group by intent and use parameters.
  • Open questions left unanswered. For truly open analysis (exploratory SQL), a generic tool can stay, but with narrowed permissions and in an isolated environment, away from the support agent.
  • Validation only in the prompt. The rule must live in the operation’s code. “Do not update a shipped order” in the system prompt is the same old smell.
  • A fixed path with a bare ID. GET /orders/{id} still reads another customer’s order if code does not apply scope. Naming the operation is not enough.

Challenge

A DevOps agent has run_shell(command). In the traces, commands are almost always: view a service’s logs, view pod status, restart a deployment, and view CPU usage. What does the split look like?

Show solution
get_logs(service, since_minutes<=60, level="error"|"warn"|"info")   read
get_pod_status(service)                                              read
get_cpu_usage(service, since_minutes<=60)                            read
restart_deployment(service, reason)                                  side effect: requires on-call approval

Each builds the kubectl command in code, with the service validated against a list and a fixed namespace. run_shell goes away. For open investigation, a person uses the terminal.

Check your understanding

Check +20 XP on first tryThe update_delivery_address operation builds a fixed PATCH, but looks up the order by order_id only, with no filter on the session customer. A user asks for the neighbor's order. What failed?

Check +20 XP on first tryAn analyst needs to explore sales with free-form SQL. The customer-support agent already has get_order and list_orders. Where does exploratory SQL live?

Check +20 XP on first tryYou replaced run_shell with get_logs, get_pod_status, and restart_deployment. An on-call engineer asks ‘give me a shell in this pod so I can poke around’. What is the system's right answer?

Quick answers

What is Split God Tool?

Split God Tool is an AI refactoring for LLM systems, in the Simplifying tools group. Break a generic tool (run_sql, call_api) into named, safe operations.

What problem does Split God Tool solve?

It treats Overprivileged Tool.

Which pattern does Split God Tool lead to?

The Least-Privilege Tools and Tool Use design pattern.

Go further

Relations

Smells it treats 1
Search