Skip to content

Commit 8110d13

Browse files
committed
Shift structured output from Agent to Platform component
1 parent bafc7b9 commit 8110d13

File tree

56 files changed

+625
-409
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+625
-409
lines changed

docs/bundles/ai-bundle.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ Advanced Example with Multiple Agents
6767
agent:
6868
rag:
6969
platform: 'ai.platform.azure.gpt_deployment'
70-
structured_output: false # Disables support for "output_structure" option, default is true
7170
track_token_usage: true # Enable tracking of token usage for the agent, default is true
7271
model: 'gpt-4o-mini'
7372
memory: 'You have access to conversation history and user preferences' # Optional: static memory content
@@ -513,11 +512,6 @@ Configuration
513512
# Fallback agent for unmatched requests (required)
514513
fallback: 'general'
515514
516-
.. important::
517-
518-
The orchestrator agent MUST have ``structured_output: true`` (the default) to work correctly.
519-
The multi-agent system uses structured output to reliably parse agent selection decisions.
520-
521515
Each multi-agent configuration automatically registers a service with the ID pattern ``ai.multi_agent.{name}``.
522516

523517
For the example above, the service ``ai.multi_agent.support`` is registered and can be injected::

docs/components/agent.rst

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -483,80 +483,6 @@ Code Examples
483483
* `RAG with MongoDB`_
484484
* `RAG with Pinecone`_
485485

486-
Structured Output
487-
-----------------
488-
489-
A typical use-case of LLMs is to classify and extract data from unstructured sources, which is supported by some models
490-
by features like Structured Output or providing a Response Format.
491-
492-
PHP Classes as Output
493-
~~~~~~~~~~~~~~~~~~~~~
494-
495-
Symfony AI supports that use-case by abstracting the hustle of defining and providing schemas to the LLM and converting
496-
the result back to PHP objects.
497-
498-
To achieve this, a specific agent processor needs to be registered::
499-
500-
use Symfony\AI\Agent\Agent;
501-
use Symfony\AI\Agent\StructuredOutput\AgentProcessor;
502-
use Symfony\AI\Agent\StructuredOutput\ResponseFormatFactory;
503-
use Symfony\AI\Fixtures\StructuredOutput\MathReasoning;
504-
use Symfony\AI\Platform\Message\Message;
505-
use Symfony\AI\Platform\Message\MessageBag;
506-
use Symfony\Component\Serializer\Encoder\JsonEncoder;
507-
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
508-
use Symfony\Component\Serializer\Serializer;
509-
510-
// Initialize Platform and LLM
511-
512-
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
513-
$processor = new AgentProcessor(new ResponseFormatFactory(), $serializer);
514-
$agent = new Agent($platform, $model, [$processor], [$processor]);
515-
516-
$messages = new MessageBag(
517-
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
518-
Message::ofUser('how can I solve 8x + 7 = -23'),
519-
);
520-
$result = $agent->call($messages, ['output_structure' => MathReasoning::class]);
521-
522-
dump($result->getContent()); // returns an instance of `MathReasoning` class
523-
524-
Array Structures as Output
525-
~~~~~~~~~~~~~~~~~~~~~~~~~~
526-
527-
Also PHP array structures as response_format are supported, which also requires the agent processor mentioned above::
528-
529-
use Symfony\AI\Platform\Message\Message;
530-
use Symfony\AI\Platform\Message\MessageBag;
531-
532-
// Initialize Platform, LLM and agent with processors and Clock tool
533-
534-
$messages = new MessageBag(Message::ofUser('What date and time is it?'));
535-
$result = $agent->call($messages, ['response_format' => [
536-
'type' => 'json_schema',
537-
'json_schema' => [
538-
'name' => 'clock',
539-
'strict' => true,
540-
'schema' => [
541-
'type' => 'object',
542-
'properties' => [
543-
'date' => ['type' => 'string', 'description' => 'The current date in the format YYYY-MM-DD.'],
544-
'time' => ['type' => 'string', 'description' => 'The current time in the format HH:MM:SS.'],
545-
],
546-
'required' => ['date', 'time'],
547-
'additionalProperties' => false,
548-
],
549-
],
550-
]]);
551-
552-
dump($result->getContent()); // returns an array
553-
554-
Code Examples
555-
~~~~~~~~~~~~~
556-
557-
* `Structured Output with PHP class`_
558-
* `Structured Output with array`_
559-
560486
Input & Output Processing
561487
-------------------------
562488

