PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.4.3
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.4.3
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 3 months ago exceptions 11 months ago modules 3 months ago query 3 months ago rest 3 months ago services 3 months ago admin.php 4 months ago api.php 3 months ago core.php 3 months ago discussion.php 11 months ago event.php 11 months ago init.php 7 months ago logging.php 11 months ago reply.php 3 months ago rest.php 3 months ago
reply.php
342 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 // TODO: DALL-E 2 and 3 were using URLs, but now they are using b64_json (gpt-image-1 kind of enforced it)
277 $url = trim( $choice['url'] );
278 $this->results[] = $url;
279 $this->result = $url;
280 }
281 else if ( isset( $choice['b64_json'] ) ) {
282 // In that case we need to create a temporary file in WordPress to store the image, and return the URL for it.
283 global $mwai_core;
284
285 // Check if the query has explicitly disabled local download
286 if ( !empty( $this->query ) && $this->query instanceof Meow_MWAI_Query_Image && $this->query->localDownload === null ) {
287 // Query explicitly doesn't want local download, save as temporary upload
288 $localDownload = 'uploads';
289 $expiry = 1 * HOUR_IN_SECONDS; // 1 hour for temporary images
290 }
291 else {
292 // Use the user's AI-generated image settings (same as DALL-E uses)
293 $localDownload = $mwai_core->get_option( 'image_local_download' );
294 $expiry = (int) $mwai_core->get_option( 'image_expires_download' );
295 }
296
297 // The expiry is already in seconds
298 $ttl = $expiry;
299
300 // Use 'library' or 'uploads' based on user settings
301 $target = ( $localDownload === 'library' ) ? 'library' : 'uploads';
302
303 // Prepare metadata similar to regular image queries
304 $metadata = [];
305 if ( !empty( $this->query ) ) {
306 $metadata['query_envId'] = $this->query->envId ?? null;
307 $metadata['query_session'] = $this->query->session ?? null;
308 $metadata['query_model'] = $this->query->model ?? 'gpt-image-1';
309 }
310
311 $url = $mwai_core->files->save_temp_image_from_b64( $choice['b64_json'], 'generated', $ttl, $target, $metadata );
312 if ( is_wp_error( $url ) ) {
313 return $url;
314 }
315 $this->results[] = $url;
316
317 // For chatbot display, append image markdown to the result
318 if ( !empty( $this->result ) ) {
319 $this->result .= "\n\n";
320 }
321 $this->result .= "![Generated Image]($url)";
322 }
323
324 // It's embedding
325 else if ( isset( $choice['embedding'] ) ) {
326 $content = $choice['embedding'];
327 $this->results[] = $content;
328 $this->result = $content;
329 }
330 }
331 }
332 else {
333 $this->result = $choices;
334 $this->results[] = $choices;
335 }
336 }
337
338 public function toJson() {
339 return json_encode( $this );
340 }
341 }
342