PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / trunk
AI Engine – The Chatbot, AI Framework & MCP for WordPress vtrunk
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 / ovh.php
ai-engine / classes / engines Last commit date
anthropic.php 20 hours ago chatml.php 1 week ago core.php 2 days ago custom.php 1 month ago factory.php 1 week ago google-interactions.php 2 days ago google.php 1 week ago mistral.php 1 week ago open-router.php 3 weeks ago openai.php 3 weeks ago ovh.php 1 week ago perplexity.php 6 months ago replicate.php 5 months ago xai.php 1 month ago
ovh.php
326 lines
1 <?php
2
3 class Meow_MWAI_Engines_OVH extends Meow_MWAI_Engines_ChatML {
4 private $endpoint = 'https://oai.endpoints.kepler.ai.cloud.ovh.net';
5 public $envType = 'ovh';
6
7 public function __construct( $core, $env ) {
8 parent::__construct( $core, $env );
9 }
10
11 /**
12 * Sets up the environment for our custom engine.
13 */
14 protected function set_environment() {
15 $env = $this->env;
16
17 if ( !isset( $env['apikey'] ) || empty( $env['apikey'] ) ) {
18 throw new Exception( 'AI Engine: OVH API key is not set. Please configure the OVH AI Endpoints access token in your settings.' );
19 }
20
21 $this->apiKey = $env['apikey'];
22
23 if ( isset( $env['endpoint'] ) && !empty( $env['endpoint'] ) ) {
24 $this->endpoint = $env['endpoint'];
25 }
26 }
27
28 protected function build_url( $query, $endpoint = null ) {
29 $endpoint = apply_filters( 'mwai_ovh_endpoint', trailingslashit( $this->endpoint ) . 'v1', $this->env );
30 return $endpoint . '/chat/completions';
31 }
32
33 protected function get_service_name() {
34 return 'OVH';
35 }
36
37 protected function build_messages( $query ) {
38 // Handle vision models like llava-next-mistral-7b
39 if ( strpos( $query->model, 'llava' ) !== false ) {
40 $query->image_remote_upload = 'data';
41 }
42
43 $messages = parent::build_messages( $query );
44 return $messages;
45 }
46
47 protected function stream_data_handler( $json ) {
48 // Handle usage-only chunks (final chunk with empty choices array).
49 // OVH sends usage data in a final chunk with choices: []
50 if ( isset( $json['usage'] ) && empty( $json['choices'] ) ) {
51 $usage = $json['usage'];
52 if ( isset( $usage['prompt_tokens'], $usage['completion_tokens'] ) ) {
53 $this->streamInTokens = (int) $usage['prompt_tokens'];
54 $this->streamOutTokens = (int) $usage['completion_tokens'];
55 }
56 return null; // No content in this chunk
57 }
58
59 // Let parent handle all other chunks
60 return parent::stream_data_handler( $json );
61 }
62
63 public function get_models() {
64 return $this->retrieve_models();
65 }
66
67 protected function build_headers( $query ) {
68 $headers = [
69 'Content-Type' => 'application/json',
70 'Authorization' => 'Bearer ' . $this->apiKey,
71 ];
72 return $headers;
73 }
74
75 protected function build_body( $query, $streamCallback = null, $extra = null ) {
76 $body = parent::build_body( $query, $streamCallback, $extra );
77
78 // Handle max_tokens parameter (OVH uses max_tokens, not max_completion_tokens)
79 if ( isset( $body['max_completion_tokens'] ) ) {
80 $body['max_tokens'] = $body['max_completion_tokens'];
81 unset( $body['max_completion_tokens'] );
82 }
83
84 // OVH supports stream_options for accurate token usage in streaming
85 if ( !empty( $streamCallback ) && !isset( $body['stream_options'] ) ) {
86 $body['stream_options'] = [
87 'include_usage' => true,
88 ];
89 }
90
91 // Remove parallel_tool_calls - OVH might not support it
92 if ( isset( $body['parallel_tool_calls'] ) ) {
93 unset( $body['parallel_tool_calls'] );
94 }
95
96 // Remove other potentially unsupported parameters
97 $unsupported_params = [ 'response_format', 'seed', 'logit_bias', 'logprobs', 'top_logprobs' ];
98 foreach ( $unsupported_params as $param ) {
99 if ( isset( $body[$param] ) ) {
100 unset( $body[$param] );
101 }
102 }
103
104 return $body;
105 }
106
107 public function handle_tokens_usage(
108 $reply,
109 $query,
110 $returned_model,
111 $returned_in_tokens,
112 $returned_out_tokens,
113 $returned_price = null
114 ) {
115
116 // Clean up the data
117 $returned_in_tokens = !is_null( $returned_in_tokens ) ?
118 $returned_in_tokens : $reply->get_in_tokens( $query );
119 $returned_out_tokens = !is_null( $returned_out_tokens ) ?
120 $returned_out_tokens : $reply->get_out_tokens();
121
122 // Calculate price based on our model definitions
123 $models = $this->get_ovh_models();
124 $model_price = null;
125
126 foreach ( $models as $model ) {
127 if ( $model['model'] === $returned_model ) {
128 $model_price = $model['price'];
129 break;
130 }
131 }
132
133 if ( $model_price ) {
134 $returned_price = ( $returned_in_tokens * $model_price['in'] +
135 $returned_out_tokens * $model_price['out'] ) / 1000000;
136 }
137 else {
138 $returned_price = 0;
139 }
140
141 // Record the usage
142 $usage = $this->core->record_tokens_usage(
143 $returned_model,
144 $returned_in_tokens,
145 $returned_out_tokens,
146 $returned_price
147 );
148
149 // Set the usage in the reply
150 $reply->set_usage( $usage );
151
152 // Set accuracy based on data availability
153 if ( !is_null( $returned_in_tokens ) && !is_null( $returned_out_tokens ) ) {
154 // Tokens from API (via stream_options), price calculated locally
155 $reply->set_usage_accuracy( 'tokens' );
156 }
157 else {
158 // Everything estimated
159 $reply->set_usage_accuracy( 'estimated' );
160 }
161 }
162
163 public function get_price( Meow_MWAI_Query_Base $query, Meow_MWAI_Reply $reply ) {
164 $models = $this->get_ovh_models();
165
166 foreach ( $models as $model ) {
167 if ( $model['model'] === $query->model ) {
168 $in_tokens = $reply->get_in_tokens( $query );
169 $out_tokens = $reply->get_out_tokens();
170 return ( $in_tokens * $model['price']['in'] +
171 $out_tokens * $model['price']['out'] ) / 1000000;
172 }
173 }
174
175 return 0;
176 }
177
178 /**
179 * Retrieve the models from the OVH OpenRouter-compatible catalog.
180 */
181 public function retrieve_models() {
182 $url = 'https://catalog.endpoints.ai.ovh.net/rest/v2/openrouter';
183 $response = wp_remote_get( $url, [ 'timeout' => 10 ] );
184
185 if ( is_wp_error( $response ) ) {
186 return $this->get_fallback_models();
187 }
188
189 $body = wp_remote_retrieve_body( $response );
190 $data = json_decode( $body, true );
191
192 if ( empty( $data ) || !is_array( $data ) ) {
193 return $this->get_fallback_models();
194 }
195
196 // Extract models from 'data' key
197 $models_data = isset( $data['data'] ) ? $data['data'] : $data;
198
199 if ( empty( $models_data ) || !is_array( $models_data ) ) {
200 return $this->get_fallback_models();
201 }
202
203 $models = [];
204 foreach ( $models_data as $model_data ) {
205 $model = $this->map_openrouter_model( $model_data );
206 if ( $model ) {
207 $models[] = $model;
208 }
209 }
210
211 return !empty( $models ) ? $models : $this->get_fallback_models();
212 }
213
214 /**
215 * Map OpenRouter model data to AI Engine format.
216 */
217 private function map_openrouter_model( $model_data ) {
218 if ( empty( $model_data['id'] ) ) {
219 return null;
220 }
221
222 $model_id = $model_data['id'];
223 $name = !empty( $model_data['name'] ) ? $model_data['name'] : $model_id;
224
225 // Extract family from model ID
226 $family_parts = explode( '/', $model_id );
227 $family = $family_parts[0];
228
229 // Base features and tags
230 $features = [ 'completion', 'chat' ];
231 $tags = [ 'core', 'chat' ];
232
233 // Check for vision support (input modality includes images)
234 if ( !empty( $model_data['input_modalities'] ) && is_array( $model_data['input_modalities'] ) ) {
235 if ( in_array( 'image', $model_data['input_modalities'] ) ) {
236 $features[] = 'vision';
237 $tags[] = 'vision';
238 }
239 }
240
241 // Map features from OpenRouter to AI Engine format
242 $openrouter_features = !empty( $model_data['supported_features'] ) ? $model_data['supported_features'] : [];
243 foreach ( $openrouter_features as $feature ) {
244 switch ( $feature ) {
245 case 'json_mode':
246 case 'structured_outputs':
247 if ( !in_array( 'json', $tags ) ) {
248 $tags[] = 'json';
249 }
250 break;
251 case 'reasoning':
252 if ( !in_array( 'reasoning', $tags ) ) {
253 $tags[] = 'reasoning';
254 }
255 break;
256 case 'tools':
257 // OVH now supports function calling via tools format
258 if ( !in_array( 'functions', $tags ) ) {
259 $tags[] = 'functions';
260 }
261 break;
262 }
263 }
264
265 // Convert pricing from per-token to per-million tokens
266 $price_in = 0;
267 $price_out = 0;
268 if ( !empty( $model_data['pricing']['prompt'] ) ) {
269 $price_in = floatval( $model_data['pricing']['prompt'] ) * 1000000;
270 }
271 if ( !empty( $model_data['pricing']['completion'] ) ) {
272 $price_out = floatval( $model_data['pricing']['completion'] ) * 1000000;
273 }
274
275 // Get context and max tokens
276 $max_context = !empty( $model_data['context_length'] ) ? intval( $model_data['context_length'] ) : 128000;
277 $max_completion = !empty( $model_data['max_output_length'] ) ? intval( $model_data['max_output_length'] ) : 4096;
278
279 return [
280 'model' => $model_id,
281 'name' => $name,
282 'family' => $family,
283 'features' => $features,
284 'price' => [
285 'in' => $price_in,
286 'out' => $price_out,
287 ],
288 'type' => 'token',
289 'unit' => 1 / 1000000,
290 'maxCompletionTokens' => $max_completion,
291 'maxContextualTokens' => $max_context,
292 'tags' => $tags,
293 ];
294 }
295
296 /**
297 * Get fallback models in case the catalog API is unavailable.
298 */
299 private function get_fallback_models() {
300 return [
301 [
302 'model' => 'meta-llama/Llama-3.3-70B-Instruct',
303 'name' => 'Llama 3.3 70B Instruct',
304 'family' => 'meta-llama',
305 'features' => [ 'completion', 'chat' ],
306 'price' => [
307 'in' => 0.06,
308 'out' => 0.09,
309 ],
310 'type' => 'token',
311 'unit' => 1 / 1000000,
312 'maxCompletionTokens' => 8192,
313 'maxContextualTokens' => 131072,
314 'tags' => [ 'core', 'chat', 'json', 'reasoning' ],
315 ],
316 ];
317 }
318
319 /**
320 * Get model info by model ID (for backward compatibility).
321 */
322 private function get_ovh_models() {
323 return $this->retrieve_models();
324 }
325 }
326