PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.9
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.9
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 1 year ago query 1 year ago rest 1 year 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 1 year ago logging.php 1 year ago reply.php 11 months ago rest.php 11 months ago
reply.php
331 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 if ( is_array( $choices ) ) {
161 foreach ( $choices as $choice ) {
162
163 // It's chat completion
164 if ( isset( $choice['message'] ) ) {
165
166 // It's text content
167 if ( isset( $choice['message']['content'] ) ) {
168 $content = trim( $choice['message']['content'] );
169 $this->results[] = $content;
170 $this->result = $content;
171 }
172
173 // It's a tool call (OpenAI-style and Anthropic-style)
174 $toolCalls = [];
175 if ( isset( $choice['message']['tool_calls'] ) ) {
176 $tools = $choice['message']['tool_calls'];
177 foreach ( $tools as $tool ) {
178 if ( $tool['type'] === 'function' ) {
179 $toolCall = [
180 'toolId' => $tool['id'],
181 //'mode' => 'interactive',
182 'type' => 'tool_call',
183 'name' => trim( $tool['function']['name'] ),
184 'arguments' => $this->extract_arguments( $tool['function']['arguments'] ),
185 // Represent the original message that triggered the function call
186 'rawMessage' => $rawMessage ? $rawMessage : ( isset( $choice['_rawMessage'] ) ? $choice['_rawMessage'] : $choice['message'] ),
187 ];
188 $toolCalls[] = $toolCall;
189 }
190 }
191 }
192
193 // If it's a function call (Open-AI style; usually for a final execution)
194 if ( isset( $choice['message']['function_call'] ) ) {
195 $content = $choice['message']['function_call'];
196 $name = trim( $content['name'] );
197 $args = $content['arguments'] ?? $content['args'] ?? null;
198 $toolCalls[] = [
199 'toolId' => null,
200 'mode' => 'static',
201 'type' => 'function_call',
202 'name' => $name,
203 'arguments' => $this->extract_arguments( $args ),
204 'rawMessage' => $rawMessage ? $rawMessage : ( isset( $choice['_rawMessage'] ) ? $choice['_rawMessage'] : $choice['message'] ),
205 ];
206 }
207
208 // Deep copy tool calls BEFORE adding function references
209 // This prevents the "Duplicate value for 'tool_call_id'" error
210 // when the same function is called multiple times
211 // Note: We need to preserve the toolId for each tool call
212 if ( !empty( $toolCalls ) ) {
213 $toolCalls = json_decode( json_encode( $toolCalls ), true );
214 }
215
216 // Resolve the original function from the query
217 if ( !empty( $toolCalls ) ) {
218 foreach ( $toolCalls as &$toolCall ) {
219 if ( $toolCall['type'] !== 'function_call' && $toolCall['type'] !== 'tool_call' ) {
220 continue;
221 }
222 foreach ( $this->query->functions as $function ) {
223 if ( $function->name == $toolCall['name'] ) {
224 $toolCall['function'] = $function;
225 break;
226 }
227 }
228 }
229 // IMPORTANT: Unset the reference to avoid PHP's foreach reference bug
230 unset( $toolCall );
231 }
232
233 // Add tool calls to existing arrays instead of resetting them
234 // This is crucial for engines like Google that create multiple choices
235 // for multiple function calls in a single response
236 foreach ( $toolCalls as $tcIdx => $toolCall ) {
237 if ( $toolCall['function']->target !== 'js' ) {
238 $this->needFeedbacks[] = $toolCall;
239 }
240 else if ( $toolCall['function']->target === 'js' ) {
241 $this->needClientActions[] = $toolCall;
242 }
243 }
244 }
245
246 // It's text completion
247 else if ( isset( $choice['text'] ) ) {
248
249 // TODO: Assistants return an array (so actually not really a text completion)
250 // We should probably make this clearer and analyze all the outputs from different endpoints.
251 if ( is_array( $choice['text'] ) ) {
252 $text = trim( $choice['text']['value'] );
253 $this->results[] = $text;
254 $this->result = $text;
255 }
256 else {
257 $text = trim( $choice['text'] );
258 $this->results[] = $text;
259 $this->result = $text;
260 }
261 }
262
263 // It's url/image
264 else if ( isset( $choice['url'] ) ) {
265 // TODO: DALL-E 2 and 3 were using URLs, but now they are using b64_json (gpt-image-1 kind of enforced it)
266 $url = trim( $choice['url'] );
267 $this->results[] = $url;
268 $this->result = $url;
269 }
270 else if ( isset( $choice['b64_json'] ) ) {
271 // In that case we need to create a temporary file in WordPress to store the image, and return the URL for it.
272 global $mwai_core;
273
274 // Check if the query has explicitly disabled local download
275 if ( !empty( $this->query ) && $this->query instanceof Meow_MWAI_Query_Image && $this->query->localDownload === null ) {
276 // Query explicitly doesn't want local download, save as temporary upload
277 $localDownload = 'uploads';
278 $expiry = 1 * HOUR_IN_SECONDS; // 1 hour for temporary images
279 }
280 else {
281 // Use the user's AI-generated image settings (same as DALL-E uses)
282 $localDownload = $mwai_core->get_option( 'image_local_download' );
283 $expiry = (int) $mwai_core->get_option( 'image_expires_download' );
284 }
285
286 // The expiry is already in seconds
287 $ttl = $expiry;
288
289 // Use 'library' or 'uploads' based on user settings
290 $target = ( $localDownload === 'library' ) ? 'library' : 'uploads';
291
292 // Prepare metadata similar to regular image queries
293 $metadata = [];
294 if ( !empty( $this->query ) ) {
295 $metadata['query_envId'] = $this->query->envId ?? null;
296 $metadata['query_session'] = $this->query->session ?? null;
297 $metadata['query_model'] = $this->query->model ?? 'gpt-image-1';
298 }
299
300 $url = $mwai_core->files->save_temp_image_from_b64( $choice['b64_json'], 'generated', $ttl, $target, $metadata );
301 if ( is_wp_error( $url ) ) {
302 return $url;
303 }
304 $this->results[] = $url;
305
306 // For chatbot display, append image markdown to the result
307 if ( !empty( $this->result ) ) {
308 $this->result .= "\n\n";
309 }
310 $this->result .= "![Generated Image]($url)";
311 }
312
313 // It's embedding
314 else if ( isset( $choice['embedding'] ) ) {
315 $content = $choice['embedding'];
316 $this->results[] = $content;
317 $this->result = $content;
318 }
319 }
320 }
321 else {
322 $this->result = $choices;
323 $this->results[] = $choices;
324 }
325 }
326
327 public function toJson() {
328 return json_encode( $this );
329 }
330 }
331