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.
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.
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.
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.
-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
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.
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.
call_api “just in case”.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.
GET /orders/{id} still reads another customer’s order if code does not apply scope. Naming the operation is not enough.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?
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 approvalEach 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.
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.
It treats Overprivileged Tool.
The Least-Privilege Tools and Tool Use design pattern.