Skip to content

Commit a3ce02a

Browse files
committed
OpenAI Responses API
1 parent aee7a35 commit a3ce02a

15 files changed

+817
-1
lines changed

examples/openai/responses-stream.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Platform\Bridge\OpenAI\GPT;
14+
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory;
15+
use Symfony\AI\Platform\Bridge\OpenAI\Responses;
16+
use Symfony\AI\Platform\Message\Message;
17+
use Symfony\AI\Platform\Message\MessageBag;
18+
use Symfony\Component\Dotenv\Dotenv;
19+
20+
require_once dirname(__DIR__).'/vendor/autoload.php';
21+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
22+
23+
if (!isset($_SERVER['OPENAI_API_KEY'])) {
24+
echo 'Please set the OPENAI_API_KEY environment variable.'.\PHP_EOL;
25+
exit(1);
26+
}
27+
28+
$platform = PlatformFactory::create($_SERVER['OPENAI_API_KEY']);
29+
$model = new Responses(Responses::GPT_4O_MINI);
30+
31+
$agent = new Agent($platform, $model);
32+
$messages = new MessageBag(
33+
Message::forSystem('You are a thoughtful philosopher.'),
34+
Message::ofUser('What is the purpose of an ant?'),
35+
);
36+
$response = $agent->call($messages, [
37+
'stream' => true, // enable streaming of response text
38+
]);
39+
40+
foreach ($response->getContent() as $word) {
41+
echo $word;
42+
}
43+
echo \PHP_EOL;
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+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\StructuredOutput\Responses\ResponsesAgentProcessor;
14+
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory;
15+
use Symfony\AI\Platform\Bridge\OpenAI\Responses;
16+
use Symfony\AI\Platform\Message\Message;
17+
use Symfony\AI\Platform\Message\MessageBag;
18+
use Symfony\Component\Dotenv\Dotenv;
19+
20+
require_once dirname(__DIR__).'/vendor/autoload.php';
21+
(new Dotenv())->loadEnv(dirname(__DIR__) . '/.env');
22+
23+
if (!isset($_SERVER['OPENAI_API_KEY'])) {
24+
echo 'Please set the OPENAI_API_KEY environment variable.' . \PHP_EOL;
25+
exit(1);
26+
}
27+
28+
$platform = PlatformFactory::create($_SERVER['OPENAI_API_KEY']);
29+
$model = new Responses(Responses::GPT_4O_MINI);
30+
31+
$structuredOutputProcessor = new ResponsesAgentProcessor();
32+
33+
$agent = new Agent($platform, $model, [$structuredOutputProcessor], [$structuredOutputProcessor]);
34+
$messages = new MessageBag(
35+
Message::ofUser('What date and time is it?'),
36+
);
37+
$response = $agent->call($messages, [
38+
'text' => [
39+
'format' => [
40+
'type' => 'json_schema',
41+
'name' => 'clock',
42+
'schema' => [
43+
'type' => 'object',
44+
'properties' => [
45+
'date' => [
46+
'type' => 'string',
47+
'description' => 'The current date in the format YYYY-MM-DD.',
48+
],
49+
'time' => [
50+
'type' => 'string',
51+
'description' => 'The current time in the format HH:MM:SS.',
52+
],
53+
],
54+
'required' => ['date', 'time'],
55+
'additionalProperties' => false,
56+
],
57+
],
58+
],
59+
]);
60+
61+
dump($response->getContent());
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\StructuredOutput\Responses\ResponsesAgentProcessor;
14+
use Symfony\AI\Fixtures\StructuredOutput\MathReasoning;
15+
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory;
16+
use Symfony\AI\Platform\Bridge\OpenAI\Responses;
17+
use Symfony\AI\Platform\Message\Message;
18+
use Symfony\AI\Platform\Message\MessageBag;
19+
use Symfony\Component\Dotenv\Dotenv;
20+
21+
require_once dirname(__DIR__).'/vendor/autoload.php';
22+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
23+
24+
if (!isset($_SERVER['OPENAI_API_KEY'])) {
25+
echo 'Please set the OPENAI_API_KEY environment variable.'.\PHP_EOL;
26+
exit(1);
27+
}
28+
29+
$platform = PlatformFactory::create($_SERVER['OPENAI_API_KEY']);
30+
$model = new Responses(Responses::GPT_4O_MINI);
31+
32+
$processor = new ResponsesAgentProcessor();
33+
34+
$agent = new Agent($platform, $model, [$processor], [$processor]);
35+
$messages = new MessageBag(
36+
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
37+
Message::ofUser('how can I solve 8x + 7 = -23'),
38+
);
39+
$response = $agent->call($messages, [
40+
'output_structure' => MathReasoning::class,
41+
]);
42+
43+
dump($response->getContent());
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory;
14+
use Symfony\AI\Platform\Bridge\OpenAI\Responses;
15+
use Symfony\AI\Platform\Message\Message;
16+
use Symfony\AI\Platform\Message\MessageBag;
17+
use Symfony\Component\Dotenv\Dotenv;
18+
19+
require_once dirname(__DIR__).'/vendor/autoload.php';
20+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
21+
22+
if (!isset($_SERVER['OPENAI_API_KEY'])) {
23+
echo 'Please set the OPENAI_API_KEY environment variable.'.\PHP_EOL;
24+
exit(1);
25+
}
26+
27+
$platform = PlatformFactory::create($_SERVER['OPENAI_API_KEY']);
28+
$model = new Responses(Responses::GPT_4O_MINI);
29+
30+
$agent = new Agent($platform, $model);
31+
$messages = new MessageBag(Message::ofUser('What was a positive news story from today?'));
32+
$response = $agent->call($messages, [
33+
'stream' => true, // enable streaming of response text
34+
'tools' => [
35+
[
36+
'type' => 'web_search_preview',
37+
],
38+
],
39+
]);
40+
41+
foreach ($response->getContent() as $word) {
42+
echo $word;
43+
}
44+
45+
echo \PHP_EOL;

