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:
- Messages must alternate between user and assistant roles.
- The first and last messages should be from the user.
- 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:
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
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
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
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
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:
$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:
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:
$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:
$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:
- Always start conversations with a user message.
- Alternate between user and assistant messages.
- End conversations with a user message or a valid function response.
- 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:
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.