Skip to content

Commit 9980c4e

Browse files
committed
feature #274 [Platform][Anthropic] Allow beta feature flags to be passed into platform invocations (TomLisankie)
This PR was squashed before being merged into the main branch. Discussion ---------- [Platform][Anthropic] Allow beta feature flags to be passed into platform invocations | Q | A | ------------- | --- | Bug fix? | No | New feature? | Yes | Docs? | Yes | Issues | See below | License | MIT <!-- Replace this notice by a description of your feature/bugfix. This will help reviewers and should be a good start for the documentation. Additionally (see https://symfony.com/releases): - Always add tests and ensure they pass. - For new features, provide some code snippets to help understand usage. - Features and deprecations must be submitted against branch main. - Update/add documentation as required (we can help!) - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry - Never break backward compatibility (see https://symfony.com/bc). --> Anthropic [supports](https://docs.anthropic.com/en/api/beta-headers) passing flags for beta features via beta headers in requests to its API. This PR allows for passing in the desired beta features via the `$options` parameter in the `invoke` function like so: ```php $platform->invoke($model, $messageBag, [ 'temperature' => 1.0, 'max_tokens' => 20000, // can now include beta features like this: 'beta_features' => [ 'code-execution-2025-05-22', 'mcp-client-2025-04-04' ] ]); ``` If the beta features option is set and contains at least one element, the beta header is constructed inside the model client and then the option is removed from `$options` before the request is built and sent to Anthropic. If we want to support beta feature flags for other providers in the future, we should perhaps add another parameter to the `invoke` function for platforms and the `request` function for model clients in order to not pollute `$options` (after all, `$options` is meant to hold _model_ options not _platform_ options). Commits ------- 41faaa6 [Platform][Anthropic] Allow beta feature flags to be passed into platform invocations
2 parents 7c969ce + 41faaa6 commit 9980c4e

File tree

3 files changed

+130
-4
lines changed

3 files changed

+130
-4
lines changed

src/platform/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ CHANGELOG
5959
* Add response promises for async operations
6060
* Add InMemoryPlatform and InMemoryRawResult for testing Platform without external Providers calls
6161
* Add tool calling support for Ollama platform
62+
* Allow beta feature flags to be passed into Anthropic model options
6263

6364

src/platform/src/Bridge/Anthropic/ModelClient.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,26 @@ public function supports(Model $model): bool
3939

4040
public function request(Model $model, array|string $payload, array $options = []): RawHttpResult
4141
{
42+
$headers = [
43+
'x-api-key' => $this->apiKey,
44+
'anthropic-version' => $this->version,
45+
];
46+
4247
if (isset($options['tools'])) {
4348
$options['tool_choice'] = ['type' => 'auto'];
4449
}
4550

51+
if (
52+
isset($options['beta_features'])
53+
&& \is_array($options['beta_features'])
54+
&& !empty($options['beta_features'])
55+
) {
56+
$headers['anthropic-beta'] = implode(',', $options['beta_features']);
57+
unset($options['beta_features']);
58+
}
59+
4660
return new RawHttpResult($this->httpClient->request('POST', 'https://api.anthropic.com/v1/messages', [
47-
'headers' => [
48-
'x-api-key' => $this->apiKey,
49-
'anthropic-version' => $this->version,
50-
],
61+
'headers' => $headers,
5162
'json' => array_merge($options, $payload),
5263
]));
5364
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Bridge\Anthropic\Tests;
13+
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
17+
use Symfony\AI\Platform\Bridge\Anthropic\ModelClient;
18+
use Symfony\Component\HttpClient\MockHttpClient;
19+
use Symfony\Component\HttpClient\Response\JsonMockResponse;
20+
21+
#[CoversClass(ModelClient::class)]
22+
class ModelClientTest extends TestCase
23+
{
24+
private MockHttpClient $httpClient;
25+
private ModelClient $modelClient;
26+
private Claude $model;
27+
28+
protected function setUp(): void
29+
{
30+
$this->model = new Claude();
31+
}
32+
33+
public function testAnthropicBetaHeaderIsSetWithSingleBetaFeature()
34+
{
35+
$this->httpClient = new MockHttpClient(function ($method, $url, $options) {
36+
$this->assertEquals('POST', $method);
37+
$this->assertEquals('https://api.anthropic.com/v1/messages', $url);
38+
39+
$headers = $this->parseHeaders($options['headers']);
40+
41+
$this->assertArrayHasKey('anthropic-beta', $headers);
42+
$this->assertEquals('feature-1', $headers['anthropic-beta']);
43+
44+
return new JsonMockResponse('{"success": true}');
45+
});
46+
47+
$this->modelClient = new ModelClient($this->httpClient, 'test-api-key');
48+
49+
$options = ['beta_features' => ['feature-1']];
50+
$this->modelClient->request($this->model, ['message' => 'test'], $options);
51+
}
52+
53+
public function testAnthropicBetaHeaderIsSetWithMultipleBetaFeatures()
54+
{
55+
$this->httpClient = new MockHttpClient(function ($method, $url, $options) {
56+
$headers = $this->parseHeaders($options['headers']);
57+
58+
$this->assertArrayHasKey('anthropic-beta', $headers);
59+
$this->assertEquals('feature-1,feature-2,feature-3', $headers['anthropic-beta']);
60+
61+
return new JsonMockResponse('{"success": true}');
62+
});
63+
64+
$this->modelClient = new ModelClient($this->httpClient, 'test-api-key', '2023-06-01');
65+
66+
$options = ['beta_features' => ['feature-1', 'feature-2', 'feature-3']];
67+
$this->modelClient->request($this->model, ['message' => 'test'], $options);
68+
}
69+
70+
public function testAnthropicBetaHeaderIsNotSetWhenBetaFeaturesIsEmpty()
71+
{
72+
$this->httpClient = new MockHttpClient(function ($method, $url, $options) {
73+
$headers = $this->parseHeaders($options['headers']);
74+
75+
$this->assertArrayNotHasKey('anthropic-beta', $headers);
76+
77+
return new JsonMockResponse('{"success": true}');
78+
});
79+
80+
$this->modelClient = new ModelClient($this->httpClient, 'test-api-key', '2023-06-01');
81+
82+
$options = ['beta_features' => []];
83+
$this->modelClient->request($this->model, ['message' => 'test'], $options);
84+
}
85+
86+
public function testAnthropicBetaHeaderIsNotSetWhenBetaFeaturesIsNotProvided()
87+
{
88+
$this->httpClient = new MockHttpClient(function ($method, $url, $options) {
89+
$headers = $this->parseHeaders($options['headers']);
90+
91+
$this->assertArrayNotHasKey('anthropic-beta', $headers);
92+
93+
return new JsonMockResponse('{"success": true}');
94+
});
95+
96+
$this->modelClient = new ModelClient($this->httpClient, 'test-api-key', '2023-06-01');
97+
98+
$options = ['some_other_option' => 'value'];
99+
$this->modelClient->request($this->model, ['message' => 'test'], $options);
100+
}
101+
102+
private function parseHeaders(array $headers): array
103+
{
104+
$parsed = [];
105+
foreach ($headers as $header) {
106+
if (str_contains($header, ':')) {
107+
[$key, $value] = explode(':', $header, 2);
108+
$parsed[trim($key)] = trim($value);
109+
}
110+
}
111+
112+
return $parsed;
113+
}
114+
}

0 commit comments

Comments
 (0)