PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.1
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.1
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
244 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 if ( $query->apiKey ) {
39 $this->apiKey = $query->apiKey;
40 }
41 if ( empty( $this->apiKey ) ) {
42 throw new Exception( 'No API Key provided. Please visit the Settings.' );
43 }
44 return array(
45 'Content-Type' => 'application/json',
46 'Authorization' => 'Bearer ' . $this->apiKey,
47 'HTTP-Referer' => $site_url,
48 'X-Title' => $site_name,
49 'User-Agent' => 'AI Engine',
50 );
51 }
52
53 protected function build_body( $query, $streamCallback = null, $extra = null ) {
54 $body = parent::build_body( $query, $streamCallback, $extra );
55 // Use transforms from OpenRouter docs
56 $body['transforms'] = ['middle-out'];
57 $body['usage'] = [ 'include' => true ];
58 return $body;
59 }
60
61 protected function get_service_name() {
62 return "OpenRouter";
63 }
64
65 public function get_models() {
66 return $this->core->get_engine_models( 'openrouter' );
67 }
68
69 /**
70 * Requests usage data if streaming was used and the usage is incomplete.
71 */
72 public function handle_tokens_usage(
73 $reply,
74 $query,
75 $returned_model,
76 $returned_in_tokens,
77 $returned_out_tokens,
78 $returned_price = null
79 ) {
80 // If streaming is not enabled, we might already have all usage data
81 $everything_is_set = !is_null( $returned_model )
82 && !is_null( $returned_in_tokens )
83 && !is_null( $returned_out_tokens );
84
85 // Clean up the data
86 $returned_in_tokens = $returned_in_tokens ?? $reply->get_in_tokens( $query );
87 $returned_out_tokens = $returned_out_tokens ?? $reply->get_out_tokens();
88 $returned_price = $returned_price ?? $reply->get_price();
89
90 // Record the usage in the database
91 $usage = $this->core->record_tokens_usage(
92 $returned_model,
93 $returned_in_tokens,
94 $returned_out_tokens,
95 $returned_price
96 );
97
98 // Set the usage back on the reply
99 $reply->set_usage( $usage );
100 }
101
102 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
103 $price = $reply->get_price();
104 return is_null( $price ) ? parent::get_price( $query, $reply ) : $price;
105 }
106
107 /**
108 * Retrieve the models from OpenRouter, adding tags/features accordingly.
109 */
110 public function retrieve_models() {
111
112 // 1. Get the list of models supporting "tools"
113 $toolsModels = $this->get_supported_models( 'tools' );
114
115 // 2. Retrieve the full list of models
116 $url = 'https://openrouter.ai/api/v1/models';
117 $response = wp_remote_get( $url );
118 if ( is_wp_error( $response ) ) {
119 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
120 }
121 $body = json_decode( $response['body'], true );
122 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
123 throw new Exception( 'AI Engine: Invalid response for the list of models.' );
124 }
125
126 $models = array();
127 foreach ( $body['data'] as $model ) {
128
129 // Basic defaults
130 $family = 'n/a';
131 $maxCompletionTokens = 4096;
132 $maxContextualTokens = 8096;
133 $priceIn = 0;
134 $priceOut = 0;
135
136 // Family from model ID (e.g. "openai/gpt-4/32k" -> "openai")
137 if ( isset( $model['id'] ) ) {
138 $parts = explode( '/', $model['id'] );
139 $family = $parts[0] ?? 'n/a';
140 }
141
142 // maxCompletionTokens
143 if ( isset( $model['top_provider']['max_completion_tokens'] ) ) {
144 $maxCompletionTokens = (int) $model['top_provider']['max_completion_tokens'];
145 }
146
147 // maxContextualTokens
148 if ( isset( $model['context_length'] ) ) {
149 $maxContextualTokens = (int) $model['context_length'];
150 }
151
152 // Pricing
153 if ( isset( $model['pricing']['prompt'] ) && $model['pricing']['prompt'] > 0 ) {
154 $priceIn = $this->truncate_float( floatval( $model['pricing']['prompt'] ) * 1000 );
155 }
156 if ( isset( $model['pricing']['completion'] ) && $model['pricing']['completion'] > 0 ) {
157 $priceOut = $this->truncate_float( floatval( $model['pricing']['completion'] ) * 1000 );
158 }
159
160 // Basic features and tags
161 $features = [ 'completion' ];
162 $tags = [ 'core', 'chat' ];
163
164 // If the name contains (beta), (alpha) or (preview), add 'preview' tag and remove from name
165 if ( preg_match( '/\((beta|alpha|preview)\)/i', $model['name'] ) ) {
166 $tags[] = 'preview';
167 $model['name'] = preg_replace( '/\((beta|alpha|preview)\)/i', '', $model['name'] );
168 }
169
170 // If model supports tools
171 if ( in_array( $model['id'], $toolsModels, true ) ) {
172 $tags[] = 'functions';
173 $features[] = 'functions';
174 }
175
176 // Check if the model supports "vision" (if "image" is in the left side of the arrow)
177 // e.g. "text+image->text" or "image->text"
178 $modality = $model['architecture']['modality'] ?? '';
179 $modality_lc = strtolower( $modality );
180 if (
181 strpos( $modality_lc, 'image->' ) !== false ||
182 strpos( $modality_lc, 'image+' ) !== false ||
183 strpos( $modality_lc, '+image->' ) !== false
184 ) {
185 // Means it can handle images as input, so we consider that "vision"
186 $tags[] = 'vision';
187 }
188
189 $models[] = array(
190 'model' => $model['id'] ?? '',
191 'name' => trim( $model['name'] ?? '' ),
192 'family' => $family,
193 'features' => $features,
194 'price' => array(
195 'in' => $priceIn,
196 'out' => $priceOut,
197 ),
198 'type' => 'token',
199 'unit' => 1 / 1000,
200 'maxCompletionTokens' => $maxCompletionTokens,
201 'maxContextualTokens' => $maxContextualTokens,
202 'tags' => $tags,
203 );
204 }
205
206 return $models;
207 }
208
209 /**
210 * Return an array of model IDs that support a certain feature (e.g. "tools").
211 */
212 private function get_supported_models( $feature ) {
213 // Make a request to get models supporting that feature
214 $url = 'https://openrouter.ai/api/v1/models?supported_parameters=' . urlencode( $feature );
215 $response = wp_remote_get( $url );
216 if ( is_wp_error( $response ) ) {
217 Meow_MWAI_Logging::error( "OpenRouter: Failed to retrieve models for '$feature': " . $response->get_error_message() );
218 return array();
219 }
220 $body = json_decode( $response['body'], true );
221 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
222 Meow_MWAI_Logging::error( "OpenRouter: Invalid response for '$feature' models." );
223 return array();
224 }
225
226 $modelIDs = array();
227 foreach ( $body['data'] as $m ) {
228 if ( isset( $m['id'] ) ) {
229 $modelIDs[] = $m['id'];
230 }
231 }
232
233 return $modelIDs;
234 }
235
236 /**
237 * Utility function to truncate a float to a specific precision.
238 */
239 private function truncate_float( $number, $precision = 4 ) {
240 $factor = pow( 10, $precision );
241 return floor( $number * $factor ) / $factor;
242 }
243 }
244