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,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Deepl\Deepl_AI_API_Client
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Deepl;
|
||||
|
||||
use ATFPP\AI_Translate\Services\Base\Generic_AI_API_Client;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Authentication;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request_Handler;
|
||||
|
||||
/**
|
||||
* Class to interact directly with the Deepl API.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.7.0 Now extends `Generic_AI_API_Client`.
|
||||
*/
|
||||
class Deepl_AI_API_Client extends Generic_AI_API_Client {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param string $default_base_url The default base URL for the API.
|
||||
* @param string $default_api_version The default API version.
|
||||
* @param string $api_name The (human-readable) API name.
|
||||
* @param Request_Handler $request_handler The request handler instance.
|
||||
* @param Authentication|null $authentication Optional. The authentication instance. Default null.
|
||||
*/
|
||||
public function __construct(
|
||||
string $default_base_url,
|
||||
string $default_api_version,
|
||||
string $api_name,
|
||||
Request_Handler $request_handler,
|
||||
?Authentication $authentication = null
|
||||
) {
|
||||
// Set custom header name for Deepl API key authentication.
|
||||
if ( $authentication ) {
|
||||
$authentication->set_authencation_scheme( 'DeepL-Auth-Key' );
|
||||
}
|
||||
|
||||
$key_name=$authentication->get_option_definitions('deepl');
|
||||
|
||||
$key_value=get_option(array_keys($key_name)[0], '');
|
||||
|
||||
$key_free=str_ends_with($key_value, ':fx');
|
||||
|
||||
if($key_free){
|
||||
$default_base_url = 'https://api-free.deepl.com';
|
||||
}
|
||||
|
||||
parent::__construct( $default_base_url, $default_api_version, $api_name, $request_handler, $authentication );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URL for the specified model and task.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $path The path to the API endpoint, relative to the base URL and version.
|
||||
* @param array<string, mixed> $request_options Optional. The request options. Default empty array.
|
||||
* @return string The request URL.
|
||||
*/
|
||||
protected function get_request_url( string $path, array $request_options = array() ): string {
|
||||
if ( isset( $request_options['stream'] ) && $request_options['stream'] && ! str_ends_with( $path, '?alt=sse' ) ) {
|
||||
$path .= '?alt=sse';
|
||||
}
|
||||
|
||||
return parent::get_request_url( $path, $request_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional default request options to the given request options.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $request_options The request options.
|
||||
* @return array<string, mixed> The updated request options.
|
||||
*/
|
||||
protected function add_default_options( array $request_options ): array {
|
||||
$request_options = parent::add_default_options( $request_options );
|
||||
|
||||
return $request_options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Deepl\Deepl_AI_Service
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Deepl;
|
||||
|
||||
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\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 Deepl AI service.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.7.0 Now extends `Abstract_AI_Service`.
|
||||
*/
|
||||
class Deepl_AI_Service extends Abstract_AI_Service implements With_API_Client {
|
||||
use With_API_Client_Trait;
|
||||
|
||||
const DEFAULT_API_BASE_URL = 'https://api.deepl.com';
|
||||
const DEFAULT_API_VERSION = 'v2';
|
||||
|
||||
/**
|
||||
* 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 Deepl_AI_API_Client(
|
||||
self::DEFAULT_API_BASE_URL,
|
||||
self::DEFAULT_API_VERSION,
|
||||
'DeepL',
|
||||
$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( 'usage', array(), $request_options );
|
||||
|
||||
$response_data = $api->make_request( $request )->get_data();
|
||||
|
||||
if ( ! isset( $response_data['character_count'] ) || ! $response_data['character_limit'] ) {
|
||||
throw $api->create_missing_response_key_exception( 'character_limit' );
|
||||
}
|
||||
|
||||
return array('languages'=>Model_Metadata::from_array(array('slug'=>'languages','name'=>'Languages','capabilities'=>array(AI_Capability::TEXT_GENERATION))));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
Deepl_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 {
|
||||
return $model_slugs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Deepl\Deepl_AI_Text_Generation_Model
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Deepl;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Content_Role;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Candidate;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Candidates;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Content;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Model_Metadata;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Parts;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Parts\Text_Part;
|
||||
use ATFPP\AI_Translate\Services\Base\Abstract_AI_Model;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Generative_AI_API_Client;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_API_Client;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Function_Calling;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Text_Generation;
|
||||
use ATFPP\AI_Translate\Services\Exception\Generative_AI_Exception;
|
||||
use ATFPP\AI_Translate\Services\Traits\Model_Param_Text_Generation_Config_Trait;
|
||||
use ATFPP\AI_Translate\Services\Traits\With_API_Client_Trait;
|
||||
use ATFPP\AI_Translate\Services\Traits\With_Text_Generation_Trait;
|
||||
use ATFPP\AI_Translate\Services\Util\Transformer;
|
||||
use Generator;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing a Deepl text generation AI model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.5.0 Renamed from `Deepl_AI_Model`.
|
||||
*/
|
||||
class Deepl_AI_Text_Generation_Model extends Abstract_AI_Model implements With_API_Client, With_Text_Generation, With_Function_Calling {
|
||||
use With_API_Client_Trait;
|
||||
use With_Text_Generation_Trait;
|
||||
use Model_Param_Text_Generation_Config_Trait;
|
||||
|
||||
/**
|
||||
* 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 Deepl_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() ) {
|
||||
$this->set_api_client( $api_client );
|
||||
$this->set_model_metadata( $metadata );
|
||||
|
||||
$this->set_text_generation_config_from_model_params( $model_params );
|
||||
|
||||
$this->set_request_options( $request_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to generate text content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param Content[] $contents Prompts for the content to generate.
|
||||
* @param array<string, mixed> $request_options The request options.
|
||||
* @return Candidates The response candidates with generated text content - usually just one.
|
||||
*
|
||||
* @throws Generative_AI_Exception Thrown if the request fails or the response is invalid.
|
||||
*/
|
||||
protected function send_generate_text_request( array $contents, array $request_options ): Candidates {
|
||||
|
||||
$api = $this->get_api_client();
|
||||
$params = $this->prepare_generate_text_params( $contents );
|
||||
|
||||
$request = $api->create_post_request(
|
||||
'translate',
|
||||
$params,
|
||||
array_merge(
|
||||
$this->get_request_options(),
|
||||
$request_options
|
||||
)
|
||||
);
|
||||
|
||||
$response = $api->make_request( $request );
|
||||
|
||||
return $api->process_response_data(
|
||||
$response,
|
||||
function ( $response_data ) {
|
||||
return $this->get_response_candidates( $response_data );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to generate text content, streaming the response.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param Content[] $contents Prompts for the content to generate.
|
||||
* @param array<string, mixed> $request_options The request options.
|
||||
* @return Generator<Candidates> Generator that yields the chunks of response candidates with generated text
|
||||
* content - usually just one candidate.
|
||||
*
|
||||
* @throws Generative_AI_Exception Thrown if the request fails or the response is invalid.
|
||||
*/
|
||||
protected function send_stream_generate_text_request( array $contents, array $request_options ): Generator {
|
||||
$api = $this->get_api_client();
|
||||
$params = $this->prepare_generate_text_params( $contents );
|
||||
|
||||
$model = $this->get_model_slug();
|
||||
if ( ! str_contains( $model, '/' ) ) {
|
||||
$model = 'models/' . $model;
|
||||
}
|
||||
|
||||
$request = $api->create_post_request(
|
||||
"{$model}:streamGenerateContent",
|
||||
$params,
|
||||
array_merge(
|
||||
$this->get_request_options(),
|
||||
$request_options,
|
||||
array( 'stream' => true )
|
||||
)
|
||||
);
|
||||
|
||||
$response = $api->make_request( $request );
|
||||
|
||||
return $api->process_response_stream(
|
||||
$response,
|
||||
function ( $response_data, $prev_chunk_candidates ) {
|
||||
return $this->get_response_candidates( $response_data, $prev_chunk_candidates );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the API request parameters for generating text content.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param Content[] $contents The contents to generate text for.
|
||||
* @return array<string, mixed> The parameters for generating text content.
|
||||
*/
|
||||
private function prepare_generate_text_params( array $contents ): array {
|
||||
$transformers = $this->get_content_transformers();
|
||||
|
||||
$params = Transformer::transform_content( $contents[0], $transformers );
|
||||
|
||||
if(isset($params['parts'])){
|
||||
$params = json_decode($params['parts'][0]['text'], true);
|
||||
}
|
||||
|
||||
return array_filter( $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the candidates with content from the response.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $response_data The response data.
|
||||
* @param ?Candidates $prev_chunk_candidates The candidates from the previous chunk in case of a streaming
|
||||
* response, or null.
|
||||
* @return Candidates The candidates with content parts.
|
||||
*
|
||||
* @throws Generative_AI_Exception Thrown if the response does not have any candidates with content.
|
||||
*/
|
||||
private function get_response_candidates( array $response_data, ?Candidates $prev_chunk_candidates = null ): Candidates {
|
||||
if ( ! isset( $response_data['translations'] ) ) {
|
||||
throw $this->get_api_client()->create_missing_response_key_exception( 'translations' );
|
||||
}
|
||||
|
||||
if ( null === $prev_chunk_candidates ) {
|
||||
$other_data = $response_data;
|
||||
unset( $other_data['translations'] );
|
||||
|
||||
$candidates = new Candidates();
|
||||
|
||||
$translate_strings=array();
|
||||
$translation_data=array();
|
||||
|
||||
foreach ( $response_data['translations'] as $index => $translation_data ) {
|
||||
$translate_strings[$index]=$translation_data['text'];
|
||||
|
||||
if(!isset($translation_data['confidence'])){
|
||||
$translation_data['confidence']=0;
|
||||
}
|
||||
|
||||
if(!isset($translation_data['detected_source_language'])){
|
||||
$translation_data['detected_source_language']='';
|
||||
}
|
||||
}
|
||||
|
||||
$translation_data['text']=json_encode($translate_strings, JSON_FORCE_OBJECT);
|
||||
|
||||
$candidates->add_candidate(
|
||||
new Candidate(
|
||||
$this->prepare_translation_content( $translation_data ),
|
||||
$other_data
|
||||
)
|
||||
);
|
||||
|
||||
return $candidates;
|
||||
}
|
||||
|
||||
// Subsequent chunk of a streaming response.
|
||||
$candidates_data = $this->merge_translation_chunk(
|
||||
$prev_chunk_candidates->to_array(),
|
||||
$response_data
|
||||
);
|
||||
|
||||
return Candidates::from_array( $candidates_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges a streaming response chunk with the previous candidates data.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param array<string, mixed> $candidates_data The candidates data from the previous chunk.
|
||||
* @param array<string, mixed> $chunk_data The response chunk data.
|
||||
* @return array<string, mixed> The merged candidates data.
|
||||
*
|
||||
* @throws Generative_AI_Exception Thrown if the response is invalid.
|
||||
*/
|
||||
private function merge_translation_chunk( array $candidates_data, array $chunk_data ): array {
|
||||
if ( ! isset( $chunk_data['translations'] ) ) {
|
||||
throw $this->get_api_client()->create_missing_response_key_exception( 'translations' );
|
||||
}
|
||||
|
||||
$other_data = $chunk_data;
|
||||
unset( $other_data['translations'] );
|
||||
|
||||
foreach ( $chunk_data['translations'] as $index => $candidate_data ) {
|
||||
$candidates_data[ $index ] = array_merge( $candidates_data[ $index ], $candidate_data, $other_data );
|
||||
}
|
||||
|
||||
return $candidates_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a given choice from the API response into a Content instance.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param array<string, mixed> $choice_data The API response candidate data.
|
||||
* @param int $index The index of the choice in the response.
|
||||
* @return Content The Content instance.
|
||||
*
|
||||
* @throws Generative_AI_Exception Thrown if the response is invalid.
|
||||
*/
|
||||
private function prepare_translation_content( array $translation_data ): Content {
|
||||
return new Content(
|
||||
Content_Role::MODEL,
|
||||
$this->prepare_translation_content_parts( $translation_data )
|
||||
);
|
||||
}
|
||||
|
||||
private function prepare_translation_content_parts( array $translation_data ): Parts {
|
||||
$parts[] = array(
|
||||
'text' => $translation_data['text'],
|
||||
'detected_source_language' => $translation_data['detected_source_language'] ?? '',
|
||||
'confidence' => $translation_data['confidence'] ?? 0,
|
||||
);
|
||||
|
||||
return Parts::from_array($parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content transformers.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since 0.7.0 Changed to non-static.
|
||||
*
|
||||
* @return array<string, callable> The content transformers.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
private function get_content_transformers(): array {
|
||||
return array(
|
||||
'role' => static function ( Content $content ) {
|
||||
return $content->get_role();
|
||||
},
|
||||
'parts' => static function ( Content $content ) {
|
||||
$parts = array();
|
||||
foreach ( $content->get_parts() as $part ) {
|
||||
if ( $part instanceof Text_Part ) {
|
||||
$parts[] = array( 'text' => $part->get_text() );
|
||||
}
|
||||
}
|
||||
return $parts;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user