PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.2.91
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.2.91
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 2 years ago core.php 2 years ago factory.php 2 years ago google.php 2 years ago huggingface.php 2 years ago openai.php 2 years ago openrouter.php 2 years ago
anthropic.php
407 lines
1 <?php
2
3 class Meow_MWAI_Engines_Anthropic extends Meow_MWAI_Engines_OpenAI
4 {
5
6 // Streaming
7 protected $streamInTokens = null;
8 protected $streamOutTokens = null;
9
10 public function __construct( $core, $env )
11 {
12 parent::__construct( $core, $env );
13 }
14
15 public function reset_stream() {
16 $this->streamContent = null;
17 $this->streamBuffer = null;
18 $this->inModel = null;
19 $this->inId = null;
20 }
21
22 protected function set_environment() {
23 $env = $this->env;
24 $this->apiKey = $env['apikey'];
25 }
26
27 protected function build_url( $query, $endpoint = null ) {
28 $endpoint = apply_filters( 'mwai_anthropic_endpoint', 'https://api.anthropic.com/v1', $this->env );
29 if ( $query instanceof Meow_MWAI_Query_Text || $query instanceof Meow_MWAI_Query_Feedback ) {
30 $url = trailingslashit( $endpoint ) . 'messages';
31 }
32 else {
33 throw new Exception( 'AI Engine: Unsupported query type.' );
34 }
35 return $url;
36 }
37
38 protected function build_headers( $query ) {
39 parent::build_headers( $query );
40 $headers = array(
41 'Content-Type' => 'application/json',
42 'x-api-key' => $this->apiKey,
43 'anthropic-version' => '2023-06-01',
44 'anthropic-beta' => 'tools-2024-04-04',
45 'User-Agent' => 'AI Engine',
46 );
47 return $headers;
48 }
49
50 public function final_checks( Meow_MWAI_Query_Base $query ) {
51 // We skip this completely.
52 // maxMessages is handed in build_messages().
53 }
54
55 protected function build_messages( $query ) {
56 $messages = [];
57
58 // Then, if any, we need to add the 'messages', they are already formatted.
59 foreach ( $query->messages as $message ) {
60 $messages[] = $message;
61 }
62
63 // Handle the maxMessages
64 if ( !empty( $query->maxMessages ) ) {
65 $messages = array_slice( $messages, -$query->maxMessages );
66 }
67
68 // If the first message is not a 'user' role, we remove it.
69 if ( !empty( $messages ) && $messages[0]['role'] !== 'user' ) {
70 array_shift( $messages );
71 }
72
73 // Finally, we need to add the message
74 // If there is a file (image), we need to sent the data (not the URL, as it's not supported by Anthropic yet).
75 $fileUrl = $query->get_file_url();
76
77 if ( !empty( $fileUrl ) ) {
78 // Currently, Claude supports the base64 source type for images, and the image/jpeg, image/png, image/gif, and image/webp media types: https://docs.anthropic.com/claude/reference/messages-examples#vision.
79 $mime = $query->get_file_mime_type();
80 $messages[] = [
81 'role' => 'user',
82 'content' => [
83 [
84 "type" => "text",
85 "text" => $query->get_message()
86 ],
87 [
88 "type" => "image",
89 "source" => [
90 "type" => "base64",
91 "media_type" => $mime,
92 "data" => $query->get_file_data()
93 ]
94 ]
95 ]
96 ];
97 }
98 else {
99 $messages[] = [ 'role' => 'user', 'content' => $query->get_message() ];
100 }
101
102 return $messages;
103 }
104
105 // Define a function to recursively replace empty arrays with empty stdClass objects
106 // To avoid errors with OpenAI's API
107 private function replaceEmptyArrayWithObject( $item ) {
108 if ( is_array( $item ) ) {
109 if ( empty( $item ) ) {
110 return new stdClass(); // Replace empty array with empty object
111 }
112 foreach ( $item as $key => $value ) {
113 $item[$key] = $this->replaceEmptyArrayWithObject( $value ); // Recurse
114 }
115 }
116 return $item;
117 }
118
119 protected function build_body( $query, $streamCallback = null, $extra = null ) {
120 if ( $query instanceof Meow_MWAI_Query_Feedback ) {
121 $body = array(
122 "model" => $query->model,
123 "max_tokens" => $query->maxTokens,
124 "temperature" => $query->temperature,
125 "stream" => !is_null( $streamCallback ),
126 "messages" => []
127 );
128
129 // Build the messages
130 $body['messages'][] = [ 'role' => 'user', 'content' => $query->message ];
131
132 if ( !empty( $query->blocks ) ) {
133 foreach ( $query->blocks as $feedback_block ) {
134 $body['messages'][] = [
135 'role' => 'assistant',
136 'content' => $feedback_block['rawMessage']['content']
137 ];
138 foreach ( $feedback_block['feedbacks'] as $feedback ) {
139 $feedbackValue = $feedback['reply']['value'];
140 if ( !is_string( $feedbackValue ) ) {
141 $feedbackValue = json_encode( $feedbackValue );
142 }
143 $body['messages'][] = [
144 'role' => 'user',
145 'content' => [
146 [
147 'type' => 'tool_result',
148 'tool_use_id' => $feedback['request']['toolId'],
149 'content' => $feedbackValue,
150 'is_error' => false // Cool, Anthropic supports errors!
151 ]
152 ]
153 ];
154 }
155 }
156 }
157
158 // TODO: This WAS COPIED FROM BELOW
159 // Support for functions
160 if ( !empty( $query->functions ) ) {
161 $model = $this->retrieve_model_info( $query->model );
162 if ( !empty( $model['tags'] ) && !in_array( 'functions', $model['tags'] ) ) {
163 error_log( 'The model "' . $query->model . '" doesn\'t support Function Calling.' );
164 }
165 else {
166 $body['tools'] = [];
167 // Dynamic function: they will interactively enhance the completion (tools).
168 foreach ( $query->functions as $function ) {
169 $body['tools'][] = $function->serializeForAnthropic();
170 }
171 // Static functions: they will be executed at the end of the completion.
172 //$body['function_call'] = $query->functionCall;
173 }
174 }
175
176 // To avoid errors with Anthropic's API, we need to replace empty arrays with empty objects
177 $body = $this->replaceEmptyArrayWithObject( $body );
178 return $body;
179 }
180 else if ( $query instanceof Meow_MWAI_Query_Text ) {
181 $body = array(
182 "model" => $query->model,
183 "max_tokens" => $query->maxTokens,
184 "temperature" => $query->temperature,
185 "stream" => !is_null( $streamCallback ),
186 );
187
188 if ( !empty( $query->stop ) ) {
189 $body['stop'] = $query->stop;
190 }
191
192 // First, we need to add the first message (the instructions).
193 if ( !empty( $query->instructions ) ) {
194 $body['system'] = $query->instructions;
195 }
196
197 // If there is a context, we need to add it.
198 if ( !empty( $query->context ) ) {
199 if ( empty( $body['system'] ) ) {
200 $body['system'] = "";
201 }
202 $body['system'] = empty( $body['system'] ) ? '' : $body['system'] . "\n\n";
203 $body['system'] = $body['system'] . "Context:\n\n" . $query->context;
204 }
205
206 // Support for functions
207 if ( !empty( $query->functions ) ) {
208 $model = $this->retrieve_model_info( $query->model );
209 if ( !empty( $model['tags'] ) && !in_array( 'functions', $model['tags'] ) ) {
210 error_log( 'The model "' . $query->model . '" doesn\'t support Function Calling.' );
211 }
212 else {
213 $body['tools'] = [];
214 // Dynamic function: they will interactively enhance the completion (tools).
215 foreach ( $query->functions as $function ) {
216 $body['tools'][] = $function->serializeForAnthropic();
217 }
218 // Static functions: they will be executed at the end of the completion.
219 //$body['function_call'] = $query->functionCall;
220 }
221 }
222
223 $body['messages'] = $this->build_messages( $query );
224 return $body;
225 }
226 else {
227 throw new Exception( 'AI Engine: Unsupported query type.' );
228 }
229 }
230
231 protected function stream_data_handler( $json ) {
232 $content = null;
233
234 // Get the data
235 if ( isset( $json['type'] ) && $json['type'] === 'message_start' ) {
236 $usage = $json['message']['usage'];
237 $this->streamInTokens = $usage['input_tokens'];
238 $this->inModel = $json['message']['model'];
239 $this->inId = $json['message']['id'];
240 }
241 else if ( isset( $json['type'] ) && ( $json['type'] === 'delta' || $json['type'] === 'content_block_delta' ) ) {
242 $content = $json['delta']['text'];
243 }
244 else if ( isset( $json['type'] ) && $json['type'] === 'message_delta' ) {
245 $usage = $json['usage'];
246 $this->streamOutTokens = $usage['output_tokens'];
247 }
248 else if ( isset( $json['type'] ) && $json['type'] === 'error' ) {
249 $error = $json['error'];
250 $message = $error['message'];
251 throw new Exception( $message );
252 }
253
254 // Avoid some endings
255 $endings = [ "<|im_end|>", "</s>" ];
256 if ( in_array( $content, $endings ) ) {
257 $content = null;
258 }
259
260 return ( $content === '0' || !empty( $content ) ) ? $content : null;
261 }
262
263 public function run_completion_query( $query, $streamCallback = null ) : Meow_MWAI_Reply {
264 if ( !is_null( $streamCallback ) ) {
265 $this->streamCallback = $streamCallback;
266 add_action( 'http_api_curl', [ $this, 'stream_handler' ], 10, 3 );
267 }
268
269 $this->reset_stream();
270 $data = null;
271 $body = $this->build_body( $query, $streamCallback );
272 $url = $this->build_url( $query );
273 $headers = $this->build_headers( $query );
274 $options = $this->build_options( $headers, $body );
275
276 try {
277 $res = $this->run_query( $url, $options, $streamCallback );
278 $reply = new Meow_MWAI_Reply( $query );
279
280 $returned_id = null;
281 $returned_model = $this->inModel;
282 $returned_choices = [];
283
284 if ( !is_null( $streamCallback ) ) {
285 // Streamed data
286 if ( empty( $this->streamContent ) ) {
287 $json = json_decode( $this->streamBuffer, true );
288 if ( isset( $json['error']['message'] ) ) {
289 throw new Exception( $json['error']['message'] );
290 }
291 }
292 $returned_id = $this->inId;
293 $returned_model = $this->inModel ? $this->inModel : $query->model;
294 $returned_in_tokens = !is_null( $this->streamInTokens ) ? $this->streamInTokens : null;
295 $returned_out_tokens = !is_null( $this->streamOutTokens ) ? $this->streamOutTokens : null;
296 $returned_choices = [
297 [
298 'message' => [
299 'content' => $this->streamContent,
300 //'function_call' => $this->streamFunctionCall
301 ]
302 ]
303 ];
304 }
305 else {
306 // Regular data
307 $data = $res['data'];
308 if ( empty( $data ) ) {
309 throw new Exception( 'No content received (res is null).' );
310 }
311 if ( !$data['model'] ) {
312 error_log( print_r( $data, 1 ) );
313 throw new Exception( 'Invalid response (no model information).' );
314 }
315 $returned_id = $data['id'];
316 $returned_model = $data['model'];
317 $returned_in_tokens = isset( $data['usage']['input_tokens'] ) ?
318 $data['usage']['input_tokens'] : null;
319 $returned_out_tokens = isset( $data['usage']['output_tokens'] ) ?
320 $data['usage']['output_tokens'] : null;
321
322 // We convert this into a format Meow_MWAI_Reply (set_choices) will understand
323 $returned_choices = [];
324 foreach ( $data['content'] as $content ) {
325 if ( $content['type'] === 'tool_use' ) {
326 $returned_choices[] = [
327 'message' => [
328 'tool_calls' => [
329 [
330 'id' => $content['id'],
331 'type' => 'function',
332 'function' => [
333 'name' => $content['name'],
334 'arguments' => empty( $content['input'] ) ? new stdClass() : $content['input'],
335 ]
336 ]
337
338 ]
339 ]
340 ];
341 }
342 else if ( $content['type'] === 'text' ) {
343 $returned_choices[] = [
344 'message' => [
345 'content' => $content['text']
346 ]
347 ];
348 }
349 }
350 }
351
352 $reply->set_choices( $returned_choices, $data );
353 if ( !empty( $returned_id ) ) {
354 $reply->set_id( $returned_id );
355 }
356
357 // Handle tokens.
358 $this->handle_tokens_usage( $reply, $query, $returned_model,
359 $returned_in_tokens, $returned_out_tokens
360 );
361
362 return $reply;
363 }
364 catch ( Exception $e ) {
365 $error = $e->getMessage();
366 $json = json_decode( $error, true );
367 if ( json_last_error() === JSON_ERROR_NONE ) {
368 if ( isset( $json['error'] ) && isset( $json['error']['message'] ) ) {
369 $error = $json['error']['message'];
370 }
371 }
372 error_log( $error );
373 $service = $this->get_service_name();
374 $message = "From $service: " . $error;
375 throw new Exception( $message );
376 }
377 }
378
379 protected function get_service_name() {
380 return "Anthropic";
381 }
382
383 public function get_models() {
384 return apply_filters( 'mwai_anthropic_models', MWAI_ANTHROPIC_MODELS );
385 }
386
387 static public function get_models_static() {
388 return MWAI_ANTHROPIC_MODELS;
389 }
390
391 public function handle_tokens_usage( $reply, $query, $returned_model,
392 $returned_in_tokens, $returned_out_tokens, $returned_price = null ) {
393 $returned_in_tokens = !is_null( $returned_in_tokens ) ?
394 $returned_in_tokens : $reply->get_in_tokens( $query );
395 $returned_out_tokens = !is_null( $returned_out_tokens ) ?
396 $returned_out_tokens : $reply->get_out_tokens();
397 if ( !empty( $reply->id ) ) {
398 // Would be cool to retrieve the usage from the API, but it's not possible.
399 }
400 $usage = $this->core->record_tokens_usage( $returned_model, $returned_in_tokens, $returned_out_tokens );
401 $reply->set_usage( $usage );
402 }
403
404 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
405 return parent::get_price( $query, $reply );
406 }
407 }