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.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 / open-router.php
ai-engine / classes / engines Last commit date
traits 1 year ago anthropic.php 1 year ago chatml.php 1 year ago core.php 1 year ago factory.php 1 year ago google.php 1 year ago hugging-face.php 1 year ago open-router.php 1 year ago openai.php 1 year ago perplexity.php 1 year ago replicate.php 1 year ago
open-router.php
290 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_ChatML {
12 /**
13 * Keep a static dictionary (query -> price) so that if we see the same query
14 * again in another instance, we can immediately return the stored price
15 * instead of recomputing.
16 * @var array
17 */
18 private static $accuratePrices = [];
19
20 public function __construct( $core, $env ) {
21 parent::__construct( $core, $env );
22 }
23
24 protected function set_environment() {
25 $env = $this->env;
26 $this->apiKey = $env['apikey'];
27 }
28
29 protected function build_url( $query, $endpoint = null ) {
30 $endpoint = apply_filters( 'mwai_openrouter_endpoint', 'https://openrouter.ai/api/v1', $this->env );
31 return parent::build_url( $query, $endpoint );
32 }
33
34 protected function build_headers( $query ) {
35 $site_url = apply_filters( 'mwai_openrouter_site_url', get_site_url(), $query );
36 $site_name = apply_filters( 'mwai_openrouter_site_name', get_bloginfo( 'name' ), $query );
37 if ( $query->apiKey ) {
38 $this->apiKey = $query->apiKey;
39 }
40 if ( empty( $this->apiKey ) ) {
41 throw new Exception( 'No API Key provided. Please visit the Settings.' );
42 }
43 return [
44 'Content-Type' => 'application/json',
45 'Authorization' => 'Bearer ' . $this->apiKey,
46 'HTTP-Referer' => $site_url,
47 'X-Title' => $site_name,
48 'User-Agent' => 'AI Engine',
49 ];
50 }
51
52 protected function build_body( $query, $streamCallback = null, $extra = null ) {
53 $body = parent::build_body( $query, $streamCallback, $extra );
54 // Use transforms from OpenRouter docs
55 $body['transforms'] = ['middle-out'];
56 $body['usage'] = [ 'include' => true ];
57 return $body;
58 }
59
60 protected function get_service_name() {
61 return 'OpenRouter';
62 }
63
64 public function get_models() {
65 return $this->core->get_engine_models( 'openrouter' );
66 }
67
68 /**
69 * Requests usage data if streaming was used and the usage is incomplete.
70 */
71 public function handle_tokens_usage(
72 $reply,
73 $query,
74 $returned_model,
75 $returned_in_tokens,
76 $returned_out_tokens,
77 $returned_price = null
78 ) {
79 // If streaming is not enabled, we might already have all usage data
80 $everything_is_set = !is_null( $returned_model )
81 && !is_null( $returned_in_tokens )
82 && !is_null( $returned_out_tokens );
83
84 // Clean up the data
85 $returned_in_tokens = $returned_in_tokens ?? $reply->get_in_tokens( $query );
86 $returned_out_tokens = $returned_out_tokens ?? $reply->get_out_tokens();
87 $returned_price = $returned_price ?? $reply->get_price();
88
89 // Record the usage in the database
90 $usage = $this->core->record_tokens_usage(
91 $returned_model,
92 $returned_in_tokens,
93 $returned_out_tokens,
94 $returned_price
95 );
96
97 // Set the usage back on the reply
98 $reply->set_usage( $usage );
99 }
100
101 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
102 $price = $reply->get_price();
103 return is_null( $price ) ? parent::get_price( $query, $reply ) : $price;
104 }
105
106 /**
107 * Retrieve the models from OpenRouter, adding tags/features accordingly.
108 */
109 public function retrieve_models() {
110
111 // 1. Get the list of models supporting "tools"
112 $toolsModels = $this->get_supported_models( 'tools' );
113
114 // 2. Retrieve the full list of models
115 $url = 'https://openrouter.ai/api/v1/models';
116 $response = wp_remote_get( $url );
117 if ( is_wp_error( $response ) ) {
118 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
119 }
120 $body = json_decode( $response['body'], true );
121 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
122 throw new Exception( 'AI Engine: Invalid response for the list of models.' );
123 }
124
125 $models = [];
126 foreach ( $body['data'] as $model ) {
127
128 // Basic defaults
129 $family = 'n/a';
130 $maxCompletionTokens = 4096;
131 $maxContextualTokens = 8096;
132 $priceIn = 0;
133 $priceOut = 0;
134
135 // Family from model ID (e.g. "openai/gpt-4/32k" -> "openai")
136 if ( isset( $model['id'] ) ) {
137 $parts = explode( '/', $model['id'] );
138 $family = $parts[0] ?? 'n/a';
139 }
140
141 // maxCompletionTokens
142 if ( isset( $model['top_provider']['max_completion_tokens'] ) ) {
143 $maxCompletionTokens = (int) $model['top_provider']['max_completion_tokens'];
144 }
145
146 // maxContextualTokens
147 if ( isset( $model['context_length'] ) ) {
148 $maxContextualTokens = (int) $model['context_length'];
149 }
150
151 // Pricing
152 if ( isset( $model['pricing']['prompt'] ) && $model['pricing']['prompt'] > 0 ) {
153 $priceIn = $this->truncate_float( floatval( $model['pricing']['prompt'] ) * 1000 );
154 }
155 if ( isset( $model['pricing']['completion'] ) && $model['pricing']['completion'] > 0 ) {
156 $priceOut = $this->truncate_float( floatval( $model['pricing']['completion'] ) * 1000 );
157 }
158
159 // Basic features and tags
160 $features = [ 'completion' ];
161 $tags = [ 'core', 'chat' ];
162
163 // If the name contains (beta), (alpha) or (preview), add 'preview' tag and remove from name
164 if ( preg_match( '/\((beta|alpha|preview)\)/i', $model['name'] ) ) {
165 $tags[] = 'preview';
166 $model['name'] = preg_replace( '/\((beta|alpha|preview)\)/i', '', $model['name'] );
167 }
168
169 // If model supports tools
170 if ( in_array( $model['id'], $toolsModels, true ) ) {
171 $tags[] = 'functions';
172 $features[] = 'functions';
173 }
174
175 // Check if the model supports "vision" (if "image" is in the left side of the arrow)
176 // e.g. "text+image->text" or "image->text"
177 $modality = $model['architecture']['modality'] ?? '';
178 $modality_lc = strtolower( $modality );
179 if (
180 strpos( $modality_lc, 'image->' ) !== false ||
181 strpos( $modality_lc, 'image+' ) !== false ||
182 strpos( $modality_lc, '+image->' ) !== false
183 ) {
184 // Means it can handle images as input, so we consider that "vision"
185 $tags[] = 'vision';
186 }
187
188 $models[] = [
189 'model' => $model['id'] ?? '',
190 'name' => trim( $model['name'] ?? '' ),
191 'family' => $family,
192 'features' => $features,
193 'price' => [
194 'in' => $priceIn,
195 'out' => $priceOut,
196 ],
197 'type' => 'token',
198 'unit' => 1 / 1000,
199 'maxCompletionTokens' => $maxCompletionTokens,
200 'maxContextualTokens' => $maxContextualTokens,
201 'tags' => $tags,
202 ];
203 }
204
205 return $models;
206 }
207
208 /**
209 * Return an array of model IDs that support a certain feature (e.g. "tools").
210 */
211 private function get_supported_models( $feature ) {
212 // Make a request to get models supporting that feature
213 $url = 'https://openrouter.ai/api/v1/models?supported_parameters=' . urlencode( $feature );
214 $response = wp_remote_get( $url );
215 if ( is_wp_error( $response ) ) {
216 Meow_MWAI_Logging::error( "OpenRouter: Failed to retrieve models for '$feature': " . $response->get_error_message() );
217 return [];
218 }
219 $body = json_decode( $response['body'], true );
220 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
221 Meow_MWAI_Logging::error( "OpenRouter: Invalid response for '$feature' models." );
222 return [];
223 }
224
225 $modelIDs = [];
226 foreach ( $body['data'] as $m ) {
227 if ( isset( $m['id'] ) ) {
228 $modelIDs[] = $m['id'];
229 }
230 }
231
232 return $modelIDs;
233 }
234
235 /**
236 * Utility function to truncate a float to a specific precision.
237 */
238 private function truncate_float( $number, $precision = 4 ) {
239 $factor = pow( 10, $precision );
240 return floor( $number * $factor ) / $factor;
241 }
242
243 /**
244 * Check the connection to OpenRouter by listing models.
245 * Uses the existing retrieve_models method for consistency.
246 */
247 public function connection_check() {
248 try {
249 // Use the existing retrieve_models method
250 $models = $this->retrieve_models();
251
252 if ( !is_array( $models ) ) {
253 throw new Exception( 'Invalid response format from OpenRouter' );
254 }
255
256 $modelCount = count( $models );
257 $availableModels = [];
258
259 // Get first 5 models for display
260 $displayModels = array_slice( $models, 0, 5 );
261 foreach ( $displayModels as $model ) {
262 if ( isset( $model['model'] ) ) {
263 $availableModels[] = $model['model'];
264 }
265 }
266
267 return [
268 'success' => true,
269 'service' => 'OpenRouter',
270 'message' => "Connection successful. Found {$modelCount} models.",
271 'details' => [
272 'endpoint' => 'https://openrouter.ai/api/v1/models',
273 'model_count' => $modelCount,
274 'sample_models' => $availableModels
275 ]
276 ];
277 }
278 catch ( Exception $e ) {
279 return [
280 'success' => false,
281 'service' => 'OpenRouter',
282 'error' => $e->getMessage(),
283 'details' => [
284 'endpoint' => 'https://openrouter.ai/api/v1/models'
285 ]
286 ];
287 }
288 }
289 }
290