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:
2025-12-28 12:44:00 +01:00
parent cf600ae727
commit cd264483f8
410 changed files with 60841 additions and 16 deletions

View File

@@ -0,0 +1,296 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\Generative_AI_API_Client_Trait
*
* @since 0.1.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\Exception\Generative_AI_Exception;
use ATFPP\AI_Translate\Services\HTTP\Contracts\Stream_Request_Handler;
use ATFPP\AI_Translate\Services\HTTP\Contracts\With_Stream;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request_Handler;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Response;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Request_Exception;
use Generator;
use InvalidArgumentException;
/**
* Trait for an API client class which implements the Generative_AI_API_Client interface.
*
* @since 0.1.0
*/
trait Generative_AI_API_Client_Trait {
/**
* Sends the given request to the API and returns the response data.
*
* @since 0.1.0
*
* @param Request $request The request instance.
* @return Response The response instance.
*
* @throws Generative_AI_Exception If an error occurs while making the request.
*/
final public function make_request( Request $request ): Response {
$request_handler = $this->get_request_handler();
$options = $request->get_options();
if ( isset( $options['stream'] ) && $options['stream'] ) {
if ( ! $request_handler instanceof Stream_Request_Handler ) {
throw new Generative_AI_Exception(
'Streaming requests are not supported by this API client.'
);
}
try {
$response = $request_handler->request_stream( $request );
} catch ( Request_Exception $e ) {
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
throw $this->create_request_exception( $e->getMessage() );
}
} else {
try {
$response = $request_handler->request( $request );
} catch ( Request_Exception $e ) {
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
throw $this->create_request_exception( $e->getMessage() );
}
}
if ( $response->get_status() < 200 || $response->get_status() >= 300 ) {
$data = $response->get_data();
if ( $data && isset( $data['error']['message'] ) && is_string( $data['error']['message'] ) ) {
$error_message = $data['error']['message'];
} elseif ( $data && isset( $data['error'] ) && is_string( $data['error'] ) ) {
$error_message = $data['error'];
} elseif ( $data && isset( $data['message'] ) && is_string( $data['message'] ) ) {
$error_message = $data['message'];
} else {
$error_message = sprintf(
'Bad status code: %d',
$response->get_status()
);
}
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
throw $this->create_request_exception( $error_message );
}
return $response;
}
/**
* Processes the response data from the API.
*
* @since 0.3.0
*
* @param Response $response The response instance. Must not be a stream response, i.e. not implement the
* With_Stream interface.
* @param callable $process_callback The callback to process the response data. Receives the JSON-decoded response
* data as associative array and should return the processed data in the desired
* format.
* @return mixed The processed response data.
*
* @throws Generative_AI_Exception If an error occurs while processing the response data.
*/
final public function process_response_data( Response $response, $process_callback ) {
if ( $response instanceof With_Stream ) {
throw new Generative_AI_Exception(
sprintf(
'Response must not implement %s.',
With_Stream::class
)
);
}
$data = $response->get_data();
if ( ! $data ) {
throw new Generative_AI_Exception(
'No data received in response.'
);
}
$processed_data = call_user_func( $process_callback, $data );
if ( ! $processed_data ) {
throw new Generative_AI_Exception(
'No data returned by process callback.'
);
}
return $processed_data;
}
/**
* Processes the response body from the API.
*
* @since 0.7.0
*
* @param Response $response The response instance. Must not be a stream response, i.e. not implement the
* With_Stream interface.
* @param callable $process_callback The callback to process the response body. Receives the response body as
* string and should return the processed data in the desired format.
* @return mixed The processed response data.
*
* @throws Generative_AI_Exception If an error occurs while processing the response body.
*/
final public function process_response_body( Response $response, $process_callback ) {
if ( $response instanceof With_Stream ) {
throw new Generative_AI_Exception(
sprintf(
'Response must not implement %s.',
With_Stream::class
)
);
}
$body = $response->get_body();
if ( ! $body ) {
throw new Generative_AI_Exception(
'No body received in response.'
);
}
$processed_data = call_user_func( $process_callback, $body );
if ( ! $processed_data ) {
throw new Generative_AI_Exception(
'No data returned by process callback.'
);
}
return $processed_data;
}
/**
* Processes the response data stream from the API.
*
* @since 0.3.0
*
* @param Response $response The response instance. Must implement With_Stream. The response data will
* be processed in chunks, with each chunk of data being passed to the process
* callback.
* @param callable $process_callback The callback to process the response data. Receives the JSON-decoded response
* data (associative array) as first parameter, and the previous processed data
* as second parameter (or null in case this is the first chunk). It should
* return the processed data for the chunk in the desired format.
* @return Generator Generator that yields the individual processed response data chunks.
*
* @throws Generative_AI_Exception If an error occurs while processing the response data.
*/
final public function process_response_stream( Response $response, $process_callback ): Generator {
if ( ! $response instanceof With_Stream ) {
throw new Generative_AI_Exception(
sprintf(
'Response does not implement %s.',
With_Stream::class
)
);
}
$stream_generator = $response->read_stream();
$previous_processed_data = null;
foreach ( $stream_generator as $data ) {
$processed_data = call_user_func( $process_callback, $data, $previous_processed_data );
if ( ! $processed_data ) {
continue;
}
$previous_processed_data = $processed_data;
yield $processed_data;
}
}
/**
* Creates a new exception for a bad request, i.e. invalid or unsupported request data.
*
* @since 0.7.0
*
* @param string $message The error message to include in the exception.
* @return InvalidArgumentException The exception instance.
*/
final public function create_bad_request_exception( string $message ): InvalidArgumentException {
return new InvalidArgumentException(
sprintf(
'Invalid request data for the %1$s API: %2$s',
$this->get_api_name(),
$message
)
);
}
/**
* Creates a new exception for an AI API request error.
*
* @since 0.1.0
* @since 0.3.0 Method made public.
*
* @param string $message The error message to include in the exception.
* @return Generative_AI_Exception The exception instance.
*/
final public function create_request_exception( string $message ): Generative_AI_Exception {
return new Generative_AI_Exception(
sprintf(
'Error while making request to the %1$s API: %2$s ',
$this->get_api_name(),
$message
)
);
}
/**
* Creates a new exception for an AI API response error.
*
* @since 0.3.0
*
* @param string $message The error message to include in the exception.
* @return Generative_AI_Exception The exception instance.
*/
final public function create_response_exception( string $message ): Generative_AI_Exception {
return new Generative_AI_Exception(
sprintf(
'Error in the response from the %1$s API: %2$s ',
$this->get_api_name(),
$message
)
);
}
/**
* Creates a new exception for an AI API response error for a missing key.
*
* @since 0.3.0
*
* @param string $key The missing key in the response data.
* @return Generative_AI_Exception The exception instance.
*/
final public function create_missing_response_key_exception( string $key ): Generative_AI_Exception {
return $this->create_response_exception(
sprintf(
'The response is missing the "%s" key.',
$key
)
);
}
/**
* Returns the request handler instance to use for requests.
*
* @since 0.1.0
* @since 0.6.0 Renamed from `get_http()`.
*
* @return Request_Handler The request handler instance.
*/
abstract protected function get_request_handler(): Request_Handler;
/**
* Returns the human readable API name (without the "API" suffix).
*
* @since 0.1.0
*
* @return string The API name.
*/
abstract protected function get_api_name(): string;
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\Model_Param_System_Instruction_Trait
*
* @since 0.7.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\API\Types\Content;
use ATFPP\AI_Translate\Services\Util\Formatter;
use InvalidArgumentException;
/**
* Trait for a model that uses a system instruction.
*
* @since 0.7.0
*/
trait Model_Param_System_Instruction_Trait {
/**
* The system instruction.
*
* @since 0.7.0
* @var Content|null
*/
private $system_instruction;
/**
* Gets the system instruction.
*
* @since 0.7.0
*
* @return Content|null The system instruction, or null if not set.
*/
final protected function get_system_instruction(): ?Content {
return $this->system_instruction;
}
/**
* Sets the system instruction.
*
* @since 0.7.0
*
* @param Content $system_instruction The system instruction.
*/
final protected function set_system_instruction( Content $system_instruction ): void {
$this->system_instruction = $system_instruction;
}
/**
* Sets the system instruction if provided in the `systemInstruction` model parameter.
*
* @since 0.7.0
*
* @param array<string, mixed> $model_params The model parameters.
*
* @throws InvalidArgumentException Thrown if the `systemInstruction` model parameter is invalid.
*/
protected function set_system_instruction_from_model_params( array $model_params ): void {
if ( ! isset( $model_params['systemInstruction'] ) ) {
return;
}
try {
$model_params['systemInstruction'] = Formatter::format_system_instruction( $model_params['systemInstruction'] );
} catch ( InvalidArgumentException $e ) {
throw new InvalidArgumentException(
sprintf(
'Invalid systemInstruction model parameter: %s',
htmlspecialchars( $e->getMessage() ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
)
);
}
$this->set_system_instruction( $model_params['systemInstruction'] );
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\Model_Param_Text_Generation_Config_Trait
*
* @since 0.7.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\API\Types\Text_Generation_Config;
use InvalidArgumentException;
/**
* Trait for a model that uses `Text_Generation_Config`.
*
* @since 0.7.0
*/
trait Model_Param_Text_Generation_Config_Trait {
/**
* The text generation configuration.
*
* @since 0.7.0
* @var Text_Generation_Config|null
*/
private $text_generation_config;
/**
* Gets the text generation configuration.
*
* @since 0.7.0
*
* @return Text_Generation_Config|null The text generation configuration, or null if not set.
*/
final protected function get_text_generation_config(): ?Text_Generation_Config {
return $this->text_generation_config;
}
/**
* Sets the text generation configuration.
*
* @since 0.7.0
*
* @param Text_Generation_Config $text_generation_config The text generation configuration.
*/
final protected function set_text_generation_config( Text_Generation_Config $text_generation_config ): void {
$this->text_generation_config = $text_generation_config;
}
/**
* Sets the text generation configuration if provided in the `generationConfig` model parameter.
*
* @since 0.7.0
*
* @param array<string, mixed> $model_params The model parameters.
*
* @throws InvalidArgumentException Thrown if the `generationConfig` model parameter is invalid.
*/
protected function set_text_generation_config_from_model_params( array $model_params ): void {
if ( ! isset( $model_params['generationConfig'] ) ) {
return;
}
if ( is_array( $model_params['generationConfig'] ) ) {
$model_params['generationConfig'] = Text_Generation_Config::from_array( $model_params['generationConfig'] );
}
if ( ! $model_params['generationConfig'] instanceof Text_Generation_Config ) {
throw new InvalidArgumentException(
sprintf(
'Invalid generationConfig model parameter: The value must be an array or an instance of %s.',
Text_Generation_Config::class
)
);
}
$this->set_text_generation_config( $model_params['generationConfig'] );
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\Model_Param_Tool_Config_Trait
*
* @since 0.7.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\API\Types\Tool_Config;
use InvalidArgumentException;
/**
* Trait for a model that uses `Tool_Config`.
*
* @since 0.7.0
*/
trait Model_Param_Tool_Config_Trait {
/**
* The tool configuration.
*
* @since 0.7.0
* @var Tool_Config|null
*/
private $tool_config;
/**
* Gets the tool configuration.
*
* @since 0.7.0
*
* @return Tool_Config|null The tool configuration, or null if not set.
*/
final protected function get_tool_config(): ?Tool_Config {
return $this->tool_config;
}
/**
* Sets the tool configuration.
*
* @since 0.7.0
*
* @param Tool_Config $tool_config The tool configuration.
*/
final protected function set_tool_config( Tool_Config $tool_config ): void {
$this->tool_config = $tool_config;
}
/**
* Sets the tool configuration if provided in the `toolConfig` model parameter.
*
* @since 0.7.0
*
* @param array<string, mixed> $model_params The model parameters.
*
* @throws InvalidArgumentException Thrown if the `toolConfig` model parameter is invalid.
*/
protected function set_tool_config_from_model_params( array $model_params ): void {
if ( ! isset( $model_params['toolConfig'] ) ) {
return;
}
if ( is_array( $model_params['toolConfig'] ) ) {
$model_params['toolConfig'] = Tool_Config::from_array( $model_params['toolConfig'] );
}
if ( ! $model_params['toolConfig'] instanceof Tool_Config ) {
throw new InvalidArgumentException(
sprintf(
'Invalid toolConfig model parameter: The value must be an array or an instance of %s.',
Tool_Config::class
)
);
}
$this->set_tool_config( $model_params['toolConfig'] );
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\Model_Param_Tools_Trait
*
* @since 0.7.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\API\Types\Tools;
use InvalidArgumentException;
/**
* Trait for a model that uses `Tools`.
*
* @since 0.7.0
*/
trait Model_Param_Tools_Trait {
/**
* The tools instance.
*
* @since 0.7.0
* @var Tools|null
*/
private $tools;
/**
* Gets the tools instance.
*
* @since 0.7.0
*
* @return Tools|null The tools instance, or null if not set.
*/
final protected function get_tools(): ?Tools {
return $this->tools;
}
/**
* Sets the tools instance.
*
* @since 0.7.0
*
* @param Tools $tools The tools instance.
*/
final protected function set_tools( Tools $tools ): void {
$this->tools = $tools;
}
/**
* Sets the tools instance if provided in the `tools` model parameter.
*
* @since 0.7.0
*
* @param array<string, mixed> $model_params The model parameters.
*
* @throws InvalidArgumentException Thrown if the `tools` model parameter is invalid.
*/
protected function set_tools_from_model_params( array $model_params ): void {
if ( ! isset( $model_params['tools'] ) ) {
return;
}
if ( is_array( $model_params['tools'] ) ) {
$model_params['tools'] = Tools::from_array( $model_params['tools'] );
}
if ( ! $model_params['tools'] instanceof Tools ) {
throw new InvalidArgumentException(
sprintf(
'Invalid tools model parameter: The value must be an array or an instance of %s.',
Tools::class
)
);
}
$this->set_tools( $model_params['tools'] );
}
}

View File

@@ -0,0 +1,273 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait
*
* @since 0.7.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\API\Types\Content;
use ATFPP\AI_Translate\Services\API\Types\Contracts\Tool;
use ATFPP\AI_Translate\Services\API\Types\Parts;
use ATFPP\AI_Translate\Services\API\Types\Parts\Function_Call_Part;
use ATFPP\AI_Translate\Services\API\Types\Parts\Function_Response_Part;
use ATFPP\AI_Translate\Services\API\Types\Tool_Config;
use ATFPP\AI_Translate\Services\API\Types\Tools\Function_Declarations_Tool;
use ATFPP\AI_Translate\Services\Exception\Generative_AI_Exception;
use InvalidArgumentException;
/**
* Trait for an OpenAI compatible text generation model which implements function calling.
*
* @since 0.7.0
*/
trait OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait {
use Model_Param_Tool_Config_Trait;
use Model_Param_Tools_Trait;
/**
* 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 = parent::prepare_generate_text_params( $contents );
if ( $this->get_tools() ) {
foreach ( $this->get_tools() as $tool ) {
$prepared = $this->prepare_tool( $params, $tool );
if ( ! $prepared ) {
throw $this->get_api_client()->create_bad_request_exception(
'Only function declarations tools are supported.'
);
}
}
}
if ( $this->get_tool_config() ) {
$params['tool_choice'] = $this->prepare_tool_choice_param( $this->get_tool_config() );
}
return $params;
}
/**
* 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 );
if ( isset( $candidate_data['message']['tool_calls'] ) && is_array( $candidate_data['message']['tool_calls'] ) ) {
foreach ( $candidate_data['message']['tool_calls'] as $tool_call ) {
$prepared = $this->prepare_response_message_tool_call( $parts, $tool_call );
if ( ! $prepared ) {
throw $this->get_api_client()->create_response_exception(
'The response includes a tool call of an unexpected type.'
);
}
}
}
return $parts;
}
/**
* Prepares a given tool call from the response message, amending the provided Parts instance as needed.
*
* @since 0.7.0
*
* @param Parts $parts The Parts instance to amend.
* @param array<string, mixed> $tool_call_data The tool call data from the response message.
* @return bool True if the tool call was successfully prepared, false otherwise.
*/
protected function prepare_response_message_tool_call( Parts $parts, array $tool_call_data ): bool {
// Not all OpenAI compatible APIs include a 'type' key, so we only check its value if it is set.
if (
( isset( $tool_call_data['type'] ) && 'function' !== $tool_call_data['type'] ) ||
! isset( $tool_call_data['function'] ) ) {
return false;
}
$parts->add_function_call_part(
$tool_call_data['id'],
$tool_call_data['function']['name'],
is_string( $tool_call_data['function']['arguments'] )
? json_decode( $tool_call_data['function']['arguments'], true )
: $tool_call_data['function']['arguments']
);
return true;
}
/**
* 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 {
if ( ! $tool instanceof Function_Declarations_Tool ) {
return false;
}
$function_declarations = $tool->get_function_declarations();
if ( count( $function_declarations ) > 0 ) {
if ( ! isset( $params['tools'] ) ) {
$params['tools'] = array();
}
foreach ( $function_declarations as $declaration ) {
$params['tools'][] = array(
'type' => 'function',
'function' => array_filter(
array(
'name' => $declaration['name'],
'description' => $declaration['description'] ?? null,
'parameters' => $declaration['parameters'] ?? null,
)
),
);
}
}
return true;
}
/**
* Prepares the API request tool choice parameter for the model.
*
* @since 0.7.0
*
* @param Tool_Config $tool_config The tool config to prepare the parameter with.
* @return array<string, mixed> The tool config parameter value.
*/
private function prepare_tool_choice_param( Tool_Config $tool_config ): array {
// Either 'auto' or 'any'.
$tool_choice_param = $tool_config->get_function_call_mode() === 'any' ? 'required' : 'auto';
if ( 'required' === $tool_choice_param ) {
// If one specific function must be called, the parameter needs to be an object, otherwise a string.
$allowed_function_names = $tool_config->get_allowed_function_names();
if ( count( $allowed_function_names ) === 1 ) {
$tool_choice_param = array(
'type' => 'function',
'function' => array( 'name' => $allowed_function_names[0] ),
);
}
}
return $tool_choice_param;
}
/**
* Gets the content transformers.
*
* @since 0.7.0
*
* @return array<string, callable> The content transformers.
*
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function get_content_transformers(): array {
$api_client = $this->get_api_client();
$transformers = parent::get_content_transformers();
$orig_role_transformer = $transformers['role'];
$orig_content_transformer = $transformers['content'];
$transformers['role'] = static function ( Content $content ) use ( $orig_role_transformer ) {
// Special case of a function response.
$parts = $content->get_parts();
if ( count( $parts ) === 1 && $parts->get( 0 ) instanceof Function_Response_Part ) {
return 'tool';
}
return $orig_role_transformer( $content );
};
$transformers['content'] = static function ( Content $content ) use ( $orig_content_transformer, $api_client ) {
// Special case of a function response.
$parts = $content->get_parts();
if ( count( $parts ) === 1 && $parts->get( 0 ) instanceof Function_Response_Part ) {
$response = $parts->get( 0 )->get_response();
return json_encode( $response ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
}
$sanitized_parts = new Parts();
foreach ( $parts as $part ) {
/*
* Special cases: Function call parts are handled as part of a separate `tool_calls` key, and
* function response parts are are only supported as the only content of a message. They are
* handled as a special case above.
*/
if ( $part instanceof Function_Response_Part ) {
throw $api_client->create_bad_request_exception(
'The API only allows a single function response, and it has to be the only content of the message.'
);
}
if ( $part instanceof Function_Call_Part ) {
// Skip function call parts, they are handled in a separate `tool_calls` key.
continue;
}
$sanitized_parts->add_part( $part );
}
$sanitized_content = new Content( $content->get_role(), $sanitized_parts );
return $orig_content_transformer( $sanitized_content );
};
$transformers['tool_calls'] = static function ( Content $content ) {
// Special key that only applies in case function calls are present.
$tool_calls = array();
foreach ( $content->get_parts() as $part ) {
if ( $part instanceof Function_Call_Part ) {
$tool_calls[] = array(
'type' => 'function',
'id' => $part->get_id(),
'function' => array(
'name' => $part->get_name(),
'arguments' => json_encode( $part->get_args() ), // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
),
);
}
}
if ( count( $tool_calls ) > 0 ) {
return $tool_calls;
}
return null;
};
$transformers['tool_call_id'] = static function ( Content $content ) {
// Special key that only applies in case of a function response.
$parts = $content->get_parts();
if ( count( $parts ) === 1 && $parts->get( 0 ) instanceof Function_Response_Part ) {
return $parts->get( 0 )->get_id();
}
return null;
};
return $transformers;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\With_API_Client_Trait
*
* @since 0.7.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\Contracts\Generative_AI_API_Client;
use RuntimeException;
/**
* Trait for a service or model which implements the With_API_Client interface.
*
* @since 0.7.0
*/
trait With_API_Client_Trait {
/**
* The AI API client instance.
*
* @since 0.7.0
* @var Generative_AI_API_Client
*/
private $api_client;
/**
* Gets the API client instance.
*
* @since 0.7.0
*
* @return Generative_AI_API_Client The API client instance.
*
* @throws RuntimeException Thrown if the API client is not set.
*/
final public function get_api_client(): Generative_AI_API_Client {
if ( ! $this->api_client instanceof Generative_AI_API_Client ) {
throw new RuntimeException( 'API client must be set in the constructor.' );
}
return $this->api_client;
}
/**
* Sets the API client instance.
*
* @since 0.7.0
*
* @param Generative_AI_API_Client $api_client The API client instance.
*/
final protected function set_api_client( Generative_AI_API_Client $api_client ): void {
$this->api_client = $api_client;
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Trait ATFPP\AI_Translate\Services\Traits\With_Text_Generation_Trait
*
* @since 0.1.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\Traits;
use ATFPP\AI_Translate\Services\API\Types\Candidates;
use ATFPP\AI_Translate\Services\API\Types\Content;
use ATFPP\AI_Translate\Services\API\Types\Parts;
use ATFPP\AI_Translate\Services\Exception\Generative_AI_Exception;
use ATFPP\AI_Translate\Services\Util\AI_Capabilities;
use ATFPP\AI_Translate\Services\Util\Formatter;
use Generator;
use InvalidArgumentException;
/**
* Trait for a model which implements the With_Text_Generation interface.
*
* @since 0.1.0
*/
trait With_Text_Generation_Trait {
/**
* Generates text content using the model.
*
* @since 0.1.0
*
* @param string|Parts|Content|Content[] $content Prompt for the content to generate. Optionally, an array
* can be passed for additional context (e.g. chat history).
* @param array<string, mixed> $request_options Optional. The request options. Default empty array.
* @return Candidates The response candidates with generated text content - usually just one.
*
* @throws InvalidArgumentException Thrown if the given content is invalid.
* @throws Generative_AI_Exception Thrown if the request fails or the response is invalid.
*/
final public function generate_text( $content, array $request_options = array() ): Candidates {
$contents = $this->sanitize_new_content( $content );
return $this->send_generate_text_request( $contents, $request_options );
}
/**
* Generates text content using the model, streaming the response.
*
* @since 0.3.0
*
* @param string|Parts|Content|Content[] $content Prompt for the content to generate. Optionally, an array
* can be passed for additional context (e.g. chat history).
* @param array<string, mixed> $request_options Optional. The request options. Default empty array.
* @return Generator<Candidates> Generator that yields the chunks of response candidates with generated text
* content - usually just one candidate.
*
* @throws InvalidArgumentException Thrown if the given content is invalid.
* @throws Generative_AI_Exception Thrown if the request fails or the response is invalid.
*/
final public function stream_generate_text( $content, array $request_options = array() ): Generator {
$contents = $this->sanitize_new_content( $content );
return $this->send_stream_generate_text_request( $contents, $request_options );
}
/**
* Sanitizes the input content for generating text.
*
* @since 0.3.0
*
* @param string|Parts|Content|Content[] $content The input content.
* @return Content[] The sanitized content.
*
* @throws InvalidArgumentException Thrown if the input content is invalid.
*/
private function sanitize_new_content( $content ) {
$capabilities = AI_Capabilities::get_model_instance_capabilities( $this );
return Formatter::format_and_validate_new_contents( $content, $capabilities );
}
/**
* 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.
*/
abstract protected function send_generate_text_request( array $contents, array $request_options ): Candidates;
/**
* 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.
*/
abstract protected function send_stream_generate_text_request( array $contents, array $request_options ): Generator;
}