| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 3 declare (strict_types=1); 4 namespace WordPress\AiClient\Providers\Models\DTO; 5 6 use WordPress\AiClient\Common\AbstractDataTransferObject; 7 use WordPress\AiClient\Common\Exception\InvalidArgumentException; 8 use WordPress\AiClient\Messages\DTO\Message; 9 use WordPress\AiClient\Messages\DTO\MessagePart; 10 use WordPress\AiClient\Messages\Enums\ModalityEnum; 11 use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; 12 use WordPress\AiClient\Providers\Models\Enums\OptionEnum; 13 /** 14 * Represents requirements that implementing code has for AI model selection. 15 * 16 * This class defines the capabilities and options that a model must support 17 * in order to be considered suitable for the implementing code's needs. 18 * 19 * @since 0.1.0 20 * 21 * @phpstan-import-type RequiredOptionArrayShape from RequiredOption 22 * 23 * @phpstan-type ModelRequirementsArrayShape array{ 24 * requiredCapabilities: list<string>, 25 * requiredOptions: list<RequiredOptionArrayShape> 26 * } 27 * 28 * @phpstan-type UnmetModelRequirementsShape array{ 29 * capabilities: list<CapabilityEnum>, 30 * options: list<RequiredOption> 31 * } 32 * 33 * @extends AbstractDataTransferObject<ModelRequirementsArrayShape> 34 */ 35 class ModelRequirements extends AbstractDataTransferObject 36 { 37 public const KEY_REQUIRED_CAPABILITIES = 'requiredCapabilities'; 38 public const KEY_REQUIRED_OPTIONS = 'requiredOptions'; 39 /** 40 * @var list<CapabilityEnum> The capabilities that the model must support. 41 */ 42 protected array $requiredCapabilities; 43 /** 44 * @var list<RequiredOption> The options that the model must support with specific values. 45 */ 46 protected array $requiredOptions; 47 /** 48 * Constructor. 49 * 50 * @since 0.1.0 51 * 52 * @param list<CapabilityEnum> $requiredCapabilities The capabilities that the model must support. 53 * @param list<RequiredOption> $requiredOptions The options that the model must support with specific values. 54 * 55 * @throws InvalidArgumentException If arrays are not lists. 56 */ 57 public function __construct(array $requiredCapabilities, array $requiredOptions) 58 { 59 if (!array_is_list($requiredCapabilities)) { 60 throw new InvalidArgumentException('Required capabilities must be a list array.'); 61 } 62 if (!array_is_list($requiredOptions)) { 63 throw new InvalidArgumentException('Required options must be a list array.'); 64 } 65 $this->requiredCapabilities = $requiredCapabilities; 66 $this->requiredOptions = $requiredOptions; 67 } 68 /** 69 * Gets the capabilities that the model must support. 70 * 71 * @since 0.1.0 72 * 73 * @return list<CapabilityEnum> The required capabilities. 74 */ 75 public function getRequiredCapabilities(): array 76 { 77 return $this->requiredCapabilities; 78 } 79 /** 80 * Gets the options that the model must support with specific values. 81 * 82 * @since 0.1.0 83 * 84 * @return list<RequiredOption> The required options. 85 */ 86 public function getRequiredOptions(): array 87 { 88 return $this->requiredOptions; 89 } 90 /** 91 * Checks whether the given model metadata meets these requirements. 92 * 93 * @since 0.2.0 94 * 95 * @param ModelMetadata $metadata The model metadata to check against. 96 * @return bool True if the model meets all requirements, false otherwise. 97 */ 98 public function areMetBy(\WordPress\AiClient\Providers\Models\DTO\ModelMetadata $metadata): bool 99 { 100 $unmetRequirements = $this->getUnmetRequirements($metadata); 101 return $unmetRequirements['capabilities'] === [] && $unmetRequirements['options'] === []; 102 } 103 /** 104 * Determines which of these requirements the given model metadata does not meet. 105 * 106 * Unlike {@see self::areMetBy()}, this method reports the specific capabilities and options that 107 * are unsupported, so that calling code can explain why a model is unsuitable. 108 * 109 * @since 1.5.0 110 * 111 * @param ModelMetadata $metadata The model metadata to check against. 112 * @return UnmetModelRequirementsShape The unsupported capabilities and options. Both lists are empty if the 113 * model meets all requirements. 114 */ 115 public function getUnmetRequirements(\WordPress\AiClient\Providers\Models\DTO\ModelMetadata $metadata): array 116 { 117 // Create lookup maps for better performance (instead of nested foreach loops) 118 $capabilitiesMap = []; 119 foreach ($metadata->getSupportedCapabilities() as $capability) { 120 $capabilitiesMap[$capability->value] = $capability; 121 } 122 $optionsMap = []; 123 foreach ($metadata->getSupportedOptions() as $option) { 124 $optionsMap[$option->getName()->value] = $option; 125 } 126 // Collect required capabilities that are not supported, using map lookup 127 $unmetCapabilities = []; 128 foreach ($this->requiredCapabilities as $requiredCapability) { 129 if (!isset($capabilitiesMap[$requiredCapability->value])) { 130 $unmetCapabilities[] = $requiredCapability; 131 } 132 } 133 // Collect required options that are either unsupported or unsupported with the required value 134 $unmetOptions = []; 135 foreach ($this->requiredOptions as $requiredOption) { 136 // Use map lookup instead of linear search 137 $supportedOption = $optionsMap[$requiredOption->getName()->value] ?? null; 138 if ($supportedOption === null || !$supportedOption->isSupportedValue($requiredOption->getValue())) { 139 $unmetOptions[] = $requiredOption; 140 } 141 } 142 return ['capabilities' => $unmetCapabilities, 'options' => $unmetOptions]; 143 } 144 /** 145 * Creates ModelRequirements from prompt data and model configuration. 146 * 147 * @since 0.2.0 148 * 149 * @param CapabilityEnum $capability The capability the model must support. 150 * @param list<Message> $messages The messages in the conversation. 151 * @param ModelConfig $modelConfig The model configuration. 152 * @return self The created requirements. 153 */ 154 public static function fromPromptData(CapabilityEnum $capability, array $messages, \WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): self 155 { 156 // Start with base capability 157 $capabilities = [$capability]; 158 $inputModalities = []; 159 // Check if we have chat history (multiple messages) 160 if (count($messages) > 1) { 161 $capabilities[] = CapabilityEnum::chatHistory(); 162 } 163 // Analyze all messages to determine required input modalities 164 $hasFunctionMessageParts = \false; 165 foreach ($messages as $message) { 166 foreach ($message->getParts() as $part) { 167 $modality = self::inputModalityForPart($part); 168 if ($modality !== null) { 169 $inputModalities[] = $modality; 170 } 171 // Check for function calls/responses (these might require special capabilities) 172 if ($part->getType()->isFunctionCall() || $part->getType()->isFunctionResponse()) { 173 $hasFunctionMessageParts = \true; 174 } 175 } 176 } 177 // Convert ModelConfig to RequiredOptions 178 $requiredOptions = self::toRequiredOptions($modelConfig); 179 // Add additional options based on message analysis 180 if ($hasFunctionMessageParts) { 181 $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true)); 182 } 183 // Add input modalities if we have any inputs 184 if (!empty($inputModalities)) { 185 // Remove duplicates 186 $inputModalities = array_unique($inputModalities, \SORT_REGULAR); 187 $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::inputModalities(), array_values($inputModalities))); 188 } 189 // Step 6: Return new ModelRequirements 190 return new self($capabilities, $requiredOptions); 191 } 192 /** 193 * Creates ModelRequirements from embedding input data and model configuration. 194 * 195 * Unlike {@see self::fromPromptData()}, embedding inputs are independent items rather than a 196 * conversation, so no chat history capability is inferred. Each input contributes its input 197 * modality (text or file) to the requirements. 198 * 199 * @since 1.4.0 200 * 201 * @param list<MessagePart> $inputs The embedding inputs. 202 * @param ModelConfig $modelConfig The model configuration. 203 * @return self The created requirements. 204 */ 205 public static function fromEmbeddingData(array $inputs, \WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): self 206 { 207 $capabilities = [CapabilityEnum::embeddingGeneration()]; 208 $inputModalities = []; 209 // Analyze each input to determine required input modalities. Function call/response parts 210 // are not valid embedding inputs and are rejected before reaching this point. 211 foreach ($inputs as $part) { 212 $modality = self::inputModalityForPart($part); 213 if ($modality !== null) { 214 $inputModalities[] = $modality; 215 } 216 } 217 // Convert ModelConfig to RequiredOptions 218 $requiredOptions = self::toRequiredOptions($modelConfig); 219 // Add input modalities if we have any inputs 220 if (!empty($inputModalities)) { 221 // Remove duplicates 222 $inputModalities = array_unique($inputModalities, \SORT_REGULAR); 223 $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::inputModalities(), array_values($inputModalities))); 224 } 225 return new self($capabilities, $requiredOptions); 226 } 227 /** 228 * Determines the input modality contributed by a message part, if any. 229 * 230 * @since 1.4.0 231 * 232 * @param MessagePart $part The message part to analyze. 233 * @return ModalityEnum|null The input modality, or null if the part contributes none. 234 */ 235 private static function inputModalityForPart(MessagePart $part): ?ModalityEnum 236 { 237 // Check for text input 238 if ($part->getType()->isText()) { 239 return ModalityEnum::text(); 240 } 241 // Check for file inputs 242 if ($part->getType()->isFile()) { 243 $file = $part->getFile(); 244 if ($file !== null) { 245 if ($file->isImage()) { 246 return ModalityEnum::image(); 247 } elseif ($file->isAudio()) { 248 return ModalityEnum::audio(); 249 } elseif ($file->isVideo()) { 250 return ModalityEnum::video(); 251 } elseif ($file->isDocument() || $file->isText()) { 252 return ModalityEnum::document(); 253 } 254 } 255 } 256 return null; 257 } 258 /** 259 * Converts ModelConfig to an array of RequiredOptions. 260 * 261 * @since 0.2.0 262 * 263 * @param ModelConfig $modelConfig The model configuration. 264 * @return list<RequiredOption> The required options. 265 */ 266 private static function toRequiredOptions(\WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): array 267 { 268 $requiredOptions = []; 269 // Map properties that have corresponding OptionEnum values 270 if ($modelConfig->getOutputModalities() !== null) { 271 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputModalities(), $modelConfig->getOutputModalities()); 272 } 273 if ($modelConfig->getSystemInstruction() !== null) { 274 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::systemInstruction(), $modelConfig->getSystemInstruction()); 275 } 276 if ($modelConfig->getCandidateCount() !== null) { 277 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::candidateCount(), $modelConfig->getCandidateCount()); 278 } 279 if ($modelConfig->getMaxTokens() !== null) { 280 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::maxTokens(), $modelConfig->getMaxTokens()); 281 } 282 if ($modelConfig->getTemperature() !== null) { 283 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::temperature(), $modelConfig->getTemperature()); 284 } 285 if ($modelConfig->getTopP() !== null) { 286 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topP(), $modelConfig->getTopP()); 287 } 288 if ($modelConfig->getTopK() !== null) { 289 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topK(), $modelConfig->getTopK()); 290 } 291 if ($modelConfig->getOutputMimeType() !== null) { 292 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMimeType(), $modelConfig->getOutputMimeType()); 293 } 294 if ($modelConfig->getOutputSchema() !== null) { 295 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputSchema(), $modelConfig->getOutputSchema()); 296 } 297 // Handle properties without OptionEnum values as custom options 298 if ($modelConfig->getStopSequences() !== null) { 299 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::stopSequences(), $modelConfig->getStopSequences()); 300 } 301 if ($modelConfig->getPresencePenalty() !== null) { 302 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::presencePenalty(), $modelConfig->getPresencePenalty()); 303 } 304 if ($modelConfig->getFrequencyPenalty() !== null) { 305 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::frequencyPenalty(), $modelConfig->getFrequencyPenalty()); 306 } 307 if ($modelConfig->getLogprobs() !== null) { 308 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::logprobs(), $modelConfig->getLogprobs()); 309 } 310 if ($modelConfig->getTopLogprobs() !== null) { 311 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topLogprobs(), $modelConfig->getTopLogprobs()); 312 } 313 if ($modelConfig->getFunctionDeclarations() !== null) { 314 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true); 315 } 316 if ($modelConfig->getWebSearch() !== null) { 317 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::webSearch(), \true); 318 } 319 if ($modelConfig->getOutputFileType() !== null) { 320 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputFileType(), $modelConfig->getOutputFileType()); 321 } 322 if ($modelConfig->getOutputMediaOrientation() !== null) { 323 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaOrientation(), $modelConfig->getOutputMediaOrientation()); 324 } 325 if ($modelConfig->getOutputMediaAspectRatio() !== null) { 326 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaAspectRatio(), $modelConfig->getOutputMediaAspectRatio()); 327 } 328 if ($modelConfig->getDimensions() !== null) { 329 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::dimensions(), $modelConfig->getDimensions()); 330 } 331 // Add custom options as individual RequiredOptions 332 foreach ($modelConfig->getCustomOptions() as $key => $value) { 333 $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::customOptions(), [$key => $value]); 334 } 335 return $requiredOptions; 336 } 337 /** 338 * Includes a RequiredOption in the array, ensuring no duplicates based on option name. 339 * 340 * @since 0.2.0 341 * 342 * @param list<RequiredOption> $requiredOptions The existing required options. 343 * @param RequiredOption $newOption The new option to include. 344 * @return list<RequiredOption> The updated required options array. 345 */ 346 private static function includeInRequiredOptions(array $requiredOptions, \WordPress\AiClient\Providers\Models\DTO\RequiredOption $newOption): array 347 { 348 // Check if we already have this option name 349 foreach ($requiredOptions as $index => $existingOption) { 350 if ($existingOption->getName()->equals($newOption->getName())) { 351 // Replace existing option with new one 352 $requiredOptions[$index] = $newOption; 353 return $requiredOptions; 354 } 355 } 356 // Option not found, add it 357 $requiredOptions[] = $newOption; 358 return $requiredOptions; 359 } 360 /** 361 * {@inheritDoc} 362 * 363 * @since 0.1.0 364 */ 365 public static function getJsonSchema(): array 366 { 367 return ['type' => 'object', 'properties' => [self::KEY_REQUIRED_CAPABILITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => CapabilityEnum::getValues()], 'description' => 'The capabilities that the model must support.'], self::KEY_REQUIRED_OPTIONS => ['type' => 'array', 'items' => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::getJsonSchema(), 'description' => 'The options that the model must support with specific values.']], 'required' => [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]]; 368 } 369 /** 370 * {@inheritDoc} 371 * 372 * @since 0.1.0 373 * 374 * @return ModelRequirementsArrayShape 375 */ 376 public function toArray(): array 377 { 378 return [self::KEY_REQUIRED_CAPABILITIES => array_map(static fn(CapabilityEnum $capability): string => $capability->value, $this->requiredCapabilities), self::KEY_REQUIRED_OPTIONS => array_map(static fn(\WordPress\AiClient\Providers\Models\DTO\RequiredOption $option): array => $option->toArray(), $this->requiredOptions)]; 379 } 380 /** 381 * {@inheritDoc} 382 * 383 * @since 0.1.0 384 */ 385 public static function fromArray(array $array): self 386 { 387 static::validateFromArrayData($array, [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]); 388 return new self(array_map(static fn(string $capability): CapabilityEnum => CapabilityEnum::from($capability), $array[self::KEY_REQUIRED_CAPABILITIES]), array_map(static fn(array $optionData): \WordPress\AiClient\Providers\Models\DTO\RequiredOption => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::fromArray($optionData), $array[self::KEY_REQUIRED_OPTIONS])); 389 } 390 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sun Nov 1 08:20:35 2026 | Cross-referenced by PHPXref |