Summary
The vendor class Symfony\AI\Agent\MultiAgent\Handoff\Decision declares a default value for $reasoning:
// vendor/symfony/ai-agent/src/MultiAgent/Handoff/Decision.php
public function __construct(
private readonly string $agentName,
private readonly string $reasoning = 'No reasoning provided',
) {}
When this class is used as a response_format for the orchestrator agent, JsonSchema\Describer derives the required array from ReflectionParameter::isRequired(), which returns false for parameters with a default value. The resulting JSON Schema therefore declares only agentName as required, leaving reasoning as optional.
Why this breaks
OpenAI's structured outputs strict mode requires all properties listed in properties to also appear in required. Any optional field is rejected with:
Invalid schema for response_format: 'reasoning' is not in required.
This affects:
- Direct OpenAI calls when
strict: true is enabled on the response format (now the recommended default).
- Any OpenAI-compatible proxy that enforces
strict: true, in particular LiteLLM, which forces strict mode for reliability. Using MultiAgent through symfony/ai-generic-platform pointed at LiteLLM fails with a 400 every time.
Reproduction
- Configure a
multi_agent.support with ai.platform.generic.default (LiteLLM) as orchestrator.
- Send any message → orchestrator call returns 400 from upstream because of the schema mismatch.
Proposed fix
Remove the default value on $reasoning. The model is already instructed to provide reasoning by the orchestrator prompt, so the default never adds value at runtime — it only serves to make the parameter "optional" for reflection, which is exactly what we don't want here.
public function __construct(
private readonly string $agentName,
- private readonly string $reasoning = 'No reasoning provided',
+ private readonly string $reasoning,
) {}
This is a minor BC break for anyone instantiating Decision manually with a single argument, but the class is essentially an internal DTO produced by the LLM, so the impact should be very small.
Alternative
Keep the default, but add an opt-in mechanism on the schema describer (e.g., a #[Schema(required: true)] flag, or a class-level attribute like #[StrictSchema] that forces every property into required). This would also help users who hit the same problem on their own DTOs.
Workaround in user code
For anyone hitting this today, the workaround is to define a local Decision class without defaults and reimplement the orchestration loop in user space — which defeats the purpose of MultiAgent.
Environment
symfony/ai-agent: 0.8.x
symfony/ai-platform: 0.8.x
- Backend: OpenAI via
symfony/ai-generic-platform → LiteLLM proxy
- PHP 8.4
Summary
The vendor class
Symfony\AI\Agent\MultiAgent\Handoff\Decisiondeclares a default value for$reasoning:When this class is used as a
response_formatfor the orchestrator agent,JsonSchema\Describerderives therequiredarray fromReflectionParameter::isRequired(), which returnsfalsefor parameters with a default value. The resulting JSON Schema therefore declares onlyagentNameas required, leavingreasoningas optional.Why this breaks
OpenAI's structured outputs strict mode requires all properties listed in
propertiesto also appear inrequired. Any optional field is rejected with:This affects:
strict: trueis enabled on the response format (now the recommended default).strict: true, in particular LiteLLM, which forces strict mode for reliability. UsingMultiAgentthroughsymfony/ai-generic-platformpointed at LiteLLM fails with a 400 every time.Reproduction
multi_agent.supportwithai.platform.generic.default(LiteLLM) as orchestrator.Proposed fix
Remove the default value on
$reasoning. The model is already instructed to provide reasoning by the orchestrator prompt, so the default never adds value at runtime — it only serves to make the parameter "optional" for reflection, which is exactly what we don't want here.public function __construct( private readonly string $agentName, - private readonly string $reasoning = 'No reasoning provided', + private readonly string $reasoning, ) {}This is a minor BC break for anyone instantiating
Decisionmanually with a single argument, but the class is essentially an internal DTO produced by the LLM, so the impact should be very small.Alternative
Keep the default, but add an opt-in mechanism on the schema describer (e.g., a
#[Schema(required: true)]flag, or a class-level attribute like#[StrictSchema]that forces every property intorequired). This would also help users who hit the same problem on their own DTOs.Workaround in user code
For anyone hitting this today, the workaround is to define a local
Decisionclass without defaults and reimplement the orchestration loop in user space — which defeats the purpose ofMultiAgent.Environment
symfony/ai-agent: 0.8.xsymfony/ai-platform: 0.8.xsymfony/ai-generic-platform→ LiteLLM proxy