PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.3
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.3
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 / core.php
ai-engine / classes / engines Last commit date
anthropic.php 1 year ago chatml.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
core.php
424 lines
1 <?php
2
3 class Meow_MWAI_Engines_Core {
4 protected $core = null;
5 public $env = null;
6 public $envId = null;
7 public $envType = null;
8
9 // Streaming
10 protected $streamCallback = null;
11 protected $streamTemporaryBuffer = "";
12 protected $streamBuffer = "";
13 protected $streamHeaders = [];
14 protected $streamContent = "";
15
16 // Debug mode for stream events
17 protected $currentDebugMode = false;
18 protected $currentQuery = null;
19 protected $emittedFunctionResults = [];
20
21 public function __construct( $core, $env ) {
22 $this->core = $core;
23 $this->env = $env;
24 $this->envId = isset( $env['id'] ) ? $env['id'] : null;
25 $this->envType = isset( $env['type'] ) ? $env['type'] : null;
26 }
27
28 public function run( $query, $streamCallback = null, $maxDepth = 5 ) {
29
30 // Check if the query is allowed.
31 $limits = $this->core->get_option( 'limits' );
32 $allowed = apply_filters( 'mwai_ai_allowed', true, $query, $limits );
33 if ( $allowed !== true ) {
34 $message = is_string( $allowed ) ? $allowed : 'Unauthorized query.';
35 throw new Exception( $message );
36 }
37
38 // Important as it makes sure everything is consolidated in the query and the engine.
39 $this->final_checks( $query );
40
41 // Run the query
42 $reply = null;
43 if ( $query instanceof Meow_MWAI_Query_Text || $query instanceof Meow_MWAI_Query_Feedback ) {
44 $reply = $this->run_completion_query( $query, $streamCallback );
45 }
46 else if ( $query instanceof Meow_MWAI_Query_Assistant || $query instanceof Meow_MWAI_Query_AssistFeedback ) {
47 $reply = $this->run_assistant_query( $query, $streamCallback );
48 if ( $reply === null ) {
49 throw new Exception( 'Assistants are not supported in this version of AI Engine.' );
50 }
51 }
52 else if ( $query instanceof Meow_MWAI_Query_Embed ) {
53 $reply = $this->run_embedding_query( $query );
54 }
55 else if ( $query instanceof Meow_MWAI_Query_EditImage ) {
56 $reply = $this->run_editimage_query( $query );
57 }
58 else if ( $query instanceof Meow_MWAI_Query_Image ) {
59 $reply = $this->run_image_query( $query );
60 }
61 else if ( $query instanceof Meow_MWAI_Query_Transcribe ) {
62 $reply = $this->run_transcribe_query( $query );
63 }
64 else {
65 throw new Exception( 'Unknown query type.' );
66 }
67
68 // Allow to modify the reply before it is sent.
69 $reply = apply_filters( 'mwai_ai_reply', $reply, $query );
70
71 // Function Call
72 if ( !empty( $reply->needFeedbacks ) ) {
73
74 // Check if we reached the maximum depth
75 if ( $maxDepth <= 0 ) {
76 throw new Exception( 'AI Engine: There seems to be a loop in the function/tools calls.' );
77 }
78
79 // We should use a feedback query around the original query
80 if ( !( $query instanceof Meow_MWAI_Query_AssistFeedback) && !( $query instanceof Meow_MWAI_Query_Feedback ) ) {
81 $queryClass = $query instanceof Meow_MWAI_Query_Assistant ?
82 Meow_MWAI_Query_AssistFeedback::class : Meow_MWAI_Query_Feedback::class;
83 $query = new $queryClass( $reply, $reply->query );
84 }
85
86 // The engine for the model will handle the feedback query nicely
87 // In the case of Anthropic, it's like a "discussion" between the user (= WordPress's AI Engine
88 // and the model (= Anthropic). The feedback is a way to communicate between the two.
89 $feedback_blocks = [];
90 foreach ( $reply->needFeedbacks as $needFeedback ) {
91 $rawMessageKey = md5( serialize( $needFeedback['rawMessage'] ) );
92
93 // Initialize the feedback block for this rawMessage if it hasn't been initialized yet
94 if ( !isset( $feedback_blocks[$rawMessageKey] ) ) {
95 $feedback_blocks[$rawMessageKey] = [
96 'rawMessage' => $needFeedback['rawMessage'],
97 'feedbacks' => []
98 ];
99 }
100
101 // Get the value related to this feedback (usually, a function call)
102 $value = apply_filters( 'mwai_ai_feedback', null, $needFeedback, $reply );
103
104 if ( $value === null ) {
105 Meow_MWAI_Logging::warn( "The returned value for '{$needFeedback['name']}' was null." );
106 $value = "[NO VALUE RETURNED - DO NOT SHOW THIS]";
107 }
108
109 // Log function result for debugging (events are emitted by engines when building feedback messages)
110 if ( $this->currentDebugMode ) {
111 // Format the result preview
112 $resultPreview = is_array( $value ) ? json_encode( $value ) : (string)$value;
113 if ( strlen( $resultPreview ) > 100 ) {
114 $resultPreview = substr( $resultPreview, 0, 100 ) . '...';
115 }
116
117 // Log the function result for debugging
118 Meow_MWAI_Logging::log( "Function '{$needFeedback['name']}' returned: " . $resultPreview );
119 }
120
121 // Add the feedback information to the appropriate feedback block
122 $feedback_blocks[$rawMessageKey]['feedbacks'][] = [
123 'request' => $needFeedback, // TODO: Meow_MWAI_Feedback_Request
124 'reply' => [ 'value' => $value ] // TODO: Meow_MWAI_Feedback_Reply
125 ];
126 }
127
128 $query->clear_feedback_blocks();
129 foreach ( $feedback_blocks as $feedback_block ) {
130 $query->add_feedback_block( $feedback_block );
131 }
132
133 // Run the feedback query
134 $reply = $this->run( $query, $streamCallback, $maxDepth - 1 );
135 }
136
137 return $reply;
138 }
139
140 public function retrieve_model_info( $model ) {
141 $models = $this->get_models();
142 foreach ( $models as $currentModel ) {
143 if ( $currentModel['model'] === $model ) {
144 return $currentModel;
145 }
146 }
147 return false;
148 }
149
150 public function final_checks( Meow_MWAI_Query_Base $query ) {
151 $query->final_checks();
152 //$found = false;
153
154 // Check if the model is available, except if it's an assistant
155 if ( !( $query instanceof Meow_MWAI_Query_Assistant ) ) {
156 // TODO: Avoid checking on the finetuned models for now.
157 if ( substr( $query->model, 0, 3 ) === 'ft:' ) {
158 return;
159 }
160 $model_info = $this->retrieve_model_info( $query->model );
161 if ( $model_info === false ) {
162 throw new Exception( "AI Engine: The model '{$query->model}' is not available." );
163 }
164 if ( isset( $model_info['mode'] ) ) {
165 $query->mode = $model_info['mode'];
166 }
167 }
168 }
169
170 // Streamline the messages:
171 // - Concatenate consecutive model messages into a single message for the model role
172 // - Make sure the first message is a user message
173 // - Make sure the last message is a user message
174 protected function streamline_messages( $messages, $systemRole = 'assistant', $messageType = 'content' )
175 {
176 $processedMessages = [];
177 $lastRole = '';
178 $concatenatedText = '';
179
180 // Determine the way to access message content based on messageType
181 $getContent = function( $message ) use ( $messageType ) {
182 if ( $messageType == 'parts' ) {
183 return $message['parts'][0]['text'];
184 }
185 else { // Default to 'content'
186 return $message['content'];
187 }
188 };
189
190 // Set content to a message depending on the messageType
191 $setContent = function( &$message, $content ) use ( $messageType ) {
192 if ( $messageType == 'parts' ) {
193 $message['parts'] = [['text' => $content]];
194 }
195 else { // Default to 'content'
196 $message['content'] = $content;
197 }
198 };
199
200 // Concatenate consecutive model messages into a single message for the model role
201 foreach ( $messages as $message ) {
202 if ( $message['role'] == $systemRole ) {
203 if ( $lastRole == $systemRole ) {
204 $concatenatedText .= "\n" . $getContent( $message );
205 }
206 else {
207 if ( $concatenatedText !== '' ) {
208 $newMessage = [ 'role' => $systemRole ];
209 $setContent( $newMessage, $concatenatedText );
210 $processedMessages[] = $newMessage;
211 }
212 $concatenatedText = $getContent( $message );
213 }
214 }
215 else {
216 if ( $lastRole == $systemRole ) {
217 $newMessage = [ 'role' => $systemRole ];
218 $setContent( $newMessage, $concatenatedText );
219 $processedMessages[] = $newMessage;
220 $concatenatedText = '';
221 }
222 $processedMessages[] = $message;
223 }
224 $lastRole = $message['role'];
225 }
226 if ( $lastRole == $systemRole && $concatenatedText !== '' ) {
227 $newMessage = [ 'role' => $systemRole ];
228 $setContent( $newMessage, $concatenatedText );
229 $processedMessages[] = $newMessage;
230 }
231
232 // Make sure the last message is a user message, if not, throw an exception
233 if ( end( $processedMessages )['role'] !== 'user' ) {
234 throw new Exception( 'The last message must be a user message.' );
235 }
236
237 // Make sure the first message is a user message, if not, add an empty user message
238 if ( $processedMessages[0]['role'] !== 'user' ) {
239 $newMessage = [ 'role' => 'user' ];
240 $setContent( $newMessage, '' );
241 array_unshift( $processedMessages, $newMessage );
242 }
243
244 return $processedMessages;
245 }
246
247 // Check for a JSON-formatted error in the data, and throw an exception if it's the case.
248 function stream_error_check( $data ) {
249 if ( strpos( $data, 'error' ) === false ) {
250 return;
251 }
252
253 $data = trim( $data );
254 $jsonPart = $data;
255 if ( strpos( $jsonPart, 'data:' ) === 0 ) {
256 $jsonPart = trim( substr( $jsonPart, strlen( 'data:' ) ) );
257 }
258
259 $json = json_decode( $jsonPart, true );
260 if ( json_last_error() !== JSON_ERROR_NONE ) {
261 return; // not valid JSON, nothing to do
262 }
263 // 1. OpenAI style: { error: {...} }
264 $error = null;
265 if ( isset( $json['error'] ) ) {
266 $error = $json['error'];
267 }
268 // 2. Google style: [ { error: {...} } ]
269 else if ( is_array( $json ) ) {
270 foreach ( $json as $item ) {
271 if ( isset( $item['error'] ) ) {
272 $error = $item['error'];
273 break;
274 }
275 }
276 }
277 // 3. Some APIs return { type: "error", message: ... }
278 else if ( isset( $json['type'] ) && $json['type'] === 'error' ) {
279 $error = $json;
280 }
281
282 if ( is_null( $error ) ) {
283 return;
284 }
285
286 $message = $error['message'] ?? ( is_string( $error ) ? $error : null );
287 $code = $error['code'] ?? null;
288 // Google uses "status" instead of "type" – accept both
289 $type = $error['type'] ?? ( $error['status'] ?? null );
290 if ( is_null( $message ) ) {
291 throw new Exception( 'Unknown error (stream_error_check).' );
292 }
293
294 $errorMessage = "Error: $message";
295 if ( !is_null( $code ) ) {
296 $errorMessage .= " ($code)";
297 }
298 if ( !is_null( $type ) ) {
299 $errorMessage .= " ($type)";
300 }
301
302 throw new Exception( $errorMessage );
303 }
304
305 protected function init_debug_mode( $query ) {
306 // Check if debug mode is enabled in settings
307 $this->currentDebugMode = $this->core->get_option('module_devtools') && $this->core->get_option( 'debug_mode' );
308 $this->currentQuery = $query;
309 }
310
311 public function stream_handler( $handle, $args, $url ) {
312 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, false );
313 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, false );
314
315 // TODO: This is breaking the response. We need to find a way to handle the headers.
316 // curl_setopt( $handle, CURLOPT_HEADERFUNCTION, function ( $curl, $header ) {
317 // $length = strlen( $header );
318 // $this->streamHeaders[] = $header;
319 // $this->stream_header_handler( $header );
320 // return $length;
321 // });
322
323 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, function ( $curl, $data ) {
324 $length = strlen( $data );
325
326 // Bufferize the unfinished stream (if it's the case)
327 $this->streamTemporaryBuffer .= $data;
328 $this->streamBuffer .= $data;
329
330 // Error Management
331 $this->stream_error_check( $this->streamBuffer );
332
333 $lines = explode( "\n", $this->streamTemporaryBuffer );
334 if ( substr( $this->streamTemporaryBuffer, -1 ) !== "\n" ) {
335 $this->streamTemporaryBuffer = array_pop( $lines );
336 }
337 else {
338 $this->streamTemporaryBuffer = "";
339 }
340
341 foreach ( $lines as $line ) {
342 if ( $line === "" ) { continue; }
343 if ( strpos( $line, 'data:' ) === 0 ) {
344 $line = trim( substr( $line, 5 ) );
345 $json = json_decode( trim( $line ), true );
346
347 if ( json_last_error() === JSON_ERROR_NONE ) {
348 $content = $this->stream_data_handler( $json );
349 if ( !is_null( $content ) ) {
350
351 // Check if content is an Event object
352 if ( is_object( $content ) && $content instanceof Meow_MWAI_Event ) {
353 // For Event objects, pass the object directly to callback
354 // Don't accumulate in streamContent as it's not regular text
355 call_user_func( $this->streamCallback, $content );
356 } else {
357 // For regular string content
358
359 // TO CHECK: Not sure why we need to do this to make sure there is a line return in the chatbot
360 // If we don't do this, HuggingFace streams "\n" as a token without anything else, and the
361 // chatbot doesn't display it.
362 if ( $content === "\n" ) {
363 $content = " \n";
364 }
365
366 $this->streamContent .= $content;
367 call_user_func( $this->streamCallback, $content );
368 }
369 }
370 }
371 else if ( $line !== '[DONE]' && !empty( $line ) ) {
372 $this->streamTemporaryBuffer .= $line . "\n";
373 }
374 }
375 }
376 return $length;
377 });
378 }
379
380 protected function stream_header_handler( $header ) {
381
382 }
383
384 protected function stream_data_handler( $json ) {
385 throw new Exception( 'Not implemented.' );
386 }
387
388 public function get_models() {
389 throw new Exception( 'Not implemented.' );
390 }
391
392 public function retrieve_models() {
393 throw new Exception( 'Not implemented.' );
394 }
395
396 public function run_completion_query( Meow_MWAI_Query_Base $query, $streamCallback = null ) : Meow_MWAI_Reply {
397 throw new Exception( 'Not implemented.' );
398 }
399
400 public function run_assistant_query( Meow_MWAI_Query_Assistant $query, $streamCallback = null ) : Meow_MWAI_Reply {
401 throw new Exception( 'Not implemented, or not supported in this version of AI Engine.' );
402 }
403
404 public function run_embedding_query( Meow_MWAI_Query_Base $query ) {
405 throw new Exception( 'Not implemented.' );
406 }
407
408 public function run_image_query( Meow_MWAI_Query_Base $query ) {
409 throw new Exception( 'Not implemented.' );
410 }
411
412 public function run_editimage_query( Meow_MWAI_Query_Base $query ) {
413 throw new Exception( 'Not implemented.' );
414 }
415
416 public function run_transcribe_query( Meow_MWAI_Query_Base $query ) {
417 throw new Exception( 'Not implemented.' );
418 }
419
420 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
421 throw new Exception( 'Not implemented.' );
422 }
423 }
424