Skip to content

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.


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:

OrderEnvironment variableProvider
1ANTHROPIC_API_KEYanthropic
2OPENAI_API_KEYopenai
3GROQ_API_KEYgroq
4GEMINI_API_KEYgemini
5MISTRAL_API_KEYmistral
6DEEPSEEK_API_KEYdeepseek
7OLLAMA_HOSTollama

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.


SlugClassDefault modelKey
anthropicAnthropicProviderclaude-haiku-4-5-20251001ANTHROPIC_API_KEY
geminiGeminiProvidergemini-3.5-flash-liteGEMINI_API_KEY
openaiOpenAIProvidergpt-4o-miniOPENAI_API_KEY
groqOpenAIProviderllama-3.1-8b-instantGROQ_API_KEY
mistralOpenAIProvidermistral-small-latestMISTRAL_API_KEY
deepseekOpenAIProviderdeepseek-flashDEEPSEEK_API_KEY
ollamaOpenAIProviderqwen2.5:7bnone
customOpenAIProvidernoneOPENAI_API_KEY

The last six share one class and differ in endpoint, default model, authentication, and whether native structured output is used (only openai).


AnthropicProvider names three models as constants and picks an output limit for each when you do not set maxTokens():

ConstantModelDefault max_tokens
MODEL_HAIKUclaude-haiku-4-5-202510018192
MODEL_SONNETclaude-sonnet-516000
MODEL_OPUSclaude-opus-4-816000
any other model4096
$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 runPrompt caching by default
Claw::builder()on
WordPress, Joomla, Drupal, Magento, PrestaShop, OpenCarton: they do not set it, so the builder default applies
Laraveloff, PHPCLAW_PROMPT_CACHE
Symfonyoff, prompt_cache
$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.


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.

Gemini 3 models attach a thoughtSignature to each function call and answer HTTP 400 (“Function call is missing a thought_signature”) when the next request drops it. The provider keeps the signature with the tool call and sends it back on the same part. Older models send no signature, so their requests are unchanged.

Model ids starting gemini-1. are treated as legacy and never receive web search.


openai, groq, mistral, deepseek, ollama and custom all use OpenAIProvider against a Chat Completions endpoint.

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.

Terminal window
export OLLAMA_HOST=http://localhost:11434

qwen2.5:7b is the default model, so pull it first or set another:

Terminal window
ollama pull qwen2.5:7b

Any OpenAI-compatible endpoint:

Terminal window
export PHPCLAW_PROVIDER=custom
export OPENAI_BASE_URL=https://llm.internal.example.com/v1/chat/completions
export OPENAI_API_KEY=...
export PHPCLAW_MODEL=my-model

Without OPENAI_BASE_URL, or without a key, 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.

Tool schemas are formatted by provider name. openai, groq, gemini, mistral, ollama, deepseek and custom receive the OpenAI function shape; anthropic and any unknown name receive the Anthropic-native shape:

openai, groq, gemini, mistral, ollama, deepseek, custom → {"type": "function", "function": {...}}
anthropic, unknown names → {"name": ..., "description": ..., "input_schema": ...}

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.

ProviderWhat is sent by default
anthropica web_search tool on every request, with no use limit
geminigoogle_search grounding, but only on a request that carries no function tools
openai presetsweb_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();
MethodEffectHonoured by
max(int)maximum searches per requestAnthropic
allow(array)allowed domainsAnthropic
location(city, region, country)approximate user locationAnthropic, and the openai presets on a search-preview model

Gemini’s grounding ignores all three.

The builder has no switch to remove it: configuring no web search means “use the default”. A provider passed to providerOverride() gets the same default when it supports web search, so only a provider that does not implement SupportsWebSearchInterface is sent without it.

On Gemini, an agent with any tools registered never gets grounding.


ModelRouter asks a model how difficult a message is and returns the provider name you mapped to that difficulty, ready for provider():

use PhpClaw\Claw;
use PhpClaw\Providers\ModelRouter;
use PhpClaw\Providers\OpenAIProvider;
$classifier = new OpenAIProvider(
apiKey: '',
model: 'qwen2.5:7b',
endpoint: 'http://127.0.0.1:11434/v1/chat/completions',
name: 'ollama',
authStyle: 'none',
);
$router = new ModelRouter($classifier, ['easy' => 'groq', 'hard' => 'anthropic']);
$message = 'What is 2 + 2?';
$agent = Claw::builder()->provider($router->resolve($message))->build();

The model is asked for JSON, {"value": "<one of your difficulties>"}. A reply that is not JSON, or names a difficulty that is not in the map, throws StructuredOutputException; a failed model call throws ProviderException. Each mapped provider needs its own API key, as with any provider() name. The choice is made once, before the agent is built; a run never switches provider.

$agent->stream($message, $onToken) behaves differently depending on tools:

AgentWhat $onToken receives
no tools registeredtokens as the provider streams them
tools registered, or durable runs onnothing until the whole run finishes, then the final answer in 10-character pieces

With tools or durable runs, 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.


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.

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, ollama, deepseek or custom if your API expects OpenAI-style tools, and anything else for the Anthropic shape.


providerOverride() bypasses provider resolution entirely. It also bypasses the system prompt: systemPrompt() and, with tools registered, the built-in doctrine are never passed to a provider you supply. Give your provider its own system prompt.

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.


ProviderCatalogue::keys() lists every provider available by slug: the two native providers carrying a #[Provider] attribute, anthropic and gemini, followed by the six presets, then any provider added with ProviderCatalogue::register() or declared in a Composer package’s extra.phpclaw.providers. activateFromSettings() registers nothing; the catalogue is for listing providers, for example in an admin dropdown.