PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.7.5
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.7.5
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
anthropic.php 1 year ago core.php 1 year ago factory.php 1 year ago google.php 1 year ago huggingface.php 1 year ago openai.php 1 year ago openrouter.php 1 year ago perplexity.php 1 year ago replicate.php 1 year ago
google.php
482 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 $streamFunctionCall = null;
17
18 public function __construct( $core, $env )
19 {
20 parent::__construct( $core, $env );
21 $this->set_environment();
22 }
23
24 protected function set_environment() {
25 $env = $this->env;
26 $this->apiKey = $env['apikey'];
27 if ( $this->envType === 'google' ) {
28 // https://{REGION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{REGION}/publishers/google
29 $this->region = $env['region'];
30 $this->projectId = $env['projectId'];
31
32 // Google Cloud API
33 // $this->endpoint = apply_filters( 'mwai_google_endpoint', "https://{$this->region}-aiplatform.googleapis.com/v1/projects/{$this->projectId}/locations/{$this->region}/publishers/google", $this->env );
34
35 // Generative Language API (less issues with auth)
36 $this->endpoint = apply_filters( 'mwai_google_endpoint', "https://generativelanguage.googleapis.com/v1beta", $this->env );
37 }
38 else {
39 throw new Exception( 'Unknown environment type: ' . $this->envType );
40 }
41 }
42
43 // Check for a JSON-formatted error in the data, and throw an exception if it's the case.
44 function check_for_error( $data ) {
45 if ( strpos( $data, 'error' ) === false ) {
46 return;
47 }
48 if ( strpos( $data, 'data: ' ) === 0 ) {
49 $jsonPart = substr( $data, strlen( 'data: ' ) );
50 }
51 else {
52 $jsonPart = $data;
53 }
54 $json = json_decode( $jsonPart, true );
55 if ( json_last_error() === JSON_ERROR_NONE ) {
56 if ( isset( $json['error'] ) ) {
57 $error = $json['error'];
58 $code = $error['code'];
59 $message = $error['message'];
60 throw new Exception( "Error $code: $message" );
61 }
62 }
63 }
64
65 private function build_messages( $query ) {
66 $messages = [];
67
68 // First, we need to add the first message (the instructions).
69 if ( !empty( $query->instructions ) ) {
70 $messages[] = [ 'role' => 'model', 'parts' => [ [ 'text' => $query->instructions ] ] ];
71 }
72
73 // Then, if any, we need to add the 'messages', they are already formatted.
74 foreach ( $query->messages as $message ) {
75 // messages contains role and content (as OpenAI does it, but we need to convert it to Google's format)
76 // role's assistant should be model, and user should be user.
77 $newMessage = [ 'role' => $message['role'], 'parts' => [] ];
78 if ( isset( $message['content'] ) ) {
79 $newMessage['parts'][] = [ 'text' => $message['content'] ];
80 }
81 if ( $newMessage['role'] === 'assistant' ) {
82 $newMessage['role'] = 'model';
83 }
84 $messages[] = $newMessage;
85 }
86
87 // If there is a context, we need to add it.
88 if ( !empty( $query->context ) ) {
89 $messages[] = [ 'role' => 'model', 'parts' => [ [ 'text' => $query->context ] ] ];
90 }
91
92 // Finally, we need to add the message, but if there is an image, we need to add it as a model message.
93 if ( $query->attachedFile) {
94 // Gemini doesn't handle URL uploads, so we need to convert it to base64.
95 //$remote_upload = $this->core->get_option( 'image_remote_upload' );
96 $data = $query->attachedFile->get_base64();
97 //$data = strpos( $data, 'data:' ) === 0;
98 $messages[] = [
99 'role' => 'user',
100 'parts' => [
101 [
102 "inlineData" => [
103 "mimeType" => "image/jpeg",
104 "data" => $data // We need to be careful here to get only the data part
105 ]
106 ],
107 [
108 "text" => $query->get_message()
109 ]
110 ]
111 ];
112 // TODO: Gemini doesn't support multiturn chat with Vision...
113 // So we only keep the message that goes with the image.
114 $messages = array_slice( $messages, -1 );
115 }
116 else {
117 $messages[] = [ 'role' => 'user', 'parts' => [ [ 'text' => $query->get_message() ] ] ];
118 }
119
120 // Streamline the messages
121 $messages = $this->streamline_messages( $messages, 'model', 'parts' );
122
123 // Add the feedback if it's a feedback query.
124 if ( $query instanceof Meow_MWAI_Query_Feedback ) {
125 if ( !empty( $query->blocks ) ) {
126 foreach ( $query->blocks as $feedback_block ) {
127 $messages[] = $feedback_block['rawMessage'];
128 foreach ( $feedback_block['feedbacks'] as $feedback ) {
129 $messages[] = [
130 'role' => "user",
131 'parts' => [
132 'functionResponse' => [
133 'name' => $feedback['request']['name'],
134 'response' => [
135 'content' => $feedback['reply']['value']
136 ]
137 ]
138 ]
139 ];
140 }
141 }
142 }
143 }
144
145 return $messages;
146 }
147
148 protected function stream_data_handler( $json ) {
149 $content = null;
150
151 // Is it a function call?
152 if ( isset( $json['candidates'][0]['content']['parts'][0]['functionCall'] ) ) {
153 $this->streamFunctionCall = $json['candidates'][0]['content']['parts'][0]['functionCall'];
154 }
155
156 // Get the content
157 if ( isset( $json['candidates'][0]['content']['parts'][0]['text'] ) ) {
158 $content = $json['candidates'][0]['content']['parts'][0]['text'];
159 }
160
161 // Avoid some endings
162 $endings = [ "<|im_end|>", "</s>" ];
163 if ( in_array( $content, $endings ) ) {
164 $content = null;
165 }
166
167 return ( $content === '0' || !empty( $content ) ) ? $content : null;
168 }
169
170 protected function build_headers( $query ) {
171 if ( $query->apiKey ) {
172 $this->apiKey = $query->apiKey;
173 }
174 if ( empty( $this->apiKey ) ) {
175 throw new Exception( 'No API Key provided. Please visit the Settings.' );
176 }
177 $headers = array(
178 'Content-Type' => 'application/json',
179 );
180 return $headers;
181 }
182
183 protected function build_options( $headers, $json = null, $forms = null, $method = 'POST' ) {
184 $body = null;
185 if ( !empty( $forms ) ) {
186 throw new Exception( 'No support for form-data requests yet.' );
187 // $boundary = wp_generate_password ( 24, false );
188 // $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
189 // $body = $this->build_form_body( $forms, $boundary );
190 }
191 else if ( !empty( $json ) ) {
192 $body = json_encode( $json );
193 }
194 $options = array(
195 'headers' => $headers,
196 'method' => $method,
197 'timeout' => MWAI_TIMEOUT,
198 'body' => $body,
199 'sslverify' => false
200 );
201 return $options;
202 }
203
204 public function run_query( $url, $options, $isStream = false ) {
205 try {
206 $options['stream'] = $isStream;
207 if ( $isStream ) {
208 $options['filename'] = tempnam( sys_get_temp_dir(), 'mwai-stream-' );
209 }
210 $res = wp_remote_get( $url, $options );
211
212 if ( is_wp_error( $res ) ) {
213 throw new Exception( $res->get_error_message() );
214 }
215
216 if ( $isStream ) {
217 return [ 'stream' => true ];
218 }
219
220 $response = wp_remote_retrieve_body( $res );
221 $headersRes = wp_remote_retrieve_headers( $res );
222 $headers = $headersRes->getAll();
223
224 // Check if Content-Type is 'multipart/form-data' or 'text/plain'
225 // If so, we don't need to decode the response
226 $normalizedHeaders = array_change_key_case( $headers, CASE_LOWER );
227 $resContentType = $normalizedHeaders['content-type'] ?? '';
228 if ( strpos( $resContentType, 'multipart/form-data' ) !== false || strpos( $resContentType, 'text/plain' ) !== false ) {
229 return [ 'stream' => false, 'headers' => $headers, 'data' => $response ];
230 }
231
232 $data = json_decode( $response, true );
233 $this->handle_response_errors( $data );
234 return [ 'headers' => $headers, 'data' => $data ];
235 }
236 catch ( Exception $e ) {
237 Meow_MWAI_Logging::error( "(Google) " . $e->getMessage() );
238 throw $e;
239 }
240 finally {
241 if ( $isStream && file_exists( $options['filename'] ) ) {
242 unlink( $options['filename'] );
243 }
244 }
245 }
246
247 public function run_completion_query( $query, $streamCallback = null ) : Meow_MWAI_Reply {
248 if ( !is_null( $streamCallback ) ) {
249 $this->streamCallback = $streamCallback;
250 add_action( 'http_api_curl', array( $this, 'stream_handler' ), 10, 3 );
251 }
252
253 $body = array(
254 "generationConfig" => [
255 "candidateCount" => $query->maxResults,
256 "maxOutputTokens" => $query->maxTokens,
257 "temperature" => $query->temperature,
258 "stopSequences" => [],
259 ],
260 );
261
262 // if ( !empty( $query->stop ) ) {
263 // $body['generationConfig']['stop'] = $query->stop;
264 // }
265
266 // if ( !empty( $query->responseFormat ) ) {
267 // if ( $query->responseFormat === 'json' ) {
268 // $body['response_format'] = [ 'type' => 'json_object' ];
269 // }
270 // }
271
272 if ( !empty( $query->functions ) ) {
273 //throw new Exception( 'AI Engine doesn\'t support Function Calling with Google models yet.' );
274 $body['tools'] = [[
275 'function_declarations' => []
276 ]];
277 // Dynamic function: they will interactively enhance the completion (tools).
278 foreach ( $query->functions as $function ) {
279 $body['tools'][0]['function_declarations'][] = $function->serializeForOpenAI();
280 }
281 $body['tool_config'] = [
282 'function_calling_config' => [
283 'mode' => 'AUTO'
284 ]
285 ];
286 }
287
288 $body['contents'] = $this->build_messages( $query );
289
290 $url = $this->endpoint;
291
292 // Streaming:
293 // $url .= '/models/' . $query->model . ':streamGenerateContent';
294
295 $url .= '/models/' . $query->model . ':generateContent';
296
297 // If streaming is enabled, we need to use the SSE endpoint.
298 if ( !is_null( $streamCallback ) ) {
299 $url .= '?alt=sse';
300 }
301
302 // Add the API key
303 if ( strpos( $url, '?' ) === false ) {
304 $url .= '?key=' . $this->apiKey;
305 }
306 else {
307 $url .= '&key=' . $this->apiKey;
308 }
309
310 $headers = $this->build_headers( $query );
311 $options = $this->build_options( $headers, $body );
312
313 try {
314 $res = $this->run_query( $url, $options, $streamCallback );
315 $reply = new Meow_MWAI_Reply( $query );
316
317 $returned_id = null;
318 $returned_model = $this->inModel;
319 $returned_in_tokens = null;
320 $returned_out_tokens = null;
321 $returned_choices = [];
322
323 if ( !is_null( $streamCallback ) ) {
324 // Streamed data
325 if ( empty( $this->streamContent ) ) {
326 $json = json_decode( $this->streamBuffer, true );
327 if ( isset( $json['error']['message'] ) ) {
328 throw new Exception( $json['error']['message'] );
329 }
330 }
331 $returned_id = $this->inId;
332 $returned_model = $this->inModel ? $this->inModel : $query->model;
333 $returned_choices = [
334 [
335 'message' => [
336 'content' => $this->streamContent,
337 'function_call' => $this->streamFunctionCall
338 ]
339 ]
340 ];
341 }
342 else {
343 // Regular data
344 $data = $res['data'];
345 if ( empty( $data ) ) {
346 throw new Exception( 'No content received (res is null).' );
347 }
348
349 // Not much information from Google's API :(
350 $returned_id = null;
351 $returned_model = $query->model;
352 $returned_in_tokens = null;
353 $returned_out_tokens = null;
354
355 // We should return the candidates formatted as OpenAI does it.
356 $returned_choices = [];
357 if ( isset( $data['candidates'] ) ) {
358 $candidates = $data['candidates'];
359 foreach ( $candidates as $candidate ) {
360 $content = $candidate['content'];
361 $text = $content['parts'][0]['text'];
362 $returned_choices[] = [ 'role' => 'assistant', 'text' => $text ];
363 }
364 }
365 }
366
367 // Set the results.
368 $reply->set_choices( $returned_choices );
369 if ( !empty( $returned_id ) ) {
370 $reply->set_id( $returned_id );
371 }
372
373 // Handle tokens.
374 $this->handle_tokens_usage( $reply, $query, $returned_model, $returned_in_tokens, $returned_out_tokens );
375
376 return $reply;
377 }
378 catch ( Exception $e ) {
379 Meow_MWAI_Logging::error( "(Google) " . $e->getMessage() );
380 $message = "From Google: " . $e->getMessage();
381 throw new Exception( $message );
382 }
383 }
384
385 public function handle_tokens_usage( $reply, $query, $returned_model,
386 $returned_in_tokens, $returned_out_tokens ) {
387 $returned_in_tokens = !is_null( $returned_in_tokens ) ?
388 $returned_in_tokens : $reply->get_in_tokens( $query );
389 $returned_out_tokens = !is_null( $returned_out_tokens ) ?
390 $returned_out_tokens : $reply->get_out_tokens();
391 $usage = $this->core->record_tokens_usage(
392 $returned_model,
393 $returned_in_tokens,
394 $returned_out_tokens
395 );
396 $reply->set_usage( $usage );
397 }
398
399 /*
400 This is the rest of the OpenAI API support, not related to the models directly.
401 */
402
403 // Check if there are errors in the response from OpenAI, and throw an exception if so.
404 public function handle_response_errors( $data ) {
405 if ( isset( $data['error'] ) ) {
406 $message = $data['error']['message'];
407 if ( preg_match( '/API key provided(: .*)\./', $message, $matches ) ) {
408 $message = str_replace( $matches[1], '', $message );
409 }
410 throw new Exception( $message );
411 }
412 }
413
414 public function get_models() {
415 return $this->core->get_engine_models( 'google' );
416 }
417
418 public function retrieve_models() {
419 $url = "https://generativelanguage.googleapis.com/v1/models";
420 $url .= "?key=" . $this->apiKey;
421 $response = wp_remote_get( $url );
422 if ( is_wp_error( $response ) ) {
423 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
424 }
425 $body = json_decode( $response['body'], true );
426 $models = array();
427 foreach ( $body['models'] as $model ) {
428 if ( strpos( $model['name'], 'gemini' ) === false ) {
429 continue;
430 }
431 $family = "gemini";
432 $maxCompletionTokens = $model['outputTokenLimit'];
433 $maxContextualTokens = $model['inputTokenLimit'];
434 $priceIn = 0;
435 $priceOut = 0;
436 $tags = [ 'core', 'chat' ];
437 // If the name contains (beta), (alpha) or (preview), add 'preview' tag and remove from name
438 if ( preg_match( '/\((beta|alpha|preview)\)/i', $model['name'], $matches ) ) {
439 $tags[] = 'preview';
440 $model['name'] = preg_replace( '/\((beta|alpha|preview)\)/i', '', $model['name'] );
441 }
442 // If the name includes 'Vision', add 'vision' tag
443 if ( preg_match( '/vision/i', $model['name'], $matches ) ) {
444 $tags[] = 'vision';
445 }
446 else if ( preg_match( '/(vision|multimodal)/i', $model['description'], $matches ) ) {
447 $tags[] = 'vision';
448 }
449 if ( preg_match( '/flash/i', $model['name'], $matches ) ) {
450 $tags[] = 'vision';
451 $tags[] = 'functions';
452 $features[] = 'functions';
453 }
454 $name = preg_replace( '/^models\//', '', $model['name'] );
455 $model = array(
456 'model' => $name,
457 'name' => $name,
458 'family' => $family,
459 'features' => ['completion'],
460 'type' => 'token',
461 'unit' => 1 / 1000,
462 'maxCompletionTokens' => $maxCompletionTokens,
463 'maxContextualTokens' => $maxContextualTokens,
464 'tags' => $tags
465 );
466 if ( $priceIn > 0 && $priceOut > 0 ) {
467 $model['price'] = array(
468 'in' => $priceIn,
469 'out' => $priceOut,
470 );
471 }
472 $models[] = $model;
473 }
474 return $models;
475 }
476
477 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
478 // TODO: Not sure how to get the price from Google's API.
479 return null;
480 }
481 }
482