[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/php-ai-client/src/ -> AiClient.php (source)

   1  <?php
   2  
   3  declare (strict_types=1);
   4  namespace WordPress\AiClient;
   5  
   6  use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface;
   7  use WordPress\AiClientDependencies\Psr\SimpleCache\CacheInterface;
   8  use WordPress\AiClient\Builders\EmbeddingBuilder;
   9  use WordPress\AiClient\Builders\PromptBuilder;
  10  use WordPress\AiClient\Common\Exception\InvalidArgumentException;
  11  use WordPress\AiClient\Common\Exception\RuntimeException;
  12  use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
  13  use WordPress\AiClient\Providers\Contracts\ProviderInterface;
  14  use WordPress\AiClient\Providers\ModelResolver;
  15  use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
  16  use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
  17  use WordPress\AiClient\Providers\ProviderRegistry;
  18  use WordPress\AiClient\Results\DTO\Embedding;
  19  use WordPress\AiClient\Results\DTO\EmbeddingResult;
  20  use WordPress\AiClient\Results\DTO\GenerativeAiResult;
  21  /**
  22   * Main AI Client class providing both fluent and traditional APIs for AI operations.
  23   *
  24   * This class serves as the primary entry point for AI operations, offering:
  25   * - Fluent API for easy-to-read chained method calls
  26   * - Traditional API for array-based configuration (WordPress style)
  27   * - Integration with provider registry for model discovery
  28   * - Support for three model specification approaches
  29   *
  30   * All model requirements analysis and capability matching is handled
  31   * automatically by the PromptBuilder, which provides intelligent model
  32   * discovery based on prompt content and configuration.
  33   *
  34   * ## Model Specification Approaches
  35   *
  36   * ### 1. Specific Model Instance
  37   * Use a specific ModelInterface instance when you know exactly which model to use:
  38   * ```php
  39   * $model = $registry->getProvider('openai')->getModel('gpt-4');
  40   * $result = AiClient::generateTextResult('What is PHP?', $model);
  41   * ```
  42   *
  43   * ### 2. ModelConfig for Auto-Discovery
  44   * Use ModelConfig to specify requirements and let the system discover the best model:
  45   * ```php
  46   * $config = new ModelConfig();
  47   * $config->setTemperature(0.7);
  48   * $config->setMaxTokens(150);
  49   *
  50   * $result = AiClient::generateTextResult('What is PHP?', $config);
  51   * ```
  52   *
  53   * ### 3. Automatic Discovery (Default)
  54   * Pass null or omit the parameter for intelligent model discovery based on prompt content:
  55   * ```php
  56   * // System analyzes prompt and selects appropriate model automatically
  57   * $result = AiClient::generateTextResult('What is PHP?');
  58   * $imageResult = AiClient::generateImageResult('A sunset over mountains');
  59   * ```
  60   *
  61   * ## Fluent API Examples
  62   * ```php
  63   * // Fluent API with automatic model discovery
  64   * $result = AiClient::prompt('Generate an image of a sunset')
  65   *     ->usingTemperature(0.7)
  66   *     ->generateImageResult();
  67   *
  68   * // Fluent API with specific model
  69   * $result = AiClient::prompt('What is PHP?')
  70   *     ->usingModel($specificModel)
  71   *     ->usingTemperature(0.5)
  72   *     ->generateTextResult();
  73   *
  74   * // Fluent API with model configuration
  75   * $result = AiClient::prompt('Explain quantum physics')
  76   *     ->usingModelConfig($config)
  77   *     ->generateTextResult();
  78   * ```
  79   *
  80   * @since 0.1.0
  81   *
  82   * @phpstan-import-type Prompt from PromptBuilder
  83   * @phpstan-import-type EmbeddingInput from EmbeddingBuilder
  84   * @phpstan-import-type ProviderModelTuple from ModelResolver
  85   *
  86   * phpcs:ignore Generic.Files.LineLength.TooLong
  87   */
  88  class AiClient
  89  {
  90      /**
  91       * @var string The version of the AI Client.
  92       */
  93      public const VERSION = '1.5.1';
  94      /**
  95       * @var ProviderRegistry|null The default provider registry instance.
  96       */
  97      private static ?ProviderRegistry $defaultRegistry = null;
  98      /**
  99       * @var EventDispatcherInterface|null The event dispatcher for prompt lifecycle events.
 100       */
 101      private static ?EventDispatcherInterface $eventDispatcher = null;
 102      /**
 103       * @var CacheInterface|null The PSR-16 cache for storing and retrieving cached data.
 104       */
 105      private static ?CacheInterface $cache = null;
 106      /**
 107       * Gets the default provider registry instance.
 108       *
 109       * @since 0.1.0
 110       *
 111       * @return ProviderRegistry The default provider registry.
 112       */
 113      public static function defaultRegistry(): ProviderRegistry
 114      {
 115          if (self::$defaultRegistry === null) {
 116              self::$defaultRegistry = new ProviderRegistry();
 117          }
 118          return self::$defaultRegistry;
 119      }
 120      /**
 121       * Sets the event dispatcher for prompt lifecycle events.
 122       *
 123       * The event dispatcher will be used to dispatch BeforeGenerateResultEvent and
 124       * AfterGenerateResultEvent during prompt generation.
 125       *
 126       * @since 0.4.0
 127       *
 128       * @param EventDispatcherInterface|null $dispatcher The event dispatcher, or null to disable.
 129       * @return void
 130       */
 131      public static function setEventDispatcher(?EventDispatcherInterface $dispatcher): void
 132      {
 133          self::$eventDispatcher = $dispatcher;
 134      }
 135      /**
 136       * Gets the event dispatcher for prompt lifecycle events.
 137       *
 138       * @since 0.4.0
 139       *
 140       * @return EventDispatcherInterface|null The event dispatcher, or null if not set.
 141       */
 142      public static function getEventDispatcher(): ?EventDispatcherInterface
 143      {
 144          return self::$eventDispatcher;
 145      }
 146      /**
 147       * Sets the PSR-16 cache for storing and retrieving cached data.
 148       *
 149       * The cache can be used to store AI responses and other data to avoid
 150       * redundant API calls and improve performance.
 151       *
 152       * @since 0.4.0
 153       *
 154       * @param CacheInterface|null $cache The PSR-16 cache instance, or null to disable caching.
 155       * @return void
 156       */
 157      public static function setCache(?CacheInterface $cache): void
 158      {
 159          self::$cache = $cache;
 160      }
 161      /**
 162       * Gets the PSR-16 cache instance.
 163       *
 164       * @since 0.4.0
 165       *
 166       * @return CacheInterface|null The cache instance, or null if not set.
 167       */
 168      public static function getCache(): ?CacheInterface
 169      {
 170          return self::$cache;
 171      }
 172      /**
 173       * Checks if a provider is configured and available for use.
 174       *
 175       * Supports multiple input formats for developer convenience:
 176       * - ProviderAvailabilityInterface: Direct availability check
 177       * - string (provider ID): e.g., AiClient::isConfigured('openai')
 178       * - string (class name): e.g., AiClient::isConfigured(OpenAiProvider::class)
 179       *
 180       * When using string input, this method leverages the ProviderRegistry's centralized
 181       * dependency management, ensuring HttpTransporter and authentication are properly
 182       * injected into availability instances.
 183       *
 184       * @since 0.1.0
 185       * @since 0.2.0 Now supports being passed a provider ID or class name.
 186       *
 187       * @param ProviderAvailabilityInterface|string|class-string<ProviderInterface> $availabilityOrIdOrClassName
 188       *        The provider availability instance, provider ID, or provider class name.
 189       * @return bool True if the provider is configured and available, false otherwise.
 190       */
 191      public static function isConfigured($availabilityOrIdOrClassName): bool
 192      {
 193          // Handle direct ProviderAvailabilityInterface (backward compatibility)
 194          if ($availabilityOrIdOrClassName instanceof ProviderAvailabilityInterface) {
 195              return $availabilityOrIdOrClassName->isConfigured();
 196          }
 197          // Handle string input (provider ID or class name) via registry
 198          if (is_string($availabilityOrIdOrClassName)) {
 199              return self::defaultRegistry()->isProviderConfigured($availabilityOrIdOrClassName);
 200          }
 201          throw new \InvalidArgumentException('Parameter must be a ProviderAvailabilityInterface instance, provider ID string, or provider class name. ' . sprintf('Received: %s', is_object($availabilityOrIdOrClassName) ? get_class($availabilityOrIdOrClassName) : gettype($availabilityOrIdOrClassName)));
 202      }
 203      /**
 204       * Creates a new prompt builder for fluent API usage.
 205       *
 206       * Returns a PromptBuilder instance configured with the specified or default registry.
 207       * The traditional API methods in this class delegate to PromptBuilder
 208       * for all generation logic.
 209       *
 210       * @since 0.1.0
 211       *
 212       * @param Prompt $prompt Optional initial prompt content.
 213       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 214       * @return PromptBuilder The prompt builder instance.
 215       */
 216      public static function prompt($prompt = null, ?ProviderRegistry $registry = null): PromptBuilder
 217      {
 218          return new PromptBuilder($registry ?? self::defaultRegistry(), $prompt, self::$eventDispatcher);
 219      }
 220      /**
 221       * Creates an embedding builder for fluent embedding generation.
 222       *
 223       * Accepts a single input or a list of inputs; each input is embedded independently. Chain an
 224       * embedding generation method such as generateEmbedding() or generateEmbeddings() to produce
 225       * one vector per input.
 226       *
 227       * A model must be specified on the returned builder via usingModel() or usingProviderModel().
 228       * Embedding vectors are only comparable to other vectors from the same model, so no model is
 229       * selected automatically.
 230       *
 231       * @since 1.4.0
 232       *
 233       * @param EmbeddingInput|list<EmbeddingInput>|null $input Optional initial input(s) to embed.
 234       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 235       * @return EmbeddingBuilder The embedding builder instance.
 236       */
 237      public static function input($input = null, ?ProviderRegistry $registry = null): EmbeddingBuilder
 238      {
 239          return new EmbeddingBuilder($registry ?? self::defaultRegistry(), $input, self::$eventDispatcher);
 240      }
 241      /**
 242       * Generates content using a unified API that automatically detects model capabilities.
 243       *
 244       * When no model is provided, this method delegates to PromptBuilder for intelligent
 245       * model discovery based on prompt content and configuration. When a model is provided,
 246       * it infers the capability from the model's interfaces and delegates to the capability-based method.
 247       *
 248       * @since 0.1.0
 249       *
 250       * @param Prompt $prompt The prompt content.
 251       * @param ModelInterface|ModelConfig $modelOrConfig Specific model to use, or model configuration
 252       *                                                  for auto-discovery.
 253       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 254       * @return GenerativeAiResult The generation result.
 255       *
 256       * @throws \InvalidArgumentException If the provided model doesn't support any known generation type.
 257       * @throws \RuntimeException If no suitable model can be found for the prompt.
 258       */
 259      public static function generateResult($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): GenerativeAiResult
 260      {
 261          self::validateModelOrConfigParameter($modelOrConfig);
 262          return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateResult();
 263      }
 264      /**
 265       * Generates text using the traditional API approach.
 266       *
 267       * @since 0.1.0
 268       *
 269       * @param Prompt $prompt The prompt content.
 270       * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
 271       *                                                        or model configuration for auto-discovery,
 272       *                                                        or null for defaults.
 273       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 274       * @return GenerativeAiResult The generation result.
 275       *
 276       * @throws \InvalidArgumentException If the prompt format is invalid.
 277       * @throws \RuntimeException If no suitable model is found.
 278       */
 279      public static function generateTextResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
 280      {
 281          self::validateModelOrConfigParameter($modelOrConfig);
 282          return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateTextResult();
 283      }
 284      /**
 285       * Generates an image using the traditional API approach.
 286       *
 287       * @since 0.1.0
 288       *
 289       * @param Prompt $prompt The prompt content.
 290       * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
 291       *                                                        or model configuration for auto-discovery,
 292       *                                                        or null for defaults.
 293       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 294       * @return GenerativeAiResult The generation result.
 295       *
 296       * @throws \InvalidArgumentException If the prompt format is invalid.
 297       * @throws \RuntimeException If no suitable model is found.
 298       */
 299      public static function generateImageResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
 300      {
 301          self::validateModelOrConfigParameter($modelOrConfig);
 302          return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateImageResult();
 303      }
 304      /**
 305       * Converts text to speech using the traditional API approach.
 306       *
 307       * @since 0.1.0
 308       *
 309       * @param Prompt $prompt The prompt content.
 310       * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
 311       *                                                        or model configuration for auto-discovery,
 312       *                                                        or null for defaults.
 313       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 314       * @return GenerativeAiResult The generation result.
 315       *
 316       * @throws \InvalidArgumentException If the prompt format is invalid.
 317       * @throws \RuntimeException If no suitable model is found.
 318       */
 319      public static function convertTextToSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
 320      {
 321          self::validateModelOrConfigParameter($modelOrConfig);
 322          return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->convertTextToSpeechResult();
 323      }
 324      /**
 325       * Generates speech using the traditional API approach.
 326       *
 327       * @since 0.1.0
 328       *
 329       * @param Prompt $prompt The prompt content.
 330       * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
 331       *                                                        or model configuration for auto-discovery,
 332       *                                                        or null for defaults.
 333       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 334       * @return GenerativeAiResult The generation result.
 335       *
 336       * @throws \InvalidArgumentException If the prompt format is invalid.
 337       * @throws \RuntimeException If no suitable model is found.
 338       */
 339      public static function generateSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
 340      {
 341          self::validateModelOrConfigParameter($modelOrConfig);
 342          return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateSpeechResult();
 343      }
 344      /**
 345       * Generates a video using the traditional API approach.
 346       *
 347       * @since 1.3.0
 348       *
 349       * @param Prompt $prompt The prompt content.
 350       * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
 351       *                                                        or model configuration for auto-discovery,
 352       *                                                        or null for defaults.
 353       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 354       * @return GenerativeAiResult The generation result.
 355       *
 356       * @throws \InvalidArgumentException If the prompt format is invalid.
 357       * @throws \RuntimeException If no suitable model is found.
 358       */
 359      public static function generateVideoResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
 360      {
 361          self::validateModelOrConfigParameter($modelOrConfig);
 362          return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateVideoResult();
 363      }
 364      /**
 365       * Generates embeddings using the traditional API approach.
 366       *
 367       * A model is required. Embedding vectors are only comparable to other vectors from the same
 368       * model, so no model is selected automatically.
 369       *
 370       * @since 1.4.0
 371       *
 372       * @param EmbeddingInput|list<EmbeddingInput> $input The input(s) to embed.
 373       * @param ModelInterface|ProviderModelTuple $model The model to use.
 374       * @param ModelConfig|null $modelConfig Optional model configuration to apply.
 375       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 376       * @return EmbeddingResult The embedding result.
 377       *
 378       * @throws \InvalidArgumentException If the input or model format is invalid, or if the model
 379       *                                   cannot fulfill the request.
 380       */
 381      public static function generateEmbeddingResult($input, $model, ?ModelConfig $modelConfig = null, ?ProviderRegistry $registry = null): EmbeddingResult
 382      {
 383          return self::getConfiguredEmbeddingBuilder($input, $model, $modelConfig, $registry)->generateEmbeddingResult();
 384      }
 385      /**
 386       * Generates an embedding using the traditional API approach.
 387       *
 388       * A model is required. Embedding vectors are only comparable to other vectors from the same
 389       * model, so no model is selected automatically.
 390       *
 391       * @since 1.4.0
 392       *
 393       * @param EmbeddingInput $input The input to embed.
 394       * @param ModelInterface|ProviderModelTuple $model The model to use.
 395       * @param ModelConfig|null $modelConfig Optional model configuration to apply.
 396       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 397       * @return Embedding The generated embedding vector.
 398       */
 399      public static function generateEmbedding($input, $model, ?ModelConfig $modelConfig = null, ?ProviderRegistry $registry = null): Embedding
 400      {
 401          return self::generateEmbeddingResult($input, $model, $modelConfig, $registry)->getEmbedding();
 402      }
 403      /**
 404       * Generates embeddings for a list of inputs using the traditional API approach.
 405       *
 406       * A model is required. Embedding vectors are only comparable to other vectors from the same
 407       * model, so no model is selected automatically.
 408       *
 409       * @since 1.4.0
 410       *
 411       * @param list<EmbeddingInput> $inputs The inputs to embed.
 412       * @param ModelInterface|ProviderModelTuple $model The model to use.
 413       * @param ModelConfig|null $modelConfig Optional model configuration to apply.
 414       * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
 415       * @return list<Embedding> The generated embedding vectors.
 416       */
 417      public static function generateEmbeddings(array $inputs, $model, ?ModelConfig $modelConfig = null, ?ProviderRegistry $registry = null): array
 418      {
 419          return self::getConfiguredEmbeddingBuilder($inputs, $model, $modelConfig, $registry)->generateEmbeddings();
 420      }
 421      /**
 422       * Creates a new message builder for fluent API usage.
 423       *
 424       * This method will be implemented once MessageBuilder is available.
 425       * MessageBuilder will provide a fluent interface for constructing complex
 426       * messages with multiple parts, attachments, and metadata.
 427       *
 428       * @since 0.1.0
 429       *
 430       * @param string|null $text Optional initial message text.
 431       * @return object MessageBuilder instance (type will be updated when MessageBuilder is available).
 432       *
 433       * @throws \RuntimeException When MessageBuilder is not yet available.
 434       */
 435      public static function message(?string $text = null)
 436      {
 437          throw new RuntimeException('MessageBuilder is not yet available. This method depends on builder infrastructure. ' . 'Use direct generation methods (generateTextResult, generateImageResult, etc.) for now.');
 438      }
 439      /**
 440       * Validates that parameter is ModelInterface, ModelConfig, or null.
 441       *
 442       * @param mixed $modelOrConfig The parameter to validate.
 443       * @return void
 444       * @throws \InvalidArgumentException If parameter is invalid type.
 445       */
 446      private static function validateModelOrConfigParameter($modelOrConfig): void
 447      {
 448          if ($modelOrConfig !== null && !$modelOrConfig instanceof ModelInterface && !$modelOrConfig instanceof ModelConfig) {
 449              throw new InvalidArgumentException('Parameter must be a ModelInterface instance (specific model), ' . 'ModelConfig instance (for auto-discovery), or null (default auto-discovery). ' . sprintf('Received: %s', is_object($modelOrConfig) ? get_class($modelOrConfig) : gettype($modelOrConfig)));
 450          }
 451      }
 452      /**
 453       * Configures PromptBuilder based on model/config parameter type.
 454       *
 455       * @param Prompt $prompt The prompt content.
 456       * @param ModelInterface|ModelConfig|null $modelOrConfig The model or config parameter.
 457       * @param ProviderRegistry|null $registry Optional custom registry to use.
 458       * @return PromptBuilder Configured prompt builder.
 459       */
 460      private static function getConfiguredPromptBuilder($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): PromptBuilder
 461      {
 462          return self::applyModelOrConfig(self::prompt($prompt, $registry), $modelOrConfig);
 463      }
 464      /**
 465       * Configures an EmbeddingBuilder with the required model and optional configuration.
 466       *
 467       * @param EmbeddingInput|list<EmbeddingInput> $input The input(s) to embed.
 468       * @param mixed $model The model to use, expected to be a ModelInterface instance or a
 469       *                     ProviderModelTuple; any other value is rejected.
 470       * @param ModelConfig|null $modelConfig Optional model configuration to apply.
 471       * @param ProviderRegistry|null $registry Optional custom registry to use.
 472       * @return EmbeddingBuilder Configured embedding builder.
 473       * @throws InvalidArgumentException If the model parameter is not of a supported type.
 474       */
 475      private static function getConfiguredEmbeddingBuilder($input, $model, ?ModelConfig $modelConfig = null, ?ProviderRegistry $registry = null): EmbeddingBuilder
 476      {
 477          $builder = self::input($input, $registry);
 478          // The builder's configuration takes precedence over the model's own configuration.
 479          if ($modelConfig !== null) {
 480              $builder->usingModelConfig($modelConfig);
 481          }
 482          if ($model instanceof ModelInterface) {
 483              return $builder->usingModel($model);
 484          }
 485          if (is_array($model) && array_is_list($model) && count($model) === 2 && is_string($model[0]) && is_string($model[1])) {
 486              return $builder->usingProviderModel($model[0], $model[1]);
 487          }
 488          throw new InvalidArgumentException('Model must be a ModelInterface instance or a [provider ID, model ID] tuple. ' . 'Embeddings are only comparable to other embeddings from the same model, so a model ' . 'is required. ' . sprintf('Received: %s', is_object($model) ? get_class($model) : gettype($model)));
 489      }
 490      /**
 491       * Applies a model or model configuration to a builder.
 492       *
 493       * Works with any builder that exposes the shared model resolution methods
 494       * (see {@see \WordPress\AiClient\Builders\Traits\ModelResolutionTrait}).
 495       *
 496       * @template T of PromptBuilder
 497       *
 498       * @param T $builder The builder to configure.
 499       * @param ModelInterface|ModelConfig|null $modelOrConfig Specific model, model configuration,
 500       *                                                        or null for default discovery.
 501       * @return T The configured builder.
 502       */
 503      private static function applyModelOrConfig($builder, $modelOrConfig)
 504      {
 505          if ($modelOrConfig instanceof ModelInterface) {
 506              $builder->usingModel($modelOrConfig);
 507          } elseif ($modelOrConfig instanceof ModelConfig) {
 508              $builder->usingModelConfig($modelOrConfig);
 509          }
 510          // null case: use default model discovery
 511          return $builder;
 512      }
 513  }


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