[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/php-ai-client/src/Providers/Models/DTO/ -> ModelRequirements.php (source)

   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  }


Generated : Sun Nov 1 08:20:35 2026 Cross-referenced by PHPXref