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