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