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