PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.3.0
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.3.0
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 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
367 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 ) {
48 $reply = $this->run_assistant_query( $query, $streamCallback );
49 // TODO: This filter is useless with Assistants v2
50 $reply = apply_filters( 'mwai_ai_query_assistant', $reply, $query );
51 if ( $reply === null ) {
52 throw new Exception( 'Assistants are not supported in this version of AI Engine.' );
53 }
54 }
55 else if ( $query instanceof Meow_MWAI_Query_Embed ) {
56 $reply = $this->run_embedding_query( $query );
57 }
58 else if ( $query instanceof Meow_MWAI_Query_Image ) {
59 $reply = $this->run_images_query( $query );
60 }
61 else if ( $query instanceof Meow_MWAI_Query_Transcribe ) {
62 $reply = $this->run_transcribe_query( $query );
63 }
64 else {
65 throw new Exception( 'Unknown query type.' );
66 }
67
68 // Allow to modify the reply before it is sent.
69 $reply = apply_filters( 'mwai_ai_reply', $reply, $query );
70
71 // Function Call
72 if ( !empty( $reply->needFeedbacks ) ) {
73
74 // Check if we reached the maximum depth
75 if ( $maxDepth <= 0 ) {
76 throw new Exception( 'AI Engine: There seems to be a loop in the function/tools calls.' );
77 }
78
79 // We should use a feedback query around the original query
80 if ( !($query instanceof Meow_MWAI_Query_Feedback) ) {
81 $query = new Meow_MWAI_Query_Feedback( $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 // Error Management
292 $this->stream_error_check( $data );
293
294 // Bufferize the unfinished stream (if it's the case)
295 $this->streamTemporaryBuffer .= $data;
296 $this->streamBuffer .= $data;
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 $this->streamContent .= $content;
315 call_user_func( $this->streamCallback, $content );
316 }
317 }
318 else if ( $line !== '[DONE]' && !empty( $line ) ) {
319 $this->streamTemporaryBuffer .= $line . "\n";
320 }
321 }
322 }
323 return $length;
324 });
325 }
326
327 protected function stream_header_handler( $header ) {
328
329 }
330
331 protected function stream_data_handler( $json ) {
332 throw new Exception( 'Not implemented.' );
333 }
334
335 public function get_models() {
336 throw new Exception( 'Not implemented.' );
337 }
338
339 public function retrieve_models() {
340 throw new Exception( 'Not implemented.' );
341 }
342
343 public function run_completion_query( Meow_MWAI_Query_Base $query, $streamCallback = null ) : Meow_MWAI_Reply {
344 throw new Exception( 'Not implemented.' );
345 }
346
347 public function run_assistant_query( Meow_MWAI_Query_Assistant $query, $streamCallback = null ) : Meow_MWAI_Reply {
348 throw new Exception( 'Not implemented, or not supported in this version of AI Engine.' );
349 }
350
351 public function run_embedding_query( Meow_MWAI_Query_Base $query ) {
352 throw new Exception( 'Not implemented.' );
353 }
354
355 public function run_images_query( Meow_MWAI_Query_Base $query ) {
356 throw new Exception( 'Not implemented.' );
357 }
358
359 public function run_transcribe_query( Meow_MWAI_Query_Base $query ) {
360 throw new Exception( 'Not implemented.' );
361 }
362
363 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
364 throw new Exception( 'Not implemented.' );
365 }
366 }
367