Providers
A provider sends the conversation to a model API and returns the reply. Core ships three native providers and six OpenAI-compatible presets, with no extra package to install.
Choosing a provider
Section titled “Choosing a provider”Set it on the builder:
use PhpClaw\Claw;
$agent = Claw::builder() ->provider('openai') ->apiKey($key) ->model('gpt-4o') ->build();Or leave it out and phpClaw picks from the environment. PHPCLAW_PROVIDER wins if set; otherwise the
first key found in this order decides:
| Order | Environment variable | Provider |
|---|---|---|
| 1 | ANTHROPIC_API_KEY | anthropic |
| 2 | OPENAI_API_KEY | openai |
| 3 | GROQ_API_KEY | groq |
| 4 | GEMINI_API_KEY | gemini |
| 5 | MISTRAL_API_KEY | mistral |
| 6 | DEEPSEEK_API_KEY | deepseek |
| 7 | OLLAMA_HOST | ollama |
With none of them set, the provider is anthropic, and building fails because there is no key.
When ->model() is not called, PHPCLAW_MODEL is used, then the provider’s default.
Built-in providers
Section titled “Built-in providers”| Slug | Class | Default model | Key |
|---|---|---|---|
anthropic | AnthropicProvider | claude-haiku-4-5-20251001 | ANTHROPIC_API_KEY |
gemini | GeminiProvider | gemini-3.5-flash-lite | GEMINI_API_KEY |
openai | OpenAIProvider | gpt-4o-mini | OPENAI_API_KEY |
groq | OpenAIProvider | llama-3.1-8b-instant | GROQ_API_KEY |
mistral | OpenAIProvider | mistral-small-latest | MISTRAL_API_KEY |
deepseek | OpenAIProvider | deepseek-flash | DEEPSEEK_API_KEY |
ollama | OpenAIProvider | qwen2.5:7b | none |
custom | OpenAIProvider | none | OPENAI_API_KEY |
The last six share one class and differ only in endpoint, default model and authentication.
Anthropic
Section titled “Anthropic”AnthropicProvider names three models as constants and picks an output limit for each when you do
not set maxTokens():
| Constant | Model | Default max_tokens |
|---|---|---|
MODEL_HAIKU | claude-haiku-4-5-20251001 | 8192 |
MODEL_SONNET | claude-sonnet-5 | 16000 |
MODEL_OPUS | claude-opus-4-8 | 16000 |
| any other model | 4096 |
Prompt caching
Section titled “Prompt caching”$agent = Claw::builder()->provider('anthropic')->promptCache(false)->build();On by default through the builder. It marks the system prompt and the last tool as cacheable
and sends the prompt-caching-2024-07-31 beta header. AnthropicProvider’s own constructor defaults
it to off, so a provider you construct yourself does not cache unless told to.
| Where you run | Prompt caching by default |
|---|---|
Claw::builder() | on |
| WordPress, Joomla, Drupal, Magento, PrestaShop, OpenCart | on: they do not set it, so the builder default applies |
| Laravel | off, PHPCLAW_PROMPT_CACHE |
| Symfony | off, prompt_cache |
Extended thinking
Section titled “Extended thinking”$agent = Claw::builder()->provider('anthropic')->thinkingBudget(2048)->build();
$response = $agent->send('...');echo $response->thinking;A budget of 1024 or more turns it on. It sends thinking: {type: enabled, budget_tokens: N} with
the interleaved-thinking-2025-05-14 beta header, and raises max_tokens to at least the budget plus
1000. Below 1024 it is off.
Laravel exposes it as PHPCLAW_THINKING_BUDGET and Symfony as thinking_budget, both default 0.
No CMS adapter exposes it.
Gemini
Section titled “Gemini”GeminiProvider sends the key as a ?key= query parameter rather than a header.
When you do not set maxTokens(), a model id starting gemini-3.5-flash-lite, gemini-3.5-flash,
gemini-2.5-flash, gemini-2.5-pro or gemini-2 gets an output limit of 8192 tokens. Any other
model gets 2048.
It strips JSON Schema keywords that Gemini rejects before sending tool schemas, including
additionalProperties, $schema, $ref, const and patternProperties, and converts nullable
union types to Gemini’s nullable flag.
Model ids starting gemini-1. are treated as legacy and never receive web search.
OpenAI-compatible presets
Section titled “OpenAI-compatible presets”openai, groq, mistral, deepseek, ollama and custom all use OpenAIProvider against a
Chat Completions endpoint.
Ollama
Section titled “Ollama”No key. The endpoint is http://127.0.0.1:11434/v1/chat/completions, or
OLLAMA_HOST followed by /v1/chat/completions when that variable is set.
export OLLAMA_HOST=http://localhost:11434qwen2.5:7b is the default model, so pull it first or set another:
ollama pull qwen2.5:7bCustom
Section titled “Custom”Any OpenAI-compatible endpoint:
export PHPCLAW_PROVIDER=customexport OPENAI_BASE_URL=https://llm.internal.example.com/v1/chat/completionsexport OPENAI_API_KEY=...export PHPCLAW_MODEL=my-modelWithout OPENAI_BASE_URL, building fails. There is no default model, so set one. A Bearer token is
always sent.
An endpoint given with no path, such as https://llm.example.com, gets /v1/chat/completions
appended. One with a path is used as written.
Web search
Section titled “Web search”Anthropic, Gemini and OpenAI support provider-native web search, and it is attached by default. When you configure none, the builder attaches one with no limits.
| Provider | What is sent by default |
|---|---|
anthropic | a web_search tool on every request, with no use limit |
gemini | google_search grounding, but only on a request that carries no function tools |
openai presets | web_search_options, but only when the model id contains search-preview |
Configure it:
use PhpClaw\Providers\Tools\WebSearch;
$agent = Claw::builder() ->provider('anthropic') ->withProviderTool((new WebSearch)->max(3)->allow(['php.net'])->location(country: 'GB')) ->build();| Method | Effect | Honoured by |
|---|---|---|
max(int) | maximum searches per request | Anthropic |
allow(array) | allowed domains | Anthropic |
location(city, region, country) | approximate user location | Anthropic |
Gemini’s grounding ignores all three.
The builder has no switch to remove it: configuring no web search means “use the default”. To send
requests without it, build the provider yourself and pass it to providerOverride().
On Gemini, an agent with any tools registered never gets grounding.
Streaming
Section titled “Streaming”$agent->stream($message, $onToken) behaves differently depending on tools:
| Agent | What $onToken receives |
|---|---|
| no tools registered | tokens as the provider streams them |
| tools registered | nothing until the whole run finishes, then the final answer in 10-byte pieces |
With tools, every model call is a normal request so that tool calls can be read, and the finished
answer is replayed through $onToken. The output looks streamed, but the first chunk arrives only
after the full answer exists.
Registering a provider
Section titled “Registering a provider”use PhpClaw\ClawConfig;use PhpClaw\Providers\ProviderRegistry;
ProviderRegistry::register('myllm', fn (ClawConfig $config) => new MyLlmProvider($config->apiKey, $config->model));
$agent = Claw::builder()->provider('myllm')->apiKey($key)->build();register() takes a factory, as above, or a class name. A class is constructed as
new $class($apiKey, new RawHttpClient, new StreamParser, $model), so its constructor must accept
those four positional arguments. Names are case-insensitive.
Your own slug has no key variable of its own, so pass the key with apiKey(). A provider key in the
environment, such as ANTHROPIC_API_KEY, is not picked up for it.
The registry is consulted before the presets, so registering openai or ollama replaces that
preset.
The contract
Section titled “The contract”namespace PhpClaw\Providers\Contracts;
interface ProviderInterface{ public function send(array $messages, array $tools = []): array; public function stream(array $messages, callable $onToken): string; public function name(): string; public function model(): string;}name() decides the tool schema shape, so return one of openai, groq, gemini, mistral or
ollama if your API expects OpenAI-style tools, and anything else for the Anthropic shape.
Testing with a mock
Section titled “Testing with a mock”providerOverride() bypasses provider resolution entirely:
use PhpClaw\Claw;use PhpClaw\Providers\Contracts\ProviderInterface;
$mock = $this->createMock(ProviderInterface::class);$mock->method('name')->willReturn('anthropic');$mock->method('model')->willReturn('claude-haiku-4-5-20251001');$mock->method('send')->willReturn([ 'type' => 'text', 'text' => 'Hello from mock', 'input_tokens' => 10, 'output_tokens' => 5,]);
$agent = Claw::builder()->providerOverride($mock)->build();
$this->assertSame('Hello from mock', $agent->send('Hello')->text);A mock of ProviderInterface does not implement web search, so no web search is attached.
The catalogue
Section titled “The catalogue”ProviderCatalogue::keys() lists every provider available by slug: the two native providers carrying
a #[Provider] attribute, anthropic and gemini, followed by the six presets.
activateFromSettings() registers nothing; the catalogue is for listing providers, for example in an
admin dropdown.