PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 1.9.94
AI Engine – The Chatbot, AI Framework & MCP for WordPress v1.9.94
3.5.9 3.5.8 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 / queries / text.php
ai-engine / classes / queries Last commit date
base.php 2 years ago embed.php 2 years ago function.php 2 years ago image.php 2 years ago parameter.php 2 years ago text.php 2 years ago transcribe.php 2 years ago
text.php
470 lines
1 <?php
2
3 class Meow_MWAI_Query_Text extends Meow_MWAI_Query_Base implements JsonSerializable {
4 public int $maxTokens = 1024;
5 public float $temperature = 0.8;
6 public int $maxSentences = 15;
7 public ?string $stop = null;
8 public array $messages = [];
9 public ?string $context = null;
10 public ?string $newMessage = null;
11 public ?string $newImage = null;
12 public ?string $newImageData = null;
13 public ?string $promptEnding = null;
14 public bool $casuallyFineTuned = false;
15 public ?int $promptTokens = null;
16
17 public function __construct( ?string $prompt = '', int $maxTokens = 1024, string $model = MWAI_FALLBACK_MODEL ) {
18 parent::__construct( $prompt );
19 $this->setModel( $model );
20 $this->setMaxTokens( $maxTokens );
21 }
22
23 #[\ReturnTypeWillChange]
24 public function jsonSerialize() {
25 return [
26 'class' => get_class( $this ),
27 'prompt' => $this->prompt,
28 'messages' => $this->messages,
29 'maxTokens' => $this->maxTokens,
30 'temperature' => $this->temperature,
31 'maxSentences' => $this->maxSentences,
32 'context' => $this->context,
33 'newMessage' => $this->newMessage,
34 'newImage' => $this->newImage,
35 'model' => $this->model,
36 'mode' => $this->mode,
37 'session' => $this->session,
38 'env' => $this->env,
39 'envId' => $this->envId,
40 'service' => $this->service,
41 'promptEnding' => $this->promptEnding,
42 'stop' => $this->stop,
43 'casuallyFineTuned' => $this->casuallyFineTuned,
44 ];
45 }
46
47 public function getPromptTokens( $refresh = false ): int {
48 if ( $this->promptTokens && !$refresh ) {
49 return $this->promptTokens;
50 }
51 $this->promptTokens = $this->estimateTokens( $this->messages );
52 return $this->promptTokens;
53 }
54
55 public function getLastPrompt(): string {
56 if ( empty( $this->messages ) ) {
57 return $this->prompt;
58 }
59 $last = $this->getLastMessage();
60 return $last;
61 }
62
63 // Quick and dirty token estimation
64 // Let's keep this synchronized with Helpers in JS
65 function estimateTokens( $content ): int
66 {
67 $text = "";
68 // https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
69 if ( is_array( $content ) ) {
70 foreach ( $content as $message ) {
71 $role = $message['role'];
72 $content = $message['content'];
73 $text .= "=#=$role\n$content=#=\n";
74 }
75 }
76 else {
77 $text = $content;
78 }
79 $tokens = 0;
80 return apply_filters( 'mwai_estimate_tokens', (int)$tokens, $text, $this->model );
81 }
82
83 /**
84 * Make sure the maxTokens is not greater than the model's context length.
85 */
86 public function finalChecks() {
87 if ( empty( $this->model ) ) { return; }
88
89 // Make sure the number of messages is not too great
90 if ( !empty( $this->maxSentences ) ) {
91 $context = array_shift( $this->messages );
92 if ( !empty( $this->messages ) ) {
93 $this->messages = array_slice( $this->messages, -$this->maxSentences );
94 }
95 else {
96 $this->messages = [];
97 }
98 if ( !empty( $context ) ) {
99 array_unshift( $this->messages, $context );
100 }
101
102 // If the last message is an array, let's look at the message which has a type === 'image_url'
103 $lastKey = key(array_slice($this->messages, -1, 1, true)); // Get the last key of the messages array
104
105 if ( !empty( $this->newImageData ) ) {
106 if ( is_array( $this->messages[$lastKey]['content'] ) ) {
107 foreach ( $this->messages[$lastKey]['content'] as &$message ) {
108 if ( $message['type'] === 'image_url' ) {
109 $message['image_url']['url'] = "data:image/jpeg;base64,{$this->newImageData}";
110 break;
111 }
112 }
113 unset( $message );
114 }
115 }
116 }
117
118 // Make sure the max tokens are respected.
119 $realMax = 4096;
120 $finetuneFamily = preg_match('/^([a-zA-Z]{0,32}):/', $this->model, $matches );
121 $finetuneFamily = ( isset( $matches ) && count( $matches ) > 0 ) ? $matches[1] : 'N/A';
122 $foundModel = null;
123 $openai_models = Meow_MWAI_Engines_OpenAI::get_openai_models();
124 foreach ( $openai_models as $currentModel ) {
125 if ( $currentModel['model'] === $this->model || $currentModel['family'] === $finetuneFamily ) {
126 $foundModel = $currentModel['name'];
127 $realMax = $currentModel['maxTokens'];
128 break;
129 }
130 }
131 $estimatedTokens = $this->getPromptTokens();
132 if ( !empty( $realMax ) && $estimatedTokens > $realMax ) {
133 throw new Exception( "AI Engine: The prompt is too long! It contains about $estimatedTokens tokens (estimation). The $foundModel model only accepts a maximum of $realMax tokens. " );
134 }
135 $realMax = (int)($realMax - $estimatedTokens) - 16;
136 if ( $this->maxTokens > $realMax ) {
137 $this->maxTokens = $realMax;
138 }
139 }
140
141 /**
142 * ID of the model to use.
143 * @param string $model ID of the model to use.
144 */
145 public function setModel( string $model ) {
146 $this->model = $model;
147 $this->mode = 'completion';
148 $found = false;
149 $openai_models = Meow_MWAI_Engines_OpenAI::get_openai_models();
150 foreach ( $openai_models as $currentModel ) {
151 if ( $currentModel['model'] === $this->model ) {
152 if ( $currentModel['mode'] ) {
153 $this->mode = $currentModel['mode'];
154 }
155 $found = true;
156 break;
157 }
158 }
159 if ( !$found ) {
160 // If the model can't be found, it's because it's probably a fine-tuned model. In the past (before August 2023),
161 // fine-tuned models were always based on GPT-3 (and therefore, using completion mode). From now on, they can be
162 // based on GPT-3.5 or 4 (and therefore, using chat mode). We need to detect that.
163 $baseModel = Meow_MWAI_Engines_OpenAI::get_finetune_base_model( $model );
164 if ( preg_match( '/^gpt-3.5|^gpt-4/', $baseModel ) ) {
165 $this->mode = 'chat';
166 }
167 }
168 }
169
170 /**
171 * Given a prompt, the model will return one or more predicted completions.
172 * It can also return the probabilities of alternative tokens at each position.
173 * @param string $prompt The prompt to generate completions.
174 */
175 public function setPrompt( $prompt ) {
176 parent::setPrompt( $prompt );
177 $this->validateMessages();
178 }
179
180 /**
181 * The prompt is used by models who uses Text Completion (and not Chat Completion).
182 * This returns the prompt if it's not a chat, otherwise it will build a prompt with
183 * all the messages nicely formatted.
184 */
185 public function getPrompt(): ?string {
186 // In the case it's really just a prompt.
187 if ( count( $this->messages ) === 1 ) {
188 $first = reset( $this->messages );
189 return $first['content'];
190 }
191
192 // In the case it's a chat that we need to convert into a prompt.
193 $first = reset( $this->messages );
194 $prompt = "";
195 if ( $first && $first['role'] === 'system' ) {
196 $prompt = $first['content'] . "\n\n";
197 }
198
199 // Casually Fine-Tuned or Prompt-Ending
200 if ( !empty( $this->promptEnding ) ) {
201 $last = end( $this->messages );
202 if ( $last && $last['role'] === 'user' ) {
203 $prompt = $last['content'] . $this->promptEnding;
204 }
205 return $prompt;
206 }
207
208 // Standard Completion
209 while ( $message = next( $this->messages ) ) {
210 $role = $message['role'];
211 $content = $message['content'];
212 if ( $role === 'system' ) {
213 $prompt .= "$content\n\n";
214 }
215 if ( $role === 'user' ) {
216 $prompt .= "User: $content\n";
217 }
218 if ( $role === 'assistant' ) {
219 $prompt .= "AI: $content\n";
220 }
221 }
222 $prompt .= "AI: ";
223 return $prompt;
224 }
225
226 /**
227 * Similar to the prompt, but focus on the new/last message.
228 * Only used when the model has a chat mode (and only used in messages).
229 * @param string $prompt The messages to generate completions.
230 */
231 public function setNewMessage( string $newMessage ): void {
232 $this->newMessage = $newMessage;
233 $this->validateMessages();
234 }
235
236 public function setNewImage( string $newImage ): void {
237 $this->newImage = $newImage;
238 $this->validateMessages();
239 }
240
241 public function setNewImageData( string $newImageData ): void {
242 $this->newImageData = $newImageData;
243 $this->validateMessages();
244 }
245
246 public function replace( $search, $replace ) {
247 $this->prompt = str_replace( $search, $replace, $this->prompt );
248 $this->validateMessages();
249 }
250
251 /**
252 * Similar to the prompt, but use an array of messages instead.
253 * @param string $prompt The messages to generate completions.
254 */
255 public function setMessages( array $messages ) {
256 $messages = array_map( function( $message ) {
257 if ( is_array( $message ) ) {
258 return [ 'role' => $message['role'], 'content' => $message['content'] ];
259 }
260 else if ( is_object( $message ) ) {
261 return [ 'role' => $message->role, 'content' => $message->content ];
262 }
263 else {
264 throw new InvalidArgumentException( 'Unsupported message type.' );
265 }
266 }, $messages );
267 $this->messages = $messages;
268 $this->validateMessages();
269 }
270
271 public function getLastMessage() {
272 if ( !empty( $this->messages ) ) {
273 $lastMessageIndex = count( $this->messages ) - 1;
274 $lastMessage = $this->messages[$lastMessageIndex];
275 if ( is_array( $lastMessage['content'] ) ) {
276 foreach( $lastMessage['content'] as $message ) {
277 if ( $message['type'] === 'text' ) {
278 return $message['text'];
279 }
280 }
281 }
282 else {
283 return $lastMessage['content'];
284 }
285 }
286 return null;
287 }
288
289 // Function that adds a message just before the last message
290 public function injectContext( string $content ): void {
291 if ( !empty( $this->messages ) ) {
292 $lastMessageIndex = count( $this->messages ) - 1;
293 $lastMessage = $this->messages[$lastMessageIndex];
294 $this->messages[$lastMessageIndex] = [ 'role' => 'system', 'content' => $content ];
295 array_push( $this->messages, $lastMessage );
296 }
297 $this->validateMessages();
298 }
299
300 /**
301 * The context that is used for the chat completion (mode === 'chat').
302 * @param string $context The context to use.
303 */
304 public function setContext( string $context ): void {
305 $this->context = apply_filters( 'mwai_ai_context', $context, $this );
306 $this->validateMessages();
307 }
308
309 private function validateMessages(): void {
310 // Messages should end with either the prompt or, if exists, the newMessage.
311 $message = empty( $this->newMessage ) ? $this->prompt : $this->newMessage;
312 $content = $message;
313
314 // If there is an image, we need to adapt it to Vision.
315 if ( !empty( $this->newImage ) ) {
316 $content = [
317 [ "type" => "text", "text" => $message ],
318 [ "type" => "image_url", "image_url" => [ "url" => $this->newImage ] ]
319 ];
320 }
321
322 if ( empty( $this->messages ) ) {
323 $this->messages = [ [ 'role' => 'user', 'content' => $content ] ];
324 }
325 else {
326 $last = &$this->messages[ count( $this->messages ) - 1 ];
327 if ( $last['role'] === 'user' ) {
328 $last['content'] = $content;
329 }
330 else {
331 array_push( $this->messages, [ 'role' => 'user', 'content' => $content ] );
332 }
333 }
334
335 // The main context must be first.
336 if ( !empty( $this->context ) ) {
337 if ( is_array( $this->messages ) && count( $this->messages ) > 0 ) {
338 if ( $this->messages[0]['role'] !== 'system' ) {
339 array_unshift( $this->messages, [ 'role' => 'system', 'content' => $this->context ] );
340 }
341 else {
342 $this->messages[0]['content'] = $this->context;
343 }
344 }
345 }
346 }
347
348 /**
349 * The maximum number of tokens to generate in the completion.
350 * The token count of your prompt plus max_tokens cannot exceed the model's context length.
351 * Most models have a context length of 2048 tokens (except for the newest models, which support 4096).
352 * @param float $prompt The maximum number of tokens.
353 */
354 public function setMaxTokens( int $maxTokens ): void {
355 $this->maxTokens = $maxTokens;
356 }
357
358 /**
359 * Set the sampling temperature to use. Higher values means the model will take more risks.
360 * Try 0.9 for more creative applications, and 0 for ones with a well-defined reply.
361 * @param float $temperature The temperature.
362 */
363 public function setTemperature( float $temperature ): void {
364 $temperature = floatval( $temperature );
365 if ( $temperature > 1 ) {
366 $temperature = 1;
367 }
368 if ( $temperature < 0 ) {
369 $temperature = 0;
370 }
371 $this->temperature = round( $temperature, 2 );
372 }
373
374 public function setMaxSentences( int $maxSentences ): void {
375 if ( !empty( $maxSentences ) ) {
376 $this->maxSentences = intval( $maxSentences );
377 $this->validateMessages();
378 }
379 }
380
381 public function setStop( string $stop ): void {
382 $this->stop = $stop;
383 }
384
385 private function convertKeys( $params )
386 {
387 $newParams = [];
388 foreach ( $params as $key => $value ) {
389 $newKey = '';
390 $capitalizeNextChar = false;
391 for ( $i = 0; $i < strlen( $key ); $i++ ) {
392 if ( $key[$i] == '_' ) {
393 $capitalizeNextChar = true;
394 }
395 else {
396 $newKey .= $capitalizeNextChar ? strtoupper($key[$i]) : $key[$i];
397 $capitalizeNextChar = false;
398 }
399 }
400 $newParams[$newKey] = $value;
401 }
402 return $newParams;
403 }
404
405 // Based on the params of the query, update the attributes
406 public function injectParams( array $params ): void
407 {
408 // Those are for the keys passed directly by the shortcode.
409 $params = $this->convertKeys( $params );
410
411 $acceptedValues = [ true, 1, '1' ];
412 if ( !empty( $params['model'] ) ) {
413 $this->setModel( $params['model'] );
414 }
415 if ( !empty( $params['casuallyFineTuned'] ) && in_array( $params['casuallyFineTuned'], $acceptedValues, true ) ) {
416 $this->promptEnding = "\n\n###\n\n";
417 $this->stop = "\n\n";
418 $this->casuallyFineTuned = true;
419 }
420 if ( !empty( $params['prompt'] ) ) {
421 $this->setPrompt( $params['prompt'] );
422 }
423 if ( !empty( $params['context'] ) ) {
424 $this->setContext( $params['context'] );
425 }
426 if ( !empty( $params['messages'] ) ) {
427 $this->setMessages( $params['messages'] );
428 }
429 if ( !empty( $params['newMessage'] ) ) {
430 $this->setNewMessage( $params['newMessage'] );
431 }
432 if ( !empty( $params['maxTokens'] ) && intval( $params['maxTokens'] ) > 0 ) {
433 $this->setMaxTokens( intval( $params['maxTokens'] ) );
434 }
435 if ( !empty( $params['maxMessages'] ) && intval( $params['maxMessages'] ) > 0 ) {
436 $this->setMaxSentences( intval( $params['maxMessages'] ) );
437 }
438 if ( !empty( $params['maxSentences'] ) && intval( $params['maxSentences'] ) > 0 ) {
439 $this->setMaxSentences( intval( $params['maxSentences'] ) );
440 }
441 if ( !empty( $params['temperature'] ) ) {
442 $this->setTemperature( $params['temperature'] );
443 }
444 if ( !empty( $params['stop'] ) ) {
445 $this->setStop( $params['stop'] );
446 }
447 if ( !empty( $params['maxResults'] ) ) {
448 $this->setMaxResults( $params['maxResults'] );
449 }
450 if ( !empty( $params['env'] ) ) {
451 $this->setEnv( $params['env'] );
452 }
453 if ( !empty( $params['session'] ) ) {
454 $this->setSession( $params['session'] );
455 }
456 // Should add the params related to Open AI and Azure
457 if ( !empty( $params['service'] ) ) {
458 $this->setService( $params['service'] );
459 }
460 if ( !empty( $params['apiKey'] ) ) {
461 $this->setApiKey( $params['apiKey'] );
462 }
463 if ( !empty( $params['botId'] ) ) {
464 $this->setBotId( $params['botId'] );
465 }
466 if ( !empty( $params['envId'] ) ) {
467 $this->setEnvId( $params['envId'] );
468 }
469 }
470 }