@@ -825,7 +751,5 @@ Code Examples
825751
.. _`Store Component`: https://github.com/symfony/ai-store
826752
.. _`RAG with MongoDB`: https://github.com/symfony/ai/blob/main/examples/rag/mongodb.php
827753
.. _`RAG with Pinecone`: https://github.com/symfony/ai/blob/main/examples/rag/pinecone.php
828-
.. _`Structured Output with PHP class`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-math.php
829-
.. _`Structured Output with array`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-clock.php
830754
.. _`Chat with static memory`: https://github.com/symfony/ai/blob/main/examples/memory/static.php
831755
.. _`Chat with embedding search memory`: https://github.com/symfony/ai/blob/main/examples/memory/mariadb.php

docs/components/platform.rst

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,76 @@ Code Examples
278278
* `Embeddings with Voyage`_
279279
* `Embeddings with Mistral`_
280280

281+
Structured Output
282+
-----------------
283+
284+
A typical use-case of LLMs is to classify and extract data from unstructured sources, which is supported by some models
285+
by features like Structured Output or providing a Response Format.
286+
287+
PHP Classes as Output
288+
~~~~~~~~~~~~~~~~~~~~~
289+
290+
Symfony AI supports that use-case by abstracting the hustle of defining and providing schemas to the LLM and converting
291+
the result back to PHP objects.
292+
293+
To achieve this, the ``Symfony\AI\Platform\StructuredOutput\PlatformSubscriber`` needs to be registered with the platform::
294+
295+
use Symfony\AI\Fixtures\StructuredOutput\MathReasoning;
296+
use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory;
297+
use Symfony\AI\Platform\Message\Message;
298+
use Symfony\AI\Platform\Message\MessageBag;
299+
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
300+
use Symfony\Component\EventDispatcher\EventDispatcher;
301+
302+
$dispatcher = new EventDispatcher();
303+
$dispatcher->addSubscriber(new PlatformSubscriber());
304+
305+
$platform = PlatformFactory::create($apiKey, eventDispatcher: $dispatcher);
306+
$messages = new MessageBag(
307+
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
308+
Message::ofUser('how can I solve 8x + 7 = -23'),
309+
);
310+
$result = $platform->invoke('mistral-small-latest', $messages, ['output_structure' => MathReasoning::class]);
311+
312+
dump($result->asObject()); // returns an instance of `MathReasoning` class
313+
314+
Array Structures as Output
315+
~~~~~~~~~~~~~~~~~~~~~~~~~~
316+
317+
Also PHP array structures as response_format are supported, which also requires the event subscriber mentioned above. On
318+
top this example uses the feature through the agent to leverage tool calling::
319+
320+
use Symfony\AI\Platform\Message\Message;
321+
use Symfony\AI\Platform\Message\MessageBag;
322+
323+
// Initialize Platform, LLM and agent with processors and Clock tool
324+
325+
$messages = new MessageBag(Message::ofUser('What date and time is it?'));
326+
$result = $agent->call($messages, ['response_format' => [
327+
'type' => 'json_schema',
328+
'json_schema' => [
329+
'name' => 'clock',
330+
'strict' => true,
331+
'schema' => [
332+
'type' => 'object',
333+
'properties' => [
334+
'date' => ['type' => 'string', 'description' => 'The current date in the format YYYY-MM-DD.'],
335+
'time' => ['type' => 'string', 'description' => 'The current time in the format HH:MM:SS.'],
336+
],
337+
'required' => ['date', 'time'],
338+
'additionalProperties' => false,
339+
],
340+
],
341+
]]);
342+
343+
dump($result->getContent()); // returns an array
344+
345+
Code Examples
346+
~~~~~~~~~~~~~
347+
348+
* `Structured Output with PHP class`_
349+
* `Structured Output with array`_
350+
281351
Server Tools
282352
------------
283353

@@ -426,6 +496,8 @@ Code Examples
426496
.. _`Embeddings with OpenAI`: https://github.com/symfony/ai/blob/main/examples/openai/embeddings.php
427497
.. _`Embeddings with Voyage`: https://github.com/symfony/ai/blob/main/examples/voyage/embeddings.php
428498
.. _`Embeddings with Mistral`: https://github.com/symfony/ai/blob/main/examples/mistral/embeddings.php
499+
.. _`Structured Output with PHP class`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-math.php
500+
.. _`Structured Output with array`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-clock.php
429501
.. _`Parallel GPT Calls`: https://github.com/symfony/ai/blob/main/examples/misc/parallel-chat-gpt.php
430502
.. _`Parallel Embeddings Calls`: https://github.com/symfony/ai/blob/main/examples/misc/parallel-embeddings.php
431503
.. _`LM Studio`: https://lmstudio.ai/

examples/deepseek/structured-output-clock.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,27 @@
1010
*/
1111

