Skip to content

如何为 Anthropic 的 Messages API 构建合规的有效载荷:避免错误

引言

在使用Anthropic 的 API,特别是 messages 端点时,开发人员经常遇到invalid_request_error。这个错误可能由于发送到 API 的有效载荷结构的各种原因而发生。在本文中,我们将探讨如何构建合规的有效载荷以避免这个错误,并确保与 Anthropic 的 API 顺畅通信。

理解 Anthropic 的 API 要求

Anthropic 的 API 对消息有效载荷的结构有特定要求。这些要求确保 AI 模型能够正确处理对话上下文。主要考虑的点是:

  1. 消息必须在用户和助手角色之间交替。
  2. 第一条和最后一条消息应该来自用户。
  3. 函数调用是助手消息,响应是用户消息。

解决方案

为了解决这个问题,我们将使用一个名为LLMProvider的辅助类。这个类包含一个名为getStandardMessagesStructure的方法,用于处理消息数组以确保其符合 Anthropic 的要求。

我们的$messages数组模式如下:

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';
}

这个模式的目标是足够灵活,可以用作聊天机器人消息数据库,同时兼容多个 LLM,如 OpenAI 和 Google Gemini,但在本文范围内,我们将专注于解决 Anthropic API 的请求错误。

请随意根据您在数据库中存储消息的方式来调整以下代码,然后再将其发送给第三方提供商。

步骤 1:实现 getStandardMessagesStructure 方法

php
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

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

这个函数确保数组中的第一条消息来自用户(角色为'contact')。

步骤 3:实现 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;
}

这个函数确保消息在用户和助手角色之间交替。

步骤 4:实现 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;
}

这个函数确保最后一条消息要么来自用户,要么是有效的函数响应。

步骤 5:在 API 调用中使用 getStandardMessagesStructure

在调用 Anthropic 的 API 时,使用getStandardMessagesStructure方法处理您的消息数组:

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
);

AnthropicProvider类中,我们将$messages数组映射到 Anthropic 兼容的消息结构,这在一个名为getParsedMessages的函数中完成。以下是该函数的一部分供参考:

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];

            ... // 其他映射

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

示例:处理前后

让我们看一个消息有效载荷结构在处理前后如何变化的例子:

处理前:

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?'],
];

处理后:

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

消息结构的最佳实践

为了最小化广泛处理的需求,在构建消息时请考虑以下最佳实践:

  1. 总是以用户消息开始对话。
  2. 在用户和助手消息之间交替。
  3. 以用户消息或有效的函数响应结束对话。
  4. 在对话流程中适当处理AI 对话中的函数调用

错误处理

当遇到invalid_request_error时,记录错误详情和导致错误的有效载荷至关重要。这可以帮助调试和改进您的消息处理逻辑。考虑实现一个 try-catch 块:

php
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 模型进行通信。

虽然这种方法是有效的,但重要的是要注意它可能会从对话历史中删除一些消息。在保留整个对话至关重要的场景中,您应该实现一个更复杂的系统,确保合规性而不丢失上下文。