golo / symfony-anthropic-wrapper
Symfony bundle for the official Anthropic PHP SDK: YAML-configured defaults, per-model options and presets, plus a profiler panel for every Claude call.
Package info
github.com/barryoneil/symfony-anthropic-wrapper
Type:symfony-bundle
pkg:composer/golo/symfony-anthropic-wrapper
Requires
- php: >=8.2
- anthropic-ai/sdk: >=0.54 <2.0
- psr/http-message: ^1.1 || ^2.0
- symfony/config: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/framework-bundle: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
- symfony/service-contracts: ^3.0
Requires (Dev)
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.5
- symfony/http-client: ^6.4 || ^7.0
Suggests
- symfony/stopwatch: Adds each Claude call to the profiler Performance timeline
- symfony/web-profiler-bundle: Shows the Anthropic panel in the profiler
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-03 12:54:48 UTC
README
A Symfony bundle for the official Anthropic PHP SDK (anthropic-ai/sdk):
- The SDK client as a service:
Anthropic\Clientis autowirable and configured from YAML. - Options in YAML: request options (model, max tokens, thinking, effort, fallbacks...) set as global defaults, per-model rules and named presets, instead of hard-coding them.
- A
Claudeservice: sends requests with those options applied, adds the right beta headers automatically, and handles model quirks (for example, thinking that can't be disabled). - A profiler panel: every Claude call with its prompt, response, token usage, cache hits, rate limits, and which config layer each option came from.
Install
composer require golo/symfony-anthropic-wrapper
// config/bundles.php Golo\SymfonyAnthropic\AnthropicBundle::class => ['all' => true],
Set ANTHROPIC_API_KEY in .env.local.
Requirements: the SDK needs a PSR-18 HTTP client and PSR-17 factories. Most Symfony apps already have them. If not, run composer require symfony/http-client nyholm/psr7. php-http/discovery's Composer plugin offers to install them when you add the SDK.
Configuration
# config/packages/anthropic.yaml anthropic: api_key: '%env(default::ANTHROPIC_API_KEY)%' # default base_url: ~ max_retries: ~ # SDK default 2 timeout: ~ # seconds, SDK default 600 cache_ttl: 5m # 5m | 1h, used by Claude::text($text, cache: true) profiler: enabled: ~ # defaults to kernel.debug # 1. Applied to every request defaults: model: claude-opus-5-5 max_tokens: 4096 # 2. Applied when that model is used models: claude-opus-5-5: effort: low max_tokens: 16000 fallbacks: default claude-sonnet-5-5: thinking: between_tools # 3. Named option sets, chosen per call presets: thorough: model: claude-opus-5-5 effort: high quick: model: claude-haiku-4-5 max_tokens: 1024
Keys under models: are Anthropic model IDs exactly as the API takes them (claude-opus-5-5, claude-haiku-4-5, or anthropic.claude-opus-5-5 on Bedrock). They are not converted to underscores like normal Symfony config keys.
Later layers win: defaults < models < presets < per-call options. An unset (null) option falls through to the earlier layer. The exception is betas, which accumulate across layers.
Request options
These are available in defaults, models.*, presets.* and per call:
| Option | Values | Notes |
|---|---|---|
model |
model ID | |
max_tokens |
int | Built-in default 4096. Thinking tokens count against it. |
thinking |
adaptive, disabled, between_tools, omit |
omit sends nothing. disabled is adjusted for models that reject it (see below). |
thinking_display |
summarized, omitted, updates |
Only applies when thinking: adaptive. updates adds its beta header. |
effort |
low, medium, high, xhigh, max |
Sent as output_config.effort |
fallbacks |
default, a list of model IDs, or false |
Server-side refusal fallbacks. The beta header is added for you. false turns off an inherited value. |
speed |
fast, standard |
Fast mode. The beta header is added for you. |
temperature, top_p, top_k |
numbers | Rejected by many newer models |
stop_sequences |
list of strings | |
service_tier, inference_geo |
string | |
betas |
list of strings | Extra beta headers |
Model adjustments: with thinking: disabled, the API would return a 400 error on some models, so the wrapper changes it:
- On Claude Opus 5.5, Fable 5.x and Mythos 5.x, thinking is always on, so it's left out. Use
effortto control it. - On Claude Sonnet 5.5,
between_toolsis sent instead.
Each adjustment is shown as a note in the profiler.
Requests that need a beta header go to the beta messages endpoint. All others go to the stable endpoint.
Usage
use Golo\SymfonyAnthropic\Claude; class ExampleService { public function __construct(private Claude $claude) {} public function ask(string $systemPrompt, string $context, string $question): ?string { $result = $this->claude->create( messages: [ $this->claude->user( $this->claude->text($context, cache: true), // cached prefix $question, // strings become text blocks ), ], system: [$this->claude->text($systemPrompt, cache: true)], preset: 'thorough', options: ['max_tokens' => 8000], // per-call override ); if ($result->isRefusal()) { // $result->refusalCategory(), $result->refusalExplanation() return null; } return $result->text(); } }
Result methods:
text(),thinking(),stopReason(),isRefusal(),refusalCategory(),refusalExplanation()isTruncated(), which is true when the response hitmax_tokensmodel(), the model that actually answered, which differs from the one requested if a fallback was usedusage()$result->message, the SDK's ownMessage/BetaMessage$result->request, the resolved options
Claude::prepare(...) builds the request without sending it, which is useful for checking what a preset resolves to.
SDK exceptions (Anthropic\Core\Exceptions\RateLimitException, APIStatusException, APIConnectionException...) are not caught by the wrapper.
Using the SDK directly
The wrapper doesn't cover streaming, batches, files or models. For those, use $claude->client() or autowire Anthropic\Client. Calls made through that client still show in the profiler.
If you build your own client, pass the middleware in to keep profiling:
new Anthropic\Client(requestOptions: ['middleware' => [$traceMiddleware]]); // Golo\SymfonyAnthropic\Profiler\TraceMiddleware
Profiler
With kernel.debug on, the Anthropic tab shows these things.
Toolbar: calls, time, errors/refusals, tokens, cache write/read, and the models used.
Totals: calls, time, tokens, cache write/read and cache-hit %.
Each call has these tabs:
- Overview:
- model (and fallback model, if one was used), request ID, stop reason, refusal details and token usage
- the resolved options, with the layer each came from, plus any model-adjustment notes
- the parameters sent
- Prompt: system prompt and messages, block by block, with cache badges
- Response: text, thinking and tool-use blocks
- Rate limits: the
anthropic-ratelimit-*headers - Raw request / raw response: full headers and JSON (API key redacted)
Each call also appears on the Performance timeline. SDK retries show as separate calls with a "retry N" badge. Streamed response bodies aren't captured, because SSE streams can't be rewound.
Development
composer install
composer test