Skip to content

[WIP] Provider base and implementation #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: trunk
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\ProviderImplementations\OpenAi;

use WordPress\AiClient\Providers\AbstractOpenAiCompatibleModelMetadataDirectory;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;

/**
* Class for the OpenAI model metadata directory.
*
* @since n.e.x.t
*/
class OpenAiModelMetadataDirectory extends AbstractOpenAiCompatibleModelMetadataDirectory
{
/**
* @inheritDoc
*/
protected function createRequest(string $path): RequestInterface

Check failure on line 20 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Return type WordPress\AiClient\ProviderImplementations\OpenAi\RequestInterface of method WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiModelMetadataDirectory::createRequest() is not covariant with return type WordPress\AiClient\Providers\RequestInterface of method WordPress\AiClient\Providers\AbstractOpenAiCompatibleModelMetadataDirectory::createRequest().

Check failure on line 20 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Method WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiModelMetadataDirectory::createRequest() has invalid return type WordPress\AiClient\ProviderImplementations\OpenAi\RequestInterface.
{
// Something like this.
return new OpenAiCompatibleRequest('https://api.openai.com/v1', $path);

Check failure on line 23 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Method WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiModelMetadataDirectory::createRequest() should return WordPress\AiClient\ProviderImplementations\OpenAi\RequestInterface but returns WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiCompatibleRequest.

Check failure on line 23 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Instantiated class WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiCompatibleRequest not found.
}

/**
* @inheritDoc
*/
protected function parseResponseToModelMetadataList(ResponseInterface $response): array

Check failure on line 29 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Parameter $response of method WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiModelMetadataDirectory::parseResponseToModelMetadataList() has invalid type WordPress\AiClient\Providers\ResponseInterface.

Check failure on line 29 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Parameter $response of method WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiModelMetadataDirectory::parseResponseToModelMetadataList() has invalid type WordPress\AiClient\ProviderImplementations\OpenAi\ResponseInterface.

Check failure on line 29 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Parameter #1 $response (WordPress\AiClient\ProviderImplementations\OpenAi\ResponseInterface) of method WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiModelMetadataDirectory::parseResponseToModelMetadataList() is not contravariant with parameter #1 $response (WordPress\AiClient\Providers\ResponseInterface) of method WordPress\AiClient\Providers\AbstractOpenAiCompatibleModelMetadataDirectory::parseResponseToModelMetadataList().
{
$responseData = $response->getData();

Check failure on line 31 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Call to method getData() on an unknown class WordPress\AiClient\ProviderImplementations\OpenAi\ResponseInterface.
if (!isset($responseData['data']) || !$responseData['data']) {

Check failure on line 32 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Cannot access offset 'data' on mixed.
throw new RuntimeException(

Check failure on line 33 in src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

View workflow job for this annotation

GitHub Actions / PHP

Instantiated class WordPress\AiClient\ProviderImplementations\OpenAi\RuntimeException not found.
'Unexpected API response: Missing the data key.'
);
}
return array_map(
static function (array $modelData): ModelMetadata {
// TODO: Create ModelMetadata object from API data.
},
$responseData['data']
);
}
}
82 changes: 82 additions & 0 deletions src/ProviderImplementations/OpenAi/OpenAiProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\ProviderImplementations\OpenAi;

use WordPress\AiClient\Providers\AbstractProvider;
use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface;
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
use WordPress\AiClient\Providers\Contracts\ProviderInterface;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Enums\ProviderTypeEnum;
use WordPress\AiClient\Providers\ListModelsApiBasedProviderAvailability;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;

/**
* Class for the OpenAI provider.
*
* @since n.e.x.t
*/
class OpenAiProvider extends AbstractProvider
{
/**
* @inheritDoc
*/
protected static function createModel(
ModelMetadata $modelMetadata,
ProviderMetadata $providerMetadata
): ModelInterface {
$capabilities = $modelMetadata->getCapabilities();
foreach ($capabilities as $capability) {
if ($capability->isTextGeneration()) {
return new OpenAiTextGenerationModel($modelMetadata, $providerMetadata);
}
if ($capability->isImageGeneration()) {
// TODO: Implement OpenAiImageGenerationModel.
return new OpenAiImageGenerationModel($modelMetadata, $providerMetadata);
}
if ($capability->isTextToSpeechConversion()) {
// TODO: Implement OpenAiTextToSpeechConversionModel.
return new OpenAiTextToSpeechConversionModel($modelMetadata, $providerMetadata);
}
}

throw new RuntimeException(
'Unsupported model capabilities: ' . implode(', ', $capabilities)
);
}

/**
* @inheritDoc
*/
protected static function createProviderMetadata(): ProviderMetadata
{
return new ProviderMetadata(
'openai',
'OpenAI',
ProviderTypeEnum::cloud()
);
}

/**
* @inheritDoc
*/
protected static function createProviderAvailability(): ProviderAvailabilityInterface
{
// Check valid API access by attempting to list models.
return new ListModelsApiBasedProviderAvailability(
static::modelMetadataDirectory()
);
}

/**
* @inheritDoc
*/
protected static function createModelMetadataDirectory(): ModelMetadataDirectoryInterface
{
return new OpenAiModelMetadataDirectory();
}
}
25 changes: 25 additions & 0 deletions src/ProviderImplementations/OpenAi/OpenAiTextGenerationModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\ProviderImplementations\OpenAi;