1212
use Symfony\AI\Agent\Agent;
13-
use Symfony\AI\Agent\StructuredOutput\AgentProcessor as StructuredOutputProcessor;
14-
use Symfony\AI\Agent\Toolbox\AgentProcessor as ToolProcessor;
13+
use Symfony\AI\Agent\Toolbox\AgentProcessor;
1514
use Symfony\AI\Agent\Toolbox\Tool\Clock;
1615
use Symfony\AI\Agent\Toolbox\Toolbox;
1716
use Symfony\AI\Platform\Bridge\DeepSeek\PlatformFactory;
1817
use Symfony\AI\Platform\Message\Message;
1918
use Symfony\AI\Platform\Message\MessageBag;
19+
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
2020
use Symfony\Component\Clock\Clock as SymfonyClock;
21+
use Symfony\Component\EventDispatcher\EventDispatcher;
2122

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

24-
$platform = PlatformFactory::create(env('DEEPSEEK_API_KEY'), http_client());
25+
$dispatcher = new EventDispatcher();
26+
$dispatcher->addSubscriber(new PlatformSubscriber());
27+
28+
$platform = PlatformFactory::create(env('DEEPSEEK_API_KEY'), http_client(), eventDispatcher: $dispatcher);
2529

2630
$clock = new Clock(new SymfonyClock());
27-
$toolbox = new Toolbox([$clock]);
28-
$toolProcessor = new ToolProcessor($toolbox);
29-
$structuredOutputProcessor = new StructuredOutputProcessor();
30-
$agent = new Agent($platform, 'deepseek-chat', [$toolProcessor, $structuredOutputProcessor], [$toolProcessor, $structuredOutputProcessor]);
31+
$toolbox = new Toolbox([$clock], logger: logger());
32+
$toolProcessor = new AgentProcessor($toolbox);
33+
$agent = new Agent($platform, 'deepseek-chat', [$toolProcessor], [$toolProcessor]);
3134

3235
$messages = new MessageBag(
3336
// for DeepSeek it is *mandatory* to mention JSON anywhere in the prompt when using structured output

examples/gemini/structured-output-clock.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,27 @@
1010
*/
1111

1212
use Symfony\AI\Agent\Agent;
13-
use Symfony\AI\Agent\StructuredOutput\AgentProcessor as StructuredOutputProcessor;
14-
use Symfony\AI\Agent\Toolbox\AgentProcessor as ToolProcessor;
13+
use Symfony\AI\Agent\Toolbox\AgentProcessor;
1514
use Symfony\AI\Agent\Toolbox\Tool\Clock;
1615
use Symfony\AI\Agent\Toolbox\Toolbox;
1716
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory;
1817
use Symfony\AI\Platform\Message\Message;
1918
use Symfony\AI\Platform\Message\MessageBag;
19+
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
2020
use Symfony\Component\Clock\Clock as SymfonyClock;
21+
use Symfony\Component\EventDispatcher\EventDispatcher;
2122

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

24-
$platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client());
25+
$dispatcher = new EventDispatcher();
26+
$dispatcher->addSubscriber(new PlatformSubscriber());
27+
28+
$platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client(), eventDispatcher: $dispatcher);
2529

2630
$clock = new Clock(new SymfonyClock());
2731
$toolbox = new Toolbox([$clock], logger: logger());
28-
$toolProcessor = new ToolProcessor($toolbox);
29-
$structuredOutputProcessor = new StructuredOutputProcessor();
30-
$agent = new Agent($platform, 'gemini-2.5-flash', [$toolProcessor, $structuredOutputProcessor], [$toolProcessor, $structuredOutputProcessor]);
32+
$toolProcessor = new AgentProcessor($toolbox);
33+
$agent = new Agent($platform, 'gemini-2.5-flash', [$toolProcessor], [$toolProcessor]);
3134

