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