PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.9.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.9.2
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
traits 1 year ago anthropic.php 11 months ago chatml.php 11 months ago core.php 11 months ago factory.php 1 year ago google.php 11 months ago hugging-face.php 1 year ago open-router.php 11 months ago openai.php 11 months ago perplexity.php 1 year ago replicate.php 1 year ago
core.php
593 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 // Debug mode for stream events
17 protected $currentDebugMode = false;
18 protected $currentQuery = null;
19 protected $emittedFunctionResults = [];
20
21 public function __construct( $core, $env ) {
22 $this->core = $core;
23 $this->env = $env;
24 $this->envId = isset( $env['id'] ) ? $env['id'] : null;
25 $this->envType = isset( $env['type'] ) ? $env['type'] : null;
26 }
27
28 /**
29 * Reset all request-specific state variables.
30 * This should be called at the start of each new request to prevent
31 * state leakage between requests.
32 */
33 protected function reset_request_state() {
34 // Reset streaming state
35 $this->streamCallback = null;
36 $this->streamTemporaryBuffer = '';
37 $this->streamBuffer = '';
38 $this->streamHeaders = [];
39 $this->streamContent = '';
40
41 // Reset debug/event state
42 $this->currentDebugMode = false;
43 $this->currentQuery = null;
44 $this->emittedFunctionResults = [];
45 }
46
47 public function run( $query, $streamCallback = null, $maxDepth = 5 ) {
48
49 // Apply filter to allow overriding maxDepth (only on first call)
50 if ( !isset( $query->_maxDepthConfigured ) ) {
51 $maxDepth = apply_filters( 'mwai_function_call_max_depth', $maxDepth, $query );
52 $query->_maxDepthConfigured = $maxDepth;
53 }
54
55 // Check if queries debug is enabled
56 $queries_debug = $this->core->get_option( 'queries_debug_mode' );
57
58 // Log query start if debug is enabled
59 if ( $queries_debug ) {
60 // We'll let the individual engines log the actual HTTP requests/responses
61 // Just log a simple start marker here
62 error_log( '[AI Engine Queries Debug] ========================================' );
63 $query_type = get_class( $query );
64 error_log( '[AI Engine Queries Debug] Starting ' . $query_type . ' to ' . ( $query->model ?? 'unknown model' ) );
65 }
66
67 // Check if the query is allowed.
68 $limits = $this->core->get_option( 'limits' );
69 $allowed = apply_filters( 'mwai_ai_allowed', true, $query, $limits );
70 if ( $allowed !== true ) {
71 $message = is_string( $allowed ) ? $allowed : 'Unauthorized query.';
72 throw new Exception( $message );
73 }
74
75 // Important as it makes sure everything is consolidated in the query and the engine.
76 $this->final_checks( $query );
77
78 // Run the query
79 $reply = null;
80 if ( $query instanceof Meow_MWAI_Query_Text || $query instanceof Meow_MWAI_Query_Feedback ) {
81 $reply = $this->run_completion_query( $query, $streamCallback );
82 }
83 else if ( $query instanceof Meow_MWAI_Query_Assistant || $query instanceof Meow_MWAI_Query_AssistFeedback ) {
84 $reply = $this->run_assistant_query( $query, $streamCallback );
85 if ( $reply === null ) {
86 throw new Exception( 'Assistants are not supported in this version of AI Engine.' );
87 }
88 }
89 else if ( $query instanceof Meow_MWAI_Query_Embed ) {
90 $reply = $this->run_embedding_query( $query );
91 }
92 else if ( $query instanceof Meow_MWAI_Query_EditImage ) {
93 $reply = $this->run_editimage_query( $query );
94 }
95 else if ( $query instanceof Meow_MWAI_Query_Image ) {
96 $reply = $this->run_image_query( $query );
97 }
98 else if ( $query instanceof Meow_MWAI_Query_Transcribe ) {
99 $reply = $this->run_transcribe_query( $query );
100 }
101 else {
102 throw new Exception( 'Unknown query type.' );
103 }
104
105 // Allow to modify the reply before it is sent.
106 $reply = apply_filters( 'mwai_ai_reply', $reply, $query );
107
108 // Log query completion if debug is enabled
109 if ( $queries_debug && empty( $reply->needFeedbacks ) ) {
110 // For embedding queries, just log the dimensions count
111 if ( $query instanceof Meow_MWAI_Query_Embed && !empty( $reply->result ) && is_array( $reply->result ) ) {
112 error_log( '[AI Engine Queries Debug] Embedding completed with ' . count( $reply->result ) . ' dimensions' );
113 }
114 else {
115 error_log( '[AI Engine Queries Debug] Query completed' );
116 }
117 error_log( '[AI Engine Queries Debug] ========================================' );
118 }
119
120 // Function Call Handling - This is where the magic happens!
121 // When the AI model requests function calls, we execute them and send results back
122 if ( !empty( $reply->needFeedbacks ) ) {
123
124 // Debug: Log how many needFeedbacks we have
125 if ( $queries_debug ) {
126 error_log( '[AI Engine Queries Debug] Core: Processing ' . count( $reply->needFeedbacks ) . ' needFeedbacks' );
127 foreach ( $reply->needFeedbacks as $idx => $feedback ) {
128 error_log( '[AI Engine Queries Debug] Core: needFeedback[' . $idx . ']: name=' . $feedback['name'] . ', toolId=' . ( $feedback['toolId'] ?? 'none' ) );
129 }
130 }
131
132
133 // Prevent infinite loops - each function call reduces maxDepth by 1
134 if ( $maxDepth <= 0 ) {
135 // Build call stack for better debugging
136 $callStack = [];
137 foreach ( $reply->needFeedbacks as $feedback ) {
138 $callStack[] = $feedback['name'] ?? 'unknown';
139 }
140
141 throw Meow_MWAI_FunctionCallException::loop_detected(
142 $query->_maxDepthConfigured ?? 5, // Use configured max depth
143 $callStack
144 );
145 }
146
147 // Create a feedback query if we're not already in one
148 // This wraps the original query with function execution results
149 if ( !( $query instanceof Meow_MWAI_Query_AssistFeedback ) && !( $query instanceof Meow_MWAI_Query_Feedback ) ) {
150 $queryClass = $query instanceof Meow_MWAI_Query_Assistant ?
151 Meow_MWAI_Query_AssistFeedback::class : Meow_MWAI_Query_Feedback::class;
152 // Note: $reply->query contains the original query that produced this reply
153 $query = new $queryClass( $reply, $reply->query );
154 }
155
156 // Validate that all function calls have proper function definitions
157 foreach ( $reply->needFeedbacks as $needFeedback ) {
158 if ( !isset( $needFeedback['function'] ) ) {
159 $functionName = $needFeedback['name'] ?? 'unknown';
160 $availableFunctions = array_map( function( $f ) { return $f->name; }, $query->functions );
161
162 throw new Exception( sprintf(
163 "Function '%s' not found in query functions. Available functions: %s",
164 $functionName,
165 implode( ', ', $availableFunctions )
166 ) );
167 }
168 }
169
170 // Group function calls by their source message to maintain proper context
171 // This ensures related function calls are processed together
172 $feedback_blocks = [];
173 foreach ( $reply->needFeedbacks as $needFeedback ) {
174 $rawMessageKey = md5( serialize( $needFeedback['rawMessage'] ) );
175
176 // Initialize the feedback block for this rawMessage if it hasn't been initialized yet
177 if ( !isset( $feedback_blocks[$rawMessageKey] ) ) {
178 $feedback_blocks[$rawMessageKey] = [
179 'rawMessage' => $needFeedback['rawMessage'],
180 'feedbacks' => []
181 ];
182 }
183
184 // Get the value related to this feedback (usually, a function call)
185 $value = apply_filters( 'mwai_ai_feedback', null, $needFeedback, $reply );
186
187 if ( $value === null ) {
188 // Check if the function handler exists
189 if ( !has_filter( 'mwai_ai_feedback' ) ) {
190 Meow_MWAI_Logging::error(
191 Meow_MWAI_FunctionCallException::missing_function_handler(
192 $needFeedback['name']
193 )->getMessage()
194 );
195 }
196 else {
197 Meow_MWAI_Logging::warn( "The returned value for '{$needFeedback['name']}' was null." );
198 }
199 $value = '[NO VALUE RETURNED - DO NOT SHOW THIS]';
200 }
201
202 // Emit "Got result" event and log for debugging
203 if ( $this->currentDebugMode ) {
204 // Format the result preview
205 $resultPreview = is_array( $value ) ? json_encode( $value ) : (string) $value;
206 if ( strlen( $resultPreview ) > 100 ) {
207 $resultPreview = substr( $resultPreview, 0, 100 ) . '...';
208 }
209
210 // Log the function result for debugging
211 Meow_MWAI_Logging::log( "Function '{$needFeedback['name']}' returned: " . $resultPreview );
212
213 // Emit function result event if we have a callback
214 if ( !empty( $streamCallback ) ) {
215 // Load event helper if not already loaded
216 if ( !class_exists( 'Meow_MWAI_Event' ) ) {
217 require_once MWAI_PATH . '/classes/event.php';
218 }
219
220 $functionName = $needFeedback['name'];
221
222 $event = Meow_MWAI_Event::function_result( $functionName )
223 ->set_metadata( 'result', $resultPreview )
224 ->set_metadata( 'tool_id', $needFeedback['toolId'] ?? null );
225 call_user_func( $streamCallback, $event );
226 }
227 }
228
229 // Add the feedback information to the appropriate feedback block
230 $feedback_blocks[$rawMessageKey]['feedbacks'][] = [
231 'request' => $needFeedback, // TODO: Meow_MWAI_Feedback_Request
232 'reply' => [ 'value' => $value ] // TODO: Meow_MWAI_Feedback_Reply
233 ];
234 }
235
236 $query->clear_feedback_blocks();
237 foreach ( $feedback_blocks as $feedback_block ) {
238 $query->add_feedback_block( $feedback_block );
239 }
240
241 // Log feedback query if debug is enabled
242 if ( $queries_debug ) {
243 error_log( '[AI Engine Queries Debug] Processing feedback query with ' . count( $feedback_blocks ) . ' feedback blocks' );
244 }
245
246 // Run the feedback query
247 $reply = $this->run( $query, $streamCallback, $maxDepth - 1 );
248 }
249
250 return $reply;
251 }
252
253 public function retrieve_model_info( $model ) {
254 $models = $this->get_models();
255 foreach ( $models as $currentModel ) {
256 if ( $currentModel['model'] === $model ) {
257 return $currentModel;
258 }
259 }
260 return false;
261 }
262
263 public function final_checks( Meow_MWAI_Query_Base $query ) {
264 $query->final_checks();
265 //$found = false;
266
267 // Check if the model is available, except if it's an assistant
268 if ( !( $query instanceof Meow_MWAI_Query_Assistant ) ) {
269 // TODO: Avoid checking on the finetuned models for now.
270 if ( substr( $query->model, 0, 3 ) === 'ft:' ) {
271 return;
272 }
273 $model_info = $this->retrieve_model_info( $query->model );
274 if ( $model_info === false ) {
275 throw new Exception( "AI Engine: The model '{$query->model}' is not available." );
276 }
277 if ( isset( $model_info['mode'] ) ) {
278 $query->mode = $model_info['mode'];
279 }
280 }
281 }
282
283 // Streamline the messages:
284 // - Concatenate consecutive model messages into a single message for the model role
285 // - Make sure the first message is a user message
286 // - Make sure the last message is a user message
287 protected function streamline_messages( $messages, $systemRole = 'assistant', $messageType = 'content' ) {
288 $processedMessages = [];
289 $lastRole = '';
290 $concatenatedText = '';
291
292 // Determine the way to access message content based on messageType
293 $getContent = function ( $message ) use ( $messageType ) {
294 if ( $messageType == 'parts' ) {
295 return $message['parts'][0]['text'];
296 }
297 else { // Default to 'content'
298 return $message['content'];
299 }
300 };
301
302 // Set content to a message depending on the messageType
303 $setContent = function ( &$message, $content ) use ( $messageType ) {
304 if ( $messageType == 'parts' ) {
305 $message['parts'] = [['text' => $content]];
306 }
307 else { // Default to 'content'
308 $message['content'] = $content;
309 }
310 };
311
312 // Concatenate consecutive model messages into a single message for the model role
313 foreach ( $messages as $message ) {
314 if ( $message['role'] == $systemRole ) {
315 if ( $lastRole == $systemRole ) {
316 $concatenatedText .= "\n" . $getContent( $message );
317 }
318 else {
319 if ( $concatenatedText !== '' ) {
320 $newMessage = [ 'role' => $systemRole ];
321 $setContent( $newMessage, $concatenatedText );
322 $processedMessages[] = $newMessage;
323 }
324 $concatenatedText = $getContent( $message );
325 }
326 }
327 else {
328 if ( $lastRole == $systemRole ) {
329 $newMessage = [ 'role' => $systemRole ];
330 $setContent( $newMessage, $concatenatedText );
331 $processedMessages[] = $newMessage;
332 $concatenatedText = '';
333 }
334 $processedMessages[] = $message;
335 }
336 $lastRole = $message['role'];
337 }
338 if ( $lastRole == $systemRole && $concatenatedText !== '' ) {
339 $newMessage = [ 'role' => $systemRole ];
340 $setContent( $newMessage, $concatenatedText );
341 $processedMessages[] = $newMessage;
342 }
343
344 // Make sure the last message is a user message, if not, throw an exception
345 if ( end( $processedMessages )['role'] !== 'user' ) {
346 throw new Exception( 'The last message must be a user message.' );
347 }
348
349 // Make sure the first message is a user message, if not, add an empty user message
350 if ( $processedMessages[0]['role'] !== 'user' ) {
351 $newMessage = [ 'role' => 'user' ];
352 $setContent( $newMessage, '' );
353 array_unshift( $processedMessages, $newMessage );
354 }
355
356 return $processedMessages;
357 }
358
359 // Check for a JSON-formatted error in the data, and throw an exception if it's the case.
360 public function stream_error_check( $data ) {
361 if ( strpos( $data, 'error' ) === false ) {
362 return;
363 }
364
365 $data = trim( $data );
366 $jsonPart = $data;
367 if ( strpos( $jsonPart, 'data:' ) === 0 ) {
368 $jsonPart = trim( substr( $jsonPart, strlen( 'data:' ) ) );
369 }
370
371 $json = json_decode( $jsonPart, true );
372 if ( json_last_error() !== JSON_ERROR_NONE ) {
373 return; // not valid JSON, nothing to do
374 }
375 // 1. OpenAI style: { error: {...} }
376 $error = null;
377 if ( isset( $json['error'] ) ) {
378 $error = $json['error'];
379 }
380 // 2. Google style: [ { error: {...} } ]
381 else if ( is_array( $json ) ) {
382 foreach ( $json as $item ) {
383 if ( isset( $item['error'] ) ) {
384 $error = $item['error'];
385 break;
386 }
387 }
388 }
389 // 3. Some APIs return { type: "error", message: ... }
390 else if ( isset( $json['type'] ) && $json['type'] === 'error' ) {
391 $error = $json;
392 }
393
394 if ( is_null( $error ) ) {
395 return;
396 }
397
398 $message = $error['message'] ?? ( is_string( $error ) ? $error : null );
399 $code = $error['code'] ?? null;
400 // Google uses "status" instead of "type" – accept both
401 $type = $error['type'] ?? ( $error['status'] ?? null );
402 if ( is_null( $message ) ) {
403 throw new Exception( 'Unknown error (stream_error_check).' );
404 }
405
406 $errorMessage = "Error: $message";
407 if ( !is_null( $code ) ) {
408 $errorMessage .= " ($code)";
409 }
410 if ( !is_null( $type ) ) {
411 $errorMessage .= " ($type)";
412 }
413
414 throw new Exception( $errorMessage );
415 }
416
417 protected function init_debug_mode( $query ) {
418 // Check if debug mode is enabled in settings
419 $this->currentDebugMode = $this->core->get_option( 'module_devtools' ) && $this->core->get_option( 'debug_mode' );
420 $this->currentQuery = $query;
421 }
422
423 public function stream_handler( $handle, $args, $url ) {
424 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, false );
425 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, false );
426
427 // TODO: This is breaking the response. We need to find a way to handle the headers.
428 // curl_setopt( $handle, CURLOPT_HEADERFUNCTION, function ( $curl, $header ) {
429 // $length = strlen( $header );
430 // $this->streamHeaders[] = $header;
431 // $this->stream_header_handler( $header );
432 // return $length;
433 // });
434
435 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, function ( $curl, $data ) use ( $url ) {
436 $length = strlen( $data );
437
438 // Log streaming data if queries debug is enabled
439 $queries_debug = $this->core->get_option( 'queries_debug_mode' );
440 static $logged_url = false;
441 if ( $queries_debug && !$logged_url ) {
442 error_log( '[AI Engine Queries Debug] Streaming from: ' . $url );
443 $logged_url = true;
444 }
445
446 // Bufferize the unfinished stream (if it's the case)
447 $this->streamTemporaryBuffer .= $data;
448 $this->streamBuffer .= $data;
449
450 // Error Management
451 $this->stream_error_check( $this->streamBuffer );
452
453 $lines = explode( "\n", $this->streamTemporaryBuffer );
454 if ( substr( $this->streamTemporaryBuffer, -1 ) !== "\n" ) {
455 $this->streamTemporaryBuffer = array_pop( $lines );
456 }
457 else {
458 $this->streamTemporaryBuffer = '';
459 }
460
461 foreach ( $lines as $line ) {
462 if ( $line === '' ) {
463 continue;
464 }
465 if ( strpos( $line, 'data:' ) === 0 ) {
466 $line = trim( substr( $line, 5 ) );
467 $json = json_decode( trim( $line ), true );
468
469 if ( json_last_error() === JSON_ERROR_NONE ) {
470 // Log individual streaming event if queries debug is enabled
471 static $event_count = 0;
472 if ( $queries_debug && $event_count < 10 ) {
473 // Log only the event type and key data, not the entire response
474 $event_log = [
475 'type' => $json['type'] ?? 'unknown'
476 ];
477
478 // Add specific details based on event type
479 if ( isset( $json['type'] ) ) {
480 if ( $json['type'] === 'response.output_item.added' && isset( $json['item'] ) ) {
481 $event_log['item_type'] = $json['item']['type'] ?? 'unknown';
482 $event_log['name'] = $json['item']['name'] ?? null;
483 $event_log['call_id'] = $json['item']['call_id'] ?? null;
484 }
485 elseif ( strpos( $json['type'], 'response.function_call' ) === 0 ) {
486 $event_log['call_id'] = $json['call_id'] ?? $json['item_id'] ?? null;
487 }
488 elseif ( $json['type'] === 'response.output_item.done' && isset( $json['item'] ) ) {
489 $event_log['item_type'] = $json['item']['type'] ?? 'unknown';
490 if ( isset( $json['item']['call_id'] ) ) {
491 $event_log['call_id'] = $json['item']['call_id'];
492 }
493 }
494 }
495
496 error_log( '[AI Engine Queries Debug] Event: ' . json_encode( $event_log ) );
497 $event_count++;
498 }
499
500 $content = $this->stream_data_handler( $json );
501 if ( !is_null( $content ) ) {
502
503 // Check if content is an Event object
504 if ( is_object( $content ) && $content instanceof Meow_MWAI_Event ) {
505 // For Event objects, pass the object directly to callback
506 // Don't accumulate in streamContent as it's not regular text
507 call_user_func( $this->streamCallback, $content );
508 }
509 else {
510 // For regular string content
511
512 // TO CHECK: Not sure why we need to do this to make sure there is a line return in the chatbot
513 // If we don't do this, HuggingFace streams "\n" as a token without anything else, and the
514 // chatbot doesn't display it.
515 if ( $content === "\n" ) {
516 $content = " \n";
517 }
518
519 $this->streamContent .= $content;
520 call_user_func( $this->streamCallback, $content );
521 }
522 }
523 }
524 else if ( $line !== '[DONE]' && !empty( $line ) ) {
525 $this->streamTemporaryBuffer .= $line . "\n";
526 }
527 }
528 }
529 return $length;
530 } );
531 }
532
533 protected function stream_header_handler( $header ) {
534
535 }
536
537 protected function stream_data_handler( $json ) {
538 throw new Exception( 'Not implemented.' );
539 }
540
541 public function get_models() {
542 throw new Exception( 'Not implemented.' );
543 }
544
545 public function retrieve_models() {
546 throw new Exception( 'Not implemented.' );
547 }
548
549 public function run_completion_query( Meow_MWAI_Query_Base $query, $streamCallback = null ): Meow_MWAI_Reply {
550 throw new Exception( 'Not implemented.' );
551 }
552
553 public function run_assistant_query( Meow_MWAI_Query_Assistant $query, $streamCallback = null ): Meow_MWAI_Reply {
554 throw new Exception( 'Not implemented, or not supported in this version of AI Engine.' );
555 }
556
557 public function run_embedding_query( Meow_MWAI_Query_Base $query ) {
558 throw new Exception( 'Not implemented.' );
559 }
560
561 public function run_image_query( Meow_MWAI_Query_Base $query ) {
562 throw new Exception( 'Not implemented.' );
563 }
564
565 public function run_editimage_query( Meow_MWAI_Query_Base $query ) {
566 throw new Exception( 'Not implemented.' );
567 }
568
569 public function run_transcribe_query( Meow_MWAI_Query_Base $query ) {
570 throw new Exception( 'Not implemented.' );
571 }
572
573 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
574 throw new Exception( 'Not implemented.' );
575 }
576
577 /**
578 * Check the connection to the AI service.
579 * This should be a minimal, cost-free API call to verify credentials and connectivity.
580 *
581 * @return array {
582 * @type bool $success Whether the connection test was successful
583 * @type string $service The service name (e.g., 'OpenAI', 'Anthropic')
584 * @type string $message A human-readable message about the test result
585 * @type array $details Additional service-specific details
586 * @type string $error Error message if the test failed
587 * }
588 */
589 public function connection_check() {
590 throw new Exception( 'Connection check not implemented for this service.' );
591 }
592 }
593