Skip to content

Commit 7b34daf

Browse files
Address PR review feedback from chr-hertel
- Move City class from example to fixtures for cleaner code - Use dump() instead of var_dump() for consistency - Use Platform's InvalidArgumentException instead of native PHP exception
1 parent 54c8a96 commit 7b34daf

File tree

4 files changed

+81
-15
lines changed

4 files changed

+81
-15
lines changed

docs/components/platform.rst

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,11 +489,85 @@ top this example uses the feature through the agent to leverage tool calling::
489489

490490
dump($result->getContent()); // returns an array
491491

492+
Populating Existing Objects
493+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
494+
495+
Instead of creating new object instances, you can pass existing object instances to have the AI populate missing data.
496+
This is useful when you have partially filled objects and want the AI to complete them::
497+
498+
use Symfony\AI\Platform\Message\Message;
499+
use Symfony\AI\Platform\Message\MessageBag;
500+
501+
// Create a partially populated object
502+
class City
503+
{
504+
public function __construct(
505+
public ?string $name = null,
506+
public ?int $population = null,
507+
public ?string $country = null,
508+
public ?string $mayor = null,
509+
) {}
510+
}
511+
512+
$city = new City(name: 'Berlin');
513+
514+
// Pass the object instance to response_format
515+
$messages = new MessageBag(
516+
Message::forSystem('You are a helpful assistant that provides information about cities.'),
517+
Message::ofUser('Please provide the population, country, and current mayor for Berlin.'),
518+
);
519+
520+
$result = $platform->invoke('gpt-4o-mini', $messages, [
521+
'response_format' => $city, // Pass the instance instead of the class
522+
]);
523+
524+
// The same object instance is returned with populated fields
525+
$populatedCity = $result->asObject();
526+
assert($city === $populatedCity); // Same object!
527+
528+
echo $city->population; // 3500000
529+
echo $city->country; // Germany
530+
echo $city->mayor; // Kai Wegner
531+
532+
The AI will populate the missing fields while preserving any existing values. This is particularly useful for:
533+
534+
- Enriching partial data from databases
535+
- Updating incomplete records
536+
- Progressive data collection workflows
537+
538+
Object Context in Messages
539+
~~~~~~~~~~~~~~~~~~~~~~~~~~
540+
541+
You can also pass objects directly to user messages for automatic serialization as context.
542+
The object will be JSON-serialized and included in the message content::
543+
544+
use Symfony\AI\Platform\Message\Message;
545+
use Symfony\AI\Platform\Message\MessageBag;
546+
547+
$city = new City(name: 'Berlin'); // Partial object
548+
549+
// Pass object as last parameter to Message::ofUser()
550+
$messages = new MessageBag(
551+
Message::forSystem('You are a helpful assistant that provides information about cities.'),
552+
Message::ofUser('Research missing data for this city', $city), // Object as context
553+
);
554+
555+
$result = $platform->invoke('gpt-4o-mini', $messages, [
556+
'response_format' => $city, // Populate the same object
557+
]);
558+
559+
// The AI sees the serialized object state and fills in missing data
560+
$populatedCity = $result->asObject();
561+
562+
The object is serialized to JSON and appended to the message, providing the AI with the current state of the object.
563+
This makes it clear what data is already present and what needs to be filled.
564+
492565
Code Examples
493566
~~~~~~~~~~~~~
494567

495568
* `Structured Output with PHP class`_
496569
* `Structured Output with array`_
570+
* `Populating existing objects`_
497571

498572
Server Tools
499573
------------
@@ -748,6 +822,7 @@ Code Examples
748822
.. _`Embeddings with Mistral`: https://github.com/symfony/ai/blob/main/examples/mistral/embeddings.php
749823
.. _`Structured Output with PHP class`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-math.php
750824
.. _`Structured Output with array`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-clock.php
825+
.. _`Populating existing objects`: https://github.com/symfony/ai/blob/main/examples/platform/structured-output-populate-object.php
751826
.. _`Parallel GPT Calls`: https://github.com/symfony/ai/blob/main/examples/misc/parallel-chat-gpt.php
752827
.. _`Parallel Embeddings Calls`: https://github.com/symfony/ai/blob/main/examples/misc/parallel-embeddings.php
753828
.. _`LM Studio`: https://lmstudio.ai/

examples/platform/structured-output-populate-object.php

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,11 @@
1313
use Symfony\AI\Platform\Message\Message;
1414
use Symfony\AI\Platform\Message\MessageBag;
1515
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
16+
use Symfony\AI\Platform\Tests\Fixtures\StructuredOutput\City;
1617
use Symfony\Component\EventDispatcher\EventDispatcher;
1718

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

20-
final class City
21-
{
22-
public function __construct(
23-
public ?string $name = null,
24-
public ?int $population = null,
25-
public ?string $country = null,
26-
public ?string $mayor = null,
27-
) {
28-
}
29-
}
30-
3121
$dispatcher = new EventDispatcher();
3222
$dispatcher->addSubscriber(new PlatformSubscriber());
3323

@@ -53,4 +43,4 @@ public function __construct(
5343

5444
// Verify that the same object instance was populated
5545
echo "\nObject identity preserved: ";
56-
var_dump($city === $result->asObject());
46+
dump($city === $result->asObject());

src/platform/src/Message/Message.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\AI\Platform\Message;
1313

14+
use Symfony\AI\Platform\Exception\InvalidArgumentException;
1415
use Symfony\AI\Platform\Message\Content\ContentInterface;
1516
use Symfony\AI\Platform\Message\Content\Text;
1617
use Symfony\AI\Platform\Result\ToolCall;
@@ -55,7 +56,7 @@ public static function ofUser(mixed ...$content): UserMessage
5556

5657
foreach ($content as $entry) {
5758
if (!\is_string($entry) && !$entry instanceof \Stringable && !$entry instanceof ContentInterface) {
58-
throw new \InvalidArgumentException(\sprintf('Content must be string, Stringable, or ContentInterface, "%s" given.', get_debug_type($entry)));
59+
throw new InvalidArgumentException(\sprintf('Content must be string, Stringable, or ContentInterface, "%s" given.', get_debug_type($entry)));
5960
}
6061
}
6162

src/platform/tests/Message/MessageTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,15 +222,15 @@ public function testCreateUserMessageWithContentInterfaceIsNotTreatedAsObject()
222222

223223
public function testCreateUserMessageThrowsExceptionForInvalidContentType()
224224
{
225-
$this->expectException(\InvalidArgumentException::class);
225+
$this->expectException(\Symfony\AI\Platform\Exception\InvalidArgumentException::class);
226226
$this->expectExceptionMessage('Content must be string, Stringable, or ContentInterface');
227227

228228
Message::ofUser('Hello', ['invalid' => 'array']);
229229
}
230230

231231
public function testCreateUserMessageWithObjectAndInvalidTypeThrowsException()
232232
{
233-
$this->expectException(\InvalidArgumentException::class);
233+
$this->expectException(\Symfony\AI\Platform\Exception\InvalidArgumentException::class);
234234
$this->expectExceptionMessage('Content must be string, Stringable, or ContentInterface');
235235

236236
$city = new City(name: 'London');

0 commit comments

Comments
 (0)