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