PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.3.6
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.3.6
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 / reply.php
ai-engine / classes Last commit date
data 1 year ago engines 5 months ago exceptions 1 year ago modules 4 months ago query 5 months ago rest 8 months ago services 6 months ago admin.php 4 months ago api.php 6 months ago core.php 4 months ago discussion.php 1 year ago event.php 1 year ago init.php 7 months ago logging.php 1 year ago reply.php 6 months ago rest.php 5 months ago
reply.php
345 lines
1 <?php
2
3 class Meow_MWAI_Reply implements JsonSerializable {
4 public $id = null;
5 public $result = '';
6 public $results = [];
7 public $usage = [
8 'prompt_tokens' => 0,
9 'completion_tokens' => 0,
10 'total_tokens' => 0,
11 'price' => null,
12 'accuracy' => 'none', // 'none', 'estimated', 'tokens', 'price', 'full'
13 ];
14 // TODO: Remove after January 2026 - Use $usage['accuracy'] instead
15 public $usageAccuracy = 'none';
16 public $query = null;
17 public $type = 'text';
18 public $model = null; // Actual model used by the API (may differ from query model)
19
20 // Code interpreter code (separate from main content)
21 public $contentCode = '';
22
23 // This is when models return a message that needs to be executed (functions, tools, etc)
24 public $needFeedbacks = [];
25 public $needClientActions = [];
26
27 public function __construct( $query = null ) {
28 $this->query = $query;
29 }
30
31 #[\ReturnTypeWillChange]
32 public function jsonSerialize() {
33 $isEmbedding = false;
34 $embeddingsDimensions = null;
35 $embedddingsMessage = null;
36 if ( is_array( $this->results ) && count( $this->results ) > 0 ) {
37 $isEmbedding = is_array( $this->results[0] );
38 if ( $isEmbedding ) {
39 $embeddingsDimensions = count( $this->results[0] );
40 $embedddingsMessage = "A $embeddingsDimensions-dimensional embedding was returned.";
41 }
42 }
43 $data = [
44 'result' => $isEmbedding ? $embedddingsMessage : $this->result,
45 'results' => $isEmbedding ? [] : $this->results,
46 'usage' => $this->usage,
47 'system' => [
48 'class' => get_class( $this ),
49 ]
50 ];
51 if ( !empty( $this->needFeedbacks ) ) {
52 $data['needFeedbacks'] = $this->needFeedbacks;
53 }
54 if ( !empty( $this->needClientActions ) ) {
55 $data['needClientActions'] = $this->needClientActions;
56 }
57 if ( !empty( $this->contentCode ) ) {
58 $data['contentCode'] = $this->contentCode;
59 }
60 return $data;
61 }
62
63 public function set_usage( $usage ) {
64 $this->usage = $usage;
65 }
66
67 public function set_usage_accuracy( $accuracy ) {
68 $this->usageAccuracy = $accuracy;
69 $this->usage['accuracy'] = $accuracy;
70 }
71
72 public function set_id( $id ) {
73 $this->id = $id;
74 }
75
76 public function set_type( $type ) {
77 $this->type = $type;
78 }
79
80 public function set_model( $model ) {
81 $this->model = $model;
82 }
83
84 public function get_total_tokens() {
85 return isset( $this->usage['total_tokens'] ) ? $this->usage['total_tokens'] : 0;
86 }
87
88 public function get_in_tokens( $query = null ) {
89 $in_tokens = isset( $this->usage['prompt_tokens'] ) ? $this->usage['prompt_tokens'] : 0;
90 if ( empty( $in_tokens ) && $query ) {
91 $in_tokens = $query->get_in_tokens();
92 }
93 return $in_tokens;
94 }
95
96 public function get_out_tokens() {
97 $out_tokens = isset( $this->usage['completion_tokens'] ) ? $this->usage['completion_tokens'] : 0;
98 if ( empty( $out_tokens ) ) {
99 $out_tokens = Meow_MWAI_Core::estimate_tokens( $this->result );
100 }
101 return $out_tokens;
102 }
103
104 public function get_price() {
105 // If it's not set return null, but it can be 0
106 if ( !isset( $this->usage['price'] ) ) {
107 return null;
108 }
109 return $this->usage['price'];
110 }
111
112 public function get_usage_accuracy() {
113 return $this->usageAccuracy;
114 }
115
116 public function get_units() {
117 if ( isset( $this->usage['total_tokens'] ) ) {
118 return $this->usage['total_tokens'];
119 }
120 else if ( isset( $this->usage['images'] ) ) {
121 return $this->usage['images'];
122 }
123 else if ( isset( $this->usage['seconds'] ) ) {
124 return $this->usage['seconds'];
125 }
126 return null;
127 }
128
129 public function get_type() {
130 return $this->type;
131 }
132
133 public function set_reply( $reply ) {
134 $this->result = $reply;
135 $this->results[] = [ $reply ];
136 }
137
138 public function replace( $search, $replace ) {
139 $this->result = str_replace( $search, $replace, $this->result );
140 $this->results = array_map( function ( $result ) use ( $search, $replace ) {
141 return str_replace( $search, $replace, $result );
142 }, $this->results );
143 }
144
145 private function extract_arguments( $funcArgs ) {
146 $finalArgs = [];
147 if ( is_string( $funcArgs ) ) {
148 $arguments = trim( str_replace( "\n", '', $funcArgs ) );
149 if ( substr( $arguments, 0, 1 ) == '{' ) {
150 $arguments = json_decode( $arguments, true );
151 $finalArgs = $arguments;
152 }
153 }
154 else if ( is_array( $funcArgs ) ) {
155 $finalArgs = $funcArgs;
156 }
157 return $finalArgs;
158 }
159
160 /**
161 * Set the choices from OpenAI as the results.
162 * The last (or only) result is set as the result.
163 * @param array $choices ID of the model to use.
164 */
165 public function set_choices( $choices, $rawMessage = null ) {
166 $this->results = [];
167
168 // Initialize feedback arrays at the start to accumulate across all choices
169 // This is important for engines like Google that split multiple function calls
170 // into separate choices
171 $this->needFeedbacks = [];
172 $this->needClientActions = [];
173
174 if ( is_array( $choices ) ) {
175 foreach ( $choices as $choice ) {
176
177 // It's chat completion
178 if ( isset( $choice['message'] ) ) {
179
180 // It's text content
181 if ( isset( $choice['message']['content'] ) ) {
182 $content = trim( $choice['message']['content'] );
183 $this->results[] = $content;
184 $this->result = $content;
185 }
186
187 // It's a tool call (OpenAI-style and Anthropic-style)
188 $toolCalls = [];
189 if ( isset( $choice['message']['tool_calls'] ) ) {
190 $tools = $choice['message']['tool_calls'];
191 foreach ( $tools as $tool ) {
192 if ( $tool['type'] === 'function' ) {
193 $toolCall = [
194 'toolId' => $tool['id'],
195 //'mode' => 'interactive',
196 'type' => 'tool_call',
197 'name' => trim( $tool['function']['name'] ),
198 'arguments' => $this->extract_arguments( $tool['function']['arguments'] ),
199 // Represent the original message that triggered the function call
200 'rawMessage' => $rawMessage ? $rawMessage : ( isset( $choice['_rawMessage'] ) ? $choice['_rawMessage'] : $choice['message'] ),
201 ];
202 $toolCalls[] = $toolCall;
203 }
204 }
205 }
206
207 // If it's a function call (Open-AI style; usually for a final execution)
208 if ( isset( $choice['message']['function_call'] ) ) {
209 $content = $choice['message']['function_call'];
210 $name = trim( $content['name'] );
211 $args = $content['arguments'] ?? $content['args'] ?? null;
212 $toolCalls[] = [
213 'toolId' => null,
214 'mode' => 'static',
215 'type' => 'function_call',
216 'name' => $name,
217 'arguments' => $this->extract_arguments( $args ),
218 'rawMessage' => $rawMessage ? $rawMessage : ( isset( $choice['_rawMessage'] ) ? $choice['_rawMessage'] : $choice['message'] ),
219 ];
220 }
221
222 // Deep copy tool calls BEFORE adding function references
223 // This prevents the "Duplicate value for 'tool_call_id'" error
224 // when the same function is called multiple times
225 // Note: We need to preserve the toolId for each tool call
226 if ( !empty( $toolCalls ) ) {
227 $toolCalls = json_decode( json_encode( $toolCalls ), true );
228 }
229
230 // Resolve the original function from the query
231 if ( !empty( $toolCalls ) ) {
232 foreach ( $toolCalls as &$toolCall ) {
233 if ( $toolCall['type'] !== 'function_call' && $toolCall['type'] !== 'tool_call' ) {
234 continue;
235 }
236 foreach ( $this->query->functions as $function ) {
237 if ( $function->name == $toolCall['name'] ) {
238 $toolCall['function'] = $function;
239 break;
240 }
241 }
242 }
243 // IMPORTANT: Unset the reference to avoid PHP's foreach reference bug
244 unset( $toolCall );
245 }
246
247 // Add tool calls to existing arrays instead of resetting them
248 // This is crucial for engines like Google that create multiple choices
249 // for multiple function calls in a single response
250 foreach ( $toolCalls as $tcIdx => $toolCall ) {
251 if ( $toolCall['function']->target !== 'js' ) {
252 $this->needFeedbacks[] = $toolCall;
253 }
254 else if ( $toolCall['function']->target === 'js' ) {
255 $this->needClientActions[] = $toolCall;
256 }
257 }
258 }
259
260 // It's text completion
261 else if ( isset( $choice['text'] ) ) {
262
263 // TODO: Assistants return an array (so actually not really a text completion)
264 // We should probably make this clearer and analyze all the outputs from different endpoints.
265 if ( is_array( $choice['text'] ) ) {
266 $text = trim( $choice['text']['value'] );
267 $this->results[] = $text;
268 $this->result = $text;
269 }
270 else {
271 $text = trim( $choice['text'] );
272 $this->results[] = $text;
273 $this->result = $text;
274 }
275 }
276
277 // It's url/image
278 else if ( isset( $choice['url'] ) ) {
279 // TODO: DALL-E 2 and 3 were using URLs, but now they are using b64_json (gpt-image-1 kind of enforced it)
280 $url = trim( $choice['url'] );
281 $this->results[] = $url;
282 $this->result = $url;
283 }
284 else if ( isset( $choice['b64_json'] ) ) {
285 // In that case we need to create a temporary file in WordPress to store the image, and return the URL for it.
286 global $mwai_core;
287
288 // Check if the query has explicitly disabled local download
289 if ( !empty( $this->query ) && $this->query instanceof Meow_MWAI_Query_Image && $this->query->localDownload === null ) {
290 // Query explicitly doesn't want local download, save as temporary upload
291 $localDownload = 'uploads';
292 $expiry = 1 * HOUR_IN_SECONDS; // 1 hour for temporary images
293 }
294 else {
295 // Use the user's AI-generated image settings (same as DALL-E uses)
296 $localDownload = $mwai_core->get_option( 'image_local_download' );
297 $expiry = (int) $mwai_core->get_option( 'image_expires_download' );
298 }
299
300 // The expiry is already in seconds
301 $ttl = $expiry;
302
303 // Use 'library' or 'uploads' based on user settings
304 $target = ( $localDownload === 'library' ) ? 'library' : 'uploads';
305
306 // Prepare metadata similar to regular image queries
307 $metadata = [];
308 if ( !empty( $this->query ) ) {
309 $metadata['query_envId'] = $this->query->envId ?? null;
310 $metadata['query_session'] = $this->query->session ?? null;
311 $metadata['query_model'] = $this->query->model ?? 'gpt-image-1';
312 }
313
314 $url = $mwai_core->files->save_temp_image_from_b64( $choice['b64_json'], 'generated', $ttl, $target, $metadata );
315 if ( is_wp_error( $url ) ) {
316 return $url;
317 }
318 $this->results[] = $url;
319
320 // For chatbot display, append image markdown to the result
321 if ( !empty( $this->result ) ) {
322 $this->result .= "\n\n";
323 }
324 $this->result .= "![Generated Image]($url)";
325 }
326
327 // It's embedding
328 else if ( isset( $choice['embedding'] ) ) {
329 $content = $choice['embedding'];
330 $this->results[] = $content;
331 $this->result = $content;
332 }
333 }
334 }
335 else {
336 $this->result = $choices;
337 $this->results[] = $choices;
338 }
339 }
340
341 public function toJson() {
342 return json_encode( $this );
343 }
344 }
345