Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions src/ProviderImplementations/OpenAi/OpenAiTextGenerationModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
use WordPress\AiClient\Results\DTO\TokenUsage;
use WordPress\AiClient\Results\Enums\FinishReasonEnum;
use WordPress\AiClient\Tools\DTO\CodeExecution;
use WordPress\AiClient\Tools\DTO\FunctionCall;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
use WordPress\AiClient\Tools\DTO\WebSearch;
Expand Down Expand Up @@ -155,29 +156,22 @@ protected function prepareGenerateTextParams(array $prompt): array

$functionDeclarations = $config->getFunctionDeclarations();
$webSearch = $config->getWebSearch();
$codeExecution = $config->getCodeExecution();
$customOptions = $config->getCustomOptions();

// Check for built-in tools via customOptions.
$codeInterpreter = !empty($customOptions['codeInterpreter']);

if (is_array($functionDeclarations) || $webSearch || $codeInterpreter) {
if (is_array($functionDeclarations) || $webSearch || $codeExecution) {
$params['tools'] = $this->prepareToolsParam(
$functionDeclarations,
$webSearch,
$codeInterpreter
$codeExecution
);
}

/*
* Any custom options are added to the parameters as well.
* This allows developers to pass other options that may be more niche or not yet supported by the SDK.
* Skip options we've already processed explicitly.
*/
$processedCustomOptions = ['codeInterpreter'];
foreach ($customOptions as $key => $value) {
if (in_array($key, $processedCustomOptions, true)) {
continue;
}
if (isset($params[$key])) {
throw new InvalidArgumentException(
sprintf(
Expand Down Expand Up @@ -396,13 +390,13 @@ protected function getMessagePartData(MessagePart $part): ?array
*
* @param list<FunctionDeclaration>|null $functionDeclarations The function declarations, or null if none.
* @param WebSearch|null $webSearch The web search config, or null if none.
* @param bool $codeInterpreter Whether to include the code interpreter tool.
* @param CodeExecution|null $codeExecution The code execution config, or null if none.
* @return list<array<string, mixed>> The prepared tools parameter.
*/
protected function prepareToolsParam(
?array $functionDeclarations,
?WebSearch $webSearch,
bool $codeInterpreter = false
?CodeExecution $codeExecution = null
): array {
$tools = [];

Expand All @@ -424,10 +418,18 @@ protected function prepareToolsParam(
$tools[] = $webSearchTool;
}

if ($codeInterpreter) {
if ($codeExecution) {
$containerId = $codeExecution->getContainerId();
if ($containerId !== null) {
// Use a specific container by ID.
$container = ['type' => 'container', 'container_id' => $containerId];
} else {
// Use auto mode with optional custom options (e.g., memory_limit).
$container = array_merge(['type' => 'auto'], $codeExecution->getCustomOptions());
}
$tools[] = [
'type' => 'code_interpreter',
'container' => ['type' => 'auto'],
'container' => $container,
];
}

Expand Down
42 changes: 42 additions & 0 deletions src/Providers/Models/DTO/ModelConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;
use WordPress\AiClient\Messages\Enums\ModalityEnum;
use WordPress\AiClient\Tools\DTO\CodeExecution;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
use WordPress\AiClient\Tools\DTO\WebSearch;

Expand All @@ -21,6 +22,7 @@
*
* @since 0.1.0
*
* @phpstan-import-type CodeExecutionArrayShape from CodeExecution
* @phpstan-import-type FunctionDeclarationArrayShape from FunctionDeclaration
* @phpstan-import-type WebSearchArrayShape from WebSearch
*
Expand All @@ -39,6 +41,7 @@
* topLogprobs?: int,
* functionDeclarations?: list<FunctionDeclarationArrayShape>,
* webSearch?: WebSearchArrayShape,
* codeExecution?: CodeExecutionArrayShape,
* outputFileType?: string,
* outputMimeType?: string,
* outputSchema?: array<string, mixed>,
Expand Down Expand Up @@ -66,6 +69,7 @@ class ModelConfig extends AbstractDataTransferObject
public const KEY_TOP_LOGPROBS = 'topLogprobs';
public const KEY_FUNCTION_DECLARATIONS = 'functionDeclarations';
public const KEY_WEB_SEARCH = 'webSearch';
public const KEY_CODE_EXECUTION = 'codeExecution';
public const KEY_OUTPUT_FILE_TYPE = 'outputFileType';
public const KEY_OUTPUT_MIME_TYPE = 'outputMimeType';
public const KEY_OUTPUT_SCHEMA = 'outputSchema';
Expand Down Expand Up @@ -151,6 +155,11 @@ class ModelConfig extends AbstractDataTransferObject
*/
protected ?WebSearch $webSearch = null;

/**
* @var CodeExecution|null Code execution configuration for the model.
*/
protected ?CodeExecution $codeExecution = null;

/**
* @var FileTypeEnum|null Output file type.
*/
Expand Down Expand Up @@ -540,6 +549,30 @@ public function getWebSearch(): ?WebSearch
return $this->webSearch;
}

/**
* Sets the code execution configuration.
*
* @since n.e.x.t
*
* @param CodeExecution $codeExecution The code execution configuration.
*/
public function setCodeExecution(CodeExecution $codeExecution): void
{
$this->codeExecution = $codeExecution;
}

/**
* Gets the code execution configuration.
*
* @since n.e.x.t
*
* @return CodeExecution|null The code execution configuration.
*/
public function getCodeExecution(): ?CodeExecution
{
return $this->codeExecution;
}

/**
* Sets the output file type.
*
Expand Down Expand Up @@ -857,6 +890,7 @@ public static function getJsonSchema(): array
'description' => 'Function declarations available to the model.',
],
self::KEY_WEB_SEARCH => WebSearch::getJsonSchema(),
self::KEY_CODE_EXECUTION => CodeExecution::getJsonSchema(),
self::KEY_OUTPUT_FILE_TYPE => [
'type' => 'string',
'enum' => FileTypeEnum::getValues(),
Expand Down Expand Up @@ -972,6 +1006,10 @@ static function (FunctionDeclaration $function_declaration): array {
$data[self::KEY_WEB_SEARCH] = $this->webSearch->toArray();
}

if ($this->codeExecution !== null) {
$data[self::KEY_CODE_EXECUTION] = $this->codeExecution->toArray();
}

if ($this->outputFileType !== null) {
$data[self::KEY_OUTPUT_FILE_TYPE] = $this->outputFileType->value;
}
Expand Down Expand Up @@ -1077,6 +1115,10 @@ static function (array $function_declaration_data): FunctionDeclaration {
$config->setWebSearch(WebSearch::fromArray($array[self::KEY_WEB_SEARCH]));
}

if (isset($array[self::KEY_CODE_EXECUTION])) {
$config->setCodeExecution(CodeExecution::fromArray($array[self::KEY_CODE_EXECUTION]));
}

if (isset($array[self::KEY_OUTPUT_FILE_TYPE])) {
$config->setOutputFileType(FileTypeEnum::from($array[self::KEY_OUTPUT_FILE_TYPE]));
}
Expand Down
2 changes: 2 additions & 0 deletions src/Providers/Models/Enums/OptionEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
* @method static self topLogprobs() Creates an instance for TOP_LOGPROBS option.
* @method static self topP() Creates an instance for TOP_P option.
* @method static self webSearch() Creates an instance for WEB_SEARCH option.
* @method static self codeExecution() Creates an instance for CODE_EXECUTION option.
* @method bool isCandidateCount() Checks if the option is CANDIDATE_COUNT.
* @method bool isCustomOptions() Checks if the option is CUSTOM_OPTIONS.
* @method bool isFrequencyPenalty() Checks if the option is FREQUENCY_PENALTY.
Expand All @@ -61,6 +62,7 @@
* @method bool isTopLogprobs() Checks if the option is TOP_LOGPROBS.
* @method bool isTopP() Checks if the option is TOP_P.
* @method bool isWebSearch() Checks if the option is WEB_SEARCH.
* @method bool isCodeExecution() Checks if the option is CODE_EXECUTION.
*
* @since 0.1.0
*/
Expand Down
133 changes: 133 additions & 0 deletions src/Tools/DTO/CodeExecution.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\Tools\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;

/**
* Represents code execution configuration for AI models.
*
* This DTO defines configuration for code execution/interpreter tools
* that AI models can use to run code.
*
* @since n.e.x.t
*
* @phpstan-type CodeExecutionArrayShape array{containerId?: string|null, customOptions?: array<string, mixed>}
*
* @extends AbstractDataTransferObject<CodeExecutionArrayShape>
*/
class CodeExecution extends AbstractDataTransferObject
{
public const KEY_CONTAINER_ID = 'containerId';
public const KEY_CUSTOM_OPTIONS = 'customOptions';

/**
* The container ID for code execution.
*
* When null, providers should use their default/auto mode.
*
* @var string|null
*/
private ?string $containerId;

/**
* Provider-specific custom options.
*
* For OpenAI, this can include options like 'memory_limit' when using auto mode.
*
* @var array<string, mixed>
*/
private array $customOptions;

/**
* Constructor.
*
* @since n.e.x.t
*
* @param string|null $containerId The container ID, or null for auto mode.
* @param array<string, mixed> $customOptions Provider-specific custom options.
*/
public function __construct(?string $containerId = null, array $customOptions = [])
{
$this->containerId = $containerId;
$this->customOptions = $customOptions;
}

/**
* Gets the container ID.
*
* @since n.e.x.t
*
* @return string|null The container ID, or null for auto mode.
*/
public function getContainerId(): ?string
{
return $this->containerId;
}

/**
* Gets the custom options.
*
* @since n.e.x.t
*
* @return array<string, mixed> The custom options.
*/
public function getCustomOptions(): array
{
return $this->customOptions;
}

/**
* {@inheritDoc}
*
* @since n.e.x.t
*/
public static function getJsonSchema(): array
{
return [
'type' => 'object',
'properties' => [
self::KEY_CONTAINER_ID => [
'type' => ['string', 'null'],
'description' => 'The container ID for code execution, or null for auto mode.',
],
self::KEY_CUSTOM_OPTIONS => [
'type' => 'object',
'additionalProperties' => true,
'description' => 'Provider-specific custom options.',
],
],
'required' => [],
];
}

/**
* {@inheritDoc}
*
* @since n.e.x.t
*
* @return CodeExecutionArrayShape
*/
public function toArray(): array
{
return [
self::KEY_CONTAINER_ID => $this->containerId,
self::KEY_CUSTOM_OPTIONS => $this->customOptions,
];
}

/**
* {@inheritDoc}
*
* @since n.e.x.t
*/
public static function fromArray(array $array): self
{
return new self(
$array[self::KEY_CONTAINER_ID] ?? null,
$array[self::KEY_CUSTOM_OPTIONS] ?? []
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use WordPress\AiClient\Results\DTO\Candidate;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
use WordPress\AiClient\Results\Enums\FinishReasonEnum;
use WordPress\AiClient\Tools\DTO\CodeExecution;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
use WordPress\AiClient\Tools\DTO\WebSearch;

Expand Down Expand Up @@ -157,15 +158,15 @@ public function exposeGetMessagePartData(MessagePart $part): ?array
*
* @param list<FunctionDeclaration>|null $functionDeclarations
* @param WebSearch|null $webSearch
* @param bool $codeInterpreter
* @param CodeExecution|null $codeExecution
* @return list<array<string, mixed>>
*/
public function exposePrepareToolsParam(
?array $functionDeclarations,
?WebSearch $webSearch,
bool $codeInterpreter = false
?CodeExecution $codeExecution = null
): array {
return $this->prepareToolsParam($functionDeclarations, $webSearch, $codeInterpreter);
return $this->prepareToolsParam($functionDeclarations, $webSearch, $codeExecution);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
use WordPress\AiClient\Results\Enums\FinishReasonEnum;
use WordPress\AiClient\Tools\DTO\CodeExecution;
use WordPress\AiClient\Tools\DTO\FunctionCall;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
use WordPress\AiClient\Tools\DTO\FunctionResponse;
Expand Down Expand Up @@ -312,11 +313,11 @@ public function testPrepareGenerateTextParamsWithWebSearch(): void
*
* @return void
*/
public function testPrepareGenerateTextParamsWithCodeInterpreter(): void
public function testPrepareGenerateTextParamsWithCodeExecution(): void
{
$prompt = [new Message(MessageRoleEnum::user(), [new MessagePart('Run some code')])];
$config = new ModelConfig();
$config->setCustomOptions(['codeInterpreter' => true]);
$config->setCodeExecution(new CodeExecution());
$model = $this->createModel($config);

$params = $model->exposePrepareGenerateTextParams($prompt);
Expand Down Expand Up @@ -511,11 +512,12 @@ public function testPrepareToolsParamWithAllTools(): void
['type' => 'object']
);
$webSearch = new WebSearch();
$codeExecution = new CodeExecution();

$tools = $model->exposePrepareToolsParam(
[$functionDeclaration],
$webSearch,
true
$codeExecution
);

$this->assertCount(3, $tools);
Expand Down
Loading