# Tool Dispatch
OpenResponses supports two kinds of tools: **external** (implemented in your client) and **hosted** (implemented on the server). Both follow the same request format.
## Defining tools in a request
Tools are defined as JSON Schema function descriptions:
```json
{
"model": "gpt-4o",
"input": [{"role": "user", "content": "Search for recent news about Elixir."}],
"tools": [
{
"type": "function",
"name": "web_search",
"description": "Search the web for recent information.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
}
},
"required": ["query"]
}
}
]
}
```
## External tool flow
When the model calls a tool that is not registered as a hosted tool, the call appears in the response output and you are responsible for executing it and submitting the result.
### Step 1 — Model calls the tool
The response (or stream) includes a `function_call` item:
```json
{
"output": [
{
"type": "function_call",
"id": "fc_01",
"call_id": "call_abc123",
"name": "web_search",
"arguments": "{\"query\": \"Elixir news 2026\"}"
}
]
}
```
### Step 2 — Execute the tool in your code
```elixir
arguments = Jason.decode!(function_call["arguments"])
result = MyApp.WebSearch.search(arguments["query"])
```
### Step 3 — Submit the result
Send a follow-up request with the previous response ID and a `function_call_output` item:
```json
{
"model": "gpt-4o",
"previous_response_id": "resp_01",
"input": [
{
"type": "function_call_output",
"call_id": "call_abc123",
"output": "Top results: ElixirConf 2026 announced..."
}
]
}
```
OpenResponses reconstructs the full conversation context from `previous_response_id` and continues from where it left off.
## Hosted tools
Hosted tools execute on the server. The loop calls them automatically without involving the client. This is ideal for tools that are safe, fast, and don't require user interaction.
### Implementing a hosted tool
Implement the `OpenResponses.Tool` behaviour:
```elixir
defmodule MyApp.Tools.TimeZone do
@behaviour OpenResponses.Tool
@impl OpenResponses.Tool
def execute(%{"timezone" => tz}, _context) do
case DateTime.now(tz) do
{:ok, dt} -> {:ok, Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S %Z")}
{:error, _} -> {:error, "Unknown timezone: #{tz}"}
end
end
end
```
`execute/2` receives:
- `arguments` — the parsed JSON arguments from the model (a map with string keys)
- `context` — a map with request context, including `response_id`
Return `{:ok, string_result}` or `{:error, reason}`.
### Registering hosted tools
In `config/config.exs`:
```elixir
config :open_responses, :hosted_tools, %{
"get_time" => MyApp.Tools.TimeZone,
"search_docs" => MyApp.Tools.DocSearch,
"run_sql" => MyApp.Tools.SQL
}
```
The key is the tool name the model uses in `function_call`. When a model emits a call for a registered name, OpenResponses dispatches it internally, appends the result, and continues the agentic loop — the client never sees the intermediate step.
## Controlling tool use
### `tool_choice`
Force or prevent tool use with the `tool_choice` field:
| Value | Behaviour |
|---|---|
| `"auto"` (default) | Model decides whether to call a tool |
| `"required"` | Model must call at least one tool |
| `"none"` | Model may not call any tools |
| `{"type": "function", "function": {"name": "my_tool"}}` | Model must call this specific tool |
```json
{
"model": "gpt-4o",
"tool_choice": "required",
"tools": [...],
"input": [...]
}
```
### `allowed_tools`
Restrict which tools the model may call, regardless of what is defined in `tools`:
```json
{
"model": "gpt-4o",
"tools": [{"name": "search"}, {"name": "calculator"}, {"name": "email"}],
"allowed_tools": ["search", "calculator"],
"input": [...]
}
```
Calls to tools not in `allowed_tools` are rejected by `OpenResponses.ToolPolicy` before dispatch.
## Error handling
If a hosted tool returns `{:error, reason}`, the error is formatted as a string and submitted back to the model as a `function_call_output`. The model can then handle the error gracefully in its response.
If a tool raises an exception, the loop catches it, submits an error output, and continues.