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,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;
|
||||
}
|
||||
@@ -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'] );
|
||||
}
|
||||
}
|
||||
@@ -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'] );
|
||||
}
|
||||
}
|
||||
@@ -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'] );
|
||||
}
|
||||
}
|
||||
@@ -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'] );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user