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