Skip to content

Schema::toSchema() includes name property inside schema array, breaking OpenRouter structured output #185

@Peppe2404

Description

@Peppe2404

Description

When using HasStructuredOutput agents with the OpenRouter provider, structured output requests fail
with:

OpenRouter Bad Request: output_format.schema: For 'object' type, property 'name' is not supported

The root cause is that Schema::toSchema() includes the name metadata field inside the JSON Schema
array, which is not a valid JSON Schema property for object type.

Root Cause

In src/Schema.php, the toSchema() method returns:

public function toSchema(): array
{
    return [
        'name' => $this->name,
        ...$this->schema->toArray(),
    ];
}

Prism's OpenRouter Structured handler (src/Providers/OpenRouter/Handlers/Structured.php) calls both
$request->schema()->name() and $request->schema()->toArray():

'response_format' => [
    'type' => 'json_schema',
    'json_schema' => [
        'name' => $request->schema()->name(),   // ✅ name at wrapper level
        'strict' => true,
        'schema' => $request->schema()->toArray(), // ❌ also contains 'name'
    ],
],

This results in name appearing twice in the payload — once correctly at the json_schema level (added
by Prism), and once incorrectly inside the schema object (from Laravel AI SDK's toSchema()):

{
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "schema_definition",
      "strict": true,
      "schema": {
        "name": "schema_definition",
        "type": "object",
        "properties": { "..." },
        "required": ["..."]
      }
    }
  }
}

OpenRouter rejects this because name is not a valid property for a JSON Schema of type object.

Suggested Fix

Remove name from toSchema() / toArray() — these methods should return a pure JSON Schema without
metadata:

public function toSchema(): array
{
    return $this->schema->toArray();
}

The name is already accessible via the dedicated name() method, which Prism correctly calls
separately.

Related

This is architecturally similar to #139 (Gemini fails with additionalProperties leaking through the
same serialization path).

Reproduction

Any agent using HasStructuredOutput targeting OpenRouter:

class StrategyAgent implements Agent, HasStructuredOutput
{
    use Promptable;

    public function schema(JsonSchema $schema): array
    {
        return [
            'queries' => $schema->array()
                ->items($schema->string())
                ->description('A list of search queries.'),
        ];
    }
}

// Trigger:
$agent->prompt(prompt: 'Generate queries.', model: 'anthropic/claude-sonnet-4.5');

Versions

- laravel/ai: v0.1.5
- prism-php/prism: v0.99.19
- PHP: 8.4
- Provider: OpenRouter

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