use WordPress\AiClient\Providers\AbstractOpenAiCompatibleTextGenerationModel;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;

/**
* Class for an OpenAI text generation model.
*
* @since n.e.x.t
*/
class OpenAiTextGenerationModel extends AbstractOpenAiCompatibleTextGenerationModel
{
/**
* @inheritDoc
*/
protected function createRequest(string $path, array $params): RequestInterface
{
// Something like this.
return new OpenAiCompatibleRequest('https://api.openai.com/v1', $path);
}
}
103 changes: 103 additions & 0 deletions src/Providers/AbstractApiBasedModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\Providers;

use InvalidArgumentException;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\Contracts\WithHttpTransporterInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
use WordPress\AiClient\Providers\Models\Traits\WithHttpTransporterTrait;

/**
* Base class for an API-based model for a provider.
*
* @since n.e.x.t
*/
abstract class AbstractApiBasedModel implements
ModelInterface,
WithHttpTransporterInterface
{
use WithHttpTransporterTrait;

/**
* @var ModelMetadata The metadata for the model.
*/
private ModelMetadata $metadata;

/**
* @var ProviderMetadata The metadata for the model's provider.
*/
private ProviderMetadata $providerMetadata;

/**
* @var ModelConfig The configuration for the model.
*/
private ModelConfig $config;

/**
* Constructor.
*
* @since n.e.x.t
*
* @param ModelMetadata $metadata The metadata for the model.
* @param ProviderMetadata $providerMetadata The metadata for the model's provider.
*/
public function __construct(ModelMetadata $metadata, ProviderMetadata $providerMetadata)
{
$this->metadata = $metadata;
$this->providerMetadata = $providerMetadata;
$this->config = ModelConfig::fromArray([]);
}

/**
* Returns the metadata for the model.
*
* @since n.e.x.t
*
* @return ModelMetadata The model metadata.
*/
public function metadata(): ModelMetadata
{
return $this->metadata;
}

/**
* Returns the metadata for the model's provider.
*
* @since n.e.x.t
*
* @return ProviderMetadata The provider metadata.
*/
public function providerMetadata(): ProviderMetadata
{
return $this->providerMetadata;
}

/**
* Sets the configuration for the model.
*
* @since n.e.x.t
*
* @param ModelConfig $config The configuration for the model.
*/
public function setConfig(ModelConfig $config): void
{
$this->config = $config;
}

/**
* Returns the configuration for the model.
*
* @since n.e.x.t
*
* @return ModelConfig The model configuration.
*/
public function getConfig(): ModelConfig
{
return $this->config;
}
}
105 changes: 105 additions & 0 deletions src/Providers/AbstractApiBasedModelMetadataDirectory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\Providers;

use InvalidArgumentException;
use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface;
use WordPress\AiClient\Providers\Models\Contracts\WithHttpTransporterInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
use WordPress\AiClient\Providers\Models\Traits\WithHttpTransporterTrait;

/**
* Base class for an API-based model metadata directory for a provider.
*
* @since n.e.x.t
*/
abstract class AbstractApiBasedModelMetadataDirectory implements
ModelMetadataDirectoryInterface,
WithHttpTransporterInterface
{
use WithHttpTransporterTrait;

/**
* @var ?array<string, ModelMetadata> Map of model ID to model metadata, effectively for caching.
*/
private ?array $modelMetadataMap = null;

/**
* Lists the metadata for all models from the provider.
*
* @since n.e.x.t
*
* @return list<ModelMetadata> List of model metadata objects.
*/
final public function listModelMetadata(): array
{
$modelsMetadata = $this->getModelMetadataMap();
return array_values($modelsMetadata);
}

/**
* Checks whether model metadata for the given model ID exists.
*
* This is effectively a check for whether the given model ID is for a valid model from the provider.
*
* @since n.e.x.t
*
* @param string $modelId The model ID.
* @return bool True if there is metadata for the model.
*/
final public function hasModelMetadata(string $modelId): bool
{
try {
$this->getModelMetadata();
} catch (InvalidArgumentException $e) {
return false;
}
return true;
}

/**
* Gets the model metadata for the given model ID.
*
* @since n.e.x.t
*
* @param string $modelId The model ID.
* @return ModelMetadata The model metadata.
* @throws InvalidArgumentException If the model for the given ID does not exist.
*/
final public function getModelMetadata(string $modelId): ModelMetadata
{
$modelsMetadata = $this->getModelMetadataMap();
if (!isset($modelsMetadata[$modelId])) {
throw new InvalidArgumentException(
sprintf('No model with ID %s was found in the provider', $modelId)
);
}
return $modelsMetadata[$modelId];
}

/**
* Returns the map of model ID to model metadata for all models from the provider.
*
* @since n.e.x.t
*
* @return array<string, ModelMetadata> Map of model ID to model metadata.
*/
private function getModelMetadataMap(): array
{
if ($this->modelMetadataMap === null) {
$this->modelMetadataMap = $this->sendListModelsRequest();
}
return $this->modelMetadataMap;
}

/**
* Sends the API request to list models from the provider and returns the map of model ID to model metadata.
*
* @since n.e.x.t
*
* @return array<string, ModelMetadata> Map of model ID to model metadata.
*/
abstract protected function sendListModelsRequest(): array;
}
Loading