Skip to content

Commit 41955d3

Browse files
committed
feature #871 [Platform] Migrate OpenAI to Responses API (paulinevos)
This PR was squashed before being merged into the main branch. Discussion ---------- [Platform] Migrate OpenAI to Responses API | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | yes | Issues | Fix #796 | License | MIT This PR migrates OpenAI from chat completions (deprecated) to the Responses API. I tried to keep the scope small and just support everything already in place as seamlessly as possible. The Responses API does offer some more functionality that we should look into supporting (e.g. MCP tool calling, expanded reasoning functionality), but I found that OOS for this migration. One thing to note it that the Responses API as of now lacks Audio capabilities ("coming soon", according to the docs), so that example is temporarily disabled and so is the `Gpt` audio capability for now. **Note: my PRs are best reviewed commit by commit** Commits ------- 82ec95b [Platform] Migrate OpenAI to Responses API
2 parents 634a0a1 + 82ec95b commit 41955d3

29 files changed

+1330
-185
lines changed

examples/openai/audio-input.php

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,19 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
13-
use Symfony\AI\Platform\Message\Content\Audio;
14-
use Symfony\AI\Platform\Message\Message;
15-
use Symfony\AI\Platform\Message\MessageBag;
12+
use Symfony\AI\Platform\Exception\RuntimeException;
1613

1714
require_once dirname(__DIR__).'/bootstrap.php';
1815

19-
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
20-
21-
$messages = new MessageBag(
22-
Message::ofUser(
23-
'What is this recording about?',
24-
Audio::fromFile(dirname(__DIR__, 2).'/fixtures/audio.mp3'),
25-
),
26-
);
27-
$result = $platform->invoke('gpt-4o-audio-preview', $messages);
28-
29-
echo $result->asText().\PHP_EOL;
16+
throw new RuntimeException('This example is temporarily unavailable due to migration to Responses API (which does not support audio yet).');
17+
// $platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
18+
//
19+
// $messages = new MessageBag(
20+
// Message::ofUser(
21+
// 'What is this recording about?',
22+
// Audio::fromFile(dirname(__DIR__, 2).'/fixtures/audio.mp3'),
23+
// ),
24+
// );
25+
// $result = $platform->invoke('gpt-4o-audio-preview', $messages);
26+
//
27+
// echo $result->asText().\PHP_EOL;

examples/openai/chat-with-string-options.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
Message::forSystem('You are a pirate and you write funny.'),
2222
Message::ofUser('What is the Symfony framework?'),
2323
);
24-
$result = $platform->invoke('gpt-4o-mini?max_tokens=7', $messages);
24+
$result = $platform->invoke('gpt-4o-mini?max_output_tokens=16', $messages);
2525

2626
echo $result->asText().\PHP_EOL;

examples/openai/chat.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
Message::ofUser('What is the Symfony framework?'),
2323
);
2424
$result = $platform->invoke('gpt-4o-mini', $messages, [
25-
'max_tokens' => 500, // specific options just for this call
25+
'max_output_tokens' => 500, // specific options just for this call
2626
]);
2727

2828
echo $result->asText().\PHP_EOL;

examples/openai/token-metadata.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
Message::ofUser('What is the Symfony framework?'),
2626
);
2727
$result = $agent->call($messages, [
28-
'max_tokens' => 500, // specific options just for this call
28+
'max_output_tokens' => 500, // specific options just for this call
2929
]);
3030

