Add PSR HTTP Message Interfaces and Dependencies
- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards. - Added getallheaders function to retrieve HTTP headers in a compatible manner. - Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts. - Introduced function for triggering deprecation notices in Symfony.
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\OpenAI\OpenAI_AI_Service
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\OpenAI;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\AI_Capability;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Model_Metadata;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Service_Metadata;
|
||||
use ATFPP\AI_Translate\Services\Base\Abstract_AI_Service;
|
||||
use ATFPP\AI_Translate\Services\Base\Generic_AI_API_Client;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Authentication;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Generative_AI_Model;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_API_Client;
|
||||
use ATFPP\AI_Translate\Services\Exception\Generative_AI_Exception;
|
||||
use ATFPP\AI_Translate\Services\HTTP\HTTP_With_Streams;
|
||||
use ATFPP\AI_Translate\Services\Traits\With_API_Client_Trait;
|
||||
use ATFPP\AI_Translate\Services\Util\AI_Capabilities;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request_Handler;
|
||||
|
||||
/**
|
||||
* Class for the OpenAI AI service.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.7.0 Now extends `Abstract_AI_Service`.
|
||||
*/
|
||||
class OpenAI_AI_Service extends Abstract_AI_Service implements With_API_Client {
|
||||
use With_API_Client_Trait;
|
||||
|
||||
const DEFAULT_API_BASE_URL = 'https://api.openai.com';
|
||||
const DEFAULT_API_VERSION = 'v1';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param Service_Metadata $metadata The service metadata.
|
||||
* @param Authentication $authentication The authentication credentials.
|
||||
* @param Request_Handler $request_handler Optional. The request handler instance to use for requests. Default is a
|
||||
* new HTTP_With_Streams instance.
|
||||
*/
|
||||
public function __construct( Service_Metadata $metadata, Authentication $authentication, ?Request_Handler $request_handler = null ) {
|
||||
$this->set_service_metadata( $metadata );
|
||||
$this->set_api_client(
|
||||
new Generic_AI_API_Client(
|
||||
self::DEFAULT_API_BASE_URL,
|
||||
self::DEFAULT_API_VERSION,
|
||||
'OpenAI',
|
||||
$request_handler ?? new HTTP_With_Streams(),
|
||||
$authentication
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the available generative model slugs and their metadata.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.5.0 Return type changed to a map of model data shapes.
|
||||
* @since 0.7.0 Return type changed to a map of model metadata objects.
|
||||
*
|
||||
* @param array<string, mixed> $request_options Optional. The request options. Default empty array.
|
||||
* @return array<string, Model_Metadata> Metadata for each model, mapped by model slug.
|
||||
*
|
||||
* @throws Generative_AI_Exception Thrown if the request fails or the response is invalid.
|
||||
*/
|
||||
public function list_models( array $request_options = array() ): array {
|
||||
$api = $this->get_api_client();
|
||||
|
||||
$request = $api->create_get_request( 'models', array(), $request_options );
|
||||
$response_data = $api->make_request( $request )->get_data();
|
||||
|
||||
if ( ! isset( $response_data['data'] ) || ! $response_data['data'] ) {
|
||||
throw $api->create_missing_response_key_exception( 'data' );
|
||||
}
|
||||
|
||||
// Unfortunately, the OpenAI API does not return model capabilities, so we have to hardcode them here.
|
||||
$gpt_capabilities = array(
|
||||
AI_Capability::FUNCTION_CALLING,
|
||||
AI_Capability::TEXT_GENERATION,
|
||||
);
|
||||
$gpt_multimodal_capabilities = array(
|
||||
AI_Capability::FUNCTION_CALLING,
|
||||
AI_Capability::MULTIMODAL_INPUT,
|
||||
AI_Capability::TEXT_GENERATION,
|
||||
);
|
||||
$image_capabilities = array(
|
||||
AI_Capability::IMAGE_GENERATION,
|
||||
);
|
||||
$tts_capabilities = array(
|
||||
AI_Capability::TEXT_TO_SPEECH,
|
||||
);
|
||||
|
||||
return array_reduce(
|
||||
$response_data['data'],
|
||||
static function ( array $models_data, array $model_data ) use ( $gpt_capabilities, $gpt_multimodal_capabilities, $image_capabilities, $tts_capabilities ) {
|
||||
$model_slug = $model_data['id'];
|
||||
|
||||
if (
|
||||
str_starts_with( $model_slug, 'dall-e-' ) ||
|
||||
str_starts_with( $model_slug, 'gpt-image-' )
|
||||
) {
|
||||
$model_caps = $image_capabilities;
|
||||
} elseif (
|
||||
str_starts_with( $model_slug, 'tts-' ) ||
|
||||
str_contains( $model_slug, '-tts' )
|
||||
) {
|
||||
$model_caps = $tts_capabilities;
|
||||
} elseif (
|
||||
( str_starts_with( $model_slug, 'gpt-' ) || str_starts_with( $model_slug, 'o1-' ) )
|
||||
&& ! str_contains( $model_slug, '-instruct' )
|
||||
&& ! str_contains( $model_slug, '-realtime' )
|
||||
) {
|
||||
if ( str_starts_with( $model_slug, 'gpt-4o' ) ) {
|
||||
$model_caps = $gpt_multimodal_capabilities;
|
||||
// New multimodal output model for audio generation.
|
||||
if ( str_contains( $model_slug, '-audio' ) ) {
|
||||
$model_caps[] = AI_Capability::MULTIMODAL_OUTPUT;
|
||||
}
|
||||
} elseif ( ! str_contains( $model_slug, '-audio' ) ) {
|
||||
$model_caps = $gpt_capabilities;
|
||||
} else {
|
||||
$model_caps = array();
|
||||
}
|
||||
} else {
|
||||
$model_caps = array();
|
||||
}
|
||||
|
||||
$models_data[ $model_slug ] = Model_Metadata::from_array(
|
||||
// The OpenAI API does not return a display name, so 'name' is omitted to auto-generate.
|
||||
array(
|
||||
'slug' => $model_slug,
|
||||
'capabilities' => $model_caps,
|
||||
)
|
||||
);
|
||||
return $models_data;
|
||||
},
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new model instance for the provided model metadata and parameters.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param Model_Metadata $model_metadata The model metadata.
|
||||
* @param array<string, mixed> $model_params Model parameters. See {@see Generative_AI_Service::get_model()} for
|
||||
* a list of available parameters.
|
||||
* @param array<string, mixed> $request_options The request options.
|
||||
* @return Generative_AI_Model The new model instance.
|
||||
*/
|
||||
protected function create_model_instance( Model_Metadata $model_metadata, array $model_params, array $request_options ): Generative_AI_Model {
|
||||
$model_class = AI_Capabilities::get_model_class_for_capabilities(
|
||||
array(
|
||||
OpenAI_AI_Text_Generation_Model::class
|
||||
),
|
||||
$model_metadata->get_capabilities()
|
||||
);
|
||||
|
||||
return new $model_class(
|
||||
$this->get_api_client(),
|
||||
$model_metadata,
|
||||
$model_params,
|
||||
$request_options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts model slugs by preference.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string[] $model_slugs The model slugs to sort.
|
||||
* @return string[] The model slugs, sorted by preference.
|
||||
*/
|
||||
protected function sort_models_by_preference( array $model_slugs ): array {
|
||||
// Prioritize latest, non-experimental models, preferring cheaper ones.
|
||||
$get_preference_group = static function ( $model_slug ) {
|
||||
if ( str_starts_with( $model_slug, 'gpt-4.1' ) ) {
|
||||
if ( str_ends_with( $model_slug, '-mini' ) ) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if ( str_starts_with( $model_slug, 'gpt-4o' ) ) {
|
||||
if ( str_ends_with( $model_slug, '-mini' ) ) {
|
||||
return 2;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
if ( str_starts_with( $model_slug, 'gpt-4' ) ) {
|
||||
if ( str_ends_with( $model_slug, '-turbo' ) ) {
|
||||
return 4;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
if ( str_starts_with( $model_slug, 'gpt-' ) ) {
|
||||
if ( str_ends_with( $model_slug, '-turbo' ) ) {
|
||||
return 6;
|
||||
}
|
||||
return 7;
|
||||
}
|
||||
return 8;
|
||||
};
|
||||
|
||||
$preference_groups = array_fill( 0, 9, array() );
|
||||
foreach ( $model_slugs as $model_slug ) {
|
||||
$group = $get_preference_group( $model_slug );
|
||||
$preference_groups[ $group ][] = $model_slug;
|
||||
}
|
||||
|
||||
return array_merge( ...$preference_groups );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\OpenAI\OpenAI_AI_Text_Generation_Model
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\OpenAI;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Types\Content;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Contracts\Tool;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Model_Metadata;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Parts;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Text_Generation_Config;
|
||||
use ATFPP\AI_Translate\Services\Base\OpenAI_Compatible_AI_Text_Generation_Model;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Generative_AI_API_Client;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Function_Calling;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Multimodal_Input;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Multimodal_Output;
|
||||
use ATFPP\AI_Translate\Services\Exception\Generative_AI_Exception;
|
||||
use ATFPP\AI_Translate\Services\Traits\OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing an OpenAI text generation AI model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.5.0 Renamed from `OpenAI_AI_Model`.
|
||||
* @since 0.7.0 Now extends `OpenAI_Compatible_AI_Text_Generation_Model` instead of `Abstract_AI_Model`.
|
||||
*/
|
||||
class OpenAI_AI_Text_Generation_Model extends OpenAI_Compatible_AI_Text_Generation_Model implements With_Function_Calling, With_Multimodal_Input, With_Multimodal_Output {
|
||||
use OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait {
|
||||
prepare_generate_text_params as prepare_generate_text_params_with_function_calling;
|
||||
prepare_tool as prepare_function_declarations_tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected MIME type of any audio output generated by the model.
|
||||
*
|
||||
* Internal temporary storage to not have to pass it around, as it should not be part of the interface.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $expected_audio_mime_type = 'audio/mpeg';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param Generative_AI_API_Client $api_client The AI API client instance.
|
||||
* @param Model_Metadata $metadata The model metadata.
|
||||
* @param array<string, mixed> $model_params Optional. Additional model parameters. See
|
||||
* {@see OpenAI_AI_Service::get_model()} for the list of available
|
||||
* parameters. Default empty array.
|
||||
* @param array<string, mixed> $request_options Optional. The request options. Default empty array.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the model parameters are invalid.
|
||||
*/
|
||||
public function __construct( Generative_AI_API_Client $api_client, Model_Metadata $metadata, array $model_params = array(), array $request_options = array() ) {
|
||||
parent::__construct( $api_client, $metadata, $model_params, $request_options );
|
||||
|
||||
$this->set_tool_config_from_model_params( $model_params );
|
||||
$this->set_tools_from_model_params( $model_params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the API request parameters for generating text content.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param Content[] $contents The contents to generate text for.
|
||||
* @return array<string, mixed> The parameters for generating text content.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if an invalid tool is provided.
|
||||
*/
|
||||
protected function prepare_generate_text_params( array $contents ): array {
|
||||
$params = $this->prepare_generate_text_params_with_function_calling( $contents );
|
||||
|
||||
// If 'audio' output is requested, the OpenAI API requires the 'audio' parameter to be set.
|
||||
if (
|
||||
isset( $params['modalities'] ) &&
|
||||
is_array( $params['modalities'] ) &&
|
||||
in_array( 'audio', $params['modalities'], true ) &&
|
||||
! isset( $params['audio'] )
|
||||
) {
|
||||
$params['audio'] = array(
|
||||
'voice' => 'alloy',
|
||||
'format' => 'mp3',
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $params['audio']['format'] ) ) {
|
||||
// Hack: Store the expected MIME type for audio output, as the OpenAI API does not return it.
|
||||
$this->expected_audio_mime_type = 'mp3' === $params['audio']['format'] ? 'audio/mpeg' : 'audio/' . $params['audio']['format'];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single tool for the API request, amending the provided parameters as needed.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $params The parameters to prepare the tools for. Passed by reference.
|
||||
* @param Tool $tool The tool to prepare.
|
||||
* @return bool True if the tool was successfully prepared, false otherwise.
|
||||
*/
|
||||
protected function prepare_tool( array &$params, Tool $tool ): bool {
|
||||
$result = $this->prepare_function_declarations_tool( $params, $tool );
|
||||
if ( ! $result ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The OpenAI API supports a 'strict' argument for function tools, which is not part of the standard OpenAI API
|
||||
* specification and therefore may not be supported by other providers.
|
||||
* Since it makes sense to always use it for OpenAI, we add it here if not set.
|
||||
*/
|
||||
if ( isset( $params['tools'] ) && is_array( $params['tools'] ) ) {
|
||||
$params['tools'] = array_map(
|
||||
function ( $openai_tool_data ) {
|
||||
if ( ! isset( $openai_tool_data['type'] ) || 'function' !== $openai_tool_data['type'] ) {
|
||||
return $openai_tool_data;
|
||||
}
|
||||
// Add the 'strict' argument to the function tool if not set.
|
||||
if ( ! isset( $openai_tool_data['function']['strict'] ) ) {
|
||||
$openai_tool_data['function']['strict'] = true;
|
||||
}
|
||||
return $openai_tool_data;
|
||||
},
|
||||
$params['tools']
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a given candidate from the API response into a Parts instance.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $candidate_data The API response candidate data.
|
||||
* @return Parts The Parts instance.
|
||||
*
|
||||
* @throws Generative_AI_Exception Thrown if the response is invalid.
|
||||
*/
|
||||
protected function prepare_response_candidate_content_parts( array $candidate_data ): Parts {
|
||||
$parts = parent::prepare_response_candidate_content_parts( $candidate_data );
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the generation configuration transformers.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, callable> The generation configuration transformers.
|
||||
*/
|
||||
protected function get_generation_config_transformers(): array {
|
||||
$transformers = parent::get_generation_config_transformers();
|
||||
|
||||
// Support multimodal output (e.g. for speech generation).
|
||||
$transformers['modalities'] = static function ( Text_Generation_Config $config ) {
|
||||
return $config->get_output_modalities();
|
||||
};
|
||||
|
||||
return $transformers;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user