-
Notifications
You must be signed in to change notification settings - Fork 44
Open
Description
AnthropicTextGenerationModel fails when sending function calls with empty or null arguments to Anthropic API, resulting in "Bad Request (400) - messages.X.content.Y.tool_use.input: Input should be a valid dictionary" error.
- Provider: Anthropic Claude (Sonnet 4.5)
- Affected file:
php-ai-client/src/ProviderImplementations/Anthropic/AnthropicTextGenerationModel.php
When an AI function/tool is called without parameters (see the example ability: list-block-patterns ability that accepts optional parameters only), the FunctionCall object may contain:
args = nullargs = [](empty array)
When preparing the request for Anthropic API in getMessagePartData() method (line ~346), the code directly passes these values:
if ($type->isFunctionCall()) {
$functionCall = $part->getFunctionCall();
return [
'type' => 'tool_use',
'id' => $functionCall->getId(),
'name' => $functionCall->getName(),
'input' => $functionCall->getArgs(), // ❌ Can be null or []
];
}Anthropic API Requirements:
- The
tool_use.inputfield MUST be a valid dictionary/object - Empty arrays
[]are rejected nullvalues are rejected- Only
{}(empty object) or populated objects are accepted
Proposed solution
Modify getMessagePartData() method in AnthropicTextGenerationModel.php to ensure input is always a valid object:
if ($type->isFunctionCall()) {
$functionCall = $part->getFunctionCall();
if (!$functionCall) {
throw new RuntimeException(
'The function_call typed message part must contain a function call.'
);
}
$args = $functionCall->getArgs();
// ✅ Ensure args is always a valid object for Anthropic API
if ($args === null || (is_array($args) && empty($args))) {
$args = new \stdClass(); // Serializes to {} in JSON
}
return [
'type' => 'tool_use',
'id' => $functionCall->getId(),
'name' => $functionCall->getName(),
'input' => $args,
];
}The example ability to see what is the real case input:
<?php
/**
* List Block Patterns Ability implementation.
*
* @package ValuDev\Abilities
*/
declare(strict_types=1);
namespace ValuDev\Abilities;
use ValuDev\Abstracts\Abstract_Valu_Ability;
use WP_Block_Patterns_Registry;
/**
* List available block patterns ability.
*/
class List_Block_Patterns_Ability extends Abstract_Valu_Ability {
/**
* {@inheritDoc}
*/
protected function input_schema(): array {
return array(
'type' => array( 'object', 'null' ),
'properties' => array(
'category' => array(
'type' => 'string',
'description' => __( 'Filter patterns by category slug (optional)', 'valu-dev-mcp' ),
),
),
'additionalProperties' => false,
);
}
/**
* {@inheritDoc}
*/
protected function output_schema(): array {
return array(
'type' => 'object',
'properties' => array(
'success' => array(
'type' => 'boolean',
'description' => __( 'Whether the patterns were retrieved successfully', 'valu-dev-mcp' ),
),
'patterns' => array(
'type' => 'array',
'description' => __( 'List of available block patterns', 'valu-dev-mcp' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'type' => array( 'integer', 'null' ),
'description' => __( 'Pattern post ID (null for registered patterns)', 'valu-dev-mcp' ),
),
'slug' => array(
'type' => 'string',
'description' => __( 'Pattern slug for use with get-block-pattern-raw and get-block-pattern-parsed', 'valu-dev-mcp' ),
),
'title' => array(
'type' => 'string',
'description' => __( 'Pattern title', 'valu-dev-mcp' ),
),
'description' => array(
'type' => 'string',
'description' => __( 'Pattern description', 'valu-dev-mcp' ),
),
),
),
),
'count' => array(
'type' => 'integer',
'description' => __( 'Total number of patterns returned', 'valu-dev-mcp' ),
),
'message' => array(
'type' => 'string',
'description' => __( 'Status message', 'valu-dev-mcp' ),
),
),
'additionalProperties' => false,
);
}
// and the code continues …
}Metadata
Metadata
Assignees
Labels
No labels