Skip to content

fix: encode empty tool arguments as JSON object instead of array#896

Open
MaximeWillinger wants to merge 1 commit intoprism-php:mainfrom
MaximeWillinger:fix/empty-tool-arguments-encoded-as-array
Open

fix: encode empty tool arguments as JSON object instead of array#896
MaximeWillinger wants to merge 1 commit intoprism-php:mainfrom
MaximeWillinger:fix/empty-tool-arguments-encoded-as-array

Conversation

@MaximeWillinger
Copy link

@MaximeWillinger MaximeWillinger commented Feb 9, 2026

Problem

When using multi-step tool calling with tools that have no parameters, the second request fails because empty tool arguments are encoded as a JSON array ("[]") instead of a JSON object ("{}").

This causes provider-side validation errors. For example, OpenRouter (which translates to Anthropic format internally) returns:

OpenRouter Bad Request: messages.1.content.0.tool_use.input: Input should be a valid dictionary

Reproduction

This happens in any multi-step tool calling scenario where a tool has no parameters:

$tool = Tool::as('get_current_time')
    ->for('Get the current server time')
    ->using(fn (): string => now()->toISOString());

$response = Prism::text()
    ->using(Provider::OpenRouter, 'anthropic/claude-sonnet-4')
    ->withTools([$tool])
    ->withMaxSteps(3)
    ->withPrompt('What time is it?')
    ->asStream();

When the model calls this tool during streaming, Prism's Stream handler:

  1. Receives arguments: "{}" from the model
  2. Decodes it with json_decode("{}", true) which returns [] (empty PHP array)
  3. Stores it in the ToolCall value object
  4. On the next step, MessageMap re-encodes it with json_encode([]) which produces "[]"

The provider then rejects "[]" because function arguments must be a JSON object, not an array.

Root cause

This is a PHP-specific ambiguity: an empty PHP array [] is both a valid list and a valid dictionary. json_encode([]) produces "[]" (JSON array), but the OpenAI API specification requires function arguments to always be a JSON object string.

Solution

Use json_encode($toolCall->arguments() ?: (object) []) to ensure empty arguments are encoded as "{}". When arguments are non-empty, the ?: operator short-circuits and behavior is unchanged.

Affected providers

The same pattern existed in all providers that encode tool call arguments in MessageMap:

  • OpenRouter
  • OpenAI
  • Groq
  • XAI
  • DeepSeek
  • Mistral

All six have been fixed with the same one-line change, along with a corresponding test for each.

When a tool has no parameters, `$toolCall->arguments()` returns an
empty PHP array. `json_encode([])` produces `"[]"` (JSON array),
but providers expect `"{}"` (JSON object) for the `arguments` field.

This causes errors like "input should be a valid dictionary" when
providers (e.g. OpenRouter → Anthropic) validate the tool call input.

Using `$toolCall->arguments() ?: (object) []` ensures empty arguments
are always encoded as `"{}"`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant