Commit d742c66
committed
feature #585 [Agent][Platform] Add support for native union types and list of polymporphic* types by using
This PR was squashed before being merged into the main branch.
Discussion
----------
[Agent][Platform] Add support for native union types and list of polymporphic* types by using `DiscriminatorMap`
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes <!-- please update src/**/CHANGELOG.md files -->
| Docs? | yes <!-- required for new features -->
| Issues | Fix #559 <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License | MIT
It's a draft, because, primarily, I want to ask if the DiscriminatorMap approach is ok with you.
**Why the discriminator map usage?**
I've achieved the native `list<Foo|Bar>` union type support for agent json schema output, but Symfony Serializer wasn't working with it correctly (`list<TableDataVisualizationDto|ChartDataVisualizationDto>`), outputting an array in `\Symfony\Component\Serializer\SerializerInterface::deserialize::112`. If you know how to make the serializer work with the "nested" union types correctly, then let me know and we might achieve a native support without the DiscriminatorMap usage.
**Example**
This is an example from my project I want the feature to work on.
Model I tried it with: `gpt-5-nano` with the `Symfony\AI\Platform\Bridge\OpenAi\Gpt`model class.
Prompt:
```
{
"message": "Provide me with a table representing my latest orders and a chart representing amount of orders for last 10 months."
}
```
Schema produced:
<details>
<summary>Schema dump</summary>
```
array:2 [
"type" => "json_schema"
"json_schema" => array:3 [
"name" => "ChatStructuredOutput"
"schema" => array:4 [
"type" => "object"
"properties" => array:4 [
"title" => array:1 [
"type" => "string"
]
"finalAnswer" => array:1 [
"type" => "string"
]
"parts" => array:2 [
"type" => "array"
"items" => array:1 [
"anyOf" => array:3 [
0 => array:4 [
"type" => "object"
"properties" => array:6 [
"title" => array:1 [
"type" => "string"
]
"chartType" => array:2 [
"type" => "string"
"enum" => array:3 [
0 => "bar"
1 => "line"
2 => "pie"
]
]
"labels" => array:2 [
"type" => "array"
"items" => array:1 [
"type" => "string"
]
]
"datasets" => array:2 [
"type" => "array"
"items" => array:4 [
"type" => "object"
"properties" => array:3 [
"label" => array:1 [
"type" => "string"
]
"data" => array:2 [
"type" => "array"
"items" => array:1 [
"type" => "number"
]
]
"bgColor" => array:2 [
"type" => "string"
"description" => "Optional param, default to empty string."
]
]
"required" => array:3 [
0 => "label"
1 => "data"
2 => "bgColor"
]
"additionalProperties" => false
]
]
"options" => array:2 [
"type" => "array"
"items" => array:1 [
"type" => "string"
]
]
"type" => array:2 [
"type" => "string"
"pattern" => "^chart$"
]
]
"required" => array:6 [
0 => "title"
1 => "chartType"
2 => "labels"
3 => "datasets"
4 => "options"
5 => "type"
]
"additionalProperties" => false
]
1 => array:4 [
"type" => "object"
"properties" => array:4 [
"columns" => array:2 [
"type" => "array"
"items" => array:1 [
"type" => "string"
]
]
"rows" => array:2 [
"type" => "array"
"items" => array:2 [
"type" => "array"
"items" => array:1 [
"type" => "string"
]
]
]
"meta" => array:2 [
"type" => "array"
"items" => array:1 [
"type" => "string"
]
]
"type" => array:2 [
"type" => "string"
"pattern" => "^table$"
]
]
"required" => array:4 [
0 => "columns"
1 => "rows"
2 => "meta"
3 => "type"
]
"additionalProperties" => false
]
2 => array:4 [
"type" => "object"
"properties" => array:2 [
"text" => array:1 [
"type" => "string"
]
"type" => array:2 [
"type" => "string"
"pattern" => "^text$"
]
]
"required" => array:2 [
0 => "text"
1 => "type"
]
"additionalProperties" => false
]
]
]
]
"reasoningStep" => array:2 [
"type" => "array"
"items" => array:4 [
"type" => "object"
"properties" => array:1 [
"reasoning" => array:1 [
"type" => "string"
]
]
"required" => array:1 [
0 => "reasoning"
]
"additionalProperties" => false
]
]
]
"required" => array:4 [
0 => "title"
1 => "finalAnswer"
2 => "parts"
3 => "reasoningStep"
]
"additionalProperties" => false
]
"strict" => true
]
]
```
</details>
The description is long enough, so I'd skip the PHP DTOs structure. They look almost identical to the ones I used in tests in the PR.
Output (the data are fake):
<details>
<summary>Output dump</summary>
```
App\Agent\Application\StructuredOutput\ChatStructuredOutput {#2041
+title: "Latest orders and orders by month (last 10 months)"
+finalAnswer: "Here is a table with your latest orders and a bar chart showing the number of orders per month for the last 10 months."
+parts: array:3 [
0 =>
App\Agent\Application\StructuredOutput
\
TablePart
{#1596
+type: "table"
+columns: array:5 [
0 => "Order #"
1 => "Date Created"
2 => "Status"
3 => "Total PLN"
4 => "Customer Email"
]
+rows: array:10 [
0 => array:5 [
0 => "115"
1 => "2025-09-14 11:46:11"
2 => "failed"
3 => "24902.21"
4 => "N/A"
]
1 => array:5 [
0 => "117"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "28029.39"
4 => "[email protected]"
]
2 => array:5 [
0 => "124"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "28424.05"
4 => "[email protected]"
]
3 => array:5 [
0 => "122"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "8692.10"
4 => "[email protected]"
]
4 => array:5 [
0 => "121"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "10583.07"
4 => "[email protected]"
]
5 => array:5 [
0 => "120"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "14509.36"
4 => "[email protected]"
]
6 => array:5 [
0 => "119"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "36850.59"
4 => "[email protected]"
]
7 => array:5 [
0 => "116"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "19252.16"
4 => "[email protected]"
]
8 => array:5 [
0 => "118"
1 => "2025-09-14 11:46:11"
2 => "on-hold"
3 => "33717.00"
4 => "N/A"
]
9 => array:5 [
0 => "123"
1 => "2025-09-14 11:46:11"
2 => "completed"
3 => "33392.61"
4 => "[email protected]"
]
]
+meta: []
}
1 =>
App\Agent\Application\StructuredOutput
\
ChartPart
{#2100
+type: "chart"
+title: "Orders in the last 10 months"
+chartType:
App\Agent\Application\StructuredOutput
\
ChartPartType
{#1107
+name: "BAR"
+value: "bar"
}
+labels: array:10 [
0 => "Dec 2024"
1 => "Jan 2025"
2 => "Feb 2025"
3 => "Mar 2025"
4 => "Apr 2025"
5 => "May 2025"
6 => "Jun 2025"
7 => "Jul 2025"
8 => "Aug 2025"
9 => "Sep 2025"
]
+datasets: array:1 [
0 =>
App\Agent\Application\StructuredOutput
\
DatasetPart
{#2079
+label: "Number of orders"
+data: array:10 [
0 => 0
1 => 0
2 => 0
3 => 0
4 => 0
5 => 0
6 => 0
7 => 0
8 => 0
9 => 85
]
+bgColor: "#4e79a7"
}
]
+options: array:2 [
0 => "responsive"
1 => "scales: {y: {beginAtZero: true}}"
]
}
2 =>
App\Agent\Application\StructuredOutput
\
TextPart
{#2120
+type: "text"
+text: "Note: The chart shows order counts per month for the last 10 months. In this dataset, only Sep 2025 contains orders (85 orders)."
}
]
+reasoningStep: array:2 [
0 =>
App\Agent\Application\StructuredOutput
\
ReasoningStep
{#2069
+reasoning: "I retrieved the latest orders using the WooCommerce orders endpoint and prepared a compact table with key fields: Order #, Date Created, Status, Total PLN, and Customer Email. Since the dataset appears to be current (all entries dated 2025-09-14), I listed the ten most recent orders as they appear from the data source."
}
1 =>
App\Agent\Application\StructuredOutput
\
ReasoningStep
{#2148
+reasoning: "For the chart, I computed a 10-month window ending in Sep 2025. Based on the provided data, all orders fall in Sep 2025, so Sep 2025 has 85 orders and the preceding months have 0 orders. This yields a bar chart with a single non-zero bar for Sep 2025. If you’d like a different window (e.g., moving 10 months from a different start date) I can adjust the chart accordingly."
}
]
}
```
</details>
As you can see all data have been correctly mapped from schema to php objects.
<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.
Additionally (see https://symfony.com/releases):
- Always add tests and ensure they pass.
- For new features, provide some code snippets to help understand usage.
- Features and deprecations must be submitted against branch main.
- Update/add documentation as required (we can help!)
- Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
- Never break backward compatibility (see https://symfony.com/bc).
-->
Commits
-------
36f5bb4 [Agent][Platform] Add support for native union types and list of polymporphic* types by using `DiscriminatorMap`DiscriminatorMap (HaKIMus)File tree
14 files changed
+524
-3
lines changed- examples/openai
- fixtures/StructuredOutput
- PolymorphicType
- UnionType
- src
- agent
- src/StructuredOutput
- tests/StructuredOutput
- platform
- src/Contract/JsonSchema
- tests/Contract/JsonSchema
14 files changed
+524
-3
lines changedLines changed: 33 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
Lines changed: 24 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
Lines changed: 33 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
Lines changed: 24 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
Lines changed: 26 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
0 commit comments