PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.6.9
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.6.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 / engines / anthropic.php
ai-engine / classes / engines Last commit date
anthropic.php 1 year ago core.php 1 year ago factory.php 1 year ago google.php 1 year ago huggingface.php 1 year ago openai.php 1 year ago openrouter.php 1 year ago replicate.php 1 year ago
anthropic.php
514 lines
1 <?php
2
3 class Meow_MWAI_Engines_Anthropic extends Meow_MWAI_Engines_OpenAI
4 {
5 // Streaming
6 protected $streamInTokens = null;
7 protected $streamOutTokens = null;
8 protected $streamBlocks;
9 protected $streamIsThinking = false;
10
11 public function __construct( $core, $env )
12 {
13 parent::__construct( $core, $env );
14 }
15
16 public function reset_stream() {
17 $this->streamContent = null;
18 $this->streamBuffer = null;
19 $this->streamFunctionCall = null;
20 $this->streamToolCalls = [];
21 $this->streamLastMessage = null;
22 $this->streamInTokens = null;
23 $this->streamOutTokens = null;
24 $this->streamIsThinking = false;
25
26 $this->streamBlocks = [
27 'role' => 'assistant',
28 'content' => []
29 ];
30
31 $this->inModel = null;
32 $this->inId = null;
33 }
34
35 protected function set_environment() {
36 $env = $this->env;
37 $this->apiKey = $env['apikey'];
38 }
39
40 protected function build_url( $query, $endpoint = null ) {
41 $endpoint = apply_filters( 'mwai_anthropic_endpoint', 'https://api.anthropic.com/v1', $this->env );
42 if ( $query instanceof Meow_MWAI_Query_Text || $query instanceof Meow_MWAI_Query_Feedback ) {
43 $url = trailingslashit( $endpoint ) . 'messages';
44 }
45 else {
46 throw new Exception( 'AI Engine: Unsupported query type.' );
47 }
48 return $url;
49 }
50
51 protected function build_headers( $query ) {
52 parent::build_headers( $query );
53 $headers = array(
54 'Content-Type' => 'application/json',
55 'x-api-key' => $this->apiKey,
56 'anthropic-version' => '2023-06-01',
57 'anthropic-beta' => 'tools-2024-04-04, pdfs-2024-09-25',
58 'User-Agent' => 'AI Engine',
59 );
60 return $headers;
61 }
62
63 public function final_checks( Meow_MWAI_Query_Base $query ) {
64 // We skip this completely.
65 // maxMessages is handed in build_messages().
66 }
67
68 protected function build_messages( $query ) {
69 $messages = [];
70
71 // Then, if any, we need to add the 'messages', they are already formatted.
72 foreach ( $query->messages as $message ) {
73 $messages[] = $message;
74 }
75
76 // Handle the maxMessages
77 if ( !empty( $query->maxMessages ) ) {
78 $messages = array_slice( $messages, -$query->maxMessages );
79 }
80
81 // If the first message is not a 'user' role, we remove it.
82 if ( !empty( $messages ) && $messages[0]['role'] !== 'user' ) {
83 array_shift( $messages );
84 }
85
86 if ( $query->attachedFile ) {
87 // https://docs.anthropic.com/claude/reference/messages-examples#vision
88 // Claude only supports image/jpeg, image/png, image/gif, and image/webp media types.
89 $mime = $query->attachedFile->get_mimeType();
90 // Claude only supports upload by data (base64), not by URL.
91 $data = $query->attachedFile->get_base64();
92 $message = $query->get_message();
93 $isPDF = $mime === 'application/pdf';
94 $isIMG = in_array( $mime, [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp' ] );
95
96 if ( $isPDF ) {
97 if ( empty( $message ) ) {
98 // Claude doesn't support messages with only PDFs, so we add a text message.
99 $message = "I uploaded a PDF. Do not consider this message as part of the conversation.";
100 }
101 $messages[] = [
102 'role' => 'user',
103 'content' => [
104 [
105 "type" => "text",
106 "text" => $message
107 ],
108 [
109 "type" => "document",
110 "source" => [
111 "type" => "base64",
112 "media_type" => "application/pdf",
113 "data" => $data
114 ]
115 ]
116 ]
117 ];
118 }
119 else if ( $isIMG ) {
120 if ( empty( $message ) ) {
121 // Claude doesn't support messages with only images, so we add a text message.
122 $message = "I uploaded an image. Do not consider this message as part of the conversation.";
123 }
124 $messages[] = [
125 'role' => 'user',
126 'content' => [
127 [
128 "type" => "text",
129 "text" => $message
130 ],
131 [
132 "type" => "image",
133 "source" => [
134 "type" => "base64",
135 "media_type" => $mime,
136 "data" => $data
137 ]
138 ]
139 ]
140 ];
141 }
142 }
143 else {
144 $messages[] = [ 'role' => 'user', 'content' => $query->get_message() ];
145 }
146
147 return $messages;
148 }
149
150 // Define a function to recursively replace empty arrays with empty stdClass objects
151 // To avoid errors with OpenAI's API
152 private function replaceEmptyArrayWithObject( $item ) {
153 if ( is_array( $item ) ) {
154 if ( empty( $item ) ) {
155 return new stdClass(); // Replace empty array with empty object
156 }
157 foreach ( $item as $key => $value ) {
158 $item[$key] = $this->replaceEmptyArrayWithObject( $value ); // Recurse
159 }
160 }
161 return $item;
162 }
163
164 protected function build_body( $query, $streamCallback = null, $extra = null ) {
165 if ( $query instanceof Meow_MWAI_Query_Feedback ) {
166 $body = array(
167 "model" => $query->model,
168 "max_tokens" => $query->maxTokens,
169 "temperature" => $query->temperature,
170 "stream" => !is_null( $streamCallback ),
171 "messages" => []
172 );
173
174 if ( !empty( $query->instructions ) ) {
175 $body['system'] = $query->instructions;
176 }
177
178 // Build the messages
179 $body['messages'][] = [ 'role' => 'user', 'content' => $query->message ];
180
181 if ( !empty( $query->blocks ) ) {
182 foreach ( $query->blocks as $feedback_block ) {
183 $contentBlock = $feedback_block['rawMessage']['content'];
184 $assistantMessageIndex = count($body['messages']);
185 $body['messages'][] = [
186 'role' => 'assistant',
187 'content' => $contentBlock
188 ];
189
190 foreach ( $feedback_block['feedbacks'] as $feedback ) {
191 $feedbackValue = $feedback['reply']['value'];
192 if ( !is_string( $feedbackValue ) ) {
193 $feedbackValue = json_encode( $feedbackValue );
194 }
195
196 // Check for the tool_use message and make sure input is not null.
197 foreach ( $body['messages'][$assistantMessageIndex]['content'] as &$message ) {
198 if ( $message['type'] === 'tool_use' && $message['id'] === $feedback['request']['toolId'] ) {
199 if ( empty( $message['input'] ) ) {
200 $message['input'] = new stdClass();
201 }
202 break;
203 }
204 }
205 unset( $message );
206
207 // Add a new message with tool_result if tool_use.
208 $body['messages'][] = [
209 'role' => 'user',
210 'content' => [
211 [
212 'type' => 'tool_result',
213 'tool_use_id' => $feedback['request']['toolId'],
214 'content' => $feedbackValue,
215 'is_error' => false // Cool, Anthropic supports errors!
216 ]
217 ]
218 ];
219 }
220 }
221 }
222
223 // TODO: This WAS COPIED FROM BELOW
224 // Support for functions
225 if ( !empty( $query->functions ) ) {
226 $model = $this->retrieve_model_info( $query->model );
227 if ( !empty( $model['tags'] ) && !in_array( 'functions', $model['tags'] ) ) {
228 Meow_MWAI_Logging::warn( 'The model "' . $query->model . '" doesn\'t support Function Calling.' );
229 }
230 else {
231 $body['tools'] = [];
232 // Dynamic function: they will interactively enhance the completion (tools).
233 foreach ( $query->functions as $function ) {
234 $body['tools'][] = $function->serializeForAnthropic();
235 }
236 // Static functions: they will be executed at the end of the completion.
237 //$body['function_call'] = $query->functionCall;
238 }
239 }
240
241 // To avoid errors with Anthropic's API, we need to replace empty arrays with empty objects
242 $body = $this->replaceEmptyArrayWithObject( $body );
243 return $body;
244 }
245 else if ( $query instanceof Meow_MWAI_Query_Text ) {
246 $body = array(
247 "model" => $query->model,
248 "stream" => !is_null( $streamCallback ),
249 );
250
251 if ( !empty( $query->maxTokens ) ) {
252 $body['max_tokens'] = $query->maxTokens;
253 }
254 else {
255 // https://docs.anthropic.com/en/docs/about-claude/models#model-comparison-table
256 $body['max_tokens'] = 4096;
257 }
258
259 if ( !empty( $query->temperature ) ) {
260 $body['temperature'] = $query->temperature;
261 }
262
263 if ( !empty( $query->stop ) ) {
264 $body['stop'] = $query->stop;
265 }
266
267 // First, we need to add the first message (the instructions).
268 if ( !empty( $query->instructions ) ) {
269 $body['system'] = $query->instructions;
270 }
271
272 // If there is a context, we need to add it.
273 if ( !empty( $query->context ) ) {
274 if ( empty( $body['system'] ) ) {
275 $body['system'] = "";
276 }
277 $body['system'] = empty( $body['system'] ) ? '' : $body['system'] . "\n\n";
278 $body['system'] = $body['system'] . "Context:\n\n" . $query->context;
279 }
280
281 // Support for functions
282 if ( !empty( $query->functions ) ) {
283 $model = $this->retrieve_model_info( $query->model );
284 if ( !empty( $model['tags'] ) && !in_array( 'functions', $model['tags'] ) ) {
285 Meow_MWAI_Logging::warn( 'The model "' . $query->model . '" doesn\'t support Function Calling.' );
286 }
287 else {
288 $body['tools'] = [];
289 // Dynamic function: they will interactively enhance the completion (tools).
290 foreach ( $query->functions as $function ) {
291 $body['tools'][] = $function->serializeForAnthropic();
292 }
293 // Static functions: they will be executed at the end of the completion.
294 //$body['function_call'] = $query->functionCall;
295 }
296 }
297
298 $body['messages'] = $this->build_messages( $query );
299 return $body;
300 }
301 else {
302 throw new Exception( 'AI Engine: Unsupported query type.' );
303 }
304 }
305
306 protected function stream_data_handler( $json ) {
307 $content = null;
308 $type = !empty( $json['type'] ) ? $json['type'] : null;
309 if ( is_null( $type ) ) {
310 return $content;
311 }
312
313 if ( $type === 'message_start' ) {
314 $usage = $json['message']['usage'];
315 $this->streamInTokens = $usage['input_tokens'];
316 $this->inModel = $json['message']['model'];
317 $this->inId = $json['message']['id'];
318 }
319 else if ( $type === 'content_block_start' ) {
320 $this->streamBlocks['content'][] = $json['content_block'];
321 }
322 else if ( $type === 'content_block_delta' ) {
323 $index = $json['index'];
324 $block = $this->streamBlocks['content'][$index];
325 if ( $json['delta']['type'] === 'text_delta' ) {
326 $block['text'] .= $json['delta']['text'];
327 if ( strpos( $block['text'], '<thinking' ) === 0 ) {
328 $this->streamIsThinking = true;
329 }
330 if ( strpos( $block['text'], '</thinking>' ) === 0 ) {
331 $this->streamIsThinking = false;
332 }
333 $content = $json['delta']['text'];
334 }
335 else if ( $json['delta']['type'] === 'input_json_delta' ) {
336 // Somehow, the input is set as an array, but it should be a string since it's JSON.
337 $block['input'] = is_array( $block['input'] ) ? "" : $block['input'];
338 $block['input'] .= $json['delta']['partial_json'];
339 }
340 $this->streamBlocks['content'][$index] = $block;
341 }
342 // At the end of a block, let's look for any 'input' not yet decoded from JSON
343 else if ( $type === 'content_block_stop' ) {
344 $index = $json['index'];
345 $block = $this->streamBlocks['content'][$index];
346 if ( isset( $block['input'] ) && is_string( $block['input'] ) ) {
347 $block['input'] = json_decode( $block['input'], true );
348 }
349 $this->streamBlocks['content'][$index] = $block;
350 }
351 else if ( $type === 'message_delta' ) {
352 $usage = $json['usage'];
353 $this->streamOutTokens = $usage['output_tokens'];
354 }
355 else if ( $type === 'error' ) {
356 $error = $json['error'];
357 $message = $error['message'];
358 throw new Exception( $message );
359 }
360
361 // Avoid some endings
362 $endings = [ "<|im_end|>", "</s>" ];
363 if ( in_array( $content, $endings ) ) {
364 $content = null;
365 }
366
367 // If the stream is thinking, we don't want to return anything yet.
368 if ( $this->streamIsThinking ) {
369 $content = null;
370 }
371
372 return ( $content === '0' || !empty( $content ) ) ? $content : null;
373 }
374
375 // This create the "choices" (even though, often, it is only one choice).
376 // It is basically the reply, but one that is understood by the Meow_MWAI_Reply class.
377 public function create_choices( $data ) {
378 $returned_choices = [];
379 foreach ( $data['content'] as $content ) {
380 if ( $content['type'] === 'tool_use' ) {
381 $returned_choices[] = [
382 'message' => [
383 'tool_calls' => [
384 [
385 'id' => $content['id'],
386 'type' => 'function',
387 'function' => [
388 'name' => $content['name'],
389 'arguments' => empty( $content['input'] ) ? new stdClass() : $content['input'],
390 ]
391 ]
392
393 ]
394 ]
395 ];
396 }
397 else if ( $content['type'] === 'text' ) {
398 $returned_choices[] = [
399 'message' => [
400 'content' => $content['text']
401 ]
402 ];
403 }
404 }
405 return $returned_choices;
406 }
407
408 public function run_completion_query( $query, $streamCallback = null ) : Meow_MWAI_Reply {
409 $isStreaming = !is_null( $streamCallback );
410
411 if ( $isStreaming ) {
412 $this->streamCallback = $streamCallback;
413 add_action( 'http_api_curl', [ $this, 'stream_handler' ], 10, 3 );
414 }
415
416 $this->reset_stream();
417 $data = null;
418 $body = $this->build_body( $query, $streamCallback );
419 $url = $this->build_url( $query );
420 $headers = $this->build_headers( $query );
421 $options = $this->build_options( $headers, $body );
422
423 try {
424 $res = $this->run_query( $url, $options, $streamCallback );
425 $reply = new Meow_MWAI_Reply( $query );
426 $returned_id = null;
427 $returned_model = null;
428 $returned_choices = [];
429
430 // Streaming Mode
431 if ( $isStreaming ) {
432 $returned_id = $this->inId;
433 $returned_model = $this->inModel ? $this->inModel : $query->model;
434 if ( !is_null( $this->streamInTokens && !is_null( $this->streamOutTokens ) ) ) {
435 $returned_in_tokens = $this->streamInTokens;
436 $returned_out_tokens = $this->streamOutTokens;
437 }
438 $data = $this->streamBlocks;
439 $returned_choices = $this->create_choices( $this->streamBlocks );
440 }
441 // Standard Mode
442 else {
443 $data = $res['data'];
444 $returned_id = $data['id'];
445 $returned_model = $data['model'];
446 $usage = $data['usage'];
447 if ( !empty( $usage ) ) {
448 $returned_in_tokens = isset( $usage['input_tokens'] ) ? $usage['input_tokens'] : null;
449 $returned_out_tokens = isset( $usage['output_tokens'] ) ? $usage['output_tokens'] : null;
450 }
451 $returned_choices = $this->create_choices( $data );
452 }
453
454 $reply->set_choices( $returned_choices, $data );
455 if ( !empty( $returned_id ) ) {
456 $reply->set_id( $returned_id );
457 }
458
459 // Handle tokens.
460 $this->handle_tokens_usage( $reply, $query, $returned_model,
461 $returned_in_tokens, $returned_out_tokens
462 );
463
464 return $reply;
465 }
466 catch ( Exception $e ) {
467 $error = $e->getMessage();
468 $json = json_decode( $error, true );
469 if ( json_last_error() === JSON_ERROR_NONE ) {
470 if ( isset( $json['error'] ) && isset( $json['error']['message'] ) ) {
471 $error = $json['error']['message'];
472 }
473 }
474 Meow_MWAI_Logging::error( "(Anthropic) " . $error );
475 $service = $this->get_service_name();
476 $message = "From $service: " . $error;
477 throw new Exception( $message );
478 }
479 finally {
480 if ( $isStreaming ) {
481 remove_action( 'http_api_curl', [ $this, 'stream_handler' ] );
482 }
483 }
484 }
485
486 protected function get_service_name() {
487 return "Anthropic";
488 }
489
490 public function get_models() {
491 return apply_filters( 'mwai_anthropic_models', MWAI_ANTHROPIC_MODELS );
492 }
493
494 static public function get_models_static() {
495 return MWAI_ANTHROPIC_MODELS;
496 }
497
498 public function handle_tokens_usage( $reply, $query, $returned_model,
499 $returned_in_tokens, $returned_out_tokens, $returned_price = null ) {
500 $returned_in_tokens = !is_null( $returned_in_tokens ) ?
501 $returned_in_tokens : $reply->get_in_tokens( $query );
502 $returned_out_tokens = !is_null( $returned_out_tokens ) ?
503 $returned_out_tokens : $reply->get_out_tokens();
504 if ( !empty( $reply->id ) ) {
505 // Would be cool to retrieve the usage from the API, but it's not possible.
506 }
507 $usage = $this->core->record_tokens_usage( $returned_model, $returned_in_tokens, $returned_out_tokens );
508 $reply->set_usage( $usage );
509 }
510
511 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
512 return parent::get_price( $query, $reply );
513 }
514 }