Skip to content

MultiAgent\Handoff\Decision::$reasoning default value breaks OpenAI strict structured output (and LiteLLM) #1985

@samiFerjaniSofrecom

Description

@samiFerjaniSofrecom

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

  1. Configure a multi_agent.support with ai.platform.generic.default (LiteLLM) as orchestrator.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions