PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.6.1
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.6.1
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 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 replicate.php 1 year ago
core.php
381 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 // 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_image_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, $reply );
101
102 if ( $value === null ) {
103 Meow_MWAI_Logging::warn( "The returned value for '{$needFeedback['name']}' was null." );
104 $value = "[NO VALUE RETURNED - DO NOT SHOW THIS]";
105 }
106
107 // Add the feedback information to the appropriate feedback block
108 $feedback_blocks[$rawMessageKey]['feedbacks'][] = [
109 'request' => $needFeedback, // TODO: Meow_MWAI_Feedback_Request
110 'reply' => [ 'value' => $value ] // TODO: Meow_MWAI_Feedback_Reply
111 ];
112 }
113
114 foreach ( $feedback_blocks as $feedback_block ) {
115 $query->add_feedback_block( $feedback_block );
116 }
117
118 // Run the feedback query
119 $reply = $this->run( $query, $streamCallback, $maxDepth - 1 );
120 }
121
122 return $reply;
123 }
124
125 public function retrieve_model_info( $model ) {
126 $models = $this->get_models();
127 foreach ( $models as $currentModel ) {
128 if ( $currentModel['model'] === $model ) {
129 return $currentModel;
130 }
131 }
132 return false;
133 }
134
135 public function final_checks( Meow_MWAI_Query_Base $query ) {
136 $query->final_checks();
137 //$found = false;
138
139 // Check if the model is available, except if it's an assistant
140 if ( !( $query instanceof Meow_MWAI_Query_Assistant ) ) {
141 // TODO: Avoid checking on the finetuned models for now.
142 if ( substr( $query->model, 0, 3 ) === 'ft:' ) {
143 return;
144 }
145 $model_info = $this->retrieve_model_info( $query->model );
146 if ( $model_info === false ) {
147 throw new Exception( "AI Engine: The model '{$query->model}' is not available." );
148 }
149 if ( isset( $model_info['mode'] ) ) {
150 $query->mode = $model_info['mode'];
151 }
152 }
153 }
154
155 // Streamline the messages:
156 // - Concatenate consecutive model messages into a single message for the model role
157 // - Make sure the first message is a user message
158 // - Make sure the last message is a user message
159 protected function streamline_messages( $messages, $systemRole = 'assistant', $messageType = 'content' )
160 {
161 $processedMessages = [];
162 $lastRole = '';
163 $concatenatedText = '';
164
165 // Determine the way to access message content based on messageType
166 $getContent = function( $message ) use ( $messageType ) {
167 if ( $messageType == 'parts' ) {
168 return $message['parts'][0]['text'];
169 }
170 else { // Default to 'content'
171 return $message['content'];
172 }
173 };
174
175 // Set content to a message depending on the messageType
176 $setContent = function( &$message, $content ) use ( $messageType ) {
177 if ( $messageType == 'parts' ) {
178 $message['parts'] = [['text' => $content]];
179 }
180 else { // Default to 'content'
181 $message['content'] = $content;
182 }
183 };
184
185 // Concatenate consecutive model messages into a single message for the model role
186 foreach ( $messages as $message ) {
187 if ( $message['role'] == $systemRole ) {
188 if ( $lastRole == $systemRole ) {
189 $concatenatedText .= "\n" . $getContent( $message );
190 }
191 else {
192 if ( $concatenatedText !== '' ) {
193 $newMessage = [ 'role' => $systemRole ];
194 $setContent( $newMessage, $concatenatedText );
195 $processedMessages[] = $newMessage;
196 }
197 $concatenatedText = $getContent( $message );
198 }
199 }
200 else {
201 if ( $lastRole == $systemRole ) {
202 $newMessage = [ 'role' => $systemRole ];
203 $setContent( $newMessage, $concatenatedText );
204 $processedMessages[] = $newMessage;
205 $concatenatedText = '';
206 }
207 $processedMessages[] = $message;
208 }
209 $lastRole = $message['role'];
210 }
211 if ( $lastRole == $systemRole && $concatenatedText !== '' ) {
212 $newMessage = [ 'role' => $systemRole ];
213 $setContent( $newMessage, $concatenatedText );
214 $processedMessages[] = $newMessage;
215 }
216
217 // Make sure the last message is a user message, if not, throw an exception
218 if ( end( $processedMessages )['role'] !== 'user' ) {
219 throw new Exception( 'The last message must be a user message.' );
220 }
221
222 // Make sure the first message is a user message, if not, add an empty user message
223 if ( $processedMessages[0]['role'] !== 'user' ) {
224 $newMessage = [ 'role' => 'user' ];
225 $setContent( $newMessage, '' );
226 array_unshift( $processedMessages, $newMessage );
227 }
228
229 return $processedMessages;
230 }
231
232 // Check for a JSON-formatted error in the data, and throw an exception if it's the case.
233 function stream_error_check( $data ) {
234 if ( strpos( $data, 'error' ) === false ) {
235 return;
236 }
237 $data = trim( $data );
238 $jsonPart = $data;
239 if ( strpos( $jsonPart, 'data:' ) === 0 ) {
240 $jsonPart = trim( substr( $jsonPart, strlen( 'data:' ) ) );
241 }
242 $json = json_decode( $jsonPart, true );
243 if ( json_last_error() === JSON_ERROR_NONE ) {
244 if ( isset( $json['error'] ) ) {
245 $error = $json['error'];
246 $code = null;
247 $type = null;
248 $message = null;
249 if ( isset( $error['message'] ) ) {
250 $message = $error['message'];
251 }
252 else if ( is_string( $error ) ) {
253 throw new Exception( "Error: $error" );
254 }
255 else {
256 throw new Exception( "Unknown error (stream_error_check)." );
257 }
258 if ( isset( $error['code'] ) ) {
259 $code = $error['code'];
260 }
261 if ( isset( $error['type'] ) ) {
262 $type = $error['type'];
263 }
264 $errorMessage = "Error: $message";
265 if ( !is_null( $code ) ) {
266 $errorMessage .= " ($code)";
267 }
268 if ( !is_null( $type ) ) {
269 $errorMessage .= " ($type)";
270 }
271 throw new Exception( $errorMessage );
272 }
273 else if ( isset( $json['type'] ) && $json['type'] === 'error' ) {
274 $type = $json['error']['type'];
275 $message = $json['error']['message'];
276 throw new Exception( "Error: $message ($type)" );
277 }
278 }
279 }
280
281 public function stream_handler( $handle, $args, $url ) {
282 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, false );
283 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, false );
284
285 // TODO: This is breaking the response. We need to find a way to handle the headers.
286 // curl_setopt( $handle, CURLOPT_HEADERFUNCTION, function ( $curl, $header ) {
287 // $length = strlen( $header );
288 // $this->streamHeaders[] = $header;
289 // $this->stream_header_handler( $header );
290 // return $length;
291 // });
292
293 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, function ( $curl, $data ) {
294 $length = strlen( $data );
295
296 // Bufferize the unfinished stream (if it's the case)
297 $this->streamTemporaryBuffer .= $data;
298 $this->streamBuffer .= $data;
299
300 // Error Management
301 $this->stream_error_check( $this->streamBuffer );
302
303 $lines = explode( "\n", $this->streamTemporaryBuffer );
304 if ( substr( $this->streamTemporaryBuffer, -1 ) !== "\n" ) {
305 $this->streamTemporaryBuffer = array_pop( $lines );
306 }
307 else {
308 $this->streamTemporaryBuffer = "";
309 }
310
311 foreach ( $lines as $line ) {
312 if ( $line === "" ) { continue; }
313 if ( strpos( $line, 'data:' ) === 0 ) {
314 $line = trim( substr( $line, 5 ) );
315 $json = json_decode( trim( $line ), true );
316
317 if ( json_last_error() === JSON_ERROR_NONE ) {
318 $content = $this->stream_data_handler( $json );
319 if ( !is_null( $content ) ) {
320
321 // TO CHECK: Not sure why we need to do this to make sure there is a line return in the chatbot
322 // If we don't do this, HuggingFace streams "\n" as a token without anything else, and the
323 // chatbot doesn't display it.
324 if ( $content === "\n" ) {
325 $content = " \n";
326 }
327
328 $this->streamContent .= $content;
329 call_user_func( $this->streamCallback, $content );
330 }
331 }
332 else if ( $line !== '[DONE]' && !empty( $line ) ) {
333 $this->streamTemporaryBuffer .= $line . "\n";
334 }
335 }
336 }
337 return $length;
338 });
339 }
340
341 protected function stream_header_handler( $header ) {
342
343 }
344
345 protected function stream_data_handler( $json ) {
346 throw new Exception( 'Not implemented.' );
347 }
348
349 public function get_models() {
350 throw new Exception( 'Not implemented.' );
351 }
352
353 public function retrieve_models() {
354 throw new Exception( 'Not implemented.' );
355 }
356
357 public function run_completion_query( Meow_MWAI_Query_Base $query, $streamCallback = null ) : Meow_MWAI_Reply {
358 throw new Exception( 'Not implemented.' );
359 }
360
361 public function run_assistant_query( Meow_MWAI_Query_Assistant $query, $streamCallback = null ) : Meow_MWAI_Reply {
362 throw new Exception( 'Not implemented, or not supported in this version of AI Engine.' );
363 }
364
365 public function run_embedding_query( Meow_MWAI_Query_Base $query ) {
366 throw new Exception( 'Not implemented.' );
367 }
368
369 public function run_image_query( Meow_MWAI_Query_Base $query ) {
370 throw new Exception( 'Not implemented.' );
371 }
372
373 public function run_transcribe_query( Meow_MWAI_Query_Base $query ) {
374 throw new Exception( 'Not implemented.' );
375 }
376
377 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
378 throw new Exception( 'Not implemented.' );
379 }
380 }
381