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