Skip to content

Commit eadaa97

Browse files
committed
Create tool (call) normalizers for Responses API
Since we're migration from chat completions to Responses, which has a differnt data structure for function calls
1 parent cc1b7cf commit eadaa97

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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\OpenAi\Contract\Gpt;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
16+
use Symfony\AI\Platform\Model;
17+
use Symfony\AI\Platform\Result\ToolCall;
18+
19+
final class ToolCallNormalizer extends ModelContractNormalizer
20+
{
21+
/**
22+
* @param ToolCall $data
23+
*
24+
* @return array{
25+
* arguments: string,
26+
* call_id: string,
27+
* name: string,
28+
* type: 'function_call'
29+
* }
30+
*/
31+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
32+
{
33+
return [
34+
'arguments' => json_encode($data->getArguments()),
35+
'call_id' => $data->getId(),
36+
'name' => $data->getName(),
37+
'type' => 'function_call',
38+
];
39+
}
40+
41+
protected function supportedDataClass(): string
42+
{
43+
return ToolCall::class;
44+
}
45+
46+
protected function supportsModel(Model $model): bool
47+
{
48+
return $model instanceof Gpt;
49+
}
50+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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\OpenAi\Contract\Gpt;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Contract\JsonSchema\Factory;
16+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
17+
use Symfony\AI\Platform\Model;
18+
use Symfony\AI\Platform\Tool\Tool;
19+
20+
/**
21+
* @phpstan-import-type JsonSchema from Factory
22+
*
23+
* @author Christopher Hertel <[email protected]>
24+
*/
25+
class ToolNormalizer extends ModelContractNormalizer
26+
{
27+
/**
28+
* @param Tool $data
29+
*
30+
* @return array{
31+
* type: 'function',
32+
* name: string,
33+
* description: string,
34+
* parameters?: JsonSchema
35+
* }
36+
*/
37+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
38+
{
39+
$function = [
40+
'type' => 'function',
41+
'name' => $data->getName(),
42+
'description' => $data->getDescription(),
43+
];
44+
45+
if (null !== $data->getParameters()) {
46+
$function['parameters'] = $data->getParameters();
47+
}
48+
49+
return $function;
50+
}
51+
52+
protected function supportedDataClass(): string
53+
{
54+
return Tool::class;
55+
}
56+
57+
protected function supportsModel(Model $model): bool
58+
{
59+
return $model instanceof Gpt;
60+
}
61+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Symfony\AI\Platform\Tests\Bridge\OpenAi\Contract\Gpt;
4+
5+
use PHPUnit\Framework\Attributes\DataProvider;
6+
use Symfony\AI\Platform\Bridge\Gemini\Gemini;
7+
use Symfony\AI\Platform\Bridge\OpenAi\Contract\Gpt\ToolCallNormalizer;
8+
use PHPUnit\Framework\TestCase;
9+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
10+
use Symfony\AI\Platform\Contract;
11+
use Symfony\AI\Platform\Message\Content\Text;
12+
use Symfony\AI\Platform\Model;
13+
use Symfony\AI\Platform\Result\ToolCall;
14+
15+
class ToolCallNormalizerTest extends TestCase
16+
{
17+
public function testNormalize()
18+
{
19+
$toolCall = new ToolCall('some-id', 'roll-die', ['sides' => 24]);
20+
21+
$actual = (new ToolCallNormalizer())->normalize($toolCall, null, [Contract::CONTEXT_MODEL => new Gpt('o3')]);
22+
$this->assertEquals([
23+
'arguments' => json_encode($toolCall->getArguments()),
24+
'call_id' => $toolCall->getId(),
25+
'name' => $toolCall->getName(),
26+
'type' => 'function_call',
27+
], $actual);
28+
}
29+
30+
#[DataProvider('supportsNormalizationProvider')]
31+
public function testSupportsNormalization(mixed $data, Model $model, bool $expected)
32+
{
33+
$this->assertSame(
34+
$expected,
35+
(new ToolCallNormalizer())->supportsNormalization($data, null, [Contract::CONTEXT_MODEL => $model])
36+
);
37+
}
38+
39+
public static function supportsNormalizationProvider(): \Generator
40+
{
41+
$toolCall = new ToolCall('some-id', 'roll-die', ['sides' => 24]);
42+
$gpt = new Gpt('o3');
43+
44+
yield 'supported' => [$toolCall, $gpt, true];
45+
yield 'unsupported model' => [$toolCall, new Gemini('foo'), false];
46+
yield 'unsupported data' => [new Text('foo'), $gpt, false];
47+
}
48+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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\Tests\Bridge\OpenAi\Contract\Gpt;
13+
14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\AI\Platform\Bridge\Gemini\Gemini;
17+
use Symfony\AI\Platform\Bridge\OpenAi\Contract\Gpt\ToolNormalizer;
18+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
19+
use Symfony\AI\Platform\Contract;
20+
use Symfony\AI\Platform\Message\Content\Text;
21+
use Symfony\AI\Platform\Model;
22+
use Symfony\AI\Platform\Tool\ExecutionReference;
23+
use Symfony\AI\Platform\Tool\Tool;
24+
25+
class ToolNormalizerTest extends TestCase
26+
{
27+
#[DataProvider('normalizeProvider')]
28+
public function testNormalize(array $expected, Tool $tool)
29+
{
30+
$actual = (new ToolNormalizer())->normalize($tool, null, [Contract::CONTEXT_MODEL => new Gpt('o3')]);
31+
$this->assertEquals($expected, $actual);
32+
}
33+
34+
public static function normalizeProvider(): \Generator
35+
{
36+
$tool = new Tool(new ExecutionReference('Foo\Bar'), 'bar', 'description');
37+
38+
$expected = [
39+
'type' => 'function',
40+
'name' => $tool->getName(),
41+
'description' => $tool->getDescription(),
42+
];
43+
44+
$parameters = ['foo' => 'bar'];
45+
46+
yield 'no parameters' => [$expected, $tool];
47+
yield 'with parameters' => [
48+
array_merge($expected, ['parameters' => $parameters]),
49+
new Tool(new ExecutionReference('Foo\Bar'), 'bar', 'description', $parameters),
50+
];
51+
}
52+
53+
#[DataProvider('supportsNormalizationProvider')]
54+
public function testSupportsNormalization(mixed $data, Model $model, bool $expected)
55+
{
56+
$this->assertSame(
57+
$expected,
58+
(new ToolNormalizer())->supportsNormalization($data, null, [Contract::CONTEXT_MODEL => $model])
59+
);
60+
}
61+
62+
public static function supportsNormalizationProvider(): \Generator
63+
{
64+
$tool = new Tool(new ExecutionReference('Foo\Bar'), 'bar', 'description');
65+
$gpt = new Gpt('o3');
66+
67+
yield 'supported' => [$tool, $gpt, true];
68+
yield 'unsupported model' => [$tool, new Gemini('foo'), false];
69+
yield 'unsupported data' => [new Text('foo'), $gpt, false];
70+
}
71+
}

0 commit comments

Comments
 (0)