Skip to content

Commit 221033f

Browse files
committed
AI: Update php-ai-client to 1.3.0.
Updates the bundled `php-ai-client` library to version 1.3.0 and aligns the WordPress-specific prompt builder (`WP_AI_Client_Prompt_Builder`) with the upstream changes. Key changes in php-ai-client 1.3.0: * Adds video generation model interfaces. * Enhances `PromptBuilder` with additional configuration methods. * Improves `TokenUsage` and `ProviderMetadata` DTOs. * Updates `MessagePart` DTO. See release: https://github.com/WordPress/php-ai-client/releases/tag/1.3.0 Developed in WordPress#11219 Props flixos90. See #64591. git-svn-id: https://develop.svn.wordpress.org/trunk@61942 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 97efabc commit 221033f

File tree

11 files changed

+524
-32
lines changed

11 files changed

+524
-32
lines changed

src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use WordPress\AiClient\Builders\PromptBuilder;
1111
use WordPress\AiClient\Files\DTO\File;
1212
use WordPress\AiClient\Files\Enums\FileTypeEnum;
13+
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;
1314
use WordPress\AiClient\Messages\DTO\Message;
1415
use WordPress\AiClient\Messages\DTO\MessagePart;
1516
use WordPress\AiClient\Messages\Enums\ModalityEnum;
@@ -66,6 +67,9 @@
6667
* @method self as_output_schema(array<string, mixed> $schema) Sets the output schema.
6768
* @method self as_output_modalities(ModalityEnum ...$modalities) Sets the output modalities.
6869
* @method self as_output_file_type(FileTypeEnum $fileType) Sets the output file type.
70+
* @method self as_output_media_orientation(MediaOrientationEnum $orientation) Sets the output media orientation.
71+
* @method self as_output_media_aspect_ratio(string $aspectRatio) Sets the output media aspect ratio.
72+
* @method self as_output_speech_voice(string $voice) Sets the output speech voice.
6973
* @method self as_json_response(?array<string, mixed> $schema = null) Configures the prompt for JSON response output.
7074
* @method bool|WP_Error is_supported(?CapabilityEnum $capability = null) Checks if the prompt is supported for the given capability.
7175
* @method bool is_supported_for_text_generation() Checks if the prompt is supported for text generation.
@@ -80,6 +84,7 @@
8084
* @method GenerativeAiResult|WP_Error generate_image_result() Generates an image result from the prompt.
8185
* @method GenerativeAiResult|WP_Error generate_speech_result() Generates a speech result from the prompt.
8286
* @method GenerativeAiResult|WP_Error convert_text_to_speech_result() Converts text to speech and returns the result.
87+
* @method GenerativeAiResult|WP_Error generate_video_result() Generates a video result from the prompt.
8388
* @method string|WP_Error generate_text() Generates text from the prompt.
8489
* @method list<string>|WP_Error generate_texts(?int $candidateCount = null) Generates multiple text candidates from the prompt.
8590
* @method File|WP_Error generate_image() Generates an image from the prompt.
@@ -88,6 +93,8 @@
8893
* @method list<File>|WP_Error convert_text_to_speeches(?int $candidateCount = null) Converts text to multiple speech outputs.
8994
* @method File|WP_Error generate_speech() Generates speech from the prompt.
9095
* @method list<File>|WP_Error generate_speeches(?int $candidateCount = null) Generates multiple speech outputs from the prompt.
96+
* @method File|WP_Error generate_video() Generates a video from the prompt.
97+
* @method list<File>|WP_Error generate_videos(?int $candidateCount = null) Generates multiple videos from the prompt.
9198
*/
9299
class WP_AI_Client_Prompt_Builder {
93100

@@ -121,6 +128,7 @@ class WP_AI_Client_Prompt_Builder {
121128
'generate_image_result' => true,
122129
'generate_speech_result' => true,
123130
'convert_text_to_speech_result' => true,
131+
'generate_video_result' => true,
124132
'generate_text' => true,
125133
'generate_texts' => true,
126134
'generate_image' => true,
@@ -129,6 +137,8 @@ class WP_AI_Client_Prompt_Builder {
129137
'convert_text_to_speeches' => true,
130138
'generate_speech' => true,
131139
'generate_speeches' => true,
140+
'generate_video' => true,
141+
'generate_videos' => true,
132142
);
133143

134144
/**

src/wp-includes/php-ai-client/src/AiClient.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class AiClient
8484
/**
8585
* @var string The version of the AI Client.
8686
*/
87-
public const VERSION = '1.2.1';
87+
public const VERSION = '1.3.0';
8888
/**
8989
* @var ProviderRegistry|null The default provider registry instance.
9090
*/
@@ -314,6 +314,26 @@ public static function generateSpeechResult($prompt, $modelOrConfig = null, ?Pro
314314
self::validateModelOrConfigParameter($modelOrConfig);
315315
return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateSpeechResult();
316316
}
317+
/**
318+
* Generates a video using the traditional API approach.
319+
*
320+
* @since 1.3.0
321+
*
322+
* @param Prompt $prompt The prompt content.
323+
* @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
324+
* or model configuration for auto-discovery,
325+
* or null for defaults.
326+
* @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
327+
* @return GenerativeAiResult The generation result.
328+
*
329+
* @throws \InvalidArgumentException If the prompt format is invalid.
330+
* @throws \RuntimeException If no suitable model is found.
331+
*/
332+
public static function generateVideoResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
333+
{
334+
self::validateModelOrConfigParameter($modelOrConfig);
335+
return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateVideoResult();
336+
}
317337
/**
318338
* Creates a new message builder for fluent API usage.
319339
*

src/wp-includes/php-ai-client/src/Builders/PromptBuilder.php

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use WordPress\AiClient\Events\BeforeGenerateResultEvent;
1111
use WordPress\AiClient\Files\DTO\File;
1212
use WordPress\AiClient\Files\Enums\FileTypeEnum;
13+
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;
1314
use WordPress\AiClient\Messages\DTO\Message;
1415
use WordPress\AiClient\Messages\DTO\MessagePart;
1516
use WordPress\AiClient\Messages\DTO\UserMessage;
@@ -26,6 +27,7 @@
2627
use WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts\SpeechGenerationModelInterface;
2728
use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface;
2829
use WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts\TextToSpeechConversionModelInterface;
30+
use WordPress\AiClient\Providers\Models\VideoGeneration\Contracts\VideoGenerationModelInterface;
2931
use WordPress\AiClient\Providers\ProviderRegistry;
3032
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
3133
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
@@ -398,7 +400,7 @@ public function usingTopK(int $topK): self
398400
*/
399401
public function usingStopSequences(string ...$stopSequences): self
400402
{
401-
$this->modelConfig->setCustomOption('stopSequences', $stopSequences);
403+
$this->modelConfig->setStopSequences($stopSequences);
402404
return $this;
403405
}
404406
/**
@@ -552,6 +554,48 @@ public function asOutputFileType(FileTypeEnum $fileType): self
552554
$this->modelConfig->setOutputFileType($fileType);
553555
return $this;
554556
}
557+
/**
558+
* Sets the output media orientation.
559+
*
560+
* @since 1.3.0
561+
*
562+
* @param MediaOrientationEnum $orientation The output media orientation.
563+
* @return self
564+
*/
565+
public function asOutputMediaOrientation(MediaOrientationEnum $orientation): self
566+
{
567+
$this->modelConfig->setOutputMediaOrientation($orientation);
568+
return $this;
569+
}
570+
/**
571+
* Sets the output media aspect ratio.
572+
*
573+
* If set, this supersedes the output media orientation, as it is a more
574+
* specific configuration.
575+
*
576+
* @since 1.3.0
577+
*
578+
* @param string $aspectRatio The aspect ratio (e.g. "16:9", "3:2").
579+
* @return self
580+
*/
581+
public function asOutputMediaAspectRatio(string $aspectRatio): self
582+
{
583+
$this->modelConfig->setOutputMediaAspectRatio($aspectRatio);
584+
return $this;
585+
}
586+
/**
587+
* Sets the output speech voice.
588+
*
589+
* @since 1.3.0
590+
*
591+
* @param string $voice The output speech voice.
592+
* @return self
593+
*/
594+
public function asOutputSpeechVoice(string $voice): self
595+
{
596+
$this->modelConfig->setOutputSpeechVoice($voice);
597+
return $this;
598+
}
555599
/**
556600
* Configures the prompt for JSON response output.
557601
*
@@ -627,6 +671,9 @@ private function inferCapabilityFromModelInterfaces(ModelInterface $model): ?Cap
627671
if ($model instanceof SpeechGenerationModelInterface) {
628672
return CapabilityEnum::speechGeneration();
629673
}
674+
if ($model instanceof VideoGenerationModelInterface) {
675+
return CapabilityEnum::videoGeneration();
676+
}
630677
// No supported interface found
631678
return null;
632679
}
@@ -825,9 +872,11 @@ private function executeModelGeneration(ModelInterface $model, CapabilityEnum $c
825872
}
826873
return $model->generateSpeechResult($messages);
827874
}
828-
// Video generation is not yet implemented
829875
if ($capability->isVideoGeneration()) {
830-
throw new RuntimeException('Output modality "video" is not yet supported.');
876+
if (!$model instanceof VideoGenerationModelInterface) {
877+
throw new RuntimeException(sprintf('Model "%s" does not support video generation.', $model->metadata()->getId()));
878+
}
879+
return $model->generateVideoResult($messages);
831880
}
832881
// TODO: Add support for other capabilities when interfaces are available
833882
throw new RuntimeException(sprintf('Capability "%s" is not yet supported for generation.', $capability->value));
@@ -896,6 +945,22 @@ public function convertTextToSpeechResult(): GenerativeAiResult
896945
// Generate and return the result with text-to-speech conversion capability
897946
return $this->generateResult(CapabilityEnum::textToSpeechConversion());
898947
}
948+
/**
949+
* Generates a video result from the prompt.
950+
*
951+
* @since 1.3.0
952+
*
953+
* @return GenerativeAiResult The generated result containing video candidates.
954+
* @throws InvalidArgumentException If the prompt or model validation fails.
955+
* @throws RuntimeException If the model doesn't support video generation.
956+
*/
957+
public function generateVideoResult(): GenerativeAiResult
958+
{
959+
// Include video in output modalities
960+
$this->includeOutputModalities(ModalityEnum::video());
961+
// Generate and return the result with video generation capability
962+
return $this->generateResult(CapabilityEnum::videoGeneration());
963+
}
899964
/**
900965
* Generates text from the prompt.
901966
*
@@ -1015,6 +1080,36 @@ public function generateSpeeches(?int $candidateCount = null): array
10151080
}
10161081
return $this->generateSpeechResult()->toFiles();
10171082
}
1083+
/**
1084+
* Generates a video from the prompt.
1085+
*
1086+
* @since 1.3.0
1087+
*
1088+
* @return File The generated video file.
1089+
* @throws InvalidArgumentException If the prompt or model validation fails.
1090+
* @throws RuntimeException If no video is generated.
1091+
*/
1092+
public function generateVideo(): File
1093+
{
1094+
return $this->generateVideoResult()->toFile();
1095+
}
1096+
/**
1097+
* Generates multiple videos from the prompt.
1098+
*
1099+
* @since 1.3.0
1100+
*
1101+
* @param int|null $candidateCount The number of videos to generate.
1102+
* @return list<File> The generated video files.
1103+
* @throws InvalidArgumentException If the prompt or model validation fails.
1104+
* @throws RuntimeException If no videos are generated.
1105+
*/
1106+
public function generateVideos(?int $candidateCount = null): array
1107+
{
1108+
if ($candidateCount !== null) {
1109+
$this->usingCandidateCount($candidateCount);
1110+
}
1111+
return $this->generateVideoResult()->toFiles();
1112+
}
10181113
/**
10191114
* Appends a MessagePart to the messages array.
10201115
*

src/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* @phpstan-type MessagePartArrayShape array{
2727
* channel: string,
2828
* type: string,
29+
* thoughtSignature?: string,
2930
* text?: string,
3031
* file?: FileArrayShape,
3132
* functionCall?: FunctionCallArrayShape,
@@ -38,6 +39,7 @@ class MessagePart extends AbstractDataTransferObject
3839
{
3940
public const KEY_CHANNEL = 'channel';
4041
public const KEY_TYPE = 'type';
42+
public const KEY_THOUGHT_SIGNATURE = 'thoughtSignature';
4143
public const KEY_TEXT = 'text';
4244
public const KEY_FILE = 'file';
4345
public const KEY_FUNCTION_CALL = 'functionCall';
@@ -50,6 +52,10 @@ class MessagePart extends AbstractDataTransferObject
5052
* @var MessagePartTypeEnum The type of this message part.
5153
*/
5254
private MessagePartTypeEnum $type;
55+
/**
56+
* @var string|null Thought signature for extended thinking.
57+
*/
58+
private ?string $thoughtSignature = null;
5359
/**
5460
* @var string|null Text content (when type is TEXT).
5561
*/
@@ -73,11 +79,13 @@ class MessagePart extends AbstractDataTransferObject
7379
*
7480
* @param mixed $content The content of this message part.
7581
* @param MessagePartChannelEnum|null $channel The channel this part belongs to. Defaults to CONTENT.
82+
* @param string|null $thoughtSignature Optional thought signature for extended thinking.
7683
* @throws InvalidArgumentException If an unsupported content type is provided.
7784
*/
78-
public function __construct($content, ?MessagePartChannelEnum $channel = null)
85+
public function __construct($content, ?MessagePartChannelEnum $channel = null, ?string $thoughtSignature = null)
7986
{
8087
$this->channel = $channel ?? MessagePartChannelEnum::content();
88+
$this->thoughtSignature = $thoughtSignature;
8189
if (is_string($content)) {
8290
$this->type = MessagePartTypeEnum::text();
8391
$this->text = $content;
@@ -117,6 +125,17 @@ public function getType(): MessagePartTypeEnum
117125
{
118126
return $this->type;
119127
}
128+
/**
129+
* Gets the thought signature.
130+
*
131+
* @since 1.3.0
132+
*
133+
* @return string|null The thought signature or null if not set.
134+
*/
135+
public function getThoughtSignature(): ?string
136+
{
137+
return $this->thoughtSignature;
138+
}
120139
/**
121140
* Gets the text content.
122141
*
@@ -169,7 +188,8 @@ public function getFunctionResponse(): ?FunctionResponse
169188
public static function getJsonSchema(): array
170189
{
171190
$channelSchema = ['type' => 'string', 'enum' => MessagePartChannelEnum::getValues(), 'description' => 'The channel this message part belongs to.'];
172-
return ['oneOf' => [['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::text()->value], self::KEY_TEXT => ['type' => 'string', 'description' => 'Text content.']], 'required' => [self::KEY_TYPE, self::KEY_TEXT], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::file()->value], self::KEY_FILE => File::getJsonSchema()], 'required' => [self::KEY_TYPE, self::KEY_FILE], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionCall()->value], self::KEY_FUNCTION_CALL => FunctionCall::getJsonSchema()], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_CALL], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionResponse()->value], self::KEY_FUNCTION_RESPONSE => FunctionResponse::getJsonSchema()], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_RESPONSE], 'additionalProperties' => \false]]];
191+
$thoughtSignatureSchema = ['type' => 'string', 'description' => 'Thought signature for extended thinking.'];
192+
return ['oneOf' => [['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::text()->value], self::KEY_TEXT => ['type' => 'string', 'description' => 'Text content.'], self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_TEXT], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::file()->value], self::KEY_FILE => File::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FILE], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionCall()->value], self::KEY_FUNCTION_CALL => FunctionCall::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_CALL], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionResponse()->value], self::KEY_FUNCTION_RESPONSE => FunctionResponse::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_RESPONSE], 'additionalProperties' => \false]]];
173193
}
174194
/**
175195
* {@inheritDoc}
@@ -192,6 +212,9 @@ public function toArray(): array
192212
} else {
193213
throw new RuntimeException('MessagePart requires one of: text, file, functionCall, or functionResponse. ' . 'This should not be a possible condition.');
194214
}
215+
if ($this->thoughtSignature !== null) {
216+
$data[self::KEY_THOUGHT_SIGNATURE] = $this->thoughtSignature;
217+
}
195218
return $data;
196219
}
197220
/**
@@ -206,15 +229,16 @@ public static function fromArray(array $array): self
206229
} else {
207230
$channel = null;
208231
}
232+
$thoughtSignature = $array[self::KEY_THOUGHT_SIGNATURE] ?? null;
209233
// Check which properties are set to determine how to construct the MessagePart
210234
if (isset($array[self::KEY_TEXT])) {
211-
return new self($array[self::KEY_TEXT], $channel);
235+
return new self($array[self::KEY_TEXT], $channel, $thoughtSignature);
212236
} elseif (isset($array[self::KEY_FILE])) {
213-
return new self(File::fromArray($array[self::KEY_FILE]), $channel);
237+
return new self(File::fromArray($array[self::KEY_FILE]), $channel, $thoughtSignature);
214238
} elseif (isset($array[self::KEY_FUNCTION_CALL])) {
215-
return new self(FunctionCall::fromArray($array[self::KEY_FUNCTION_CALL]), $channel);
239+
return new self(FunctionCall::fromArray($array[self::KEY_FUNCTION_CALL]), $channel, $thoughtSignature);
216240
} elseif (isset($array[self::KEY_FUNCTION_RESPONSE])) {
217-
return new self(FunctionResponse::fromArray($array[self::KEY_FUNCTION_RESPONSE]), $channel);
241+
return new self(FunctionResponse::fromArray($array[self::KEY_FUNCTION_RESPONSE]), $channel, $thoughtSignature);
218242
} else {
219243
throw new InvalidArgumentException('MessagePart requires one of: text, file, functionCall, or functionResponse.');
220244
}

0 commit comments

Comments
 (0)