Skip to content
Open
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
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,36 @@ $imageFile = AiClient::prompt('Generate an illustration of the PHP elephant in t
->generateImage();
```

### Embedding generation using any compatible model

```php
use WordPress\AiClient\AiClient;

$vectors = AiClient::prompt()
->withEmbeddingInputs('Summarize this document', 'Summarize that document')
->generateEmbeddings();

// Or work with the detailed result object:
$result = AiClient::prompt(['Embed this input'])
->generateEmbeddingsResult();
```

See the [`PromptBuilder` class](https://github.com/WordPress/php-ai-client/blob/trunk/src/Builders/PromptBuilder.php) and its public methods for all the ways you can configure the prompt.

**More documentation is coming soon.**

## CLI usage

This repository ships with a thin CLI wrapper for quick experiments:

```
php cli.php 'Explain WordPress in one sentence'
php cli.php 'Create a postcard photo of the WordPress logo' --outputFormat=image-json
php cli.php '["Embed this document", "And this one"]' --capability=embeddings --outputFormat=embeddings-vectors
```

Available embedding output formats are `embeddings-vectors` (default), `embedding-first-vector`, and `embeddings-json`. Use `--capability=embeddings` to explicitly request embeddings while still supporting the existing image/text detection flags.

## Further reading

For more information on the requirements and guiding principles, please review:
Expand Down
94 changes: 72 additions & 22 deletions cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* GOOGLE_API_KEY=123456 php cli.php 'Your prompt here' --providerId=google --modelId=gemini-2.5-flash
* OPENAI_API_KEY=123456 php cli.php 'Your prompt here' --providerId=openai
* GOOGLE_API_KEY=123456 OPENAI_API_KEY=123456 php cli.php 'Your prompt here'
* OPENAI_API_KEY=123456 php cli.php '["Embed this", "and this"]' --capability=embeddings --outputFormat=embeddings-json
*/

declare(strict_types=1);
Expand Down Expand Up @@ -97,11 +98,32 @@ function logError(string $message, int $exit_code = 1): void
}
}

// Provider ID, model ID, and output format.
// Provider ID, model ID, and capability/output format.
$providerId = $named_args['providerId'] ?? null;
$modelId = $named_args['modelId'] ?? null;
$modelPreference = $named_args['modelPreference'] ?? null;
$outputFormat = $named_args['outputFormat'] ?? 'message-text';
$capabilityInput = $named_args['capability'] ?? 'text';
$capability = is_string($capabilityInput) ? strtolower($capabilityInput) : 'text';
$validCapabilities = ['text', 'image', 'embeddings'];
if (!in_array($capability, $validCapabilities, true)) {
logWarning(sprintf('Invalid capability "%s". Defaulting to "text".', (string) $capabilityInput));
$capability = 'text';
}
$defaultOutputFormat = 'message-text';
if ($capability === 'image') {
$defaultOutputFormat = 'image-json';
} elseif ($capability === 'embeddings') {
$defaultOutputFormat = 'embeddings-vectors';
}
$outputFormat = $named_args['outputFormat'] ?? $defaultOutputFormat;
$imageOutputFormats = ['image-json', 'image-base64'];
if ($capability === 'embeddings' && in_array($outputFormat, $imageOutputFormats, true)) {
logWarning('Image output formats are not supported for embeddings. Using embeddings-vectors.');
$outputFormat = 'embeddings-vectors';
}
if ($capability !== 'embeddings' && in_array($outputFormat, $imageOutputFormats, true)) {
$capability = 'image';
}

// Any model configuration options.
$schema = ModelConfig::getJsonSchema()['properties'];
Expand Down Expand Up @@ -142,7 +164,8 @@ function logError(string $message, int $exit_code = 1): void
try {
$modelConfig = ModelConfig::fromArray($model_config_data);

$promptBuilder = AiClient::prompt($promptInput);
$initialPrompt = $capability === 'embeddings' ? null : $promptInput;
$promptBuilder = AiClient::prompt($initialPrompt);
$promptBuilder = $promptBuilder->usingModelConfig($modelConfig);
if ($providerId && $modelId) {
$providerClassName = AiClient::defaultRegistry()->getProviderClassName($providerId);
Expand All @@ -163,43 +186,70 @@ static function ($item) {
);
$promptBuilder = $promptBuilder->usingModelPreference(...$modelPreference);
}
if ($capability === 'embeddings') {
$promptBuilder = $promptBuilder->withEmbeddingInputs($promptInput);
}
} catch (InvalidArgumentException $e) {
logError('Invalid arguments while trying to set up prompt builder: ' . $e->getMessage());
} catch (ResponseException $e) {
logError('Request failed while trying to set up prompt builder: ' . $e->getMessage());
}

try {
if ($outputFormat === 'image-json' || $outputFormat === 'image-base64') {
$generationAction = 'generate text result';
if ($capability === 'image') {
$generationAction = 'generate image result';
} elseif ($capability === 'embeddings') {
$generationAction = 'generate embeddings result';
}

if ($capability === 'image') {
$result = $promptBuilder->generateImageResult();
} elseif ($capability === 'embeddings') {
$result = $promptBuilder->generateEmbeddingsResult();
} else {
$result = $promptBuilder->generateTextResult();
}
} catch (InvalidArgumentException $e) {
logError('Invalid arguments while trying to generate text result: ' . $e->getMessage());
logError('Invalid arguments while trying to ' . $generationAction . ': ' . $e->getMessage());
} catch (ResponseException $e) {
logError('Request failed while trying to generate text result: ' . $e->getMessage());
logError('Request failed while trying to ' . $generationAction . ': ' . $e->getMessage());
}

logInfo("Using provider ID: \"{$result->getProviderMetadata()->getId()}\"");
logInfo("Using model ID: \"{$result->getModelMetadata()->getId()}\"");

switch ($outputFormat) {
case 'result-json':
$output = json_encode($result, JSON_PRETTY_PRINT);
break;
case 'candidates-json':
$output = json_encode($result->getCandidates(), JSON_PRETTY_PRINT);
break;
case 'image-json':
$output = json_encode($result->toFile(), JSON_PRETTY_PRINT);
break;
case 'image-base64':
$output = $result->toFile()->getBase64Data();
break;
case 'message-text':
default:
$output = $result->toText();
if ($capability === 'embeddings') {
switch ($outputFormat) {
case 'embeddings-json':
$output = json_encode($result, JSON_PRETTY_PRINT);
break;
case 'embedding-first-vector':
$output = json_encode($result->toVector(), JSON_PRETTY_PRINT);
break;
case 'embeddings-vectors':
default:
$output = json_encode($result->toVectors(), JSON_PRETTY_PRINT);
break;
}
} else {
switch ($outputFormat) {
case 'result-json':
$output = json_encode($result, JSON_PRETTY_PRINT);
break;
case 'candidates-json':
$output = json_encode($result->getCandidates(), JSON_PRETTY_PRINT);
break;
case 'image-json':
$output = json_encode($result->toFile(), JSON_PRETTY_PRINT);
break;
case 'image-base64':
$output = $result->toFile()->getBase64Data();
break;
case 'message-text':
default:
$output = $result->toText();
}
}

printOutput($output);
56 changes: 56 additions & 0 deletions src/AiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use WordPress\AiClient\Builders\PromptBuilder;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Operations\DTO\EmbeddingOperation;
use WordPress\AiClient\ProviderImplementations\Anthropic\AnthropicProvider;
use WordPress\AiClient\ProviderImplementations\Google\GoogleProvider;
use WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiProvider;
Expand All @@ -16,6 +17,7 @@
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\ProviderRegistry;
use WordPress\AiClient\Results\DTO\EmbeddingResult;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;

/**
Expand Down Expand Up @@ -298,6 +300,60 @@ public static function generateSpeechResult(
return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateSpeechResult();
}

/**
* Generates embeddings for the given input using the traditional API.
*
* @since 0.2.0
*
* @param Prompt $input The input to embed. Accepts strings, messages, or arrays of those types.
* @param ModelInterface|ModelConfig|null $modelOrConfig Optional model specification.
* @param ProviderRegistry|null $registry Optional custom registry.
* @return EmbeddingResult The embeddings result.
*/
public static function generateEmbeddingsResult(
$input,
$modelOrConfig = null,
?ProviderRegistry $registry = null
): EmbeddingResult {
self::validateModelOrConfigParameter($modelOrConfig);
$builder = self::prompt(null, $registry);
if ($modelOrConfig instanceof ModelInterface) {
$builder->usingModel($modelOrConfig);
} elseif ($modelOrConfig instanceof ModelConfig) {
$builder->usingModelConfig($modelOrConfig);
}

$builder->withEmbeddingInputs($input);
return $builder->generateEmbeddingsResult();
}

/**
* Generates an embeddings operation for asynchronous processing.
*
* @since 0.2.0
*
* @param Prompt $input The input to embed.
* @param ModelInterface|ModelConfig|null $modelOrConfig Optional model specification.
* @param ProviderRegistry|null $registry Optional custom registry.
* @return EmbeddingOperation The embeddings operation.
*/
public static function generateEmbeddingsOperation(
$input,
$modelOrConfig = null,
?ProviderRegistry $registry = null
): EmbeddingOperation {
self::validateModelOrConfigParameter($modelOrConfig);
$builder = self::prompt(null, $registry);
if ($modelOrConfig instanceof ModelInterface) {
$builder->usingModel($modelOrConfig);
} elseif ($modelOrConfig instanceof ModelConfig) {
$builder->usingModelConfig($modelOrConfig);
}

$builder->withEmbeddingInputs($input);
return $builder->generateEmbeddingsOperation();
}

/**
* Creates a new message builder for fluent API usage.
*
Expand Down
Loading