PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.7.4
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.7.4
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
239 lines
1 <?php
2
3 class Meow_MWAI_Engines_OpenRouter extends Meow_MWAI_Engines_OpenAI
4 {
5 public function __construct( $core, $env ) {
6 parent::__construct( $core, $env );
7 }
8
9 protected function set_environment() {
10 $env = $this->env;
11 $this->apiKey = $env['apikey'];
12 }
13
14 protected function build_url( $query, $endpoint = null ) {
15 $endpoint = apply_filters( 'mwai_openrouter_endpoint', 'https://openrouter.ai/api/v1', $this->env );
16 return parent::build_url( $query, $endpoint );
17 }
18
19 protected function build_headers( $query ) {
20 $site_url = apply_filters( 'mwai_openrouter_site_url', get_site_url(), $query );
21 $site_name = apply_filters( 'mwai_openrouter_site_name', get_bloginfo( 'name' ), $query );
22 return array(
23 'Content-Type' => 'application/json',
24 'Authorization' => 'Bearer ' . $this->apiKey,
25 'HTTP-Referer' => $site_url,
26 'X-Title' => $site_name,
27 'User-Agent' => 'AI Engine',
28 );
29 }
30
31 protected function build_body( $query, $streamCallback = null, $extra = null ) {
32 $body = parent::build_body( $query, $streamCallback, $extra );
33 // Use transforms from OpenRouter docs
34 $body['transforms'] = ['middle-out'];
35 return $body;
36 }
37
38 protected function get_service_name() {
39 return "OpenRouter";
40 }
41
42 public function get_models() {
43 return $this->core->get_engine_models( 'openrouter' );
44 }
45
46 /**
47 * Requests usage data if streaming was used and the usage is incomplete.
48 */
49 public function handle_tokens_usage( $reply, $query, $returned_model,
50 $returned_in_tokens, $returned_out_tokens, $returned_price = null ) {
51
52 // If streaming is not enabled, we might already have all usage data
53 $everything_is_set = !is_null( $returned_model ) && !is_null( $returned_in_tokens ) && !is_null( $returned_out_tokens );
54
55 // Clean up the data
56 $returned_in_tokens = $returned_in_tokens ?? $reply->get_in_tokens( $query );
57 $returned_out_tokens = $returned_out_tokens ?? $reply->get_out_tokens();
58 $returned_price = $returned_price ?? $reply->get_price();
59
60 // Fetch usage data from OpenRouter if needed
61 if ( !empty( $reply->id ) && !$everything_is_set ) {
62 $url = 'https://openrouter.ai/api/v1/generation?id=' . $reply->id;
63 try {
64 $ch = curl_init();
65 curl_setopt( $ch, CURLOPT_URL, $url );
66 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
67 curl_setopt( $ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $this->apiKey ] );
68 curl_setopt( $ch, CURLOPT_USERAGENT, 'AI Engine' );
69 $res = curl_exec( $ch );
70 curl_close( $ch );
71 $res = json_decode( $res, true );
72 if ( isset( $res['data'] ) ) {
73 $data = $res['data'];
74 $returned_model = $data['model'] ?? $returned_model;
75 $returned_in_tokens = $data['tokens_prompt'] ?? $returned_in_tokens;
76 $returned_out_tokens= $data['tokens_completion'] ?? $returned_out_tokens;
77 $returned_price = $data['total_cost'] ?? $returned_price;
78 }
79 }
80 catch ( Exception $e ) {
81 Meow_MWAI_Logging::error( 'OpenRouter: ' . $e->getMessage() );
82 }
83 }
84
85 // Record the usage in the database
86 $usage = $this->core->record_tokens_usage(
87 $returned_model,
88 $returned_in_tokens,
89 $returned_out_tokens,
90 $returned_price
91 );
92
93 // Set the usage back on the reply
94 $reply->set_usage( $usage );
95 }
96
97 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
98 $price = $reply->get_price();
99 return is_null( $price ) ? parent::get_price( $query, $reply ) : $price;
100 }
101
102 /**
103 * Retrieve the models from OpenRouter, adding tags/features accordingly.
104 */
105 public function retrieve_models() {
106
107 // 1. Get the list of models supporting "tools"
108 $toolsModels = $this->get_supported_models( 'tools' );
109
110 // 2. Retrieve the full list of models
111 $url = 'https://openrouter.ai/api/v1/models';
112 $response = wp_remote_get( $url );
113 if ( is_wp_error( $response ) ) {
114 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
115 }
116 $body = json_decode( $response['body'], true );
117 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
118 throw new Exception( 'AI Engine: Invalid response for the list of models.' );
119 }
120
121 $models = array();
122 foreach ( $body['data'] as $model ) {
123
124 // Basic defaults
125 $family = 'n/a';
126 $maxCompletionTokens = 4096;
127 $maxContextualTokens = 8096;
128 $priceIn = 0;
129 $priceOut = 0;
130
131 // Family from model ID (e.g. "openai/gpt-4/32k" -> "openai")
132 if ( isset( $model['id'] ) ) {
133 $parts = explode( '/', $model['id'] );
134 $family = $parts[0] ?? 'n/a';
135 }
136
137 // maxCompletionTokens
138 if ( isset( $model['top_provider']['max_completion_tokens'] ) ) {
139 $maxCompletionTokens = (int) $model['top_provider']['max_completion_tokens'];
140 }
141
142 // maxContextualTokens
143 if ( isset( $model['context_length'] ) ) {
144 $maxContextualTokens = (int) $model['context_length'];
145 }
146
147 // Pricing
148 if ( isset( $model['pricing']['prompt'] ) && $model['pricing']['prompt'] > 0 ) {
149 $priceIn = $this->truncate_float( floatval( $model['pricing']['prompt'] ) * 1000 );
150 }
151 if ( isset( $model['pricing']['completion'] ) && $model['pricing']['completion'] > 0 ) {
152 $priceOut = $this->truncate_float( floatval( $model['pricing']['completion'] ) * 1000 );
153 }
154
155 // Basic features and tags
156 $features = [ 'completion' ];
157 $tags = [ 'core', 'chat' ];
158
159 // If the name contains (beta), (alpha) or (preview), add 'preview' tag and remove from name
160 if ( preg_match( '/\((beta|alpha|preview)\)/i', $model['name'] ) ) {
161 $tags[] = 'preview';
162 $model['name'] = preg_replace( '/\((beta|alpha|preview)\)/i', '', $model['name'] );
163 }
164
165 // If model supports tools
166 if ( in_array( $model['id'], $toolsModels, true ) ) {
167 $tags[] = 'functions';
168 $features[] = 'functions';
169 }
170
171 // Check if the model supports "vision" (if "image" is in the left side of the arrow)
172 // e.g. "text+image->text" or "image->text"
173 $modality = $model['architecture']['modality'] ?? '';
174 // Lowercase for easier detection
175 $modality_lc = strtolower( $modality );
176 if ( strpos( $modality_lc, 'image->' ) !== false ||
177 strpos( $modality_lc, 'image+' ) !== false ||
178 strpos( $modality_lc, '+image->' ) !== false ) {
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
239