PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.2.0
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.2.0
3.5.9 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / engines / google.php
ai-engine / classes / engines Last commit date
core.php 2 years ago factory.php 2 years ago google.php 2 years ago openai.php 2 years ago openrouter.php 2 years ago
google.php
565 lines
1 <?php
2
3 class Meow_MWAI_Engines_Google extends Meow_MWAI_Engines_Core
4 {
5 // Base (Google)
6 protected $apiKey = null;
7 protected $region = null;
8 protected $projectId = null;
9 protected $endpoint = null;
10
11 // Response
12 protected $inModel = null;
13 protected $inId = null;
14
15 // Streaming
16 private $streamTemporaryBuffer = "";
17 private $streamBuffer = "";
18 private $streamContent = "";
19 private $streamFunctionCall = null;
20 private $streamCallback = null;
21
22 public function __construct( $core, $env )
23 {
24 parent::__construct( $core, $env );
25 $this->set_environment();
26 }
27
28 protected function set_environment() {
29 $env = $this->env;
30 $this->apiKey = $env['apikey'];
31 if ( $this->envType === 'google' ) {
32 // https://{REGION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{REGION}/publishers/google
33 $this->region = $env['region'];
34 $this->projectId = $env['projectId'];
35
36 // Google Cloud API
37 // $this->endpoint = apply_filters( 'mwai_google_endpoint', "https://{$this->region}-aiplatform.googleapis.com/v1/projects/{$this->projectId}/locations/{$this->region}/publishers/google", $this->env );
38
39 // Generative Language API (less issues with auth)
40 $this->endpoint = apply_filters( 'mwai_google_endpoint', "https://generativelanguage.googleapis.com/v2", $this->env );
41 }
42 else {
43 throw new Exception( 'Unknown environment type: ' . $this->envType );
44 }
45 }
46
47 // Check for a JSON-formatted error in the data, and throw an exception if it's the case.
48 function check_for_error( $data ) {
49 if ( strpos( $data, 'error' ) === false ) {
50 return;
51 }
52 if ( strpos( $data, 'data: ' ) === 0 ) {
53 $jsonPart = substr( $data, strlen( 'data: ' ) );
54 }
55 else {
56 $jsonPart = $data;
57 }
58 $json = json_decode( $jsonPart, true );
59 if ( json_last_error() === JSON_ERROR_NONE ) {
60 if ( isset( $json['error'] ) ) {
61 $error = $json['error'];
62 $code = $error['code'];
63 $message = $error['message'];
64 throw new Exception( "Error $code: $message" );
65 }
66 }
67 }
68
69 // Process messages to concatenate consecutive model messages as it is required by Google's API
70 private function streamline_messages($messages)
71 {
72 $processedMessages = [];
73 $lastRole = '';
74 $concatenatedText = '';
75
76 foreach ($messages as $message) {
77 if ($message['role'] == 'model') {
78 if ($lastRole == 'model') {
79 // Concatenate text if previous message was also 'model'
80 $concatenatedText .= "\n" . $message['parts'][0]['text']; // Assume there's always at least one part
81 } else {
82 // If this is the first 'model' message after a 'user' message or if it's the very first message
83 if ($concatenatedText !== '') {
84 // Add the previous 'model' concatenated text before starting a new one
85 $processedMessages[] = [
86 'role' => 'model',
87 'parts' => [['text' => $concatenatedText]]
88 ];
89 }
90 $concatenatedText = $message['parts'][0]['text'];
91 }
92 }
93 else {
94 if ($lastRole == 'model') {
95 // Add the last concatenated 'model' text before adding a 'user' message
96 $processedMessages[] = [
97 'role' => 'model',
98 'parts' => [['text' => $concatenatedText]]
99 ];
100 // Reset concatenated text for 'model' messages
101 $concatenatedText = '';
102 }
103 // Add 'user' message
104 $processedMessages[] = $message;
105 }
106 $lastRole = $message['role'];
107 }
108
109 // After loop: add any remaining concatenated 'model' text
110 if ($lastRole == 'model' && $concatenatedText !== '') {
111 $processedMessages[] = [
112 'role' => 'model',
113 'parts' => [['text' => $concatenatedText]]
114 ];
115 }
116
117 // Make sure the last message is a user message, if not, throw an exception
118 if (end($processedMessages)['role'] !== 'user') {
119 throw new Exception('The last message must be a user message.');
120 }
121
122 // Make sure the first message is a user message, if not, add an empty user message
123 if ($processedMessages[0]['role'] !== 'user') {
124 array_unshift($processedMessages, [
125 'role' => 'user',
126 'parts' => [['text' => '']]
127 ]);
128 }
129
130 return $processedMessages;
131 }
132
133 private function build_messages( $query ) {
134 $messages = [];
135
136 // First, we need to add the first message (the instructions).
137 if ( !empty( $query->instructions ) ) {
138 $messages[] = [ 'role' => 'model', 'parts' => [ [ 'text' => $query->instructions ] ] ];
139 }
140
141 // Then, if any, we need to add the 'messages', they are already formatted.
142 foreach ( $query->messages as $message ) {
143 // messages contains role and content (as OpenAI does it, but we need to convert it to Google's format)
144 // role's assistant should be model, and user should be user.
145 $newMessage = [ 'role' => $message['role'], 'parts' => [] ];
146 if ( isset( $message['content'] ) ) {
147 $newMessage['parts'][] = [ 'text' => $message['content'] ];
148 }
149 if ( $newMessage['role'] === 'assistant' ) {
150 $newMessage['role'] = 'model';
151 }
152 $messages[] = $newMessage;
153 }
154
155 // If there is a context, we need to add it.
156 if ( !empty( $query->context ) ) {
157 $messages[] = [ 'role' => 'model', 'parts' => [ [ 'text' => $query->context ] ] ];
158 }
159
160 // Finally, we need to add the message, but if there is an image, we need to add it as a model message.
161 $fileUrl = $query->get_file_url();
162 if ( !empty( $fileUrl ) ) {
163 // If the fileUrl actually is data (starts with "data:")
164 $isData = strpos( $fileUrl, 'data:' ) === 0;
165 if ( $isData ) {
166 $messages[] = [
167 'role' => 'user',
168 'parts' => [
169 [
170 "inlineData" => [
171 "mimeType" => "image/jpeg",
172 "data" => $query->file // We need to be careful here to get only the data part
173 ]
174 ],
175 [
176 "text" => $query->get_message()
177 ]
178 ]
179 ];
180 }
181 else {
182 $messages[] = [
183 'role' => 'user',
184 'parts' => [
185 [
186 "fileData" => [
187 "mimeType" => "image/jpeg",
188 "fileUri" => $fileUrl
189 ]
190 ],
191 [
192 "text" => $query->get_message()
193 ]
194 ]
195 ];
196 }
197 // TODO: Gemini doesn't support multiturn chat with Vision...
198 // So we only keep the message that goes with the image.
199 $messages = array_slice( $messages, -1 );
200 }
201 else {
202 $messages[] = [ 'role' => 'user', 'parts' => [ [ 'text' => $query->get_message() ] ] ];
203 }
204
205 // Streamline the messages
206 $messages = $this->streamline_messages( $messages );
207
208 return $messages;
209 }
210
211 /*
212 This used to be in the core.php, but since it's relative to OpenAI, it's better to have it here.
213 */
214
215 public function stream_handler( $handle, $args, $url ) {
216 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, false );
217 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, false );
218
219 // Maybe we could get some info from headers, as for now, there is only the model.
220 // curl_setopt( $handle, CURLOPT_HEADERFUNCTION, function( $curl, $headerLine ) {
221 // $line = trim( $headerLine );
222 // return strlen( $headerLine );
223 // });
224
225 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, function ( $curl, $data ) {
226 $length = strlen( $data );
227
228 // FOR DEBUG:
229 // preg_match_all( '/"content":"(.*?)"/', $data, $matches );
230 // $contents = $matches[1];
231 // foreach ( $contents as $content ) {
232 // error_log( "Content: $content" );
233 // }
234
235 // Error Management
236 $this->check_for_error( $data );
237
238 // Bufferize the unfinished stream (if it's the case)
239 $this->streamTemporaryBuffer .= $data;
240 $this->streamBuffer .= $data;
241 $lines = explode( "\n", $this->streamTemporaryBuffer );
242 if ( substr( $this->streamTemporaryBuffer, -1 ) !== "\n" ) {
243 $this->streamTemporaryBuffer = array_pop( $lines );
244 }
245 else {
246 $this->streamTemporaryBuffer = "";
247 }
248
249 foreach ( $lines as $line ) {
250 if ( $line === "" ) {
251 continue;
252 }
253 if ( strpos( $line, 'data: ' ) === 0 ) {
254 $line = substr( $line, 6 );
255 $json = json_decode( $line, true );
256
257 if ( json_last_error() === JSON_ERROR_NONE ) {
258 $content = null;
259 // Get the content
260 if ( isset( $json['candidates'][0]['content']['parts'][0]['text'] ) ) {
261 $content = $json['candidates'][0]['content']['parts'][0]['text'];
262 }
263 // else if ( isset( $json['candidates'][0]['delta']['content'] ) ) {
264 // $content = $json['candidates'][0]['delta']['content'];
265 // }
266 if ( $content !== null && $content !== "" ) {
267 $this->streamContent .= $content;
268 call_user_func( $this->streamCallback, $content );
269 }
270 }
271 else {
272 $this->streamTemporaryBuffer .= $line . "\n";
273 }
274 }
275 }
276 return $length;
277 });
278 }
279
280 protected function build_headers( $query ) {
281 if ( $query->apiKey ) {
282 $this->apiKey = $query->apiKey;
283 }
284 if ( empty( $this->apiKey ) ) {
285 throw new Exception( 'No API Key provided. Please visit the Settings.' );
286 }
287 $headers = array(
288 'Content-Type' => 'application/json',
289 );
290 return $headers;
291 }
292
293 protected function build_options( $headers, $json = null, $forms = null, $method = 'POST' ) {
294 $body = null;
295 if ( !empty( $forms ) ) {
296 throw new Exception( 'No support for form-data requests yet.' );
297 // $boundary = wp_generate_password ( 24, false );
298 // $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
299 // $body = $this->build_form_body( $forms, $boundary );
300 }
301 else if ( !empty( $json ) ) {
302 $body = json_encode( $json );
303 }
304 $options = array(
305 'headers' => $headers,
306 'method' => $method,
307 'timeout' => MWAI_TIMEOUT,
308 'body' => $body,
309 'sslverify' => false
310 );
311 return $options;
312 }
313
314 public function run_query( $url, $options, $isStream = false ) {
315 try {
316 $options['stream'] = $isStream;
317 if ( $isStream ) {
318 $options['filename'] = tempnam( sys_get_temp_dir(), 'mwai-stream-' );
319 }
320 $res = wp_remote_get( $url, $options );
321
322 if ( is_wp_error( $res ) ) {
323 throw new Exception( $res->get_error_message() );
324 }
325
326 if ( $isStream ) {
327 return [ 'stream' => true ];
328 }
329
330 $response = wp_remote_retrieve_body( $res );
331 $headersRes = wp_remote_retrieve_headers( $res );
332 $headers = $headersRes->getAll();
333
334 // Check if Content-Type is 'multipart/form-data' or 'text/plain'
335 // If so, we don't need to decode the response
336 $normalizedHeaders = array_change_key_case( $headers, CASE_LOWER );
337 $resContentType = $normalizedHeaders['content-type'] ?? '';
338 if ( strpos( $resContentType, 'multipart/form-data' ) !== false || strpos( $resContentType, 'text/plain' ) !== false ) {
339 return [ 'stream' => false, 'headers' => $headers, 'data' => $response ];
340 }
341
342 $data = json_decode( $response, true );
343 $this->handle_response_errors( $data );
344 return [ 'headers' => $headers, 'data' => $data ];
345 }
346 catch ( Exception $e ) {
347 error_log( $e->getMessage() );
348 throw $e;
349 }
350 }
351
352 public function run_completion_query( $query, $streamCallback = null ) : Meow_MWAI_Reply {
353 if ( !is_null( $streamCallback ) ) {
354 $this->streamCallback = $streamCallback;
355 add_action( 'http_api_curl', array( $this, 'stream_handler' ), 10, 3 );
356 }
357
358 $body = array(
359 "generationConfig" => [
360 "candidateCount" => $query->maxResults,
361 "maxOutputTokens" => $query->maxTokens,
362 "temperature" => $query->temperature,
363 "stopSequences" => [],
364 ],
365 );
366
367 // if ( !empty( $query->stop ) ) {
368 // $body['generationConfig']['stop'] = $query->stop;
369 // }
370
371 // if ( !empty( $query->responseFormat ) ) {
372 // if ( $query->responseFormat === 'json' ) {
373 // $body['response_format'] = [ 'type' => 'json_object' ];
374 // }
375 // }
376
377 if ( !empty( $query->functions ) ) {
378 throw new Exception( 'AI Engine doesn\'t support Function Calling with Google models yet.' );
379 //$body['functions'] = $query->functions;
380 //$body['function_call'] = $query->functionCall;
381 }
382
383 if ( $query->mode !== 'chat' ) {
384 throw new Exception( 'Google models only support chat mode.' );
385 }
386
387 $body['contents'] = $this->build_messages( $query );
388 $url = $this->endpoint;
389
390 // Streaming:
391 // $url .= '/models/' . $query->model . ':streamGenerateContent';
392
393 $url .= '/models/' . $query->model . ':generateContent';
394
395 // If streaming is enabled, we need to use the SSE endpoint.
396 if ( !is_null( $streamCallback ) ) {
397 $url .= '?alt=sse';
398 }
399
400 // Add the API key
401 if ( strpos( $url, '?' ) === false ) {
402 $url .= '?key=' . $this->apiKey;
403 }
404 else {
405 $url .= '&key=' . $this->apiKey;
406 }
407
408 $headers = $this->build_headers( $query );
409 $options = $this->build_options( $headers, $body );
410
411 try {
412 $res = $this->run_query( $url, $options, $streamCallback );
413 $reply = new Meow_MWAI_Reply( $query );
414
415 $returned_id = null;
416 $returned_model = $this->inModel;
417 $returned_in_tokens = null;
418 $returned_out_tokens = null;
419 $returned_choices = [];
420
421 if ( !is_null( $streamCallback ) ) {
422 // Streamed data
423 if ( empty( $this->streamContent ) ) {
424 $json = json_decode( $this->streamBuffer, true );
425 if ( isset( $json['error']['message'] ) ) {
426 throw new Exception( $json['error']['message'] );
427 }
428 }
429 $returned_id = $this->inId;
430 $returned_model = $this->inModel ? $this->inModel : $query->model;
431 $returned_choices = [
432 [
433 'message' => [
434 'content' => $this->streamContent,
435 'function_call' => $this->streamFunctionCall
436 ]
437 ]
438 ];
439 }
440 else {
441 // Regular data
442 $data = $res['data'];
443 if ( empty( $data ) ) {
444 throw new Exception( 'No content received (res is null).' );
445 }
446
447 // Not much information from Google's API :(
448 $returned_id = null;
449 $returned_model = $query->model;
450 $returned_in_tokens = 0;
451 $returned_out_tokens = 0;
452
453 // We should return the candidates formatted as OpenAI does it.
454 $returned_choices = [];
455 if ( isset( $data['candidates'] ) ) {
456 $candidates = $data['candidates'];
457 foreach ( $candidates as $candidate ) {
458 $content = $candidate['content'];
459 $text = $content['parts'][0]['text'];
460 $returned_choices[] = [ 'role' => 'assistant', 'text' => $text ];
461 }
462 }
463 }
464
465 // Set the results.
466 $reply->set_choices( $returned_choices );
467 if ( !empty( $returned_id ) ) {
468 $reply->set_id( $returned_id );
469 }
470
471 // Handle tokens.
472 $this->handle_tokens_usage( $reply, $query, $returned_model, $returned_in_tokens, $returned_out_tokens );
473
474 return $reply;
475 }
476 catch ( Exception $e ) {
477 error_log( $e->getMessage() );
478 $message = "From Google: " . $e->getMessage();
479 throw new Exception( $message );
480 }
481 }
482
483 public function handle_tokens_usage( $reply, $query, $returned_model, $returned_in_tokens, $returned_out_tokens ) {
484 $returned_in_tokens = !is_null( $returned_in_tokens ) ? $returned_in_tokens : $reply->get_in_tokens( $query );
485 $returned_out_tokens = !is_null( $returned_out_tokens ) ? $returned_out_tokens : $reply->get_out_tokens();
486 $usage = $this->core->record_tokens_usage( $returned_model, $returned_in_tokens, $returned_out_tokens );
487 $reply->set_usage( $usage );
488 }
489
490 /*
491 This is the rest of the OpenAI API support, not related to the models directly.
492 */
493
494 // Check if there are errors in the response from OpenAI, and throw an exception if so.
495 public function handle_response_errors( $data ) {
496 if ( isset( $data['error'] ) ) {
497 $message = $data['error']['message'];
498 if ( preg_match( '/API key provided(: .*)\./', $message, $matches ) ) {
499 $message = str_replace( $matches[1], '', $message );
500 }
501 throw new Exception( $message );
502 }
503 }
504
505 public function get_models() {
506 return $this->core->get_option( 'google_models' );
507 }
508
509 public function retrieve_models() {
510 $url = "https://generativelanguage.googleapis.com/v1/models";
511 $url .= "?key=" . $this->apiKey;
512 $response = wp_remote_get( $url );
513 if ( is_wp_error( $response ) ) {
514 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
515 }
516 $body = json_decode( $response['body'], true );
517 $models = array();
518 foreach ( $body['models'] as $model ) {
519 if ( strpos( $model['name'], 'gemini' ) === false ) {
520 continue;
521 }
522 $family = "gemini";
523 $maxCompletionTokens = $model['outputTokenLimit'];
524 $maxContextualTokens = $model['inputTokenLimit'];
525 $priceIn = 0;
526 $priceOut = 0;
527 $tags = [ 'core', 'chat' ];
528 // If the name contains (beta), (alpha) or (preview), add 'preview' tag and remove from name
529 if ( preg_match( '/\((beta|alpha|preview)\)/i', $model['name'], $matches ) ) {
530 $tags[] = 'preview';
531 $model['name'] = preg_replace( '/\((beta|alpha|preview)\)/i', '', $model['name'] );
532 }
533 // If the name includes 'Vision', add 'vision' tag
534 if ( preg_match( '/vision/i', $model['name'], $matches ) ) {
535 $tags[] = 'vision';
536 }
537 $name = preg_replace( '/^models\//', '', $model['name'] );
538 $model = array(
539 'model' => $name,
540 'name' => $name,
541 'family' => $family,
542 'mode' => 'chat',
543 'type' => 'token',
544 'unit' => 1 / 1000,
545 'maxCompletionTokens' => $maxCompletionTokens,
546 'maxContextualTokens' => $maxContextualTokens,
547 'tags' => $tags
548 );
549 if ( $priceIn > 0 && $priceOut > 0 ) {
550 $model['price'] = array(
551 'in' => $priceIn,
552 'out' => $priceOut,
553 );
554 }
555 $models[] = $model;
556 }
557 return $models;
558 }
559
560 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
561 // TODO: Not sure how to get the price from Google's API.
562 return null;
563 }
564 }
565