|
13 | 13 | class GoogleProvider extends BaseAiProvider |
14 | 14 | { |
15 | 15 | private const string API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent'; // v1beta is the latest supported version |
16 | | - private const string DEFAULT_MODEL = 'gemini-pro'; |
| 16 | + private const string DEFAULT_MODEL = 'gemini-2.5-flash'; |
17 | 17 | private const float DEFAULT_TEMPERATURE = 0.7; |
18 | | - private const int DEFAULT_MAX_TOKENS = 2000; |
| 18 | + private const int DEFAULT_MAX_TOKENS = 8192; // Increased default for Gemini 2.5 models (supports up to 65K output tokens) |
19 | 19 | private const string URL_PARAM_KEY = '?key='; |
20 | 20 | private const string REQUEST_KEY_CONTENTS = 'contents'; |
21 | 21 | private const string REQUEST_KEY_PARTS = 'parts'; |
@@ -112,14 +112,96 @@ protected function callApi(string $prompt, string $systemInstruction): string |
112 | 112 |
|
113 | 113 | $response = $this->makeRequest($url, $data); |
114 | 114 |
|
115 | | - if (!isset($response[self::RESPONSE_KEY_CANDIDATES][0][self::RESPONSE_KEY_CONTENT][self::REQUEST_KEY_PARTS][0][self::REQUEST_KEY_TEXT])) { |
| 115 | + // Check for error in response |
| 116 | + if (isset($response['error'])) { |
| 117 | + $errorCode = $response['error']['code'] ?? 'unknown'; |
| 118 | + $errorMessage = $response['error']['message'] ?? 'Unknown error'; |
116 | 119 | throw new QueryException( |
117 | | - 'Invalid response format from Google API', |
| 120 | + "Google API error ({$errorCode}): {$errorMessage}", |
118 | 121 | 0 |
119 | 122 | ); |
120 | 123 | } |
121 | 124 |
|
122 | | - return (string)$response[self::RESPONSE_KEY_CANDIDATES][0][self::RESPONSE_KEY_CONTENT][self::REQUEST_KEY_PARTS][0][self::REQUEST_KEY_TEXT]; |
| 125 | + // Validate response structure |
| 126 | + if (!isset($response[self::RESPONSE_KEY_CANDIDATES])) { |
| 127 | + $responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
| 128 | + $responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response'; |
| 129 | + throw new QueryException( |
| 130 | + 'Invalid response format from Google API: missing "candidates" key. Response: ' . $responseStr, |
| 131 | + 0 |
| 132 | + ); |
| 133 | + } |
| 134 | + |
| 135 | + if (empty($response[self::RESPONSE_KEY_CANDIDATES])) { |
| 136 | + $responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
| 137 | + $responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response'; |
| 138 | + throw new QueryException( |
| 139 | + 'Invalid response format from Google API: empty "candidates" array. Response: ' . $responseStr, |
| 140 | + 0 |
| 141 | + ); |
| 142 | + } |
| 143 | + |
| 144 | + $candidate = $response[self::RESPONSE_KEY_CANDIDATES][0]; |
| 145 | + |
| 146 | + // Check finish reason |
| 147 | + $finishReason = $candidate['finishReason'] ?? null; |
| 148 | + if ($finishReason !== null && $finishReason !== 'STOP') { |
| 149 | + $reasonMessages = [ |
| 150 | + 'MAX_TOKENS' => 'Response was truncated due to token limit. Consider increasing max_tokens.', |
| 151 | + 'SAFETY' => 'Response was blocked due to safety filters.', |
| 152 | + 'RECITATION' => 'Response was blocked due to recitation detection.', |
| 153 | + 'OTHER' => 'Response was stopped for an unknown reason.', |
| 154 | + ]; |
| 155 | + $message = $reasonMessages[$finishReason] ?? "Response was stopped (reason: {$finishReason})."; |
| 156 | + |
| 157 | + // Try to get partial content if available |
| 158 | + $content = $candidate[self::RESPONSE_KEY_CONTENT] ?? null; |
| 159 | + if ($content !== null && isset($content[self::REQUEST_KEY_PARTS]) && !empty($content[self::REQUEST_KEY_PARTS])) { |
| 160 | + $part = $content[self::REQUEST_KEY_PARTS][0]; |
| 161 | + if (isset($part[self::REQUEST_KEY_TEXT])) { |
| 162 | + // Return partial content with warning |
| 163 | + return (string)$part[self::REQUEST_KEY_TEXT] . "\n\n[Note: Response was truncated. {$message}]"; |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + throw new QueryException( |
| 168 | + "Google API response issue: {$message}", |
| 169 | + 0 |
| 170 | + ); |
| 171 | + } |
| 172 | + |
| 173 | + if (!isset($candidate[self::RESPONSE_KEY_CONTENT])) { |
| 174 | + $responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
| 175 | + $responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response'; |
| 176 | + throw new QueryException( |
| 177 | + 'Invalid response format from Google API: missing "content" key in candidate. Response: ' . $responseStr, |
| 178 | + 0 |
| 179 | + ); |
| 180 | + } |
| 181 | + |
| 182 | + $content = $candidate[self::RESPONSE_KEY_CONTENT]; |
| 183 | + |
| 184 | + if (!isset($content[self::REQUEST_KEY_PARTS]) || empty($content[self::REQUEST_KEY_PARTS])) { |
| 185 | + $responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
| 186 | + $responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response'; |
| 187 | + throw new QueryException( |
| 188 | + 'Invalid response format from Google API: missing or empty "parts" array. Response: ' . $responseStr, |
| 189 | + 0 |
| 190 | + ); |
| 191 | + } |
| 192 | + |
| 193 | + $part = $content[self::REQUEST_KEY_PARTS][0]; |
| 194 | + |
| 195 | + if (!isset($part[self::REQUEST_KEY_TEXT])) { |
| 196 | + $responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
| 197 | + $responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response'; |
| 198 | + throw new QueryException( |
| 199 | + 'Invalid response format from Google API: missing "text" key in part. Response: ' . $responseStr, |
| 200 | + 0 |
| 201 | + ); |
| 202 | + } |
| 203 | + |
| 204 | + return (string)$part[self::REQUEST_KEY_TEXT]; |
123 | 205 | } |
124 | 206 |
|
125 | 207 | protected function buildSystemPrompt(string $type): string |
|
0 commit comments