3131
print_token_usage($result->getMetadata());
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Message;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
16+
use Symfony\AI\Platform\Message\AssistantMessage;
17+
use Symfony\AI\Platform\Model;
18+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
19+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
20+
21+
/**
22+
* @author Guillermo Lengemann <guillermo.lengemann@gmail.com>
23+
*/
24+
final class AssistantMessageNormalizer extends ModelContractNormalizer implements NormalizerAwareInterface
25+
{
26+
use NormalizerAwareTrait;
27+
28+
/**
29+
* @param AssistantMessage $data
30+
*
31+
* @return array{
32+
* role: 'assistant',
33+
* type: 'message',
34+
* content: ?string
35+
* }
36+
*/
37+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
38+
{
39+
if ($data->hasToolCalls()) {
40+
return $this->normalizer->normalize($data->getToolCalls(), $format, $context);
41+
}
42+
43+
return [
44+
'role' => $data->getRole()->value,
45+
'type' => 'message',
46+
'content' => $data->getContent(),
47+
];
48+
}
49+
50+
protected function supportedDataClass(): string
51+
{
52+
return AssistantMessage::class;
53+
}
54+
55+
protected function supportsModel(Model $model): bool
56+
{
57+
return $model instanceof Gpt;
58+
}
59+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Message\Content;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Capability;
16+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
17+
use Symfony\AI\Platform\Message\Content\Document;
18+
use Symfony\AI\Platform\Message\Content\File;
19+
use Symfony\AI\Platform\Model;
20+
21+
/**
22+
* @author Guillermo Lengemann <guillermo.lengemann@gmail.com>
23+
*/
24+
class DocumentNormalizer extends ModelContractNormalizer
25+
{
26+
/**
27+
* @param File $data
28+
*
29+
* @return array{type: 'input_file', filename: string, file_data: string}
30+
*/
31+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
32+
{
33+
return [
34+
'type' => 'input_file',
35+
'filename' => $data->getFilename(),
36+
'file_data' => $data->asDataUrl(),
37+
];
38+
}
39+
40+
protected function supportedDataClass(): string
41+
{
42+
return Document::class;
43+
}
44+
45+
protected function supportsModel(Model $model): bool
46+
{
47+
return $model instanceof Gpt && $model->supports(Capability::INPUT_PDF);
48+
}
49+
}
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 <fabien@symfony.com>
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\Message\Content;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Capability;
16+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
17+
use Symfony\AI\Platform\Message\Content\Image;
18+
use Symfony\AI\Platform\Model;
19+
20+
/**
21+
* See: https://platform.openai.com/docs/guides/images-vision#giving-a-model-images-as-input.
22+
*/
23+
final class ImageNormalizer extends ModelContractNormalizer
24+
{
25+
/**
26+
* @param Image $data
27+
*
28+
* @return array{
29+
* type: 'input_image',
30+
* image_url: string
31+
* }
32+
*/
33+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
34+
{
35+
return [
36+
'type' => 'input_image',
37+
'image_url' => $data->asDataUrl(),
38+
];
39+
}
40+
41+
protected function supportedDataClass(): string
42+
{
43+
return Image::class;
44+
}
45+
46+
protected function supportsModel(Model $model): bool
47+
{
48+
return $model instanceof Gpt && $model->supports(Capability::INPUT_IMAGE);
49+
}
50+
}
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 <fabien@symfony.com>
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\Message\Content;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Capability;
16+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
17+
use Symfony\AI\Platform\Message\Content\ImageUrl;
18+
use Symfony\AI\Platform\Model;
19+
20+
/**
21+
* See: https://platform.openai.com/docs/guides/images-vision#giving-a-model-images-as-input.
22+
*/
23+
final class ImageUrlNormalizer extends ModelContractNormalizer
24+
{
25+
/**
26+
* @param ImageUrl $data
27+
*
28+
* @return array{
29+
* type: 'input_image',
30+
* image_url: string
31+
* }
32+
*/
33+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
34+
{
35+
return [
36+
'type' => 'input_image',
37+
'image_url' => $data->getUrl(),
38+
];
39+
}
40+
41+
protected function supportedDataClass(): string
42+
{
43+
return ImageUrl::class;
44+
}
45+
46+
protected function supportsModel(Model $model): bool
47+
{
48+
return $model instanceof Gpt && $model->supports(Capability::INPUT_IMAGE);
49+
}
50+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Message\Content;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
16+
use Symfony\AI\Platform\Message\Content\Text;
17+
use Symfony\AI\Platform\Model;
18+
19+
/**
20+
* See: https://platform.openai.com/docs/guides/images-vision#giving-a-model-images-as-input.
21+
*/
22+
final class TextNormalizer extends ModelContractNormalizer
23+
{
24+
/**
25+
* @param Text $data
26+
*
27+
* @return array{
28+
* type: 'input_text',
29+
* text: string
30+
* }
31+
*/
32+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
33+
{
34+
return [
35+
'type' => 'input_text',
36+
'text' => $data->getText(),
37+
];
38+
}
39+
40+
protected function supportedDataClass(): string
41+
{
42+
return Text::class;
43+
}
44+
45+
protected function supportsModel(Model $model): bool
46+
{
47+
return $model instanceof Gpt;
48+
}
49+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Message;
13+
14+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15+
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
16+
use Symfony\AI\Platform\Message\AssistantMessage;
17+
use Symfony\AI\Platform\Message\MessageBag;
18+
use Symfony\AI\Platform\Model;
19+
use Symfony\Component\Serializer\Exception\ExceptionInterface;
20+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
21+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
22+
23+
/**
24+
* @author Pauline Vos <pauline.vos@mongodb.com>
25+
*/
26+
final class MessageBagNormalizer extends ModelContractNormalizer implements NormalizerAwareInterface
27+
{
28+
use NormalizerAwareTrait;
29+
30+
/**
31+
* @param MessageBag $data
32+
*
33+
* @return array{
34+
* input: array<string, mixed>,
35+
* model?: string,
36+
* system?: string,
37+
* }
38+
*
39+
* @throws ExceptionInterface
40+
*/
41+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
42+
{
43+
$messages['input'] = [];
44+
45+
foreach ($data->withoutSystemMessage()->getMessages() as $message) {
46+
$normalized = $this->normalizer->normalize($message, $format, $context);
47+
48+
if ($message instanceof AssistantMessage && $message->hasToolCalls()) {
49+
$messages['input'] = array_merge($messages['input'], $normalized);
50+
continue;
51+
}
52+
53+
$messages['input'][] = $normalized;
54+
}
55+
56+
if ($data->getSystemMessage()) {
57+
$messages['instructions'] = $data->getSystemMessage()->getContent();
58+
}
59+
60+
return $messages;
61+
}
62+
63+
protected function supportedDataClass(): string
64+
{
65+
return MessageBag::class;
66+
}
67+
68+
protected function supportsModel(Model $model): bool
69+
{
70+
return $model instanceof Gpt;
71+
}
72+
}

0 commit comments

Comments
 (0)