Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions examples/openai/structured-output-list-of-polymorphic-items.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\StructuredOutput\AgentProcessor;
use Symfony\AI\Fixtures\StructuredOutput\PolymorphicType\ListOfPolymorphicTypesDto;
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

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

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
$model = new Gpt(Gpt::GPT_4O_MINI);

$processor = new AgentProcessor();
$agent = new Agent($platform, $model, [$processor], [$processor], logger: logger());
$messages = new MessageBag(
Message::forSystem('You are a persona data collector! Return all the data you can gather from the user input.'),
Message::ofUser('Hi! My name is John Doe, I am 30 years old and I live in Paris.'),
);
$result = $agent->call($messages, ['output_structure' => ListOfPolymorphicTypesDto::class]);

dump($result->getContent());
36 changes: 36 additions & 0 deletions examples/openai/structured-output-union-types.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\StructuredOutput\AgentProcessor;
use Symfony\AI\Fixtures\StructuredOutput\UnionType\UnionTypeDto;
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

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

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
$model = new Gpt(Gpt::GPT_4O_MINI);

$processor = new AgentProcessor();
$agent = new Agent($platform, $model, [$processor], [$processor], logger: logger());
$messages = new MessageBag(
Message::forSystem(<<<PROMPT
You are a time assistant! You can provide time either as a unix timestamp or as a human readable time format.
If you don't know the time, return null.
PROMPT),
Message::ofUser('What is the current time?'),
);
$result = $agent->call($messages, ['output_structure' => UnionTypeDto::class]);

dump($result->getContent());
24 changes: 24 additions & 0 deletions fixtures/StructuredOutput/PolymorphicType/ListItemAge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Fixtures\StructuredOutput\PolymorphicType;

use Symfony\AI\Platform\Contract\JsonSchema\Attribute\With;

final class ListItemAge implements ListItemDiscriminator
{
public function __construct(
public int $age,
#[With(pattern: '^age$')]
public string $type = 'age',
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Fixtures\StructuredOutput\PolymorphicType;

use Symfony\Component\Serializer\Attribute\DiscriminatorMap;

#[DiscriminatorMap(
typeProperty: 'type',
mapping: [
'name' => ListItemName::class,
'age' => ListItemAge::class,
]
)]
/**
* @property string $type
*
* With the PHP 8.4^ you can replace the property annotation with a property hook:
* public string $type {
* get;
* }
*/
interface ListItemDiscriminator
{
}
24 changes: 24 additions & 0 deletions fixtures/StructuredOutput/PolymorphicType/ListItemName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Fixtures\StructuredOutput\PolymorphicType;

use Symfony\AI\Platform\Contract\JsonSchema\Attribute\With;

class ListItemName implements ListItemDiscriminator
{
public function __construct(
public string $name,
#[With(pattern: '^name$')]
public string $type = 'name',
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Fixtures\StructuredOutput\PolymorphicType;

/**
* Useful when you need to tell an agent that any of the items are acceptable types.
* Real life example could be a list of possible analytical data visualization like charts or tables.
*/
final class ListOfPolymorphicTypesDto
{
/**
* @param list<ListItemDiscriminator> $items
*/
public function __construct(public array $items)
{
}
}
19 changes: 19 additions & 0 deletions fixtures/StructuredOutput/UnionType/HumanReadableTimeUnion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Fixtures\StructuredOutput\UnionType;

final class HumanReadableTimeUnion
{
public function __construct(public string $readableTime)
{
}
}
20 changes: 20 additions & 0 deletions fixtures/StructuredOutput/UnionType/UnionTypeDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Fixtures\StructuredOutput\UnionType;

final class UnionTypeDto
{
public function __construct(
public UnixTimestampUnion|HumanReadableTimeUnion|null $time,
) {
}
}
19 changes: 19 additions & 0 deletions fixtures/StructuredOutput/UnionType/UnixTimestampUnion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Fixtures\StructuredOutput\UnionType;

final class UnixTimestampUnion
{
public function __construct(public int $timestamp)
{
}
}
1 change: 1 addition & 0 deletions src/agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
0.1
---

* Add support for union types and polymorphic types via DiscriminatorMap
* Add Agent class as central orchestrator for AI interactions through the Platform component
* Add input/output processing pipeline:
- `InputProcessorInterface` for pre-processing messages and options
Expand Down
21 changes: 19 additions & 2 deletions src/agent/src/StructuredOutput/AgentProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@
use Symfony\AI\Platform\Capability;
use Symfony\AI\Platform\Result\ObjectResult;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;
Expand All @@ -39,8 +44,20 @@ public function __construct(
private ?SerializerInterface $serializer = null,
) {
if (null === $this->serializer) {
$propertyInfo = new PropertyInfoExtractor([], [new PhpDocExtractor()]);
$normalizers = [new ObjectNormalizer(propertyTypeExtractor: $propertyInfo), new ArrayDenormalizer()];
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
$propertyInfo = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);

$normalizers = [
new BackedEnumNormalizer(),
new ObjectNormalizer(
classMetadataFactory: $classMetadataFactory,
propertyTypeExtractor: $propertyInfo,
classDiscriminatorResolver: $discriminator
),
new ArrayDenormalizer(),
];

$this->serializer = new Serializer($normalizers, [new JsonEncoder()]);
}
}
Expand Down
Loading