3235
$messages = new MessageBag(Message::ofUser('What date and time is it?'));
3336
$result = $agent->call($messages, ['response_format' => [

examples/gemini/structured-output-math.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Agent\Agent;
13-
use Symfony\AI\Agent\StructuredOutput\AgentProcessor;
1412
use Symfony\AI\Fixtures\StructuredOutput\MathReasoning;
1513
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory;
1614
use Symfony\AI\Platform\Message\Message;
1715
use Symfony\AI\Platform\Message\MessageBag;
16+
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
17+
use Symfony\Component\EventDispatcher\EventDispatcher;
1818

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

21-
$platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client());
21+
$dispatcher = new EventDispatcher();
22+
$dispatcher->addSubscriber(new PlatformSubscriber());
2223

23-
$processor = new AgentProcessor();
24-
$agent = new Agent($platform, 'gemini-2.5-flash', [$processor], [$processor]);
24+
$platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client(), eventDispatcher: $dispatcher);
2525
$messages = new MessageBag(
2626
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
2727
Message::ofUser('how can I solve 8x + 7 = -23'),
2828
);
29-
$result = $agent->call($messages, ['output_structure' => MathReasoning::class]);
3029

31-
dump($result->getContent());
30+
$result = $platform->invoke('gemini-2.5-flash', $messages, ['output_structure' => MathReasoning::class]);
31+
32+
dump($result->asObject());

examples/mistral/structured-output-math.php

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

12-
use Symfony\AI\Agent\Agent;
13-
use Symfony\AI\Agent\StructuredOutput\AgentProcessor;
14-
use Symfony\AI\Agent\StructuredOutput\ResponseFormatFactory;
1512
use Symfony\AI\Fixtures\StructuredOutput\MathReasoning;
1613
use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory;
1714
use Symfony\AI\Platform\Message\Message;
1815
use Symfony\AI\Platform\Message\MessageBag;
19-
use Symfony\Component\Serializer\Encoder\JsonEncoder;
20-
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
21-
use Symfony\Component\Serializer\Serializer;
16+
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
17+
use Symfony\Component\EventDispatcher\EventDispatcher;
2218

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

25-
$platform = PlatformFactory::create(env('MISTRAL_API_KEY'), http_client());
21+
$dispatcher = new EventDispatcher();
22+
$dispatcher->addSubscriber(new PlatformSubscriber());
2623

27-
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
28-
29-
$processor = new AgentProcessor(new ResponseFormatFactory(), $serializer);
30-
$agent = new Agent($platform, 'mistral-small-latest', [$processor], [$processor]);
24+
$platform = PlatformFactory::create(env('MISTRAL_API_KEY'), http_client(), eventDispatcher: $dispatcher);
3125
$messages = new MessageBag(
3226
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
3327
Message::ofUser('how can I solve 8x + 7 = -23'),
3428
);
35-
$result = $agent->call($messages, ['output_structure' => MathReasoning::class]);
29+
$result = $platform->invoke('mistral-small-latest', $messages, ['output_structure' => MathReasoning::class]);
3630

37-
dump($result->getContent());
31+
dump($result->asObject());

examples/multi-agent/orchestrator.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,23 @@
1313
use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor;
1414
use Symfony\AI\Agent\MultiAgent\Handoff;
1515
use Symfony\AI\Agent\MultiAgent\MultiAgent;
16-
use Symfony\AI\Agent\StructuredOutput\AgentProcessor;
1716
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
1817
use Symfony\AI\Platform\Message\Message;
1918
use Symfony\AI\Platform\Message\MessageBag;
19+
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
20+
use Symfony\Component\EventDispatcher\EventDispatcher;
2021

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

23-
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
24-
25-
// Create structured output processor for the orchestrator
26-
$structuredOutputProcessor = new AgentProcessor();
24+
$dispatcher = new EventDispatcher();
25+
$dispatcher->addSubscriber(new PlatformSubscriber());
26+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client(), eventDispatcher: $dispatcher);
2727

2828
// Create orchestrator agent for routing decisions
2929
$orchestrator = new Agent(
3030
$platform,
3131
'gpt-4o-mini',
32-
[new SystemPromptInputProcessor('You are an intelligent agent orchestrator that routes user questions to specialized agents.'), $structuredOutputProcessor],
33-
[$structuredOutputProcessor],
32+
[new SystemPromptInputProcessor('You are an intelligent agent orchestrator that routes user questions to specialized agents.')],
3433
);
3534

3635
// Create technical agent for handling technical issues

examples/ollama/structured-output-math.php

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

12-
use Symfony\AI\Agent\Agent;
13-
use Symfony\AI\Agent\StructuredOutput\AgentProcessor;
1412
use Symfony\AI\Fixtures\StructuredOutput\MathReasoning;
1513
use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory;
1614
use Symfony\AI\Platform\Message\Message;
1715
use Symfony\AI\Platform\Message\MessageBag;
16+
use Symfony\AI\Platform\StructuredOutput\PlatformSubscriber;
17+
use Symfony\Component\EventDispatcher\EventDispatcher;
1818

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

21-
$platform = PlatformFactory::create(env('OLLAMA_HOST_URL'), http_client());
21+
$dispatcher = new EventDispatcher();
22+
$dispatcher->addSubscriber(new PlatformSubscriber());
2223

23-
$processor = new AgentProcessor();
24-
$agent = new Agent($platform, env('OLLAMA_LLM'), [$processor], [$processor]);
24+
$platform = PlatformFactory::create(env('OLLAMA_HOST_URL'), http_client(), eventDispatcher: $dispatcher);
2525
$messages = new MessageBag(
2626
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
2727
Message::ofUser('how can I solve 8x + 7 = -23'),
2828
);
29-
$result = $agent->call($messages, ['output_structure' => MathReasoning::class]);
29+
$result = $platform->invoke(env('OLLAMA_LLM'), $messages, ['output_structure' => MathReasoning::class]);
3030

31-
dump($result->getContent());
31+
dump($result->asObject());

0 commit comments

Comments
 (0)