Skip to main content

Input & Output Controls

Tool Budget

tool-budget blocks a request whose max_tokens is larger than the limit you set for a tool that the request declares. For example, with a 100-token limit for web_search, a request that declares web_search with max_tokens: 250 is blocked.

Despite its name, this is a check of one request field, not a spending budget. It does not count tool runs or token use. For spending limits, see Usage Costs and Budgets.

Configuration​

policy-config.yaml
pack:
  name: tool-budget-example-1
  version: "1.0.0"
  enabled: true

policies:
  chain:
    - tool-budget

policy:
  tool-budget:
    budgets:
      web_search:
        max_tokens: 100

Verify the block with a pack test​

Create a JSON test next to the configuration. The request goes in input.request:

tests/blocks-search-over-budget.json
{
  "name": "blocks-search-over-budget",
  "input": {
    "messages": [
      {
        "role": "user",
        "content": "Search the documentation."
      }
    ],
    "request": {
      "model": "replace-with-model-id",
      "max_tokens": 250,
      "tools": [
        {
          "type": "function",
          "function": {
            "name": "web_search"
          }
        }
      ]
    }
  },
  "expected": {
    "verdict": "block",
    "reason_code": "tool-budget.exceeded"
  }
}
verdictan policy lint --file policy-config.yaml
verdictan policy test --json

The test passes, and its details.policy_results entry shows exceeded_tools: ["web_search"]. Copy the file for the boundary cases:

RequestResult
max_tokens: 100allow, ok
max_tokens: 101block, tool-budget.exceeded
A tool without a budgetallow, ok
No max_tokensallow, ok

Fields​

FieldTypeDefaultDescription
budgetsobject{}Limits by tool name. The key must equal the name that the client sends.
budgets.<tool>.max_tokensinteger, at least 1Token limit for the tool. Required in each entry.

Lint rejects the removed field max_cost_usd.

How it works​

  1. The gateway reads the top-level max_tokens. A missing, negative, fractional, or string value counts as 0.
  2. It reads each tool name from tools[*].function.name, or from tools[*].name if that is missing.
  3. For each tool with a budget, it compares max_tokens with the limit.
  4. A value above the limit adds the tool to exceeded_tools. A value equal to the limit is fine.
  5. Any exceeded tool blocks the request with tool-budget.exceeded: HTTP 400 before the provider call.
  6. Otherwise, the request is allowed with tool-budget.ok.

The tools array is what the client offers the model, not what the model called. Both name forms are read:

Request fragment
{
  "max_tokens": 250,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "web_search"
      }
    },
    {
      "name": "database_query"
    }
  ]
}

Phase and verdicts​

ItemValue
Input phaseCompares max_tokens with the limit of each declared tool
Output phaseNot checked
Verdictsallow, block
No exceeded tooltool-budget.ok
An exceeded tooltool-budget.exceeded

An allowed decision keeps the top-level reason ok. The nested tool-budget result shows tool-budget.ok.

Limits and failure behavior​

  • Only the top-level max_tokens is read. A request with max_completion_tokens or max_output_tokens counts as 0 and passes.
  • Without a tools array, nothing is checked. A tool without a budget passes.
  • Names must match the budget keys exactly, including case.
  • Nothing is counted across requests, sessions, users, or teams.
  • The control does not limit tool calls, validate arguments, or examine model tool calls. Use tool-validation, tool-security, agent-firewall, and rate limits for that.
  • If an application declares many tools in every request, a low limit on one tool blocks every request. Set each limit to the largest completion size that the tool allows.
ProblemCauseAction
A large request was allowedThe client used max_completion_tokens or max_output_tokens, left out max_tokens, or sent a non-integer.Send an integer max_tokens at the top level.
A tool was not checkedIts name is missing, elsewhere in the object, or different from the key.Match function.name or name with the key.
Blocked, although the model called no toolThe control checks declared tools, not calls.Remove the tool from the request, or use a control for calls.
Lint rejects max_cost_usdThe field was removed.Use max_tokens.
A request at the limit passedThe check is "greater than".Set the limit one lower.

Next steps​