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,151 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Candidates_Stream_Processor
|
||||
*
|
||||
* @since 0.3.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Types\Candidates;
|
||||
use Generator;
|
||||
|
||||
/**
|
||||
* Class to process a candidates stream.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*/
|
||||
final class Candidates_Stream_Processor {
|
||||
|
||||
/**
|
||||
* Generator that yields the chunks of response candidates.
|
||||
*
|
||||
* @since 0.3.0
|
||||
* @var Generator<Candidates>
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
/**
|
||||
* The overall candidates instance.
|
||||
*
|
||||
* May be incomplete if the stream has not been fully processed yet.
|
||||
*
|
||||
* @since 0.3.0
|
||||
* @var Candidates|null
|
||||
*/
|
||||
private $candidates;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param Generator<Candidates> $generator The generator that yields the chunks of response candidates.
|
||||
*/
|
||||
public function __construct( Generator $generator ) { // phpcs:ignore Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all chunks from the generator and adds them to the overall candidates instance.
|
||||
*
|
||||
* A callback can be passed that is called for each chunk of candidates. You could use such a callback for example
|
||||
* to echo the text contents of each chunk as they are being processed.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param callable|null $chunk_callback Optional. Callback that is called for each chunk of candidates.
|
||||
* @return Candidates The complete candidates instance.
|
||||
*/
|
||||
public function read_all( ?callable $chunk_callback = null ): Candidates {
|
||||
foreach ( $this->generator as $candidates ) {
|
||||
$this->add_chunk( $candidates );
|
||||
if ( null !== $chunk_callback ) {
|
||||
$chunk_callback( $candidates );
|
||||
}
|
||||
}
|
||||
return $this->get_complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a chunk of candidates to the overall candidates instance.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param Candidates $candidates The chunk of candidates to add.
|
||||
*/
|
||||
public function add_chunk( Candidates $candidates ): void {
|
||||
if ( null === $this->candidates ) {
|
||||
$this->candidates = $candidates;
|
||||
return;
|
||||
}
|
||||
|
||||
$existing_candidates = $this->candidates->to_array();
|
||||
$new_candidates = $candidates->to_array();
|
||||
|
||||
foreach ( $new_candidates as $index => $new_candidate ) {
|
||||
if ( ! isset( $existing_candidates[ $index ] ) ) {
|
||||
$existing_candidates[] = $new_candidate;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $existing_candidates[ $index ]['content'] ) && isset( $new_candidate['content'] ) ) {
|
||||
$existing_candidates[ $index ]['content'] = $this->append_content(
|
||||
$existing_candidates[ $index ]['content'],
|
||||
$new_candidate['content']
|
||||
);
|
||||
unset( $new_candidate['content'] );
|
||||
}
|
||||
|
||||
$existing_candidates[ $index ] = array_merge( $existing_candidates[ $index ], $new_candidate );
|
||||
}
|
||||
|
||||
$this->candidates = Candidates::from_array( $existing_candidates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the complete candidates instance.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @return Candidates|null The complete candidates instance, or null if the generator is not done yet.
|
||||
*/
|
||||
public function get_complete(): ?Candidates {
|
||||
// Only return the candidates if the generator is done.
|
||||
if ( $this->generator->valid() ) {
|
||||
return null;
|
||||
}
|
||||
return $this->candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the content of a new candidate to the content of an existing candidate.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param array<string, mixed> $existing_content The existing content data.
|
||||
* @param array<string, mixed> $new_content The new content data.
|
||||
* @return array<string, mixed> The combined content data.
|
||||
*/
|
||||
private function append_content( array $existing_content, array $new_content ) {
|
||||
if ( ! isset( $existing_content['parts'] ) || ! isset( $new_content['parts'] ) ) {
|
||||
return $existing_content;
|
||||
}
|
||||
|
||||
foreach ( $new_content['parts'] as $index => $new_part ) {
|
||||
if ( ! isset( $existing_content['parts'][ $index ] ) ) {
|
||||
$existing_content['parts'][] = $new_part;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset( $existing_content['parts'][ $index ]['text'] ) || ! isset( $new_part['text'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing_content['parts'][ $index ]['text'] .= $new_part['text'];
|
||||
}
|
||||
|
||||
return $existing_content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Enums\AI_Capability
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Enums;
|
||||
|
||||
/**
|
||||
* Class for the AI capability enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
final class AI_Capability extends Abstract_Enum {
|
||||
|
||||
const CHAT_HISTORY = 'chat_history';
|
||||
const FUNCTION_CALLING = 'function_calling';
|
||||
const IMAGE_GENERATION = 'image_generation';
|
||||
const MULTIMODAL_INPUT = 'multimodal_input';
|
||||
const MULTIMODAL_OUTPUT = 'multimodal_output';
|
||||
const TEXT_GENERATION = 'text_generation';
|
||||
const TEXT_TO_SPEECH = 'text_to_speech';
|
||||
const WEB_SEARCH = 'web_search';
|
||||
|
||||
/**
|
||||
* Gets all values for the enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string[] The list of all values.
|
||||
*/
|
||||
protected static function get_all_values(): array {
|
||||
return array(
|
||||
self::CHAT_HISTORY,
|
||||
self::FUNCTION_CALLING,
|
||||
self::IMAGE_GENERATION,
|
||||
self::MULTIMODAL_INPUT,
|
||||
self::MULTIMODAL_OUTPUT,
|
||||
self::TEXT_GENERATION,
|
||||
self::TEXT_TO_SPEECH,
|
||||
self::WEB_SEARCH,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Enums\Abstract_Enum
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Enums;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Contracts\Enum;
|
||||
|
||||
/**
|
||||
* Base class for an enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
abstract class Abstract_Enum implements Enum {
|
||||
|
||||
/**
|
||||
* The value map, to store in memory which values are valid.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @var array<string, array<string, bool>>
|
||||
*/
|
||||
private static $value_map = array();
|
||||
|
||||
/**
|
||||
* Checks if the given value is valid for the enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param string $value The value to check.
|
||||
* @return bool True if the value is valid, false otherwise.
|
||||
*/
|
||||
final public static function is_valid_value( string $value ): bool {
|
||||
$value_map = self::get_value_map_for_class( static::class );
|
||||
return isset( $value_map[ $value ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of valid values for the enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string[] The list of valid values.
|
||||
*/
|
||||
final public static function get_values(): array {
|
||||
$value_map = self::get_value_map_for_class( static::class );
|
||||
return array_keys( $value_map );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value map for the given child class name.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param string $class_name The child class name.
|
||||
* @return array<string, bool> The value map.
|
||||
*/
|
||||
private static function get_value_map_for_class( string $class_name ): array {
|
||||
if ( ! isset( self::$value_map[ $class_name ] ) ) {
|
||||
self::$value_map[ $class_name ] = array_fill_keys( call_user_func( array( $class_name, 'get_all_values' ) ), true );
|
||||
}
|
||||
return self::$value_map[ $class_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all values for the enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string[] The list of all values.
|
||||
*/
|
||||
abstract protected static function get_all_values(): array;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Enums\Content_Role
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Enums;
|
||||
|
||||
/**
|
||||
* Class for the content role enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
final class Content_Role extends Abstract_Enum {
|
||||
|
||||
const USER = 'user';
|
||||
const MODEL = 'model';
|
||||
const SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
* Gets all values for the enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string[] The list of all values.
|
||||
*/
|
||||
protected static function get_all_values(): array {
|
||||
return array(
|
||||
self::USER,
|
||||
self::MODEL,
|
||||
self::SYSTEM,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface ATFPP\AI_Translate\Services\API\Enums\Contracts\Enum
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Enums\Contracts;
|
||||
|
||||
/**
|
||||
* Interface for a class for an enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
interface Enum {
|
||||
|
||||
/**
|
||||
* Checks if the given value is valid for the enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param string $value The value to check.
|
||||
* @return bool True if the value is valid, false otherwise.
|
||||
*/
|
||||
public static function is_valid_value( string $value ): bool;
|
||||
|
||||
/**
|
||||
* Gets the list of valid values for the enum.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string[] The list of valid values.
|
||||
*/
|
||||
public static function get_values(): array;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Enums\Modality
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Enums;
|
||||
|
||||
/**
|
||||
* Class for the modality enum.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
final class Modality extends Abstract_Enum {
|
||||
|
||||
const TEXT = 'text';
|
||||
const IMAGE = 'image';
|
||||
const AUDIO = 'audio';
|
||||
|
||||
/**
|
||||
* Gets all values for the enum.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string[] The list of all values.
|
||||
*/
|
||||
protected static function get_all_values(): array {
|
||||
return array(
|
||||
self::TEXT,
|
||||
self::IMAGE,
|
||||
self::AUDIO,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Enums\Service_Type
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Enums;
|
||||
|
||||
/**
|
||||
* Class for the service type enum.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
final class Service_Type extends Abstract_Enum {
|
||||
|
||||
const CLOUD = 'cloud';
|
||||
const SERVER = 'server';
|
||||
const CLIENT = 'client';
|
||||
|
||||
/**
|
||||
* Gets all values for the enum.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string[] The list of all values.
|
||||
*/
|
||||
protected static function get_all_values(): array {
|
||||
return array(
|
||||
self::CLOUD,
|
||||
self::SERVER,
|
||||
self::CLIENT,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Helpers
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Content_Role;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Blob;
|
||||
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\API\Types\Parts\Text_Part;
|
||||
use ATFPP\AI_Translate\Services\Util\Formatter;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Current_User;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Meta_Repository;
|
||||
use Generator;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class providing static helper methods as part of the public API.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
|
||||
*/
|
||||
final class Helpers {
|
||||
|
||||
/**
|
||||
* Converts a text string to a Content instance.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param string $text The text.
|
||||
* @param string $role Optional. The role to use for the content. Default 'user'.
|
||||
* @return Content The content instance.
|
||||
*/
|
||||
public static function text_to_content( string $text, string $role = Content_Role::USER ): Content {
|
||||
return Formatter::format_content( $text, $role );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Content instance to a text string.
|
||||
*
|
||||
* This method will return the combined text from all consecutive text parts in the content.
|
||||
* Realistically, this should almost always return the text from just one part, as API responses typically do not
|
||||
* contain multiple text parts in a row - but it might be possible.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param Content $content The content instance.
|
||||
* @return string The text, or an empty string if there are no text parts.
|
||||
*/
|
||||
public static function content_to_text( Content $content ): string {
|
||||
$parts = $content->get_parts();
|
||||
|
||||
$text_parts = array();
|
||||
foreach ( $parts as $part ) {
|
||||
/*
|
||||
* If there is any non-text part present, we want to ensure that no interrupted text content is returned.
|
||||
* Therefore, we break the loop as soon as we encounter a non-text part, unless no text parts have been
|
||||
* found yet, in which case the text may only start with a later part.
|
||||
*/
|
||||
if ( ! $part instanceof Text_Part ) {
|
||||
if ( count( $text_parts ) > 0 ) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$text_parts[] = trim( $part->get_text() );
|
||||
}
|
||||
|
||||
if ( count( $text_parts ) === 0 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return implode( "\n\n", $text_parts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text from the first Content instance in the given list which contains text.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param Content[] $contents The list of Content instances.
|
||||
* @return string The text, or an empty string if no Content instance has text parts.
|
||||
*/
|
||||
public static function get_text_from_contents( array $contents ): string {
|
||||
foreach ( $contents as $content ) {
|
||||
$text = self::content_to_text( $content );
|
||||
if ( '' !== $text ) {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first Content instance in the given list which contains text.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param Content[] $contents The list of Content instances.
|
||||
* @return Content|null The Content instance, or null if no Content instance has text parts.
|
||||
*/
|
||||
public static function get_text_content_from_contents( array $contents ): ?Content {
|
||||
foreach ( $contents as $content ) {
|
||||
$text = self::content_to_text( $content );
|
||||
if ( '' !== $text ) {
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Content instances for each candidate in the given list.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param Candidates $candidates The list of candidates.
|
||||
* @return Content[] The list of Content instances.
|
||||
*/
|
||||
public static function get_candidate_contents( Candidates $candidates ): array {
|
||||
$contents = array();
|
||||
|
||||
foreach ( $candidates as $candidate ) {
|
||||
$content = $candidate->get_content();
|
||||
if ( ! $content ) {
|
||||
continue;
|
||||
}
|
||||
$contents[] = $content;
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a stream of candidates, aggregating the candidates chunks into a single candidates instance.
|
||||
*
|
||||
* This method returns a stream processor instance that can be used to read all chunks from the given candidates
|
||||
* generator and process them with a callback. Alternatively, you can read from the generator yourself and provide
|
||||
* all chunks to the processor manually.
|
||||
*
|
||||
* @since 0.3.0
|
||||
*
|
||||
* @param Generator<Candidates> $generator The generator that yields the chunks of response candidates.
|
||||
* @return Candidates_Stream_Processor The stream processor instance.
|
||||
*/
|
||||
public static function process_candidates_stream( Generator $generator ): Candidates_Stream_Processor { // phpcs:ignore Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
return new Candidates_Stream_Processor( $generator );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base64-encoded data URL representation of the given file URL.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $file Absolute path to the file, or its URL.
|
||||
* @param string $mime_type Optional. The MIME type of the file. If provided, the base64-encoded data URL will
|
||||
* be prefixed with `data:{mime_type};base64,`. Default empty string.
|
||||
* @return string The base64-encoded file data URL, or empty string on failure.
|
||||
*/
|
||||
public static function file_to_base64_data_url( string $file, string $mime_type = '' ): string {
|
||||
$blob = self::file_to_blob( $file, $mime_type );
|
||||
if ( ! $blob ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::blob_to_base64_data_url( $blob );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary data blob representation of the given file URL.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $file Absolute path to the file, or its URL.
|
||||
* @param string $mime_type Optional. The MIME type of the file. If provided, the automatically detected MIME type
|
||||
* will be overwritten. Default empty string.
|
||||
* @return Blob|null The binary data blob, or null on failure.
|
||||
*/
|
||||
public static function file_to_blob( string $file, string $mime_type = '' ): ?Blob {
|
||||
try {
|
||||
return Blob::from_file( $file, $mime_type );
|
||||
} catch ( InvalidArgumentException $e ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base64-encoded data URL representation of the given binary data blob.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param Blob $blob The binary data blob.
|
||||
* @return string The base64-encoded file data URL, or empty string on failure.
|
||||
*/
|
||||
public static function blob_to_base64_data_url( Blob $blob ): string {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
$base64 = base64_encode( $blob->get_binary_data() );
|
||||
$mime_type = $blob->get_mime_type();
|
||||
return "data:$mime_type;base64,$base64";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary data blob representation of the given base64-encoded data URL.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $base64_data_url The base64-encoded data URL.
|
||||
* @return Blob|null The binary data blob, or null on failure.
|
||||
*/
|
||||
public static function base64_data_url_to_blob( string $base64_data_url ): ?Blob {
|
||||
if ( ! preg_match( '/^data:([a-z0-9-]+\/[a-z0-9-]+);base64,/', $base64_data_url, $matches ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$base64 = substr( $base64_data_url, strlen( $matches[0] ) );
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
$binary_data = base64_decode( $base64 );
|
||||
if ( false === $binary_data ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Blob( $binary_data, $matches[1] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given base64 data is prefixed correctly to be a data URL.
|
||||
*
|
||||
* @since 0.6.0
|
||||
*
|
||||
* @param string $base64_data Base64-encoded data. If it is already a data URL, it will be returned as is.
|
||||
* @param string $mime_type MIME type for the data.
|
||||
* @return string The base64 data URL.
|
||||
*/
|
||||
public static function base64_data_to_base64_data_url( string $base64_data, string $mime_type ): string {
|
||||
if ( str_starts_with( $base64_data, 'data:' ) ) {
|
||||
return $base64_data;
|
||||
}
|
||||
|
||||
return 'data:' . $mime_type . ';base64,' . $base64_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given base64 data URL has its prefix removed to be just the base64 data.
|
||||
*
|
||||
* @since 0.6.0
|
||||
*
|
||||
* @param string $base64_data_url Base64 data URL. If it is already without prefix, it will be returned as is.
|
||||
* @return string The base64-encoded data.
|
||||
*/
|
||||
public static function base64_data_url_to_base64_data( string $base64_data_url ): string {
|
||||
if ( ! str_starts_with( $base64_data_url, 'data:' ) ) {
|
||||
return $base64_data_url;
|
||||
}
|
||||
|
||||
return preg_replace(
|
||||
'/^data:[a-z0-9-]+\/[a-z0-9-]+;base64,/',
|
||||
'',
|
||||
$base64_data_url
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Blob
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Simple value class representing a binary data blob, e.g. from a file.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class Blob {
|
||||
|
||||
/**
|
||||
* The binary data of the blob.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var string
|
||||
*/
|
||||
private $binary_data;
|
||||
|
||||
/**
|
||||
* The MIME type of the blob.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var string
|
||||
*/
|
||||
private $mime_type;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $binary_data The binary data of the blob.
|
||||
* @param string $mime_type The MIME type of the blob.
|
||||
*/
|
||||
public function __construct( string $binary_data, string $mime_type ) {
|
||||
$this->binary_data = $binary_data;
|
||||
$this->mime_type = $mime_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the binary data of the blob.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The binary data.
|
||||
*/
|
||||
public function get_binary_data(): string {
|
||||
return $this->binary_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the MIME type of the blob.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The MIME type.
|
||||
*/
|
||||
public function get_mime_type(): string {
|
||||
return $this->mime_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blob instance from a file.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $file The file path or URL.
|
||||
* @param string $mime_type Optional. MIME type, to override the automatic detection. Default empty string.
|
||||
* @return Blob The blob instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the file could not be read or if the MIME type cannot be determined.
|
||||
*/
|
||||
public static function from_file( string $file, string $mime_type = '' ): self {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
$blob = file_get_contents( $file );
|
||||
if ( ! $blob ) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Could not read file %s.',
|
||||
htmlspecialchars( $file ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $mime_type ) {
|
||||
$file_type = wp_check_filetype( $file );
|
||||
if ( ! $file_type['type'] ) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Could not determine MIME type of file %s.',
|
||||
htmlspecialchars( $file ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
)
|
||||
);
|
||||
}
|
||||
$mime_type = $file_type['type'];
|
||||
}
|
||||
|
||||
return new self( $blob, $mime_type );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Candidate
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Content_Role;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing a candidate for a content response from a generative AI model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Candidate implements Arrayable {
|
||||
|
||||
/**
|
||||
* The content, unless no content is available as part of the candidate.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var ?Content
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* Additional data for the candidate, if any.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $additional_data;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param ?Content $content The content, or null to indicate no content is available.
|
||||
* @param array<string, mixed> $additional_data Additional data for the candidate, if any.
|
||||
*/
|
||||
public function __construct( ?Content $content, array $additional_data = array() ) {
|
||||
$this->content = $content;
|
||||
|
||||
// Remove the content from the additional data, if present, to prevent conflicts.
|
||||
unset( $additional_data['content'] );
|
||||
$this->additional_data = $additional_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return ?Content The content.
|
||||
*/
|
||||
public function get_content(): ?Content {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field value from the additional data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $field The field name.
|
||||
* @return mixed|null The field value, or null if not found.
|
||||
*/
|
||||
public function get_field_value( string $field ) {
|
||||
if ( isset( $this->additional_data[ $field ] ) ) {
|
||||
return $this->additional_data[ $field ];
|
||||
}
|
||||
|
||||
if ( str_contains( $field, '_' ) ) {
|
||||
$camel_case_field = $this->underscore_to_camel_case( $field );
|
||||
if ( isset( $this->additional_data[ $camel_case_field ] ) ) {
|
||||
return $this->additional_data[ $camel_case_field ];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A few common special cases.
|
||||
* For instance, "finish_reason" is sometimes called "stop_reason".
|
||||
*/
|
||||
switch ( $field ) {
|
||||
case 'finish_reason':
|
||||
return $this->get_field_value( 'stop_reason' );
|
||||
case 'finishReason':
|
||||
return $this->get_field_value( 'stopReason' );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, mixed> The additional data.
|
||||
*/
|
||||
public function get_additional_data(): array {
|
||||
return $this->additional_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array_merge(
|
||||
array(
|
||||
'content' => $this->content ? $this->content->to_array() : null,
|
||||
),
|
||||
$this->additional_data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Candidate instance from an array of content data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $data The content data.
|
||||
* @return Candidate Candidate instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the data is missing required fields.
|
||||
*/
|
||||
public static function from_array( array $data ): Candidate {
|
||||
if ( ! isset( $data['content'] ) ) {
|
||||
return new Candidate( null, $data );
|
||||
}
|
||||
|
||||
/*
|
||||
* Apparently, the API sometimes omits this.
|
||||
* Given candidates are always part of a model response, we can safely assume the role is 'model'.
|
||||
*/
|
||||
if ( ! isset( $data['content']['role'] ) ) {
|
||||
$data['content']['role'] = Content_Role::MODEL;
|
||||
}
|
||||
|
||||
$content = Content::from_array( $data['content'] );
|
||||
unset( $data['content'] );
|
||||
|
||||
return new Candidate( $content, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'content' => array_merge(
|
||||
array(
|
||||
'description' => __( 'Candidate content.', 'ai-services' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
Content::get_json_schema()
|
||||
),
|
||||
),
|
||||
'additionalProperties' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a snake_case string to camelCase.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $input The snake_case string.
|
||||
* @return string The camelCase string.
|
||||
*/
|
||||
private function underscore_to_camel_case( string $input ): string {
|
||||
return lcfirst( str_replace( '_', '', ucwords( $input, '_' ) ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Candidates
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ArrayIterator;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Collection;
|
||||
use InvalidArgumentException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class representing a collection of response candidates for a generative model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Candidates implements Collection, Arrayable {
|
||||
|
||||
/**
|
||||
* The candidates.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var Candidate[]
|
||||
*/
|
||||
private $candidates = array();
|
||||
|
||||
/**
|
||||
* Adds a candidate to the collection.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param Candidate $candidate The candidate.
|
||||
*/
|
||||
public function add_candidate( Candidate $candidate ): void {
|
||||
$this->candidates[] = $candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the candidates collection.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return ArrayIterator<int, Candidate> Collection iterator.
|
||||
*/
|
||||
public function getIterator(): Traversable {
|
||||
return new ArrayIterator( $this->candidates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the candidates collection.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return int Collection size.
|
||||
*/
|
||||
public function count(): int {
|
||||
return count( $this->candidates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the parts collection by the given criteria.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $args {
|
||||
* The filter arguments.
|
||||
*
|
||||
* @type string $part_class_name The class name to only allow candidates with content parts of that class.
|
||||
* }
|
||||
* @return Candidates The filtered parts collection.
|
||||
*/
|
||||
public function filter( array $args ): self {
|
||||
if ( isset( $args['part_class_name'] ) ) {
|
||||
$part_class_name = $args['part_class_name'];
|
||||
$map = static function ( Candidate $candidate ) use ( $part_class_name ) {
|
||||
$candidate_content = $candidate->get_content();
|
||||
if ( ! $candidate_content ) {
|
||||
return null;
|
||||
}
|
||||
$filtered_parts = $candidate_content->get_parts()->filter( array( 'class_name' => $part_class_name ) );
|
||||
if ( count( $filtered_parts ) > 0 ) {
|
||||
$candidate_data = $candidate->to_array();
|
||||
$candidate_data['content']['parts'] = $filtered_parts->to_array();
|
||||
return Candidate::from_array( $candidate_data );
|
||||
}
|
||||
return null;
|
||||
};
|
||||
} else {
|
||||
$map = static function ( Candidate $candidate ) {
|
||||
return Candidate::from_array( $candidate->to_array() );
|
||||
};
|
||||
}
|
||||
|
||||
$candidates = new Candidates();
|
||||
foreach ( $this->candidates as $candidate ) {
|
||||
$mapped_candidate = $map( $candidate );
|
||||
if ( $mapped_candidate ) {
|
||||
$candidates->add_candidate( $mapped_candidate );
|
||||
}
|
||||
}
|
||||
return $candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the candidate at the given index.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $index The index.
|
||||
* @return Candidate The candidate.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the index is out of bounds.
|
||||
*/
|
||||
public function get( int $index ): Candidate {
|
||||
if ( ! isset( $this->candidates[ $index ] ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
'Index out of bounds.'
|
||||
);
|
||||
}
|
||||
return $this->candidates[ $index ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, mixed>[] Array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array_map(
|
||||
static function ( Candidate $candidate ) {
|
||||
return $candidate->to_array();
|
||||
},
|
||||
$this->candidates
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Candidates instance from an array of candidates data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed>[] $data The candidates data.
|
||||
* @return Candidates The Candidates instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the candidates data is invalid.
|
||||
*/
|
||||
public static function from_array( array $data ): Candidates {
|
||||
$candidates = new Candidates();
|
||||
|
||||
foreach ( $data as $candidate ) {
|
||||
if ( ! is_array( $candidate ) ) {
|
||||
throw new InvalidArgumentException( 'Invalid candidate data.' );
|
||||
}
|
||||
|
||||
$candidates->add_candidate( Candidate::from_array( $candidate ) );
|
||||
}
|
||||
|
||||
return $candidates;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Content
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Content_Role;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing an entry of content for a generative AI model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Content implements Arrayable, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The role of the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var string
|
||||
*/
|
||||
private $role;
|
||||
|
||||
/**
|
||||
* The parts of the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var Parts
|
||||
*/
|
||||
private $parts;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $role The role of the content.
|
||||
* @param Parts $parts The parts of the content.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the given role is invalid.
|
||||
*/
|
||||
public function __construct( string $role, Parts $parts ) {
|
||||
if ( ! Content_Role::is_valid_value( $role ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'The role %s is invalid.',
|
||||
htmlspecialchars( $role ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->role = $role;
|
||||
$this->parts = $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the role of the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string The role of the content.
|
||||
*/
|
||||
public function get_role(): string {
|
||||
return $this->role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parts of the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return Parts The parts of the content.
|
||||
*/
|
||||
public function get_parts(): Parts {
|
||||
return $this->parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array(
|
||||
'role' => $this->role,
|
||||
'parts' => $this->parts->to_array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Content instance from an array of content data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $data The content data.
|
||||
* @return Content Content instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the data is missing required fields.
|
||||
*/
|
||||
public static function from_array( array $data ): Content {
|
||||
if ( ! isset( $data['role'], $data['parts'] ) ) {
|
||||
throw new InvalidArgumentException( 'Content data must contain role and parts.' );
|
||||
}
|
||||
|
||||
return new Content( $data['role'], Parts::from_array( $data['parts'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'role' => array(
|
||||
'description' => __( 'The role of the content, i.e. which source it comes from.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'enum' => array(
|
||||
Content_Role::USER,
|
||||
Content_Role::MODEL,
|
||||
Content_Role::SYSTEM,
|
||||
),
|
||||
),
|
||||
'parts' => array_merge(
|
||||
array( 'description' => __( 'Content parts, including optional multimodal input.', 'ai-services' ) ),
|
||||
Parts::get_json_schema()
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface ATFPP\AI_Translate\Services\API\Types\Contracts\Part
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Contracts;
|
||||
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
|
||||
/**
|
||||
* Interface for a class representing a part of content for a generative model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
interface Part extends Arrayable {
|
||||
|
||||
/**
|
||||
* Sets data for the part.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $data The part data.
|
||||
*/
|
||||
public function set_data( array $data ): void;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface ATFPP\AI_Translate\Services\API\Types\Contracts\Tool
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Contracts;
|
||||
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
|
||||
/**
|
||||
* Interface for a class representing a tool for a generative model.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
interface Tool extends Arrayable {
|
||||
|
||||
/**
|
||||
* Sets data for the tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The tool data.
|
||||
*/
|
||||
public function set_data( array $data ): void;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\History
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing a chat history.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class History {
|
||||
|
||||
/**
|
||||
* The feature the history is associated with.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var string
|
||||
*/
|
||||
private $feature;
|
||||
|
||||
/**
|
||||
* The history slug, unique within the feature.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var string
|
||||
*/
|
||||
private $slug;
|
||||
|
||||
/**
|
||||
* When the history was last updated, as MySQL datetime string in GMT.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var string
|
||||
*/
|
||||
private $last_updated;
|
||||
|
||||
/**
|
||||
* The history entries.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var History_Entry[]
|
||||
*/
|
||||
private $entries;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $feature The feature the history is associated with.
|
||||
* @param string $slug The history slug.
|
||||
* @param string $last_updated When the history was last updated, as MySQL datetime string in GMT.
|
||||
* @param History_Entry[] $entries The history entries.
|
||||
*/
|
||||
public function __construct( string $feature, string $slug, string $last_updated, array $entries ) {
|
||||
$this->feature = $feature;
|
||||
$this->slug = $slug;
|
||||
$this->last_updated = $last_updated;
|
||||
$this->entries = $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the feature the history is associated with.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The feature.
|
||||
*/
|
||||
public function get_feature(): string {
|
||||
return $this->feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the history slug.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The history slug.
|
||||
*/
|
||||
public function get_slug(): string {
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets when the history was last updated.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The last updated MySQL datetime string in GMT.
|
||||
*/
|
||||
public function get_last_updated(): string {
|
||||
return $this->last_updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the history entries.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return History_Entry[] The history entries.
|
||||
*/
|
||||
public function get_entries(): array {
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the history entries.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param History_Entry[]|array<string, mixed>[] $entries The history entries.
|
||||
*/
|
||||
public function set_entries( array $entries ): void {
|
||||
$this->entries = array_map(
|
||||
function ( $entry_data ) {
|
||||
if ( ! $entry_data instanceof History_Entry ) {
|
||||
return History_Entry::from_array( $entry_data );
|
||||
}
|
||||
return $entry_data;
|
||||
},
|
||||
$entries
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array(
|
||||
'feature' => $this->feature,
|
||||
'slug' => $this->slug,
|
||||
'lastUpdated' => $this->last_updated,
|
||||
'entries' => array_map(
|
||||
function ( History_Entry $entry ) {
|
||||
return $entry->to_array();
|
||||
},
|
||||
$this->entries
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a History instance from an array of history data.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The history data.
|
||||
* @return History History instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the data is missing required fields.
|
||||
*/
|
||||
public static function from_array( array $data ): History {
|
||||
if ( ! isset( $data['feature'], $data['slug'], $data['lastUpdated'], $data['entries'] ) ) {
|
||||
throw new InvalidArgumentException( 'History data must contain feature, slug, lastUpdated, and entries.' );
|
||||
}
|
||||
|
||||
return new History(
|
||||
$data['feature'],
|
||||
$data['slug'],
|
||||
$data['lastUpdated'],
|
||||
array_map(
|
||||
function ( array $entry_data ) {
|
||||
return History_Entry::from_array( $entry_data );
|
||||
},
|
||||
$data['entries']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'feature' => array(
|
||||
'description' => __( 'Unique identifier of the feature. Must only contain lowercase letters, numbers, hyphens.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'Unique identifier of the history within the feature. Must only contain lowercase letters, numbers, hyphens.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'lastUpdated' => array(
|
||||
'description' => __( 'When the history was last updated, as MySQL datetime string in GMT.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'entries' => array(
|
||||
'description' => __( 'The history entries, in ascending order.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => History_Entry::get_json_schema(),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\History_Entry
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing a single entry in a chat history.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class History_Entry {
|
||||
|
||||
/**
|
||||
* The history entry's content.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var Content
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* Additional data for the history entry, if any.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $additional_data;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param Content $content The history entry content.
|
||||
* @param array<string, mixed> $additional_data Additional data for the history entry, if any.
|
||||
*/
|
||||
public function __construct( Content $content, array $additional_data = array() ) {
|
||||
$this->content = $content;
|
||||
|
||||
// Remove the content from the additional data, if present, to prevent conflicts.
|
||||
unset( $additional_data['content'] );
|
||||
$this->additional_data = $additional_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the history entry content.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return Content The content.
|
||||
*/
|
||||
public function get_content(): Content {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional data.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The additional data.
|
||||
*/
|
||||
public function get_additional_data(): array {
|
||||
return $this->additional_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the candidate to an array.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The array representation of the candidate.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array_merge(
|
||||
array(
|
||||
'content' => $this->content->to_array(),
|
||||
),
|
||||
$this->additional_data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a History_Entry instance from an array of content data.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The content data.
|
||||
* @return History_Entry History_Entry instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the data is missing required fields.
|
||||
*/
|
||||
public static function from_array( array $data ): History_Entry {
|
||||
if ( ! isset( $data['content'] ) ) {
|
||||
throw new InvalidArgumentException( 'History entry data must contain content.' );
|
||||
}
|
||||
|
||||
$content = Content::from_array( $data['content'] );
|
||||
unset( $data['content'] );
|
||||
|
||||
return new History_Entry( $content, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'content' => array_merge(
|
||||
array(
|
||||
'description' => __( 'History entry content.', 'ai-services' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
Content::get_json_schema()
|
||||
),
|
||||
),
|
||||
'additionalProperties' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Model_Metadata
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\AI_Capability;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value class representing metadata about a generative AI model.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
final class Model_Metadata implements Arrayable, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The model slug.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $slug;
|
||||
|
||||
/**
|
||||
* The model name.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* List of AI capabilities supported by the model.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string[]
|
||||
*/
|
||||
private $capabilities;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $args {
|
||||
* The arguments for the model metadata.
|
||||
*
|
||||
* @type string $slug The model slug.
|
||||
* @type string $name Optional. The model name. Default will be generated from the slug.
|
||||
* @type string[] $capabilities Optional. The list of AI capabilities supported by the model.
|
||||
* Default empty array.
|
||||
* }
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the given slug is invalid.
|
||||
*/
|
||||
public function __construct( array $args ) {
|
||||
$args = $this->parse_args( $args );
|
||||
|
||||
$this->slug = $args['slug'];
|
||||
$this->name = $args['name'];
|
||||
$this->capabilities = $args['capabilities'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model slug.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string The model slug.
|
||||
*/
|
||||
public function get_slug(): string {
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model name.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string The model name.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of AI capabilities supported by the model.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string[] List of AI capabilities supported by the model.
|
||||
*/
|
||||
public function get_capabilities(): array {
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, mixed> The array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array(
|
||||
'slug' => $this->slug,
|
||||
'name' => $this->name,
|
||||
'capabilities' => $this->capabilities,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Model_Metadata instance from an array of model metadata arguments.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $args The model metadata arguments.
|
||||
* @return Model_Metadata The Model_Metadata instance.
|
||||
*/
|
||||
public static function from_array( array $args ): Model_Metadata {
|
||||
return new Model_Metadata( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the model metadata arguments.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $args The model metadata arguments.
|
||||
* @return array<string, mixed> The parsed model metadata arguments.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if an invalid argument is provided.
|
||||
*/
|
||||
private function parse_args( array $args ): array {
|
||||
if ( ! isset( $args['slug'] ) ) {
|
||||
throw new InvalidArgumentException( 'The slug is required.' );
|
||||
}
|
||||
|
||||
if ( isset( $args['name'] ) ) {
|
||||
$args['name'] = (string) $args['name'];
|
||||
} else {
|
||||
$args['name'] = ucwords( str_replace( array( '-', '_' ), ' ', $args['slug'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $args['capabilities'] ) ) {
|
||||
if ( ! is_array( $args['capabilities'] ) ) {
|
||||
throw new InvalidArgumentException( 'The capabilities must be an array.' );
|
||||
}
|
||||
foreach ( $args['capabilities'] as $capability ) {
|
||||
if ( ! AI_Capability::is_valid_value( $capability ) ) {
|
||||
throw new InvalidArgumentException( 'The capabilities contain an invalid value.' );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$args['capabilities'] = array();
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the model metadata.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'slug' => array(
|
||||
'description' => __( 'Unique model slug.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'User-facing model name.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'capabilities' => array(
|
||||
'description' => __( 'List of AI capabilities supported by the model.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'enum' => AI_Capability::get_values(),
|
||||
),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Parts
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ArrayIterator;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Contracts\Part;
|
||||
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\Parts\Text_Part;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Collection;
|
||||
use InvalidArgumentException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class representing a collection of content parts for a generative model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Parts implements Collection, Arrayable, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The parts of the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var Part[]
|
||||
*/
|
||||
private $parts = array();
|
||||
|
||||
/**
|
||||
* Adds a text part to the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $text The text.
|
||||
*/
|
||||
public function add_text_part( string $text ): void {
|
||||
$this->add_part(
|
||||
Text_Part::from_array( array( 'text' => $text ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a function call part to the content.
|
||||
*
|
||||
* Every function call must have at least one of $id and $name provided.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $id The ID of the function call, or an empty string.
|
||||
* @param string $name The name of the function, or an empty string.
|
||||
* @param array<string, mixed> $args The arguments of the function call.
|
||||
*/
|
||||
public function add_function_call_part( string $id, string $name, array $args ): void {
|
||||
$data = array();
|
||||
if ( $id ) {
|
||||
$data['id'] = $id;
|
||||
}
|
||||
if ( $name ) {
|
||||
$data['name'] = $name;
|
||||
}
|
||||
$data['args'] = $args;
|
||||
|
||||
$this->add_part(
|
||||
Function_Call_Part::from_array(
|
||||
array( 'functionCall' => $data )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a function response part to the content.
|
||||
*
|
||||
* Every function response must have at least one of $id and $name provided.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string $id The ID of the function response, or an empty string. If present, this must match the
|
||||
* function call ID.
|
||||
* @param string $name The name of the function, or an empty string. If present, this must match the name of
|
||||
* the function called.
|
||||
* @param mixed $response The function output response.
|
||||
*/
|
||||
public function add_function_response_part( string $id, string $name, $response ): void {
|
||||
$data = array();
|
||||
if ( $id ) {
|
||||
$data['id'] = $id;
|
||||
}
|
||||
if ( $name ) {
|
||||
$data['name'] = $name;
|
||||
}
|
||||
$data['response'] = $response;
|
||||
|
||||
$this->add_part(
|
||||
Function_Response_Part::from_array(
|
||||
array( 'functionResponse' => $data )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a part to the content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param Part $part The part.
|
||||
*/
|
||||
public function add_part( Part $part ): void {
|
||||
$this->parts[] = $part;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the parts collection.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return ArrayIterator<int, Part> Collection iterator.
|
||||
*/
|
||||
public function getIterator(): Traversable {
|
||||
return new ArrayIterator( $this->parts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the parts collection.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return int Collection size.
|
||||
*/
|
||||
public function count(): int {
|
||||
return count( $this->parts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the parts collection by the given criteria.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $args {
|
||||
* The filter arguments.
|
||||
*
|
||||
* @type string $class_name The class name to only allow parts of that class.
|
||||
* }
|
||||
* @return Parts The filtered parts collection.
|
||||
*/
|
||||
public function filter( array $args ): self {
|
||||
if ( isset( $args['class_name'] ) ) {
|
||||
$class_name = $args['class_name'];
|
||||
$map = static function ( Part $part ) use ( $class_name ) {
|
||||
if ( $part instanceof $class_name ) {
|
||||
return call_user_func( array( $class_name, 'from_array' ), $part->to_array() );
|
||||
}
|
||||
return null;
|
||||
};
|
||||
} else {
|
||||
$map = static function ( Part $part ) {
|
||||
return call_user_func( array( get_class( $part ), 'from_array' ), $part->to_array() );
|
||||
};
|
||||
}
|
||||
|
||||
$parts = new Parts();
|
||||
foreach ( $this->parts as $part ) {
|
||||
$mapped_part = $map( $part );
|
||||
if ( $mapped_part ) {
|
||||
$parts->add_part( $mapped_part );
|
||||
}
|
||||
}
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part at the given index.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $index The index.
|
||||
* @return Part The part.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the index is out of bounds.
|
||||
*/
|
||||
public function get( int $index ): Part {
|
||||
if ( ! isset( $this->parts[ $index ] ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
'Index out of bounds.'
|
||||
);
|
||||
}
|
||||
return $this->parts[ $index ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array_map(
|
||||
static function ( Part $part ) {
|
||||
return $part->to_array();
|
||||
},
|
||||
$this->parts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Parts instance from an array of parts data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param mixed[] $data The parts data.
|
||||
* @return Parts The Parts instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the parts data is invalid.
|
||||
*/
|
||||
public static function from_array( array $data ): Parts {
|
||||
$parts = new Parts();
|
||||
|
||||
foreach ( $data as $part ) {
|
||||
if ( ! is_array( $part ) ) {
|
||||
throw new InvalidArgumentException( 'Invalid part data.' );
|
||||
}
|
||||
|
||||
if ( isset( $part['text'] ) ) {
|
||||
$parts->add_part( Text_Part::from_array( $part ) );
|
||||
} elseif ( isset( $part['functionCall'] ) ) {
|
||||
$parts->add_part( Function_Call_Part::from_array( $part ) );
|
||||
} elseif ( isset( $part['functionResponse'] ) ) {
|
||||
$parts->add_part( Function_Response_Part::from_array( $part ) );
|
||||
} else {
|
||||
throw new InvalidArgumentException( 'Invalid part data.' );
|
||||
}
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
$text_part_schema = Text_Part::get_json_schema();
|
||||
$function_call_part_schema = Function_Call_Part::get_json_schema();
|
||||
$function_response_part_schema = Function_Response_Part::get_json_schema();
|
||||
unset(
|
||||
$text_part_schema['type'],
|
||||
$function_call_part_schema['type'],
|
||||
$function_response_part_schema['type']
|
||||
);
|
||||
|
||||
return array(
|
||||
'type' => 'array',
|
||||
'minItems' => 1,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'oneOf' => array(
|
||||
$text_part_schema,
|
||||
$function_call_part_schema,
|
||||
$function_response_part_schema,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Parts\Abstract_Part
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Parts;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Types\Contracts\Part;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base class for a part of content for a generative model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
abstract class Abstract_Part implements Part, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The part data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $data = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final public function __construct() {
|
||||
// Empty constructor, only to prevent override.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data for the part.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $data The part data.
|
||||
*/
|
||||
final public function set_data( array $data ): void {
|
||||
$this->data = $this->format_data( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data for the part.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $data The part data.
|
||||
* @return array<string, mixed> Formatted data.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the part data is invalid.
|
||||
*/
|
||||
abstract protected function format_data( array $data ): array;
|
||||
|
||||
/**
|
||||
* Gets the default data for the part.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, mixed> Default data.
|
||||
*/
|
||||
abstract protected function get_default_data(): array;
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
final public function to_array(): array {
|
||||
if ( ! $this->data ) {
|
||||
$this->data = $this->get_default_data();
|
||||
}
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a specific Part instance from an array of part data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $data The part data.
|
||||
* @return Part The Part instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the parts data is invalid.
|
||||
*/
|
||||
final public static function from_array( array $data ): Part {
|
||||
$part = new static();
|
||||
$part->set_data( $data );
|
||||
return $part;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Parts\Function_Call_Part
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Parts;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class for a function call part of content for a generative model.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class Function_Call_Part extends Abstract_Part {
|
||||
|
||||
/**
|
||||
* Gets the ID of the function call from the part.
|
||||
*
|
||||
* Every function call must have at least one of 'id' or 'name' present.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The function call ID, or empty string if none set.
|
||||
*/
|
||||
public function get_id(): string {
|
||||
$data = $this->to_array();
|
||||
if ( ! isset( $data['functionCall']['id'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return $data['functionCall']['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the function name from the part.
|
||||
*
|
||||
* Every function call must have at least one of 'id' or 'name' present.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The function name, or empty string if none set.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
$data = $this->to_array();
|
||||
if ( ! isset( $data['functionCall']['name'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return $data['functionCall']['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the function input arguments from the part.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The function input arguments.
|
||||
*/
|
||||
public function get_args(): array {
|
||||
return $this->to_array()['functionCall']['args'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data for the part.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The part data.
|
||||
* @return array<string, mixed> Formatted data.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the part data is invalid.
|
||||
*/
|
||||
protected function format_data( array $data ): array {
|
||||
if ( ! isset( $data['functionCall'] ) || ! is_array( $data['functionCall'] ) ) {
|
||||
throw new InvalidArgumentException( 'The function call part data must contain an associative array functionCall value.' );
|
||||
}
|
||||
|
||||
$function_call = $data['functionCall'];
|
||||
|
||||
if (
|
||||
( ! isset( $function_call['id'] ) || ! is_string( $function_call['id'] ) ) &&
|
||||
( ! isset( $function_call['name'] ) || ! is_string( $function_call['name'] ) )
|
||||
) {
|
||||
throw new InvalidArgumentException( 'The function call part data must contain either a string id value or a string name value.' );
|
||||
}
|
||||
|
||||
if ( ! isset( $function_call['args'] ) || ! is_array( $function_call['args'] ) ) {
|
||||
throw new InvalidArgumentException( 'The function call part data must contain an object / associative array args value.' );
|
||||
}
|
||||
|
||||
$function_call_formatted = array();
|
||||
if ( isset( $function_call['id'] ) ) {
|
||||
$function_call_formatted['id'] = $function_call['id'];
|
||||
}
|
||||
if ( isset( $function_call['name'] ) ) {
|
||||
$function_call_formatted['name'] = $function_call['name'];
|
||||
}
|
||||
$function_call_formatted['args'] = $function_call['args'];
|
||||
|
||||
return array(
|
||||
'functionCall' => $function_call_formatted,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default data for the part.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> Default data.
|
||||
*/
|
||||
protected function get_default_data(): array {
|
||||
return array(
|
||||
'functionCall' => array(
|
||||
'name' => '',
|
||||
'args' => array(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'functionCall' => array(
|
||||
'description' => __( 'Function call as part of the prompt.', 'ai-services' ),
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'ID of the function call. Either this or a name must be present.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Name of the function to call. Either this or a name must be present.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'args' => array(
|
||||
'description' => __( 'Arguments input for the function to call.', 'ai-services' ),
|
||||
'type' => 'object',
|
||||
'additionalProperties' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Parts\Function_Response_Part
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Parts;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class for a function response part of content for a generative model.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class Function_Response_Part extends Abstract_Part {
|
||||
|
||||
/**
|
||||
* Gets the ID of the function response from the part.
|
||||
*
|
||||
* If present, this must match the function call ID.
|
||||
* Every function response must have at least one of 'id' or 'name' present.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The function response ID, or empty string if none set.
|
||||
*/
|
||||
public function get_id(): string {
|
||||
$data = $this->to_array();
|
||||
if ( ! isset( $data['functionResponse']['id'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return $data['functionResponse']['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the function name from the part.
|
||||
*
|
||||
* If present, this must match the name of the function called.
|
||||
* Every function response must have at least one of 'id' or 'name' present.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The function name, or empty string if none set.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
$data = $this->to_array();
|
||||
if ( ! isset( $data['functionResponse']['name'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return $data['functionResponse']['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the function output response from the part.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return mixed The function output response.
|
||||
*/
|
||||
public function get_response() {
|
||||
return $this->to_array()['functionResponse']['response'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data for the part.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The part data.
|
||||
* @return array<string, mixed> Formatted data.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the part data is invalid.
|
||||
*/
|
||||
protected function format_data( array $data ): array {
|
||||
if ( ! isset( $data['functionResponse'] ) || ! is_array( $data['functionResponse'] ) ) {
|
||||
throw new InvalidArgumentException( 'The function response part data must contain an associative array functionResponse value.' );
|
||||
}
|
||||
|
||||
$function_response = $data['functionResponse'];
|
||||
|
||||
if (
|
||||
( ! isset( $function_response['id'] ) || ! is_string( $function_response['id'] ) ) &&
|
||||
( ! isset( $function_response['name'] ) || ! is_string( $function_response['name'] ) )
|
||||
) {
|
||||
throw new InvalidArgumentException( 'The function response part data must contain either a string id value or a string name value.' );
|
||||
}
|
||||
|
||||
if ( ! isset( $function_response['response'] ) ) {
|
||||
throw new InvalidArgumentException( 'The function response part data must contain a response value.' );
|
||||
}
|
||||
|
||||
$function_response_formatted = array();
|
||||
if ( isset( $function_response['id'] ) ) {
|
||||
$function_response_formatted['id'] = $function_response['id'];
|
||||
}
|
||||
if ( isset( $function_response['name'] ) ) {
|
||||
$function_response_formatted['name'] = $function_response['name'];
|
||||
}
|
||||
$function_response_formatted['response'] = $function_response['response'];
|
||||
|
||||
return array(
|
||||
'functionResponse' => $function_response_formatted,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default data for the part.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> Default data.
|
||||
*/
|
||||
protected function get_default_data(): array {
|
||||
return array(
|
||||
'functionResponse' => array(
|
||||
'name' => '',
|
||||
'response' => null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'functionResponse' => array(
|
||||
'description' => __( 'Function response as part of the prompt.', 'ai-services' ),
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'ID of the function response. If present, it must match the function call ID. Either this or a name must be present.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Name of the function called. Either this or a name must be present.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'response' => array(
|
||||
'description' => __( 'Response from the function called.', 'ai-services' ),
|
||||
'type' => array( 'string', 'number', 'boolean', 'array', 'object' ),
|
||||
'additionalProperties' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Parts\Text_Part
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Parts;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class for a text part of content for a generative model.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Text_Part extends Abstract_Part {
|
||||
|
||||
/**
|
||||
* Gets the text from the part.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string The text.
|
||||
*/
|
||||
public function get_text(): string {
|
||||
return $this->to_array()['text'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data for the part.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param array<string, mixed> $data The part data.
|
||||
* @return array<string, mixed> Formatted data.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the part data is invalid.
|
||||
*/
|
||||
protected function format_data( array $data ): array {
|
||||
if ( ! isset( $data['text'] ) || ! is_string( $data['text'] ) ) {
|
||||
throw new InvalidArgumentException( 'The text part data must contain a string text value.' );
|
||||
}
|
||||
|
||||
return array(
|
||||
'text' => $data['text'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default data for the part.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, mixed> Default data.
|
||||
*/
|
||||
protected function get_default_data(): array {
|
||||
return array(
|
||||
'text' => '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'text' => array(
|
||||
'description' => __( 'Prompt text content.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Service_Metadata
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\AI_Capability;
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Service_Type;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value class representing metadata about a generative AI service.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
final class Service_Metadata implements Arrayable, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The service slug.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $slug;
|
||||
|
||||
/**
|
||||
* The service name.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* The service credentials URL.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $credentials_url;
|
||||
|
||||
/**
|
||||
* The service type.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* List of AI capabilities supported by the service and its models.
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @var string[]
|
||||
*/
|
||||
private $capabilities;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $args {
|
||||
* The arguments for the service metadata.
|
||||
*
|
||||
* @type string $slug The service slug.
|
||||
* @type string $name Optional. The service name. Default will be generated from the slug.
|
||||
* @type string $credentials_url Optional. The service credentials URL. Default empty string.
|
||||
* @type string $type Optional. The service type. Default `Service_Type::CLOUD`.
|
||||
* @type string[] $capabilities Optional. The list of AI capabilities supported by the service and its
|
||||
* models. Default empty array.
|
||||
* }
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the given slug is invalid.
|
||||
*/
|
||||
public function __construct( array $args ) {
|
||||
$args = $this->parse_args( $args );
|
||||
|
||||
$this->slug = $args['slug'];
|
||||
$this->name = $args['name'];
|
||||
$this->credentials_url = $args['credentials_url'];
|
||||
$this->type = $args['type'];
|
||||
$this->capabilities = $args['capabilities'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service slug.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string The service slug.
|
||||
*/
|
||||
public function get_slug(): string {
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service name.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string The service name.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service credentials URL.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string The service credentials URL.
|
||||
*/
|
||||
public function get_credentials_url(): string {
|
||||
return $this->credentials_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service type.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string The service type.
|
||||
*/
|
||||
public function get_type(): string {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of AI capabilities supported by the service and its models.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string[] List of AI capabilities supported by the service and its models.
|
||||
*/
|
||||
public function get_capabilities(): array {
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, mixed> The array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array(
|
||||
'slug' => $this->slug,
|
||||
'name' => $this->name,
|
||||
'credentials_url' => $this->credentials_url,
|
||||
'type' => $this->type,
|
||||
'capabilities' => $this->capabilities,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Service_Metadata instance from an array of service metadata arguments.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $args The service metadata arguments.
|
||||
* @return Service_Metadata The Service_Metadata instance.
|
||||
*/
|
||||
public static function from_array( array $args ): Service_Metadata {
|
||||
return new Service_Metadata( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the service metadata arguments.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $args The service metadata arguments.
|
||||
* @return array<string, mixed> The parsed service metadata arguments.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if an invalid argument is provided.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
*/
|
||||
private function parse_args( array $args ): array {
|
||||
if ( ! isset( $args['slug'] ) ) {
|
||||
throw new InvalidArgumentException( 'The slug is required.' );
|
||||
}
|
||||
|
||||
if ( ! preg_match( '/^[a-z0-9-]+$/', $args['slug'] ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
'The service slug must only contain lowercase letters, numbers, and hyphens.'
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $args['name'] ) ) {
|
||||
$args['name'] = (string) $args['name'];
|
||||
} else {
|
||||
$args['name'] = ucwords( str_replace( array( '-', '_' ), ' ', $args['slug'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $args['credentials_url'] ) ) {
|
||||
$args['credentials_url'] = (string) $args['credentials_url'];
|
||||
|
||||
// Basic sanity check to ensure a protocol is present.
|
||||
if ( ! str_contains( $args['credentials_url'], ':' ) && ! in_array( $args['credentials_url'][0], array( '/', '#', '?' ), true ) ) {
|
||||
$args['credentials_url'] = 'https://' . $args['credentials_url'];
|
||||
}
|
||||
} else {
|
||||
$args['credentials_url'] = '';
|
||||
}
|
||||
|
||||
if ( isset( $args['type'] ) ) {
|
||||
if ( ! Service_Type::is_valid_value( $args['type'] ) ) {
|
||||
throw new InvalidArgumentException( 'The service type is invalid.' );
|
||||
}
|
||||
} else {
|
||||
$args['type'] = Service_Type::CLOUD;
|
||||
}
|
||||
|
||||
if ( isset( $args['capabilities'] ) ) {
|
||||
if ( ! is_array( $args['capabilities'] ) ) {
|
||||
throw new InvalidArgumentException( 'The capabilities must be an array.' );
|
||||
}
|
||||
foreach ( $args['capabilities'] as $capability ) {
|
||||
if ( ! AI_Capability::is_valid_value( $capability ) ) {
|
||||
throw new InvalidArgumentException( 'The capabilities contain an invalid value.' );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$args['capabilities'] = array();
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the service metadata.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'slug' => array(
|
||||
'description' => __( 'Unique service slug.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'User-facing service name.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'credentials_url' => array(
|
||||
'description' => __( 'Service credentials URL, or empty string if not specified.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'type' => array(
|
||||
'description' => __( 'Service type.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'enum' => Service_Type::get_values(),
|
||||
'readonly' => true,
|
||||
),
|
||||
'capabilities' => array(
|
||||
'description' => __( 'List of AI capabilities supported by the service and its models.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'enum' => AI_Capability::get_values(),
|
||||
),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Text_Generation_Config
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Modality;
|
||||
use ATFPP\AI_Translate\Services\Base\Abstract_Generation_Config;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing text configuration options for a generative AI model.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since 0.5.0 Renamed from `Generation_Config`.
|
||||
* @since 0.7.0 Now extends `Abstract_Generation_Config`.
|
||||
*/
|
||||
class Text_Generation_Config extends Abstract_Generation_Config {
|
||||
|
||||
/**
|
||||
* Returns the stop sequences.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string[] The stop sequences, or empty array if not set.
|
||||
*/
|
||||
public function get_stop_sequences(): array {
|
||||
return $this->get_arg( 'stopSequences' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response MIME type.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return string The response MIME type, or empty string if not set.
|
||||
*/
|
||||
public function get_response_mime_type(): string {
|
||||
return $this->get_arg( 'responseMimeType' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response schema.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return array<string, mixed> The response schema, or empty array if not set.
|
||||
*/
|
||||
public function get_response_schema(): array {
|
||||
return $this->get_arg( 'responseSchema' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the candidate count.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return int The candidate count (default 1).
|
||||
*/
|
||||
public function get_candidate_count(): int {
|
||||
return $this->get_arg( 'candidateCount' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum output tokens.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return int The maximum output tokens, or 0 if not set.
|
||||
*/
|
||||
public function get_max_output_tokens(): int {
|
||||
return $this->get_arg( 'maxOutputTokens' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the temperature.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return float The temperature (between 0.0 and 1.0), or 0.0 if not set.
|
||||
*/
|
||||
public function get_temperature(): float {
|
||||
return $this->get_arg( 'temperature' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top P.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return float The top P, or 0.0 if not set.
|
||||
*/
|
||||
public function get_top_p(): float {
|
||||
return $this->get_arg( 'topP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top K.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return int The top K, or 0 if not set.
|
||||
*/
|
||||
public function get_top_k(): int {
|
||||
return $this->get_arg( 'topK' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the presence penalty.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return float The presence penalty, or 0.0 if not set.
|
||||
*/
|
||||
public function get_presence_penalty(): float {
|
||||
return $this->get_arg( 'presencePenalty' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the frequency penalty.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return float The frequency penalty, or 0.0 if not set.
|
||||
*/
|
||||
public function get_frequency_penalty(): float {
|
||||
return $this->get_arg( 'frequencyPenalty' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to include the response logprobs.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return bool Whether to include the response logprobs.
|
||||
*/
|
||||
public function get_response_logprobs(): bool {
|
||||
return $this->get_arg( 'responseLogprobs' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top logprobs.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return int The top logprobs, or 0 if not set.
|
||||
*/
|
||||
public function get_logprobs(): int {
|
||||
return $this->get_arg( 'logprobs' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output modalities.
|
||||
*
|
||||
* @since 0.6.0
|
||||
*
|
||||
* @return string[] The output modalities, or empty array if not set.
|
||||
*/
|
||||
public function get_output_modalities(): array {
|
||||
return $this->get_arg( 'outputModalities' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the definition for the supported arguments.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, mixed> The supported arguments definition.
|
||||
*/
|
||||
protected function get_supported_args_definition(): array {
|
||||
$schema = self::get_json_schema();
|
||||
return $schema['properties'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given value based on the given type.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param mixed $value The value to sanitize.
|
||||
* @param string $type The type to sanitize the value to. Must be one of 'array', 'string', 'object',
|
||||
* 'integer', 'float', or 'boolean'.
|
||||
* @param string $arg_name The name of the argument being sanitized.
|
||||
* @return mixed The sanitized value.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the type is not supported or the value is invalid.
|
||||
*/
|
||||
protected function sanitize_arg( $value, string $type, string $arg_name ) {
|
||||
if ( 'temperature' === $arg_name && ( (float) $value < 0.0 || (float) $value > 1.0 ) ) {
|
||||
throw new InvalidArgumentException( 'Temperature must be between 0.0 and 1.0.' );
|
||||
}
|
||||
|
||||
return parent::sanitize_arg( $value, $type, $arg_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'stopSequences' => array(
|
||||
'description' => __( 'Set of character sequences that will stop output generation.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array( 'type' => 'string' ),
|
||||
),
|
||||
'responseMimeType' => array(
|
||||
'description' => __( 'MIME type of the generated candidate text.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'enum' => array( 'text/plain', 'application/json' ),
|
||||
),
|
||||
'responseSchema' => array(
|
||||
'description' => __( 'Output schema of the generated candidate text (only relevant if responseMimeType is application/json).', 'ai-services' ),
|
||||
'type' => 'object',
|
||||
'properties' => array(),
|
||||
'additionalProperties' => true,
|
||||
),
|
||||
'candidateCount' => array(
|
||||
'description' => __( 'Number of response candidates to generate.', 'ai-services' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
),
|
||||
'maxOutputTokens' => array(
|
||||
'description' => __( 'The maximum number of tokens to include in a response candidate.', 'ai-services' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
),
|
||||
'temperature' => array(
|
||||
'description' => sprintf(
|
||||
/* translators: 1: Minimum value, 2: Maximum value */
|
||||
__( 'Floating point value to control the randomness of the output, between %1$s and %2$s.', 'ai-services' ),
|
||||
'0.0',
|
||||
'1.0'
|
||||
),
|
||||
'type' => 'number',
|
||||
'minimum' => 0.0,
|
||||
'maximum' => 1.0,
|
||||
),
|
||||
'topP' => array(
|
||||
'description' => __( 'The maximum cumulative probability of tokens to consider when sampling.', 'ai-services' ),
|
||||
'type' => 'number',
|
||||
),
|
||||
'topK' => array(
|
||||
'description' => __( 'The maximum number of tokens to consider when sampling.', 'ai-services' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'presencePenalty' => array(
|
||||
'description' => __( 'Presence penalty applied to the next token’s logprobs if the token has already been seen in the response.', 'ai-services' ),
|
||||
'type' => 'number',
|
||||
),
|
||||
'frequencyPenalty' => array(
|
||||
'description' => __( 'Frequency penalty applied to the next token’s logprobs, multiplied by the number of times each token has been seen in the response so far.', 'ai-services' ),
|
||||
'type' => 'number',
|
||||
),
|
||||
'responseLogprobs' => array(
|
||||
'description' => __( 'Whether to return log probabilities of the output tokens in the response or not.', 'ai-services' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'logprobs' => array(
|
||||
'description' => __( 'The number of top logprobs to return at each decoding step.', 'ai-services' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'outputModalities' => array(
|
||||
'description' => __( 'The modalities that the response can contain.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array(
|
||||
Modality::TEXT,
|
||||
Modality::IMAGE,
|
||||
Modality::AUDIO,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Tool_Config
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use ATFPP\AI_Translate\Services\Util\Strings;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class representing tool configuration for a generative AI model.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class Tool_Config implements Arrayable, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The sanitized configuration arguments.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $sanitized_args;
|
||||
|
||||
/**
|
||||
* Type definitions for the supported arguments.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $supported_args = array(
|
||||
'functionCallMode' => 'string',
|
||||
'allowedFunctionNames' => 'array',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $args The configuration arguments.
|
||||
*/
|
||||
public function __construct( array $args ) {
|
||||
$this->sanitized_args = $this->sanitize_args( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the function call mode.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string The function call mode, or empty string if not set.
|
||||
*/
|
||||
public function get_function_call_mode(): string {
|
||||
return $this->sanitized_args['functionCallMode'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed function names.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return string[] The allowed function names, or empty array if not set.
|
||||
*/
|
||||
public function get_allowed_function_names(): array {
|
||||
return $this->sanitized_args['allowedFunctionNames'] ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return $this->sanitized_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tool_Config instance from an array of content data.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The content data.
|
||||
* @return Tool_Config Tool_Config instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the data is missing required fields.
|
||||
*/
|
||||
public static function from_array( array $data ): Tool_Config {
|
||||
return new Tool_Config( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'functionCallMode' => array(
|
||||
'description' => __( 'Mode for how to consider function calling.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
'enum' => array( 'auto', 'any' ),
|
||||
),
|
||||
'allowedFunctionNames' => array(
|
||||
'description' => __( 'List of function names allowed to call.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array( 'type' => 'string' ),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given arguments.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $args The arguments to sanitize.
|
||||
* @return array<string, mixed> Sanitized arguments.
|
||||
*/
|
||||
private function sanitize_args( array $args ): array {
|
||||
$sanitized = array();
|
||||
|
||||
foreach ( $args as $key => $value ) {
|
||||
if ( isset( $this->supported_args[ $key ] ) ) {
|
||||
$sanitized[ $key ] = $this->sanitize_arg( $value, $this->supported_args[ $key ], $key );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( str_contains( $key, '_' ) ) {
|
||||
$camelcase_key = Strings::snake_case_to_camel_case( $key );
|
||||
if ( isset( $this->supported_args[ $camelcase_key ] ) ) {
|
||||
$sanitized[ $camelcase_key ] = $this->sanitize_arg( $value, $this->supported_args[ $camelcase_key ], $camelcase_key );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizies the given value based on the given type.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param mixed $value The value to sanitize.
|
||||
* @param string $type The type to sanitize the value to. Must be one of 'array', 'string', 'object',
|
||||
* 'integer', 'float', or 'boolean'.
|
||||
* @param string $arg_name The name of the argument being sanitized.
|
||||
* @return mixed The sanitized value.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the type is not supported.
|
||||
*/
|
||||
private function sanitize_arg( $value, string $type, string $arg_name ) {
|
||||
if ( 'functionCallMode' === $arg_name && ! in_array( $value, array( 'auto', 'any' ), true ) ) {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
switch ( $type ) {
|
||||
case 'array':
|
||||
if ( ! is_array( $value ) ) {
|
||||
if ( ! $value ) {
|
||||
return array();
|
||||
}
|
||||
return array( $value );
|
||||
}
|
||||
return array_values( $value );
|
||||
case 'string':
|
||||
return (string) $value;
|
||||
case 'object':
|
||||
if ( ! is_array( $value ) ) {
|
||||
if ( is_object( $value ) ) {
|
||||
if ( $value instanceof Arrayable ) {
|
||||
return $value->to_array();
|
||||
}
|
||||
return (array) $value;
|
||||
}
|
||||
return array();
|
||||
}
|
||||
return $value;
|
||||
case 'integer':
|
||||
return (int) $value;
|
||||
case 'float':
|
||||
return (float) $value;
|
||||
case 'boolean':
|
||||
return (bool) $value;
|
||||
default:
|
||||
throw new InvalidArgumentException( 'Unsupported type.' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Tools
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types;
|
||||
|
||||
use ArrayIterator;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Contracts\Tool;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Tools\Function_Declarations_Tool;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Tools\Web_Search_Tool;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Collection;
|
||||
use InvalidArgumentException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class representing a collection of content tools for a generative model.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class Tools implements Collection, Arrayable, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The tools.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var Tool[]
|
||||
*/
|
||||
private $tools = array();
|
||||
|
||||
/**
|
||||
* Adds a function declarations tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed>[] $function_declarations The function declarations.
|
||||
*/
|
||||
public function add_function_declarations_tool( array $function_declarations ): void {
|
||||
$this->add_tool(
|
||||
Function_Declarations_Tool::from_array(
|
||||
array( 'functionDeclarations' => $function_declarations )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a web search tool.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param string[] $allowed_domains Optional. The allowed domains. Default empty array.
|
||||
* @param string[] $disallowed_domains Optional. The disallowed domains. Default empty array.
|
||||
*/
|
||||
public function add_web_search_tool( array $allowed_domains = array(), array $disallowed_domains = array() ): void {
|
||||
$this->add_tool(
|
||||
Web_Search_Tool::from_array(
|
||||
array(
|
||||
'webSearch' => array(
|
||||
'allowedDomains' => $allowed_domains,
|
||||
'disallowedDomains' => $disallowed_domains,
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param Tool $tool The tool.
|
||||
*/
|
||||
public function add_tool( Tool $tool ): void {
|
||||
$this->tools[] = $tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the tools collection.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return ArrayIterator<int, Tool> Collection iterator.
|
||||
*/
|
||||
public function getIterator(): Traversable {
|
||||
return new ArrayIterator( $this->tools );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the tools collection.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return int Collection size.
|
||||
*/
|
||||
public function count(): int {
|
||||
return count( $this->tools );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tool at the given index.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param int $index The index.
|
||||
* @return Tool The tool.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the index is out of bounds.
|
||||
*/
|
||||
public function get( int $index ): Tool {
|
||||
if ( ! isset( $this->tools[ $index ] ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
'Index out of bounds.'
|
||||
);
|
||||
}
|
||||
return $this->tools[ $index ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array_map(
|
||||
static function ( Tool $tool ) {
|
||||
return $tool->to_array();
|
||||
},
|
||||
$this->tools
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tools instance from an array of tools data.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param mixed[] $data The tools data.
|
||||
* @return Tools The Tools instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the tools data is invalid.
|
||||
*/
|
||||
public static function from_array( array $data ): Tools {
|
||||
$tools = new Tools();
|
||||
|
||||
foreach ( $data as $tool ) {
|
||||
if ( ! is_array( $tool ) ) {
|
||||
throw new InvalidArgumentException( 'Invalid tool data.' );
|
||||
}
|
||||
|
||||
if ( isset( $tool['functionDeclarations'] ) ) {
|
||||
$tools->add_tool( Function_Declarations_Tool::from_array( $tool ) );
|
||||
} elseif ( isset( $tool['webSearch'] ) ) {
|
||||
$tools->add_tool( Web_Search_Tool::from_array( $tool ) );
|
||||
} else {
|
||||
throw new InvalidArgumentException( 'Invalid tool data.' );
|
||||
}
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
$function_declarations_tool_schema = Function_Declarations_Tool::get_json_schema();
|
||||
unset( $function_declarations_tool_schema['type'] );
|
||||
|
||||
$web_search_tool_schema = Web_Search_Tool::get_json_schema();
|
||||
unset( $web_search_tool_schema['type'] );
|
||||
|
||||
return array(
|
||||
'type' => 'array',
|
||||
'minItems' => 1,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'oneOf' => array(
|
||||
$function_declarations_tool_schema,
|
||||
$web_search_tool_schema,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Tools\Abstract_Tool
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Tools;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Types\Contracts\Tool;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_JSON_Schema;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base class for a tool for a generative model.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
abstract class Abstract_Tool implements Tool, With_JSON_Schema {
|
||||
|
||||
/**
|
||||
* The tool data.
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $data = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final public function __construct() {
|
||||
// Empty constructor, only to prevent override.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data for the tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The tool data.
|
||||
*/
|
||||
final public function set_data( array $data ): void {
|
||||
$this->data = $this->format_data( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data for the tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The tool data.
|
||||
* @return array<string, mixed> Formatted data.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the tool data is invalid.
|
||||
*/
|
||||
abstract protected function format_data( array $data ): array;
|
||||
|
||||
/**
|
||||
* Gets the default data for the tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> Default data.
|
||||
*/
|
||||
abstract protected function get_default_data(): array;
|
||||
|
||||
/**
|
||||
* Returns the array representation.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return mixed[] Array representation.
|
||||
*/
|
||||
final public function to_array(): array {
|
||||
if ( ! $this->data ) {
|
||||
$this->data = $this->get_default_data();
|
||||
}
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a specific Tool instance from an array of tool data.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The tool data.
|
||||
* @return Tool The Tool instance.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the tools data is invalid.
|
||||
*/
|
||||
final public static function from_array( array $data ): Tool {
|
||||
$tool = new static();
|
||||
$tool->set_data( $data );
|
||||
return $tool;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Tools\Function_Declarations_Tool
|
||||
*
|
||||
* @since 0.5.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Tools;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class for a function declarations tool for a generative model.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*/
|
||||
final class Function_Declarations_Tool extends Abstract_Tool {
|
||||
|
||||
/**
|
||||
* Gets the function declarations from the tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed>[] The function declarations.
|
||||
*/
|
||||
public function get_function_declarations(): array {
|
||||
return $this->to_array()['functionDeclarations'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data for the tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $data The tool data.
|
||||
* @return array<string, mixed> Formatted data.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the tool data is invalid.
|
||||
*/
|
||||
protected function format_data( array $data ): array {
|
||||
if ( ! isset( $data['functionDeclarations'] ) || ! is_array( $data['functionDeclarations'] ) ) {
|
||||
throw new InvalidArgumentException( 'The function declarations tool data must contain an array functionDeclarations value.' );
|
||||
}
|
||||
|
||||
foreach ( $data['functionDeclarations'] as &$function_declaration ) {
|
||||
if ( ! isset( $function_declaration['name'] ) || ! is_string( $function_declaration['name'] ) ) {
|
||||
throw new InvalidArgumentException( 'Each function declaration data must contain a string name value.' );
|
||||
}
|
||||
if ( isset( $function_declaration['description'] ) && ! is_string( $function_declaration['description'] ) ) {
|
||||
throw new InvalidArgumentException( 'The description value of a function declaration must be a string.' );
|
||||
}
|
||||
if ( isset( $function_declaration['parameters'] ) && ! is_array( $function_declaration['parameters'] ) ) {
|
||||
throw new InvalidArgumentException( 'The parameters value of a function declaration must be an object / associative array.' );
|
||||
}
|
||||
|
||||
$function_declaration['parameters'] = $this->sanitize_parameters( $function_declaration['parameters'] );
|
||||
}
|
||||
|
||||
return array(
|
||||
'functionDeclarations' => $data['functionDeclarations'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default data for the tool.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> Default data.
|
||||
*/
|
||||
protected function get_default_data(): array {
|
||||
return array(
|
||||
'functionDeclarations' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the parameters schema, ensuring every object property is required and additional properties are disallowed.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param array<string, mixed> $schema The schema to sanitize.
|
||||
* @return array<string, mixed> Sanitized schema.
|
||||
*/
|
||||
protected function sanitize_parameters( array $schema ): array {
|
||||
// Every schema must have a type, but that will be checked elsewhere so we can ignore it here.
|
||||
if ( ! isset( $schema['type'] ) ) {
|
||||
return $schema;
|
||||
}
|
||||
|
||||
$type = (array) $schema['type'];
|
||||
if ( in_array( 'object', $type, true ) ) {
|
||||
if ( isset( $schema['properties'] ) ) {
|
||||
$schema['required'] = array_keys( $schema['properties'] );
|
||||
foreach ( $schema['properties'] as $key => $child_schema ) {
|
||||
$schema['properties'][ $key ] = $this->sanitize_parameters( $child_schema );
|
||||
}
|
||||
}
|
||||
$schema['additionalProperties'] = false;
|
||||
}
|
||||
|
||||
if ( in_array( 'array', $type, true ) && isset( $schema['items'] ) ) {
|
||||
$schema['items'] = $this->sanitize_parameters( $schema['items'] );
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'functionDeclarations' => array(
|
||||
'description' => __( 'Function declarations for the tool.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Name of the function.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'Description of the function.', 'ai-services' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'parameters' => array(
|
||||
'description' => __( 'Supported parameters of the function, as an object in JSON schema.', 'ai-services' ),
|
||||
'type' => 'object',
|
||||
'additionalProperties' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\API\Types\Tools\Web_Search_Tool
|
||||
*
|
||||
* @since 0.7.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\API\Types\Tools;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class for a web search tool for a generative model.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
final class Web_Search_Tool extends Abstract_Tool {
|
||||
|
||||
/**
|
||||
* Gets the allowed domains for the tool.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string[] The allowed domains.
|
||||
*/
|
||||
public function get_allowed_domains(): array {
|
||||
return $this->to_array()['webSearch']['allowedDomains'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the disallowed domains for the tool.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return string[] The disallowed domains.
|
||||
*/
|
||||
public function get_disallowed_domains(): array {
|
||||
return $this->to_array()['webSearch']['disallowedDomains'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data for the tool.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param array<string, mixed> $data The tool data.
|
||||
* @return array<string, mixed> Formatted data.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the tool data is invalid.
|
||||
*/
|
||||
protected function format_data( array $data ): array {
|
||||
if ( isset( $data['webSearch']['allowedDomains'] ) && ! is_array( $data['webSearch']['allowedDomains'] ) ) {
|
||||
throw new InvalidArgumentException( 'The allowedDomains value for the web search tool data must be an array of strings.' );
|
||||
}
|
||||
if ( isset( $data['webSearch']['disallowedDomains'] ) && ! is_array( $data['webSearch']['disallowedDomains'] ) ) {
|
||||
throw new InvalidArgumentException( 'The disallowedDomains value for the web search tool data must be an array of strings.' );
|
||||
}
|
||||
|
||||
return array(
|
||||
'webSearch' => array(
|
||||
'allowedDomains' => isset( $data['webSearch']['allowedDomains'] ) ? array_values( array_filter( $data['webSearch']['allowedDomains'], 'is_string' ) ) : array(),
|
||||
'disallowedDomains' => isset( $data['webSearch']['disallowedDomains'] ) ? array_values( array_filter( $data['webSearch']['disallowedDomains'], 'is_string' ) ) : array(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default data for the tool.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, mixed> Default data.
|
||||
*/
|
||||
protected function get_default_data(): array {
|
||||
return array(
|
||||
'webSearch' => array(
|
||||
'allowedDomains' => array(),
|
||||
'disallowedDomains' => array(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema for the expected input.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @return array<string, mixed> The JSON schema.
|
||||
*/
|
||||
public static function get_json_schema(): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'webSearch' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'allowedDomains' => array(
|
||||
'description' => __( 'Web search allowed domains for the tool.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'disallowedDomains' => array(
|
||||
'description' => __( 'Web search disallowed domains for the tool.', 'ai-services' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user