Skip to content

How to Build a Compliant Payload for Anthropic's Messages API: Avoid Errors

Introduction

When working with Anthropic's API, specifically the messages endpoint, developers often encounter the invalid_request_error. This error can occur due to various reasons related to the structure of the payload sent to the API. In this article, we'll explore how to build a compliant payload to avoid this error and ensure smooth communication with Anthropic's API.

Understanding Anthropic's API Requirements

Anthropic's API has specific requirements for the structure of message payloads. These requirements ensure that the AI model can process the conversation context correctly. The main points to consider are:

  1. Messages must alternate between user and assistant roles.
  2. The first and last messages should be from the user.
  3. Function calls are assistant messages and responses are user messages.

Solution

To address this issue, we'll be using a helper class called LLMProvider. This class contains a method getStandardMessagesStructure that processes the messages array to ensure it complies with Anthropic's requirements.

Our $messages array schema looks like this:

php
class MessageSchema
{
    const string table = 'messages';

    const string id = 'id';

    const string message = 'message';

    const string caption = 'caption';

    const string role = 'role'; // admin, contact, bot, function

    const string function_id = 'function_id';

    const string function_name = 'function_name';

    const string function_arguments = 'function_arguments';

    const string message_type = 'message_type'; // text, image, function_call, function_response

    const string created_at = 'created_at';

    const string updated_at = 'updated_at';

    const string deleted_at = 'deleted_at';
}

The goal of this schema is to be flexible enough so it can be used as a chatbot message database, and at the same time, compatible with multiple LLMs like OpenAI and Google Gemini, but for the scope of this article, let's focus on addressing request errors with the Anthropic's API.

Feel free to adapt the code below to however you structure your messages in the database before sending them to a third-party provider.

Step 1: Implement the getStandardMessagesStructure Method

php
private static function getStandardMessagesStructure(array $messages): array
{
    // Remove messages from the beginning until the first message with role 'contact' is found
    $messages = self::removeMessagesUntilFirstContact($messages);

    // Ensure the role 'contact' is always preceded by a 'bot' role unless it is the first element
    $messages = self::ensureContactPrecededByBot($messages);

    // Remove messages from the end until the last message is either:
    // - role 'contact' with message_type 'text' or 'image'
    // - role 'function' with message_type 'function_response'
    return self::removeMessagesFromEndUntilValid($messages);
}

This method calls three helper functions to process the messages array:

Step 2: Implement removeMessagesUntilFirstContact

php
private static function removeMessagesUntilFirstContact(array $messages): array
{
    while (!empty($messages) && $messages[0][MessageSchema::role] !== 'contact') {
        array_shift($messages);
    }
    return $messages;
}

This function ensures that the first message in the array is from the user (role 'contact').

Step 3: Implement ensureContactPrecededByBot

php
private static function ensureContactPrecededByBot(array $messages): array
{
    for ($i = 1; $i < count($messages); $i++) {
        if ($messages[$i][MessageSchema::role] === 'contact' && $messages[$i - 1][MessageSchema::role] !== 'bot') {
            while ($i > 0 && $messages[$i - 1][MessageSchema::role] !== 'bot') {
                array_splice($messages, $i - 1, 1);
                $i--;
            }
        }
    }
    return $messages;
}

This function ensures that messages alternate between user and assistant roles.

Step 4: Implement removeMessagesFromEndUntilValid

php
private static function removeMessagesFromEndUntilValid(array $messages): array
{
    while (!empty($messages)) {
        $last_message = $messages[count($messages) - 1];
        $role = $last_message[MessageSchema::role];
        $message_type = $last_message[MessageSchema::message_type] ?? 'text';
        $is_contact_with_valid_type = $role === 'contact' && in_array($message_type, ['text', 'image']);
        $is_function_with_valid_type = $role === 'function' && $message_type === 'function_response';

        if ($is_contact_with_valid_type || $is_function_with_valid_type) {
            break;
        }

        array_pop($messages);
    }
    return $messages;
}

This function ensures that the last message is either from the user or a valid function response.

Step 5: Use the getStandardMessagesStructure in Your API Call

When making a call to Anthropic's API, use the getStandardMessagesStructure method to process your messages array:

php
$messages = self::getStandardMessagesStructure($messages);

$response = AnthropicProvider::chat(
    model: AnthropicProvider::CLAUDE_3_SONNET,
    messages: $messages,
    max_tokens: $max_tokens,
    system: $system,
    temperature: $temperature,
    tools: $tools
);

Inside the AnthropicProvider class, we map the $messages array to Anthropic-compatible message structure in a function called getParsedMessages. Here's a section of that for reference:

php
    private static function getParsedMessages(array $messages): array
    {
        return array_map(function ($m) {
            $role = $m[MessageSchema::role];

            switch ($role) {
                case 'contact':
                    $role = 'user';
                    break;
                case 'admin':
                case 'bot':
                case 'function':
                    $role = 'assistant';
                    break;
            }

            $message = $m[MessageSchema::message];

            ... // other mappings

            return [
                'content' => $message,
                'role' => $role,
            ];

Example: Before and After Processing

Let's look at an example of how the message payload structure changes after processing:

Before processing:

php
$messages = [
    ['role' => 'bot', 'message' => 'Hello!'],
    ['role' => 'contact', 'message' => 'Hi there!'],
    ['role' => 'contact', 'message' => 'How are you?'],
    ['role' => 'bot', 'message' => 'I'm doing well, thank you!'],
    ['role' => 'bot', 'message' => 'How can I assist you today?'],
];

After processing:

php
$messages = [
    ['role' => 'contact', 'message' => 'Hi there!'],
    ['role' => 'bot', 'message' => 'I'm doing well, thank you!'],
    ['role' => 'contact', 'message' => 'How are you?'],
];

Best Practices for Message Structuring

To minimize the need for extensive processing, consider these best practices when structuring your messages:

  1. Always start conversations with a user message.
  2. Alternate between user and assistant messages.
  3. End conversations with a user message or a valid function response.
  4. Handle function calls in AI conversations appropriately within the conversation flow.

Error Handling

When you encounter an invalid_request_error, it's crucial to log the error details and the payload that caused it. This can help in debugging and improving your message processing logic. Consider implementing a try-catch block:

php
use Illuminate\Support\Facades\Log;

...

try {
    $response = AnthropicProvider::chat(/* ... */);
} catch (Exception $e) {
    if (str_contains($e->getMessage(), 'invalid_request_error')) {
        // Log the error and the payload
        Log::error('Invalid request error: ' . json_encode($messages));
    }
    throw $e;
}

Conclusion

By implementing these methods and using them to process your messages array before sending it to Anthropic's API, you can avoid the invalid_request_error and ensure that your payload is compliant with the API's requirements. This approach handles the alternation of user and assistant messages, ensures proper structure for function calls and responses, and maintains the correct order of messages.

Remember to adapt the MessageSchema constants and other specifics to match your project's structure. With this solution in place, you should be able to communicate with Anthropic's Claude model reliably.

While this approach is effective, it's important to note that it may remove some messages from the conversation history. In scenarios where preserving the entire conversation is crucial, you should implement a more sophisticated system that ensures compliance without losing context.