# 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.toolsdeny 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"
\]

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](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInRvb2xfY2FsbHNcIjogW1xuICAgIHtcbiAgICAgIFwidG9vbFwiOiBcIkJhc2hcIixcbiAgICAgIFwicGFyYW1zXCI6IHtcbiAgICAgICAgXCJjb21tYW5kXCI6IFwibHMgLWxhXCIsXG4gICAgICAgIFwidGltZW91dFwiOiA1MDAwXG4gICAgICB9XG4gICAgfSxcbiAgICB7XG4gICAgICBcInRvb2xcIjogXCJXZWJGZXRjaFwiLFxuICAgICAgXCJwYXJhbXNcIjoge1xuICAgICAgICBcInVybFwiOiBcImh0dHA6Ly9leGFtcGxlLmNvbVwiLFxuICAgICAgICBcInRpbWVvdXRcIjogNTAwMFxuICAgICAgfVxuICAgIH1cbiAgXVxufSIsImQiOiJ7fSIsInAiOiJwYWNrYWdlIGNvZGluZy50b29sc1xuXG5kZW55IGNvbnRhaW5zICRcIlRvb2wge3RjLnRvb2x9IGlzIG5vdCBhbGxvd2VkXCIgaWYge1xuXHRzb21lIHRjIGluIGlucHV0LnRvb2xfY2FsbHNcblx0dGMudG9vbCBpbiBfZGlzYWxsb3dlZF90b29sc1xufVxuXG5kZW55IGNvbnRhaW5zICRcIldlYkZldGNoIGNhbiBvbmx5IGxvYWQgZnJvbSBIVFRQUyBVUkxzXCIgaWYge1xuXHRzb21lIHRjIGluIGlucHV0LnRvb2xfY2FsbHNcblx0dGMudG9vbCA9PSBcIldlYkZldGNoXCJcblx0bm90IHN0YXJ0c3dpdGgodGMucGFyYW1zLnVybCwgXCJodHRwczovL1wiKVxufVxuXG5kZW55IGNvbnRhaW5zICRcIldlYlNlYXJjaCBjYW5ub3QgbG9hZCBtb3JlIHRoYW4ge19tYXhfcmVzdWx0c30gcmVzdWx0c1wiIGlmIHtcblx0c29tZSB0YyBpbiBpbnB1dC50b29sX2NhbGxzXG5cdHRjLnRvb2wgPT0gXCJXZWJTZWFyY2hcIlxuXHR0Yy5wYXJhbXMubnVtX3Jlc3VsdHMgPiBfbWF4X3Jlc3VsdHNcbn1cblxuZGVueSBjb250YWlucyAkXCJUb29sIHRpbWVvdXQgY2Fubm90IGJlIG1vcmUgdGhhbiAxMHNcIiBpZiB7XG5cdHNvbWUgdGMgaW4gaW5wdXQudG9vbF9jYWxsc1xuXHR0Yy5wYXJhbXMudGltZW91dCA+IDEwMDAwXG59XG5cbl9kaXNhbGxvd2VkX3Rvb2xzIDo9IHtcIkJhc2hcIiwgXCJXcml0ZVwiLCBcIkVkaXRcIn1cblxuX21heF9yZXN1bHRzIDo9IDEwXG4ifQ==)

## Using OPA as a decision point

The harness can ask OPA for a decision with the [REST API](/docs/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](/docs/management-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](/docs/management-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

*   [Function calling](https://developers.openai.com/api/docs/guides/function-calling) in the OpenAI developer docs.