PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.7.6
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.7.6
3.6.2 3.6.1 3.6.0 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 / engines / openrouter.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 perplexity.php 1 year ago replicate.php 1 year ago
openrouter.php
275 lines
1 <?php
2
3 // If this isn't defined elsewhere, set it here by default. You can override
4 // it in your theme's functions.php or your main wp-config.php. If set to true,
5 // additional time will be spent fetching exact pricing info from OpenRouter
6 // after each query, resulting in more accurate but potentially slower responses.
7 if ( ! defined( 'MWAI_OPENROUTER_ACCURATE_PRICING' ) ) {
8 define( 'MWAI_OPENROUTER_ACCURATE_PRICING', false );
9 }
10
11 class Meow_MWAI_Engines_OpenRouter extends Meow_MWAI_Engines_OpenAI
12 {
13 /**
14 * Keep a static dictionary (query -> price) so that if we see the same query
15 * again in another instance, we can immediately return the stored price
16 * instead of recomputing.
17 * @var array
18 */
19 private static $accuratePrices = array();
20
21 public function __construct( $core, $env ) {
22 parent::__construct( $core, $env );
23 }
24
25 protected function set_environment() {
26 $env = $this->env;
27 $this->apiKey = $env['apikey'];
28 }
29
30 protected function build_url( $query, $endpoint = null ) {
31 $endpoint = apply_filters( 'mwai_openrouter_endpoint', 'https://openrouter.ai/api/v1', $this->env );
32 return parent::build_url( $query, $endpoint );
33 }
34
35 protected function build_headers( $query ) {
36 $site_url = apply_filters( 'mwai_openrouter_site_url', get_site_url(), $query );
37 $site_name = apply_filters( 'mwai_openrouter_site_name', get_bloginfo( 'name' ), $query );
38 return array(
39 'Content-Type' => 'application/json',
40 'Authorization' => 'Bearer ' . $this->apiKey,
41 'HTTP-Referer' => $site_url,
42 'X-Title' => $site_name,
43 'User-Agent' => 'AI Engine',
44 );
45 }
46
47 protected function build_body( $query, $streamCallback = null, $extra = null ) {
48 $body = parent::build_body( $query, $streamCallback, $extra );
49 // Use transforms from OpenRouter docs
50 $body['transforms'] = ['middle-out'];
51 return $body;
52 }
53
54 protected function get_service_name() {
55 return "OpenRouter";
56 }
57
58 public function get_models() {
59 return $this->core->get_engine_models( 'openrouter' );
60 }
61
62 /**
63 * Requests usage data if streaming was used and the usage is incomplete.
64 */
65 public function handle_tokens_usage(
66 $reply,
67 $query,
68 $returned_model,
69 $returned_in_tokens,
70 $returned_out_tokens,
71 $returned_price = null
72 ) {
73 // If streaming is not enabled, we might already have all usage data
74 $everything_is_set = !is_null( $returned_model )
75 && !is_null( $returned_in_tokens )
76 && !is_null( $returned_out_tokens );
77
78 // Clean up the data
79 $returned_in_tokens = $returned_in_tokens ?? $reply->get_in_tokens( $query );
80 $returned_out_tokens = $returned_out_tokens ?? $reply->get_out_tokens();
81 $returned_price = $returned_price ?? $reply->get_price();
82
83 // Fetch usage data from OpenRouter if needed AND if constant is set
84 if ( MWAI_OPENROUTER_ACCURATE_PRICING && !empty( $reply->id ) ) {
85 usleep( 1000 );
86 $url = 'https://openrouter.ai/api/v1/generation?id=' . $reply->id;
87 try {
88 $ch = curl_init();
89 curl_setopt( $ch, CURLOPT_URL, $url );
90 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
91 curl_setopt( $ch, CURLOPT_HTTPHEADER, [
92 'Authorization: Bearer ' . $this->apiKey
93 ] );
94 curl_setopt( $ch, CURLOPT_USERAGENT, 'AI Engine' );
95 $res = curl_exec( $ch );
96 curl_close( $ch );
97 $res = json_decode( $res, true );
98 if ( isset( $res['data'] ) ) {
99 $data = $res['data'];
100 $returned_model = $data['model'] ?? $returned_model;
101 $returned_in_tokens = $data['tokens_prompt'] ?? $returned_in_tokens;
102 $returned_out_tokens = $data['tokens_completion']?? $returned_out_tokens;
103 $returned_price = $data['total_cost'] ?? $returned_price;
104 }
105 } catch ( Exception $e ) {
106 Meow_MWAI_Logging::error( 'OpenRouter: ' . $e->getMessage() );
107 }
108 }
109
110 // Record the usage in the database
111 $usage = $this->core->record_tokens_usage(
112 $returned_model,
113 $returned_in_tokens,
114 $returned_out_tokens,
115 $returned_price
116 );
117
118 // Set the usage back on the reply
119 $reply->set_usage( $usage );
120
121 // Store the price for this query in our static dictionary
122 $hash = spl_object_hash( $query );
123 self::$accuratePrices[ $hash ] = $reply->get_price();
124 }
125
126 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
127 // If we already computed the price for this query, just return it
128 $hash = spl_object_hash( $query );
129 if ( isset( self::$accuratePrices[ $hash ] ) ) {
130 return self::$accuratePrices[ $hash ];
131 }
132
133 // Otherwise, get the price from the reply if it exists
134 $price = $reply->get_price();
135 return is_null( $price ) ? parent::get_price( $query, $reply ) : $price;
136 }
137
138 /**
139 * Retrieve the models from OpenRouter, adding tags/features accordingly.
140 */
141 public function retrieve_models() {
142
143 // 1. Get the list of models supporting "tools"
144 $toolsModels = $this->get_supported_models( 'tools' );
145
146 // 2. Retrieve the full list of models
147 $url = 'https://openrouter.ai/api/v1/models';
148 $response = wp_remote_get( $url );
149 if ( is_wp_error( $response ) ) {
150 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
151 }
152 $body = json_decode( $response['body'], true );
153 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
154 throw new Exception( 'AI Engine: Invalid response for the list of models.' );
155 }
156
157 $models = array();
158 foreach ( $body['data'] as $model ) {
159
160 // Basic defaults
161 $family = 'n/a';
162 $maxCompletionTokens = 4096;
163 $maxContextualTokens = 8096;
164 $priceIn = 0;
165 $priceOut = 0;
166
167 // Family from model ID (e.g. "openai/gpt-4/32k" -> "openai")
168 if ( isset( $model['id'] ) ) {
169 $parts = explode( '/', $model['id'] );
170 $family = $parts[0] ?? 'n/a';
171 }
172
173 // maxCompletionTokens
174 if ( isset( $model['top_provider']['max_completion_tokens'] ) ) {
175 $maxCompletionTokens = (int) $model['top_provider']['max_completion_tokens'];
176 }
177
178 // maxContextualTokens
179 if ( isset( $model['context_length'] ) ) {
180 $maxContextualTokens = (int) $model['context_length'];
181 }
182
183 // Pricing
184 if ( isset( $model['pricing']['prompt'] ) && $model['pricing']['prompt'] > 0 ) {
185 $priceIn = $this->truncate_float( floatval( $model['pricing']['prompt'] ) * 1000 );
186 }
187 if ( isset( $model['pricing']['completion'] ) && $model['pricing']['completion'] > 0 ) {
188 $priceOut = $this->truncate_float( floatval( $model['pricing']['completion'] ) * 1000 );
189 }
190
191 // Basic features and tags
192 $features = [ 'completion' ];
193 $tags = [ 'core', 'chat' ];
194
195 // If the name contains (beta), (alpha) or (preview), add 'preview' tag and remove from name
196 if ( preg_match( '/\((beta|alpha|preview)\)/i', $model['name'] ) ) {
197 $tags[] = 'preview';
198 $model['name'] = preg_replace( '/\((beta|alpha|preview)\)/i', '', $model['name'] );
199 }
200
201 // If model supports tools
202 if ( in_array( $model['id'], $toolsModels, true ) ) {
203 $tags[] = 'functions';
204 $features[] = 'functions';
205 }
206
207 // Check if the model supports "vision" (if "image" is in the left side of the arrow)
208 // e.g. "text+image->text" or "image->text"
209 $modality = $model['architecture']['modality'] ?? '';
210 $modality_lc = strtolower( $modality );
211 if (
212 strpos( $modality_lc, 'image->' ) !== false ||
213 strpos( $modality_lc, 'image+' ) !== false ||
214 strpos( $modality_lc, '+image->' ) !== false
215 ) {
216 // Means it can handle images as input, so we consider that "vision"
217 $tags[] = 'vision';
218 }
219
220 $models[] = array(
221 'model' => $model['id'] ?? '',
222 'name' => trim( $model['name'] ?? '' ),
223 'family' => $family,
224 'features' => $features,
225 'price' => array(
226 'in' => $priceIn,
227 'out' => $priceOut,
228 ),
229 'type' => 'token',
230 'unit' => 1 / 1000,
231 'maxCompletionTokens' => $maxCompletionTokens,
232 'maxContextualTokens' => $maxContextualTokens,
233 'tags' => $tags,
234 );
235 }
236
237 return $models;
238 }
239
240 /**
241 * Return an array of model IDs that support a certain feature (e.g. "tools").
242 */
243 private function get_supported_models( $feature ) {
244 // Make a request to get models supporting that feature
245 $url = 'https://openrouter.ai/api/v1/models?supported_parameters=' . urlencode( $feature );
246 $response = wp_remote_get( $url );
247 if ( is_wp_error( $response ) ) {
248 Meow_MWAI_Logging::error( "OpenRouter: Failed to retrieve models for '$feature': " . $response->get_error_message() );
249 return array();
250 }
251 $body = json_decode( $response['body'], true );
252 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
253 Meow_MWAI_Logging::error( "OpenRouter: Invalid response for '$feature' models." );
254 return array();
255 }
256
257 $modelIDs = array();
258 foreach ( $body['data'] as $m ) {
259 if ( isset( $m['id'] ) ) {
260 $modelIDs[] = $m['id'];
261 }
262 }
263
264 return $modelIDs;
265 }
266
267 /**
268 * Utility function to truncate a float to a specific precision.
269 */
270 private function truncate_float( $number, $precision = 4 ) {
271 $factor = pow( 10, $precision );
272 return floor( $number * $factor ) / $factor;
273 }
274 }
275