Skip to content

Conversation

Jameswlepage
Copy link

Summary

Fixes critical API compatibility issues when AI models make function/tool calls with no parameters. The SDK was generating invalid payloads rejected by OpenAI and Anthropic APIs.

Changes

  • Fix empty tool_calls handling: Only include tool_calls field when array contains items (OpenAI rejects empty arrays)
  • Fix empty arguments encoding: Convert empty PHP arrays to objects before JSON encoding (APIs expect {} not [] for empty parameters)
  • Add test coverage: New tests for both edge cases to prevent regression

Technical Details

The issue manifests in two ways:

  1. OpenAI API Error: Invalid 'messages[X].tool_calls': empty array. Expected at least one item

    • Occurs when tool_calls: [] is included in messages
    • Fixed by conditionally including the field only when non-empty
  2. Anthropic API Error: messages.X.content.0.tool_use.input: Input should be a valid dictionary

    • Occurs when empty arguments encode as "[]" instead of "{}"
    • Fixed by converting empty arrays to objects before JSON encoding

Testing

# Run new tests
./vendor/bin/phpunit --filter="testGetMessagePartToolCallDataEmptyArguments|testPrepareMessagesParamNoToolCalls"

# Verify all tests pass
composer run phpunit  # 878 tests, 3547 assertions ✓
composer run phpstan  # No errors ✓

Impact

  • Enables WordPress Abilities API integration (many parameterless functions)
  • Fixes production failures with function calling
  • Maintains full backward compatibility

Related

- Only include tool_calls field when there are actual tool calls (OpenAI rejects empty arrays)
- Convert empty argument arrays to objects in JSON encoding (both OpenAI and Anthropic require objects)
- Add comprehensive test coverage for both edge cases

This fixes critical API failures when AI models make function/tool calls with no parameters,
making the SDK production-ready for parameterless functions like those in WordPress Abilities API.
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes critical compatibility issues with OpenAI and Anthropic APIs when AI models make function/tool calls with empty parameters. The changes ensure proper JSON encoding and conditional field inclusion to prevent API rejections.

  • Fix empty tool_calls array handling by only including the field when non-empty
  • Fix empty arguments encoding to produce {} instead of [] for parameterless function calls
  • Add comprehensive test coverage for both edge cases

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php Implements conditional tool_calls inclusion and empty arguments object conversion
tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModelTest.php Adds test cases for empty tool calls and empty arguments scenarios

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +406 to +407
if (is_array($args) && empty($args)) {
$args = (object) array();
Copy link

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The (object) array() conversion can be simplified to new \stdClass() for better readability and clarity of intent when creating an empty object.

Suggested change
if (is_array($args) && empty($args)) {
$args = (object) array();
$args = new \stdClass();

Copilot uses AI. Check for mistakes.

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jameswlepage Thanks, great catch!

For the empty tool_calls, this makes total sense. About the other change, I have a follow up question - technically always making it an empty object doesn't seem right (unless this is truly the only way to make it work against the OpenAI API).

Comment on lines +406 to +408
if (is_array($args) && empty($args)) {
$args = (object) array();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is tricky. Technically speaking, for JSON-encoding this should still only be turned into stdClass when the expected input shape is object. While that's often the case, it only depends on the schema - it could also be array, in which case leaving it a PHP array would be correct.

What happens if the parameters JSON schema specified for the function expects an actual array (indexed/numeric)? Does the OpenAI API always fail if it's an empty array? If so, that would seem like a flaw in the API (although, if that's the case, of course we still need to fix / work around it in our SDK).

$functionCall = new FunctionCall(
'call_1',
'list_capabilities',
[] // Empty arguments array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my above comment, the problem is in PHP it's undefined whether this is an empty associative array (object in JSON schema) or an empty indexed/numeric array (array in JSON schema). Just to clarify what I meant above.

Comment on lines +532 to +535

$model = $this->createModel();
$prepared = $model->exposePrepareMessagesParam([$message], null);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPCS errors here:

Suggested change
$model = $this->createModel();
$prepared = $model->exposePrepareMessagesParam([$message], null);
$model = $this->createModel();
$prepared = $model->exposePrepareMessagesParam([$message], null);

@felixarntz felixarntz added this to the 0.2.0 milestone Sep 2, 2025
@felixarntz felixarntz added the [Type] Bug An existing feature does not function as intended label Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants