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