PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.6.0
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.6.0
3.6.0 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 / labs / wpai-gateway-model.php
ai-engine / labs Last commit date
mcp-core.php 10 hours ago mcp-oauth.php 1 month ago mcp-rest.php 3 weeks ago mcp.conf 1 year ago mcp.js 8 months ago mcp.md 8 months ago mcp.php 1 week ago wpai-connectors.php 2 months ago wpai-gateway-availability.php 2 months ago wpai-gateway-directory.php 2 months ago wpai-gateway-image-model.php 2 months ago wpai-gateway-model.php 2 months ago wpai-gateway-providers.php 2 months ago wpai-gateway.php 2 months ago
wpai-gateway-model.php
245 lines
1 <?php
2 /**
3 * TextGenerationModel for the AI Engine gateway.
4 *
5 * Translates AiClient's `array<Message> $prompt` into `Meow_MWAI_Query_Text`,
6 * dispatches it through AI Engine's engine for the matching provider, and
7 * wraps the reply as a `GenerativeAiResult`.
8 */
9
10 use WordPress\AiClient\Common\Exception\RuntimeException;
11 use WordPress\AiClient\Messages\DTO\Message;
12 use WordPress\AiClient\Messages\DTO\MessagePart;
13 use WordPress\AiClient\Messages\DTO\ModelMessage;
14 use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
15 use WordPress\AiClient\Providers\DTO\ProviderMetadata;
16 use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
17 use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
18 use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
19 use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface;
20 use WordPress\AiClient\Results\DTO\Candidate;
21 use WordPress\AiClient\Results\DTO\GenerativeAiResult;
22 use WordPress\AiClient\Results\DTO\TokenUsage;
23 use WordPress\AiClient\Results\Enums\FinishReasonEnum;
24
25 class Meow_MWAI_Labs_WPAI_Gateway_Model implements ModelInterface, TextGenerationModelInterface {
26
27 private ModelMetadata $modelMetadata;
28 private ProviderMetadata $providerMetadata;
29 private ModelConfig $config;
30
31 public function __construct( ModelMetadata $modelMetadata, ProviderMetadata $providerMetadata ) {
32 $this->modelMetadata = $modelMetadata;
33 $this->providerMetadata = $providerMetadata;
34 $this->config = new ModelConfig();
35 }
36
37 public function metadata(): ModelMetadata { return $this->modelMetadata; }
38 public function providerMetadata(): ProviderMetadata { return $this->providerMetadata; }
39 public function setConfig( ModelConfig $config ): void { $this->config = $config; }
40 public function getConfig(): ModelConfig { return $this->config; }
41
42 public function generateTextResult( array $prompt ): GenerativeAiResult {
43 // KNOWN LIMITATIONS (to address as we harden this gateway):
44 //
45 // 1. Phase 4 TODO — validate model-specific constraints before
46 // dispatching. E.g. cap `maxTokens` to the model's real ceiling
47 // (AI Engine stores it on each model), reject `temperature` on
48 // models that forbid it (o1 family), give a clear error up-front
49 // instead of a downstream API rejection.
50 // 2. File parts (image, document, audio) are now round-tripped via
51 // `extract_parts` → `Meow_MWAI_Query_DroppedFile` → `$query->add_file`.
52 // Function-call / function-response parts on inbound messages are
53 // still ignored on purpose: AI Engine handles tools through its own
54 // filter pipeline (`mwai_functions_list`, `mwai_ai_feedback`).
55 // 3. Streaming is not exposed. AiClient has no streaming hook today,
56 // but if it ever does, we need to bridge to `run_query( $q, $cb )`.
57 // 4. Errors from `run_query()` surface as thrown RuntimeException;
58 // partial failures (e.g. a function-call tool erroring mid-loop)
59 // aren't translated into the `GenerativeAiResult` error shape.
60 //
61 $core = Meow_MWAI_Labs_WPAI_Gateway::$core;
62 if ( ! $core ) {
63 throw new RuntimeException( 'AI Engine core unavailable.' );
64 }
65
66 $model_id = $this->modelMetadata->getId();
67 $env = $this->resolve_env_for_model( $core, $model_id );
68 if ( ! $env ) {
69 throw new RuntimeException( sprintf(
70 'No AI Engine environment is configured for model "%s".',
71 $model_id
72 ) );
73 }
74
75 // Translate AiClient messages → AI Engine role/content pairs, and collect
76 // file parts (images, documents, audio) for attachment on the query.
77 $messages = [];
78 $files = [];
79 foreach ( $prompt as $msg ) {
80 if ( ! $msg instanceof Message ) {
81 continue;
82 }
83 $role = $msg->getRole()->isModel() ? 'assistant' : 'user';
84 list( $text, $msg_files ) = $this->extract_parts( $msg );
85 foreach ( $msg_files as $f ) {
86 $files[] = $f;
87 }
88 if ( $text === '' && empty( $msg_files ) ) {
89 continue;
90 }
91 // AI Engine expects a non-empty content per message; if the message
92 // carried only files, leave a placeholder so the message slot exists.
93 if ( $text === '' ) {
94 $text = '[attachment]';
95 }
96 $messages[] = [ 'role' => $role, 'content' => $text ];
97 }
98
99 // Split off the last user turn as the main "message"; everything before
100 // is history.
101 $last_user_idx = null;
102 for ( $i = count( $messages ) - 1; $i >= 0; $i-- ) {
103 if ( $messages[ $i ]['role'] === 'user' ) {
104 $last_user_idx = $i;
105 break;
106 }
107 }
108 $current = $last_user_idx !== null ? $messages[ $last_user_idx ]['content'] : '';
109 $history = $last_user_idx !== null
110 ? array_merge( array_slice( $messages, 0, $last_user_idx ), array_slice( $messages, $last_user_idx + 1 ) )
111 : $messages;
112
113 $query = new Meow_MWAI_Query_Text( $current );
114 // Stamp the call with the framework name so it's identifiable in Insights.
115 $query->set_scope( 'wp-ai-client' );
116 $query->set_env_id( $env['id'] );
117 $query->set_model( $model_id );
118 if ( ! empty( $history ) ) {
119 $query->set_messages( $history );
120 }
121 foreach ( $files as $dropped ) {
122 $query->add_file( $dropped );
123 }
124 $system = $this->config->getSystemInstruction();
125 if ( $system ) {
126 $query->set_instructions( $system );
127 }
128 $maxTokens = $this->config->getMaxTokens();
129 if ( $maxTokens !== null ) {
130 $query->set_max_tokens( (int) $maxTokens );
131 }
132 $temperature = $this->config->getTemperature();
133 if ( $temperature !== null ) {
134 $query->set_temperature( (float) $temperature );
135 }
136
137 $reply = $core->run_query( $query );
138
139 return $this->build_result( $reply );
140 }
141
142 // ───────────────────────────────────────────────────────────────────────
143
144 private function resolve_env_for_model( $core, string $model_id ): ?array {
145 $options = $core->get_all_options();
146 $engines = $options['ai_engines'] ?? [];
147 $engine_type_for_model = null;
148 foreach ( $engines as $engine ) {
149 foreach ( $engine['models'] ?? [] as $m ) {
150 if ( ( $m['model'] ?? '' ) === $model_id ) {
151 $engine_type_for_model = $engine['type'] ?? null;
152 break 2;
153 }
154 }
155 }
156 if ( ! $engine_type_for_model ) {
157 return null;
158 }
159 foreach ( $options['ai_envs'] ?? [] as $env ) {
160 if ( ( $env['type'] ?? '' ) === $engine_type_for_model ) {
161 return $env;
162 }
163 }
164 return null;
165 }
166
167 /**
168 * Pulls text and file attachments out of an AiClient Message.
169 * Function-call / function-response parts are intentionally ignored —
170 * AI Engine handles tool-calling through its own filter pipeline
171 * (`mwai_functions_list`, `mwai_ai_feedback`), not via inbound messages.
172 *
173 * @return array{0: string, 1: array<int, Meow_MWAI_Query_DroppedFile>}
174 */
175 private function extract_parts( Message $msg ): array {
176 $text = '';
177 $files = [];
178 foreach ( $msg->getParts() as $part ) {
179 if ( ! $part instanceof MessagePart ) {
180 continue;
181 }
182 $type = $part->getType();
183 if ( $type->isText() ) {
184 $text .= (string) $part->getText();
185 }
186 elseif ( $type->isFile() ) {
187 $file = $part->getFile();
188 if ( $file === null ) {
189 continue;
190 }
191 $dropped = $this->file_to_dropped( $file );
192 if ( $dropped !== null ) {
193 $files[] = $dropped;
194 }
195 }
196 }
197 return [ $text, $files ];
198 }
199
200 /**
201 * Converts an AiClient `File` (URL, base64, or data URI) into AI Engine's
202 * `Meow_MWAI_Query_DroppedFile` so the underlying engine can attach it to
203 * the request (vision payload, document upload, etc.).
204 */
205 private function file_to_dropped( $file ): ?Meow_MWAI_Query_DroppedFile {
206 try {
207 $mime = $file->getMimeType();
208 $url = $file->getUrl();
209 if ( $url ) {
210 return Meow_MWAI_Query_DroppedFile::from_url( $url, 'analysis', $mime );
211 }
212 $b64 = $file->getBase64Data();
213 if ( $b64 ) {
214 return Meow_MWAI_Query_DroppedFile::from_data( base64_decode( $b64 ), 'analysis', $mime );
215 }
216 }
217 catch ( \Throwable $e ) {
218 // Skip the file rather than failing the whole prompt.
219 }
220 return null;
221 }
222
223 private function build_result( $reply ): GenerativeAiResult {
224 $text = (string) ( $reply->result ?? '' );
225 $part = new MessagePart( $text );
226 $message = new ModelMessage( [ $part ] );
227 $candidate = new Candidate( $message, FinishReasonEnum::stop() );
228
229 $usage = $reply->usage ?? [];
230 $prompt = (int) ( $usage['prompt_tokens'] ?? 0 );
231 $completion = (int) ( $usage['completion_tokens'] ?? 0 );
232 $total = (int) ( $usage['total_tokens'] ?? ( $prompt + $completion ) );
233 $tokenUsage = new TokenUsage( $prompt, $completion, $total );
234
235 $id = (string) ( $reply->id ?? uniqid( 'mwai-', true ) );
236 return new GenerativeAiResult(
237 $id,
238 [ $candidate ],
239 $tokenUsage,
240 $this->providerMetadata,
241 $this->modelMetadata
242 );
243 }
244 }
245