# Providers
OpenResponses routes each request to a provider adapter based on the model name. Adapters translate between the Open Responses spec and the provider's native API.
## Configuration
API keys and per-provider options live in `config/runtime.exs`:
```elixir
config :open_responses, :provider_config, %{
openai: [
api_key: System.fetch_env!("OPENAI_API_KEY")
],
anthropic: [
api_key: System.fetch_env!("ANTHROPIC_API_KEY")
],
gemini: [
api_key: System.fetch_env!("GEMINI_API_KEY")
]
}
```
## Routing
The routing table maps model name patterns to adapter modules. The default routing:
```elixir
config :open_responses, :routing, %{
~r/^gpt-/ => OpenResponses.Adapters.OpenAI,
~r/^claude-/ => OpenResponses.Adapters.Anthropic,
~r/^gemini-/ => OpenResponses.Adapters.Gemini,
~r/^llama|^mistral|^phi|^qwen/ => OpenResponses.Adapters.Ollama,
"default" => OpenResponses.Adapters.Mock
}
```
Patterns are evaluated in insertion order. The first match wins. The `"default"` key is a fallback for any model that doesn't match a pattern.
To add your own routing, override this in config:
```elixir
config :open_responses, :routing, %{
~r/^gpt-4/ => OpenResponses.Adapters.OpenAI,
~r/^gpt-3/ => MyApp.Adapters.OpenAILegacy,
~r/^claude-/ => OpenResponses.Adapters.Anthropic,
"default" => OpenResponses.Adapters.OpenAI
}
```
## OpenAI
The Open Responses spec is derived from OpenAI's Responses API, so this adapter is nearly a direct pass-through.
**Supported models:** `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`, `o1`, `o3-mini`, and any future `gpt-*` models.
```elixir
config :open_responses, :provider_config, %{
openai: [
api_key: System.fetch_env!("OPENAI_API_KEY")
]
}
```
To use a custom endpoint (Azure OpenAI, OpenAI-compatible proxies):
```elixir
openai: [
api_key: System.fetch_env!("AZURE_OPENAI_KEY"),
base_url: "https://your-resource.openai.azure.com/openai/deployments/gpt-4o"
]
```
## Anthropic
The Anthropic adapter translates between the Open Responses spec and the Anthropic Messages API. Key translations handled automatically:
- System prompts extracted from input and sent as the top-level `system` field
- Tool definitions converted from `parameters` to Anthropic's `input_schema`
- `content_block_delta` streaming events mapped to `response.output_text.delta`
- `tool_use` blocks mapped to `function_call` items
- `thinking` blocks mapped to `reasoning` items
- `stop_reason: "end_turn"` mapped to `response.completed`
**Supported models:** `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5`, and any future `claude-*` models.
```elixir
config :open_responses, :provider_config, %{
anthropic: [
api_key: System.fetch_env!("ANTHROPIC_API_KEY")
]
}
```
### z.ai
z.ai uses the Anthropic API. Use the Anthropic adapter with a custom `base_url` and add a routing rule:
```elixir
config :open_responses, :routing, %{
~r/^zai-/ => OpenResponses.Adapters.Anthropic,
# ... other routes
}
config :open_responses, :provider_config, %{
anthropic: [
api_key: System.fetch_env!("ZAI_API_KEY"),
base_url: "https://api.z.ai/v1"
]
}
```
## Google Gemini
The Gemini adapter translates messages to the `contents`/`parts` format expected by the Gemini API.
Key translations:
- `assistant` role mapped to `model` role
- System messages extracted as `system_instruction`
- `finishReason: "STOP"` mapped to `response.completed`
- `finishReason: "MAX_TOKENS"` mapped to `response.incomplete`
**Supported models:** `gemini-2.0-flash`, `gemini-1.5-pro`, `gemini-1.5-flash`, and any future `gemini-*` models.
```elixir
config :open_responses, :provider_config, %{
gemini: [
api_key: System.fetch_env!("GEMINI_API_KEY")
]
}
```
## Ollama (local models)
Ollama runs models locally. No API key is required. OpenResponses defaults to `http://localhost:11434`.
**Supported models:** Any model pulled into your Ollama installation — `llama3.2`, `mistral`, `phi4`, `qwen2.5`, `deepseek-r1`, and more.
```bash
ollama pull llama3.2
```
```elixir
config :open_responses, :routing, %{
~r/^llama|^mistral|^phi|^qwen|^deepseek/ => OpenResponses.Adapters.Ollama
}
```
To point at a remote Ollama instance:
```elixir
config :open_responses, :provider_config, %{
# Ollama uses the adapter name :ollama — add it to provider_config
# by overriding the config key in your Loop opts, or set a custom base_url
# via per-request provider config (see below).
}
```
## Per-request provider config
Any request can override the provider config by including a `provider` key:
```json
{
"model": "gpt-4o",
"provider": {
"api_key": "sk-project-specific-key",
"base_url": "https://my-proxy.example.com/v1"
},
"input": [...]
}
```
This is useful for multi-tenant applications where each user brings their own API key.
## Writing a custom adapter
Implement the `OpenResponses.Adapter` behaviour in any module:
```elixir
defmodule MyApp.Adapters.MyProvider do
@behaviour OpenResponses.Adapter
@impl OpenResponses.Adapter
def build_request(%{response: response, input: input}) do
%{
model: response.model,
messages: input
# ... provider-specific fields
}
end
@impl OpenResponses.Adapter
def stream(request, config) do
api_key = Keyword.fetch!(config, :api_key)
# Return {:ok, stream} where stream yields raw provider events
{:ok, my_streaming_function(request, api_key)}
end
@impl OpenResponses.Adapter
def normalize_event(raw_event) do
# Translate provider-native events to Open Responses spec events
case raw_event["type"] do
"my_provider.text_delta" ->
%{"type" => "response.output_text.delta", "delta" => raw_event["text"]}
"my_provider.done" ->
%{"type" => "response.completed"}
other ->
raw_event
end
end
end
```
Then add it to your routing config:
```elixir
config :open_responses, :routing, %{
~r/^myprovider-/ => MyApp.Adapters.MyProvider
}
```