如何为 Anthropic 的 Messages API 构建合规的有效载荷:避免错误
引言
在使用Anthropic 的 API,特别是 messages 端点时,开发人员经常遇到invalid_request_error
。这个错误可能由于发送到 API 的有效载荷结构的各种原因而发生。在本文中,我们将探讨如何构建合规的有效载荷以避免这个错误,并确保与 Anthropic 的 API 顺畅通信。
理解 Anthropic 的 API 要求
Anthropic 的 API 对消息有效载荷的结构有特定要求。这些要求确保 AI 模型能够正确处理对话上下文。主要考虑的点是:
- 消息必须在用户和助手角色之间交替。
- 第一条和最后一条消息应该来自用户。
- 函数调用是助手消息,响应是用户消息。
解决方案
为了解决这个问题,我们将使用一个名为LLMProvider
的辅助类。这个类包含一个名为getStandardMessagesStructure
的方法,用于处理消息数组以确保其符合 Anthropic 的要求。
我们的$messages
数组模式如下:
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';
}
这个模式的目标是足够灵活,可以用作聊天机器人消息数据库,同时兼容多个 LLM,如 OpenAI 和 Google Gemini,但在本文范围内,我们将专注于解决 Anthropic API 的请求错误。
请随意根据您在数据库中存储消息的方式来调整以下代码,然后再将其发送给第三方提供商。
步骤 1:实现 getStandardMessagesStructure 方法
private static function getStandardMessagesStructure(array $messages): array
{
// 从开头删除消息,直到找到第一个角色为'contact'的消息
$messages = self::removeMessagesUntilFirstContact($messages);
// 确保'contact'角色总是在'bot'角色之后,除非它是第一个元素
$messages = self::ensureContactPrecededByBot($messages);
// 从末尾删除消息,直到最后一条消息是:
// - 角色为'contact'且message_type为'text'或'image'
// - 角色为'function'且message_type为'function_response'
return self::removeMessagesFromEndUntilValid($messages);
}
这个方法调用三个辅助函数来处理消息数组:
步骤 2:实现 removeMessagesUntilFirstContact
private static function removeMessagesUntilFirstContact(array $messages): array
{
while (!empty($messages) && $messages[0][MessageSchema::role] !== 'contact') {
array_shift($messages);
}
return $messages;
}
这个函数确保数组中的第一条消息来自用户(角色为'contact')。
步骤 3:实现 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;
}
这个函数确保消息在用户和助手角色之间交替。
步骤 4:实现 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;
}
这个函数确保最后一条消息要么来自用户,要么是有效的函数响应。
步骤 5:在 API 调用中使用 getStandardMessagesStructure
在调用 Anthropic 的 API 时,使用getStandardMessagesStructure
方法处理您的消息数组:
$messages = self::getStandardMessagesStructure($messages);
$response = AnthropicProvider::chat(
model: AnthropicProvider::CLAUDE_3_SONNET,
messages: $messages,
max_tokens: $max_tokens,
system: $system,
temperature: $temperature,
tools: $tools
);
在AnthropicProvider
类中,我们将$messages
数组映射到 Anthropic 兼容的消息结构,这在一个名为getParsedMessages
的函数中完成。以下是该函数的一部分供参考:
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];
... // 其他映射
return [
'content' => $message,
'role' => $role,
];
示例:处理前后
让我们看一个消息有效载荷结构在处理前后如何变化的例子:
处理前:
$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?'],
];
处理后:
$messages = [
['role' => 'contact', 'message' => 'Hi there!'],
['role' => 'bot', 'message' => 'I'm doing well, thank you!'],
['role' => 'contact', 'message' => 'How are you?'],
];
消息结构的最佳实践
为了最小化广泛处理的需求,在构建消息时请考虑以下最佳实践:
- 总是以用户消息开始对话。
- 在用户和助手消息之间交替。
- 以用户消息或有效的函数响应结束对话。
- 在对话流程中适当处理AI 对话中的函数调用。
错误处理
当遇到invalid_request_error
时,记录错误详情和导致错误的有效载荷至关重要。这可以帮助调试和改进您的消息处理逻辑。考虑实现一个 try-catch 块:
use Illuminate\Support\Facades\Log;
...
try {
$response = AnthropicProvider::chat(/* ... */);
} catch (Exception $e) {
if (str_contains($e->getMessage(), 'invalid_request_error')) {
// 记录错误和有效载荷
Log::error('Invalid request error: ' . json_encode($messages));
}
throw $e;
}
结论
通过实现这些方法并在将消息数组发送到 Anthropic 的 API 之前使用它们进行处理,您可以避免invalid_request_error
并确保您的有效载荷符合 API 的要求。这种方法处理了用户和助手消息的交替,确保了函数调用和响应的正确结构,并维护了消息的正确顺序。
记得根据您项目的结构调整MessageSchema
常量和其他具体细节。有了这个解决方案,您应该能够可靠地与 Anthropic 的 Claude 模型进行通信。
虽然这种方法是有效的,但重要的是要注意它可能会从对话历史中删除一些消息。在保留整个对话至关重要的场景中,您应该实现一个更复杂的系统,确保合规性而不丢失上下文。