examples/openai/responses.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory;
14+
use Symfony\AI\Platform\Bridge\OpenAI\Responses;
15+
use Symfony\AI\Platform\Message\Message;
16+
use Symfony\AI\Platform\Message\MessageBag;
17+
use Symfony\Component\Dotenv\Dotenv;
18+
19+
require_once dirname(__DIR__).'/vendor/autoload.php';
20+
(new Dotenv())->loadEnv(dirname(__DIR__) . '/.env');
21+
22+
if (!isset($_SERVER['OPENAI_API_KEY'])) {
23+
echo 'Please set the OPENAI_API_KEY environment variable.' . \PHP_EOL;
24+
exit(1);
25+
}
26+
27+
$platform = PlatformFactory::create($_SERVER['OPENAI_API_KEY']);
28+
$model = new Responses(Responses::GPT_4O_MINI, [
29+
'temperature' => 0.5, // default options for the model
30+
]);
31+
32+
$agent = new Agent($platform, $model);
33+
$messages = new MessageBag(
34+
Message::forSystem('You are a pirate and you write funny.'),
35+
Message::ofUser('What is the Symfony framework?'),
36+
);
37+
$response = $agent->call($messages, [
38+
'max_output_tokens' => 500, // specific options just for this call
39+
]);
40+
41+
echo $response->getContent().\PHP_EOL;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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\Agent\StructuredOutput\Responses;
13+
14+
use Symfony\AI\Agent\Exception\InvalidArgumentException;
15+
use Symfony\AI\Agent\Exception\MissingModelSupportException;
16+
use Symfony\AI\Agent\Input;
17+
use Symfony\AI\Agent\InputProcessorInterface;
18+
use Symfony\AI\Agent\Output;
19+
use Symfony\AI\Agent\OutputProcessorInterface;
20+
use Symfony\AI\Platform\Capability;
21+
use Symfony\AI\Platform\Response\ObjectResponse;
22+
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
23+
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
24+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
25+
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
26+
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
27+
use Symfony\Component\Serializer\Serializer;
28+
use Symfony\Component\Serializer\SerializerInterface;
29+
30+
/**
31+
* @author Christopher Hertel <[email protected]>
32+
*/
33+
final class ResponsesAgentProcessor implements InputProcessorInterface, OutputProcessorInterface
34+
{
35+
private string $outputStructure;
36+
37+
public function __construct(
38+
private readonly ResponsesResponseFormatFactoryInterface $responseFormatFactory = new ResponsesResponseFormatFactory(),
39+
private ?SerializerInterface $serializer = null,
40+
)
41+
{
42+
if (null === $this->serializer) {
43+
$propertyInfo = new PropertyInfoExtractor([], [new PhpDocExtractor()]);
44+
$normalizers = [new ObjectNormalizer(propertyTypeExtractor: $propertyInfo), new ArrayDenormalizer()];
45+
$this->serializer = new Serializer($normalizers, [new JsonEncoder()]);
46+
}
47+
}
48+
49+
public function processInput(Input $input): void
50+
{
51+
$options = $input->getOptions();
52+
53+
if (!isset($options['output_structure'])) {
54+
return;
55+
}
56+
57+
if (!$input->model->supports(Capability::OUTPUT_STRUCTURED)) {
58+
throw MissingModelSupportException::forStructuredOutput($input->model::class);
59+
}
60+
61+
if (true === ($options['stream'] ?? false)) {
62+
throw new InvalidArgumentException('Streamed responses are not supported for structured output');
63+
}
64+
65+
$options['text'] = $this->responseFormatFactory->create($options['output_structure']);
66+
67+
$this->outputStructure = $options['output_structure'];
68+
unset($options['output_structure']);
69+
70+
$input->setOptions($options);
71+
}
72+
73+
public function processOutput(Output $output): void
74+
{
75+
$options = $output->options;
76+
77+
if ($output->response instanceof ObjectResponse) {
78+
return;
79+
}
80+
81+
if (!isset($options['text']['format'])) {
82+
return;
83+
}
84+
85+
if (!isset($this->outputStructure)) {
86+
$output->response = new ObjectResponse(json_decode($output->response->getContent(), true));
87+
88+
return;
89+
}
90+
91+
$output->response = new ObjectResponse(
92+
$this->serializer->deserialize($output->response->getContent(), $this->outputStructure, 'json')
93+
);
94+
}
95+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Agent\StructuredOutput\Responses;
13+
14+
use Symfony\AI\Platform\Contract\JsonSchema\Factory;
15+
use function Symfony\Component\String\u;
16+
17+
/**
18+
* @author Christopher Hertel <[email protected]>
19+
*/
20+
final readonly class ResponsesResponseFormatFactory implements ResponsesResponseFormatFactoryInterface
21+
{
22+
public function __construct(
23+
private Factory $schemaFactory = new Factory(),
24+
) {
25+
}
26+
27+
public function create(string $responseClass): array
28+
{
29+
return [
30+
'format' => [
31+
'type' => 'json_schema',
32+
'name' => u($responseClass)->afterLast('\\')->toString(),
33+
'strict' => true,
34+
'schema' => $this->schemaFactory->buildProperties($responseClass),
35+
],
36+
];
37+
}
38+
}

0 commit comments

Comments
 (0)