Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
44 / 44 |
|
100.00% |
6 / 6 |
CRAP | |
100.00% |
1 / 1 |
| AbstractAnthropicGenerator | |
100.00% |
44 / 44 |
|
100.00% |
6 / 6 |
13 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| getPromptData | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
| createPayload | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
| formatContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| ensureExpectedKeys | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
| sendApiRequest | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| getExpectedKeys | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getLoggerName | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| 1 | <?php |
| 2 | declare(strict_types=1); |
| 3 | |
| 4 | namespace App\Service\Api\Anthropic; |
| 5 | |
| 6 | use App\Model\Table\AipromptsTable; |
| 7 | use App\Service\Api\AiProviderInterface; |
| 8 | use Cake\Log\LogTrait; |
| 9 | use InvalidArgumentException; |
| 10 | |
| 11 | /** |
| 12 | * AbstractAnthropicGenerator |
| 13 | * |
| 14 | * Base class for all Anthropic API generators providing common functionality |
| 15 | * for prompt retrieval, payload creation, and response validation. |
| 16 | */ |
| 17 | abstract class AbstractAnthropicGenerator |
| 18 | { |
| 19 | use LogTrait; |
| 20 | |
| 21 | /** |
| 22 | * The AI provider service used for sending requests and parsing responses. |
| 23 | * |
| 24 | * @var \App\Service\Api\AiProviderInterface |
| 25 | */ |
| 26 | protected AiProviderInterface $apiService; |
| 27 | |
| 28 | /** |
| 29 | * The AI prompts table for retrieving prompt data necessary for generation. |
| 30 | * |
| 31 | * @var \App\Model\Table\AipromptsTable |
| 32 | */ |
| 33 | protected AipromptsTable $aipromptsTable; |
| 34 | |
| 35 | /** |
| 36 | * AbstractAnthropicGenerator constructor. |
| 37 | * |
| 38 | * Initializes the API service and AI prompts table. |
| 39 | * |
| 40 | * @param \App\Service\Api\AiProviderInterface $apiService The AI provider service. |
| 41 | * @param \App\Model\Table\AipromptsTable $aipromptsTable The AI prompts table. |
| 42 | */ |
| 43 | public function __construct(AiProviderInterface $apiService, AipromptsTable $aipromptsTable) |
| 44 | { |
| 45 | $this->apiService = $apiService; |
| 46 | $this->aipromptsTable = $aipromptsTable; |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * Retrieves prompt data for a specific task from the AI prompts table. |
| 51 | * |
| 52 | * The model returned depends on the configured provider: |
| 53 | * - For 'openrouter': uses the openrouter_model field |
| 54 | * - For 'anthropic' (or any other): uses the model field |
| 55 | * |
| 56 | * @param string $task The task type for which to retrieve prompt data. |
| 57 | * @return array The retrieved prompt data. |
| 58 | * @throws \InvalidArgumentException If the task is unknown or not found. |
| 59 | */ |
| 60 | protected function getPromptData(string $task): array |
| 61 | { |
| 62 | $prompt = $this->aipromptsTable->find() |
| 63 | ->where(['task_type' => $task]) |
| 64 | ->first(); |
| 65 | |
| 66 | if (!$prompt) { |
| 67 | throw new InvalidArgumentException("Unknown task: {$task}"); |
| 68 | } |
| 69 | |
| 70 | // Select the appropriate model based on the provider |
| 71 | $providerName = $this->apiService->getProviderName(); |
| 72 | $model = $providerName === 'openrouter' && !empty($prompt->openrouter_model) |
| 73 | ? $prompt->openrouter_model |
| 74 | : $prompt->model; |
| 75 | |
| 76 | return [ |
| 77 | 'system_prompt' => $prompt->system_prompt, |
| 78 | 'model' => $model, |
| 79 | 'max_tokens' => $prompt->max_tokens, |
| 80 | 'temperature' => $prompt->temperature, |
| 81 | ]; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Creates a basic payload for the API request using the provided prompt data and content. |
| 86 | * |
| 87 | * This method handles the standard payload structure. Child classes can override |
| 88 | * this method for specialized payload requirements (e.g., image data). |
| 89 | * |
| 90 | * @param array $promptData The prompt data retrieved from the AI prompts table. |
| 91 | * @param mixed $content The content to be included in the payload. |
| 92 | * @return array The created payload for the API request. |
| 93 | */ |
| 94 | protected function createPayload(array $promptData, mixed $content): array |
| 95 | { |
| 96 | return [ |
| 97 | 'model' => $promptData['model'], |
| 98 | 'max_tokens' => $promptData['max_tokens'], |
| 99 | 'temperature' => $promptData['temperature'], |
| 100 | 'system' => $promptData['system_prompt'], |
| 101 | 'messages' => [ |
| 102 | [ |
| 103 | 'role' => 'user', |
| 104 | 'content' => $this->formatContent($content), |
| 105 | ], |
| 106 | ], |
| 107 | ]; |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Formats content for inclusion in the API payload. |
| 112 | * |
| 113 | * Most generators use JSON encoding for arrays, plain strings for text. |
| 114 | * Child classes can override for specialized formatting. |
| 115 | * |
| 116 | * @param mixed $content The content to format. |
| 117 | * @return mixed The formatted content. |
| 118 | */ |
| 119 | protected function formatContent(mixed $content): mixed |
| 120 | { |
| 121 | return is_array($content) ? json_encode($content) : $content; |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Ensures that the result contains all expected keys, initializing them if necessary. |
| 126 | * |
| 127 | * @param array $result The result array to check and modify. |
| 128 | * @param array|null $expectedKeys Optional array of expected keys. If null, uses getExpectedKeys(). |
| 129 | * @return array The result array with all expected keys initialized. |
| 130 | */ |
| 131 | protected function ensureExpectedKeys(array $result, ?array $expectedKeys = null): array |
| 132 | { |
| 133 | $keys = $expectedKeys ?? $this->getExpectedKeys(); |
| 134 | |
| 135 | foreach ($keys as $key) { |
| 136 | if (!isset($result[$key])) { |
| 137 | $result[$key] = ''; |
| 138 | $this->log( |
| 139 | sprintf('%s did not find expected key: %s', $this->getLoggerName(), $key), |
| 140 | 'error', |
| 141 | ['group_name' => 'anthropic'], |
| 142 | ); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | return $result; |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Sends a request to the API with optional timeout override. |
| 151 | * |
| 152 | * @param array $payload The payload to send. |
| 153 | * @param int|null $timeout Optional timeout override. |
| 154 | * @return array The parsed response. |
| 155 | */ |
| 156 | protected function sendApiRequest(array $payload, ?int $timeout = null): array |
| 157 | { |
| 158 | $response = $timeout !== null |
| 159 | ? $this->apiService->sendRequest($payload, $timeout) |
| 160 | : $this->apiService->sendRequest($payload); |
| 161 | |
| 162 | return $this->apiService->parseResponse($response); |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Gets the expected keys for the API response. |
| 167 | * Each generator must define what keys it expects from the API. |
| 168 | * |
| 169 | * @return array Array of expected response keys. |
| 170 | */ |
| 171 | abstract protected function getExpectedKeys(): array; |
| 172 | |
| 173 | /** |
| 174 | * Gets the logger name for this generator. |
| 175 | * Used in error logging to identify which generator logged the message. |
| 176 | * |
| 177 | * @return string The logger name. |
| 178 | */ |
| 179 | abstract protected function getLoggerName(): string; |
| 180 | } |