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