Skip to main content

Agent Tool Calling

LLM models can call tools, which is sometimes called tool use. The LLM does not run a tool itself, but returns a structured request that names the tool and its parameters, and the program around the LLM, often called the harness or AI agent, runs the tool. For example, an LLM that needs to get the latest OPA release might search online for it with this tool call:

{
"tool": "WebSearch",
"params": {
"query": "open policy agent latest release",
"num_results": 5
}
}

Tool calls are structured data, so they are a good fit for policy based approval with OPA and Rego. You can write rules about which tools are allowed and which parameters are safe.

Policy enforcement flow​

The harness is the Policy Enforcement Point (PEP), which enforces decisions. Before the harness runs a tool call, the harness asks OPA, the Policy Decision Point (PDP), for a decision.

  1. The LLM returns one or more tool calls.
  2. The harness sends the tool calls to OPA as input.
  3. OPA returns a decision. In the example below, the decision is a set of reasons to deny.
  4. If the set is empty, the harness runs the tools. If the set has reasons, the harness refuses to run the tools.
info

The harness can pass the reasons to deny back to the LLM, which helps the LLM make better tool calls later. The harness can also hide the reasons from the LLM for security reasons. The right choice depends on your use case.

Example policy​

This policy denies the Bash tool. The policy also denies WebFetch calls that load a URL that does not start with https://. To see how the output changes, edit the input or the policy and select Evaluate.

OPA lets you enforce fine-grained policies over which tools an AI agent can call, what parameters are permitted, and how those tools can be used.

policy.rego
package coding.tools

deny contains $"Tool {tc.tool} is not allowed" if {
some tc in input.tool_calls
tc.tool in _disallowed_tools
}

deny contains $"WebFetch can only load from HTTPS URLs" if {
some tc in input.tool_calls
tc.tool == "WebFetch"
not startswith(tc.params.url, "https://")
}

deny contains $"WebSearch cannot load more than {_max_results} results" if {
some tc in input.tool_calls
tc.tool == "WebSearch"
tc.params.num_results > _max_results
}

deny contains $"Tool timeout cannot be more than 10s" if {
some tc in input.tool_calls
tc.params.timeout > 10000
}

_disallowed_tools := {"Bash", "Write", "Edit"}

_max_results := 10
Output
[
  "Tool Bash is not allowed",
  "WebFetch can only load from HTTPS URLs"
]
Loading...
input.json
{
"tool_calls": [
{
"tool": "Bash",
"params": {
"command": "ls -la",
"timeout": 5000
}
},
{
"tool": "WebFetch",
"params": {
"url": "http://example.com",
"timeout": 5000
}
}
]
}
data.json
{}

Open in OPA Playground

Using OPA as a decision point​

The harness can ask OPA for a decision with the REST API. This request queries the deny rule from the policy above. The request has one WebFetch call with an http:// URL.

curl -s localhost:8181/v1/data/coding/tools/deny -d '{
"input": {
"tool_calls": [
{"tool": "WebFetch", "params": {"url": "http://example.com", "timeout": 5000}}
]
}
}'
{
"result": [
"WebFetch can only load from HTTPS URLs"
]
}

If result is empty, OPA denied nothing, and the harness runs the tool calls. If result has reasons, the harness must not run the tool calls.

Operational benefits​

AI agents often run for a long time, and you do not want to restart them to change a rule. OPA bundles solve this. OPA downloads new policy from a server on a schedule, and OPA checks the next tool call against the new policy. The AI agent and the harness do not change.

OPA decision logs record each decision with its input and result. You can use these logs to audit which tool calls AI agents tried to make and which ones OPA denied.

Further reading​