Search by

aimeos / prisma

aimeos

A powerful PHP package for integrating media related Large Language Models (LLMs) into your applications

Package info

github.com/aimeos/prisma

pkg:composer/aimeos/prisma

Statistics

Installs: 6 385

Dependents: 3

Suggesters: 0

Stars: 223

Open Issues: 1

0.7.0 2026-09-08 07:31 UTC

README

Light-weight PHP package for integrating multi-media and text related Large Language Models (LLMs) into your applications using a unified interface.

Supported providers API usage Schemas Tools Audio API
  • demix: Separate an audio file into its individual tracks
  • denoise: Remove noise from an audio file
  • describe: Describe the content of an audio file
  • revoice: Exchange the voice in an audio file
  • speak: Convert text to speech in an audio file
  • transcribe: Converts speech of an audio file to text
Image API
  • background: Replace background according to the prompt
  • describe: Describe the content of an image
  • detext: Remove all text from the image
  • erase: Erase parts of the image
  • imagine: Generate an image from the prompt
  • inpaint: Edit an image area according to a prompt
  • isolate: Remove the image background
  • recognize: Recognize the text in an image (OCR)
  • relocate: Place the foreground object on a new background
  • repaint: Repaint an image according to the prompt
  • uncrop: Extend/outpaint the image
  • upscale: Scale up the image
  • vectorize: Creates embedding vectors from images
Text API
  • stream: Stream text deltas as they arrive
  • structure: Generate structured output from a prompt and schema
  • translate: Translate texts from one language to another
  • vectorize: Creates embedding vectors from texts
  • write: Generate text from the given prompt
Video API
  • describe: Describe the content of a video
  • extend: Continue a video according to the prompt
  • imagine: Generate a video from text and optional media
  • repaint: Repaint a video according to the prompt
  • uncrop: Extend/outpaint the video frame
  • upscale: Scale up a video
Custom providers

Supported providers

Audio

demix denoise describe revoice speak transcribe
Alibaba - - - - yes -
AudioPod yes yes - yes yes yes
Deepgram - - - - yes yes
ElevenLabs - - - yes yes yes
Google Gemini - - yes - - -
Groq - - yes - yes yes
Mistral - - yes - - yes
Murf - - - yes yes -
OpenAI - - yes - yes yes
Openrouter - - yes - yes yes
Z.AI - - - - - yes

Image

background describe detext erase imagine inpaint isolate recognize relocate repaint uncrop upscale vectorize
Alibaba - - - - yes - - - - - - - yes
Bedrock Titan - - - - yes yes yes - - - - - yes
Black Forest Labs - - - - beta beta - - - - beta - -
Clipdrop yes - yes yes yes - yes - - - yes yes -
Cohere - - - - - - - - - - - - yes
Google Gemini - yes - - yes - - - - yes - - -
Groq - yes - - - - - - - - - - -
Ideogram yes yes yes yes yes yes yes - - yes - yes -
Mistral - - - - - - - yes - - - - -
ModelsLab - - - - beta - - - - - - - -
OpenAI - yes - - yes yes - - - - - - -
Openrouter - yes - - yes - - yes - yes - - yes
RemoveBG - - - - - - yes - yes - - - -
Replicate - - - - beta - - - - - - - -
StabilityAI - - - yes yes yes yes - - - yes yes -
VoyageAI - - - - - - - - - - - - yes
xAI - - - - beta - - - - - - - -
Z.AI - - - - yes - - - - - - - -

Text

stream structure translate vectorize write citations custom tools provider tools system prompt thinking budget
Alibaba yes yes yes yes - yes yes yes yes
Anthropic yes yes - yes yes yes yes yes yes
Azure beta beta beta beta - yes yes yes
Bedrock - yes yes yes - yes yes yes
Cohere - yes yes yes - yes yes -
Deepseek yes yes - yes - yes yes yes
DeepL yes
Google Gemini yes yes yes yes yes yes yes yes yes
Google yes
Groq yes yes - yes - yes yes yes
Kimi yes yes - yes - yes yes yes
Mistral yes yes yes yes - yes yes yes yes
Ollama beta beta beta beta - yes yes yes
OpenAI yes yes yes yes yes yes yes yes yes
Openrouter yes yes - yes - yes yes yes yes
Perplexity beta beta - beta yes yes yes yes
Requesty yes yes yes yes - yes yes yes
Vertexai beta beta beta beta yes yes yes yes yes
xAI beta beta - beta yes yes yes yes yes
Z.AI yes - - yes - yes yes yes yes

Thinking-budget entries indicate request mapping in Prisma. The selected model must accept the mapped token budget or effort level; see withThinkingBudget.

Video

describe extend imagine repaint uncrop upscale
Alibaba yes beta beta beta beta -
Bedrock Nova beta - beta - - -
BytePlus beta beta beta beta - -
Google Gemini yes - - - - -
Google Omni - - beta beta - -
Google Veo - - beta - - -
Luma - - beta beta beta -
MiniMax - - beta - - -
Openrouter yes - beta - - -
Runway - - beta beta - beta
xAI - beta beta beta - -

Installation

composer req aimeos/prisma

API usage

Basic usage:

use Aimeos\Prisma\Prisma;

$image = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->model( '<modelname>' ) // if model can be selected
    ->ensure( 'imagine' ) // make sure interface is implemented
    ->imagine( 'a grumpy cat' )
    ->binary();

$texts = Prisma::text()
    ->using( 'deepl', ['api_key' => 'xxx'])
    ->ensure( 'translate' )
    ->translate( ['Hello'], 'de' )
    ->texts();

OpenAI-compatible gateways

The openai text provider uses v1/responses for write(), stream() and structure(). A gateway must implement the Responses API to work with these methods. Override the base url (without /v1) to point to such a gateway:

$text = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx', 'url' => 'https://my-gateway.example.com'] )
    ->model( 'my-model' )
    ->write( 'Hello' )
    ->text();

For gateways that expose only Chat Completions, use a matching provider such as ollama, or implement one with OpenaiApi::completions() as shown in the custom provider guide.

ensure

Ensures that the provider has implemented the method.

public function ensure( string $method ) : self
  • @param string $method Method name
  • @return Provider
  • @throws \Aimeos\Prisma\Exceptions\NotImplementedException

Example:

\Aimeos\Prisma\Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->ensure( 'imagine' );

has

Tests if the provider has implemented the method.

public function has( string $method ) : bool
  • @param string $method Method name
  • @return bool TRUE if implemented, FALSE if absent

Example:

\Aimeos\Prisma\Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->has( 'imagine' );

model

Use the model passed by its name.

Used if the provider supports more than one model and allows to select between the different models. Otherwise, it's ignored.

public function model( ?string $model ) : self
  • @param string|null $model Model name
  • @return self Provider interface

Example:

\Aimeos\Prisma\Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->model( 'dall-e-3' );

withClientOptions

Add options for the Guzzle HTTP client.

public function withClientOptions( array $options ) : self
  • @param array<string, mixed> $options Associative list of name/value pairs
  • @return self Provider interface

Example:

\Aimeos\Prisma\Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->withClientOptions( ['timeout' => 120] );

withClientRetry

Configure automatic retry for failed HTTP requests.

public function withClientRetry( int $maxAttempts = 3, \Closure|int $delayMs = 100, ?\Closure $when = null ) : self
  • @param int $maxAttempts Total number of attempts including the initial request
  • @param \Closure|int $delayMs Fixed delay in ms or closure: fn(int $attempt, ?ResponseInterface $response): int
  • @param \Closure|null $when Retry condition: fn(ResponseInterface $response, int $attempt): bool
  • @return self Provider interface

By default, retries HTTP responses with status codes 429, 500, 502, 503 and 504. Connection failures without an HTTP response are not retried. Configure retry and client options before the provider's first request.

Examples:

// Fixed delay of 200ms between retries
\Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->withClientRetry( 3, 200 );

// Exponential backoff
\Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->withClientRetry( 3, fn( $attempt, $response ) => 100 * pow( 2, $attempt ) );

// Custom retry condition
\Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->withClientRetry( 3, 100, fn( $response, $attempt ) => $response->getStatusCode() === 429 );

withMaxResponseSize

Set the maximum number of bytes read for a single provider response.

Bounds the bytes read through Prisma's JSON and SSE response readers, including text, reasoning and tool-call arguments. Defaults to 64 MiB and applies to each HTTP response, so each tool-loop turn has its own limit. Direct binary response reads and file downloads are separate; use $file->maxSize( $bytes ) to configure a file's URL download limit.

public function withMaxResponseSize( int $bytes ) : self
  • @param int $bytes Maximum bytes per response (minimum 1)
  • @return self Provider interface

Example:

\Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->withMaxResponseSize( 16 * 1024 * 1024 ); // cap responses at 16 MB

withSystemPrompt

Add a system prompt for the LLM.

It may be used by providers supporting system prompts. Otherwise, it's ignored.

public function withSystemPrompt( ?string $prompt ) : self
  • @param string|null $prompt System prompt
  • @return self Provider interface

Example:

\Aimeos\Prisma\Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->withSystemPrompt( 'You are a professional illustrator' );

withMessages

Add prior conversation turns sent before the current prompt, so the model has context from earlier exchanges in a multi-turn chat.

Each entry is an array with a role of user or assistant and a string content. User turns may add a files key with an array of File objects for multimodal input, subject to the provider's file support: images where the selected model supports them; PDFs additionally on Anthropic; images, audio and video on Openrouter; images, audio, video and PDFs on Google Gemini/Vertexai. Set system context via withSystemPrompt. The current prompt passed to stream()/write()/structure() is appended as the final user turn.

public function withMessages( array $messages ) : self
  • @param array $messages Conversation turns (['role' => 'user'|'assistant', 'content' => '…', 'files' => []])
  • @return self Provider interface

Example:

\Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'] )
    ->withMessages( [
        ['role' => 'user', 'content' => 'Recommend a laptop'],
        ['role' => 'assistant', 'content' => 'Sure - what is your budget?'],
    ] )
    ->write( 'Around $1500' );

withMaxTokens

Set the maximum number of output tokens for the response.

public function withMaxTokens( ?int $tokens ) : self
  • @param int|null $tokens Maximum output tokens
  • @return self Provider interface

Example:

\Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'] )
    ->withMaxTokens( 4096 )
    ->write( 'Tell me a story' );

withReasoning

Enable reasoning or ask supported providers to minimize it. Providers map false to their closest native control; for example, Google Gemini and Ollama disable thinking, OpenRouter excludes reasoning, and OpenAI uses minimal effort. Explicit provider options passed to write(), stream() or structure() take precedence.

public function withReasoning( bool $enabled = true ) : self

Example:

$response = \Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'] )
    ->withReasoning( false )
    ->write( 'Answer briefly' );

Providers without a native reasoning control ignore this setting.

withThinkingBudget

Set the thinking/reasoning budget in tokens for models that support extended thinking. The budget is mapped to each provider's native format automatically: token counts for Anthropic, OpenAI, Google Gemini and Bedrock; effort levels for other OpenAI-API providers (≤ 1024 → low, ≤ 8192 → medium, > 8192 → high). Kimi uses its native low/high/max levels for those same three ranges. Mistral applies the budget on its Chat Completions path; its Agents path for provider tools does not map this setting.

public function withThinkingBudget( ?int $budget ) : self
  • @param int|null $budget Thinking budget in tokens
  • @return self Provider interface

Example:

$response = \Aimeos\Prisma\Prisma::text()
    ->using( '<provider>', ['api_key' => 'xxx'] )
    ->withThinkingBudget( 5000 )
    ->withMaxTokens( 8192 )
    ->write( 'Solve this step by step' );

// Access the model's reasoning (if returned by the provider)
$thinking = $response->meta()->thinking();

Response objects

The methods return a FileResponse, TextResponse or VectorResponse object that contains the returned data with optional meta/usage/description information.

FileResponse objects:

$base64 = $response->base64(); // first file as base64 data, waits for async requests
$file = $response->binary(); // first file as binary data, waits for async requests
$stream = $response->stream(); // first file as a readable PHP stream; close it after use
$url = $response->url(); // first URL, only if URLs are returned, otherwise NULL
$mime = $response->mimeType(); // image mime type, waits for async requests
$text = $response->description(); // image description if returned by provider
$bool = $response->ready(); // FALSE for async APIs until file is available
$file = $response->first(); // first available file object
$array = $response->files(); // all available file objects

// loop over all available files
foreach( $response as $name => $file ) {
    $file->binary();
}

File content is loaded and converted lazily when the requested representation is accessed.

TextResponse objects:

$text = $response->text(); // first text content (non-streaming)
$text = $response->first(); // first available text
$texts = $response->texts(); // all texts (non-streaming)

// loop over all available texts
foreach( $response as $text ) {
    echo $text;
}

VectorResponse objects:

$vector = $response->first(); // first embedding vector if only one input has been passed
$vectors = $response->vectors(); // embedding vectors for the passed inputs in the same order

// loop over all available vectors
foreach( $response as $vector ) {
    print_r( $vector );
}

Included meta data (optional):

$meta = $response->meta();

$meta->id();               // provider response ID or NULL
$meta->model();            // model that produced the response or NULL
$meta->thinking();         // extended thinking/reasoning output or NULL
$meta->reasoningDetails(); // encrypted reasoning blocks for multi-turn continuity or NULL

meta() returns a Values\Meta object with typed accessors for the fields shared across providers. It also behaves like the array it replaced, so provider-specific keys stay reachable by subscript, iteration and json_encode():

$created = $response->meta()['created'] ?? null; // raw provider key
$raw = $response->meta()->all();                 // complete provider map as array

Included usage data (optional):

$usage = $response->usage();

$usage->promptTokens();     // input tokens or NULL
$usage->completionTokens(); // generated output tokens or NULL
$usage->totalTokens();      // total tokens (falls back to prompt + completion) or NULL
$usage->cacheReadTokens();  // cached input tokens or NULL
$usage->cacheWriteTokens(); // tokens written to the provider cache or NULL
$usage->thoughtTokens();    // reasoning/thinking tokens or NULL
$usage->used();             // used units as float or NULL (tokens for text, credits/cost for media)

usage() returns a Values\Usage object whose typed accessors normalize the differing token keys each provider reports (e.g. input_tokens, prompt_tokens, promptTokenCount, inputTokens all map to promptTokens()). Accessors return NULL when a provider does not report that figure. Like meta(), it stays array-compatible for raw keys:

$used = $response->usage()['used'];  // raw key, still works
$raw = $response->usage()->all();    // complete provider map as array

Citations

TextResponse objects include citations when returned by providers that support them (Anthropic, Google Gemini, OpenAI, Perplexity, xAI). Each citation is a Values\Citation object with four fields:

$response = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->write( 'What is the capital of France?' );

$citations = $response->citations(); // array of Citation objects

foreach( $citations as $citation ) {
    $citation->title();  // string|null — source title
    $citation->url();    // string|null — source URL
    $citation->text();   // string|null — output text that references the source
    $citation->source(); // string|null — verbatim quote from the source document
}

The text field contains the snippet from the model's output that cites the source (populated by OpenAI, xAI, Google Gemini). The source field contains a verbatim quote from the input/source document (populated by Anthropic). For Perplexity, only url is available.

Anthropic requires opting in via options:

$response = Prisma::text()
    ->using( 'anthropic', ['api_key' => 'xxx'] )
    ->write( 'Summarize this document', $files, ['citations' => true] );

Finish reason

TextResponse objects include a finish reason indicating why the model stopped generating:

$response = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->withTools( [$tool] )
    ->withMaxSteps( 5 )
    ->write( 'What is the weather in Berlin?' );

$reason = $response->reason(); // 'stop', 'tool', 'length', 'content', 'error', or 'unknown'
Reason Meaning
stop The model finished normally (reached a natural end or stop sequence)
tool The model stopped to request tool calls; returned when withMaxSteps() is exhausted mid-loop
length Output was truncated because it hit the max token limit
content Output was blocked or truncated by a safety/content filter
error The provider returned an error during generation
unknown The provider returned an unrecognized finish reason

Tool steps

After a tool-using request completes, inspect the full history of tool calls and their results via steps():

$response = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->withTools( [$tool] )
    ->withMaxSteps( 5 )
    ->write( 'What is the weather in Berlin?' );

foreach( $response->steps() as $step ) {
    $step->id();        // tool call ID from the provider
    $step->name();      // tool name (e.g. 'weather')
    $step->arguments(); // arguments the model passed (e.g. ['city' => 'Berlin'])
    $step->result();    // result string returned to the model
}

Rate limit

TextResponse and FileResponse objects can include rate limit information from the provider:

$rateLimit = $response->rateLimit(); // RateLimit object or null

$rateLimit?->limit();      // int|null — request limit
$rateLimit?->remaining();  // int|null — remaining requests
$rateLimit?->reset();      // string|null — reset timestamp
$rateLimit?->retryAfter(); // int|null — retry after seconds

Returns null if the provider does not return rate limit headers.

Schemas

Schemas define the parameters that tools accept. They are used by Tools::make() to tell the LLM what arguments a tool expects.

Building schemas

Use the fluent Schema builder to define tool parameters:

use Aimeos\Prisma\Schema\Schema;

$schema = Schema::for( 'search', [
    'query' => Schema::string()->description( 'Search query' )->required(),
    'limit' => Schema::integer()->description( 'Max results' )->min( 1 )->max( 100 ),
] );

Schema::for() creates a named schema with an object type. The first argument is the schema name, the second is an associative array of property names to types.

Nested objects:

$schema = Schema::for( 'create_event', [
    'title' => Schema::string()->required(),
    'location' => Schema::object( [
        'city' => Schema::string()->required(),
        'country' => Schema::string(),
    ] )->required(),
] );

Arrays:

$schema = Schema::for( 'tag', [
    'tags' => Schema::array()->items( Schema::string() )->min( 1 )->max( 10 )->required(),
    'scores' => Schema::array()->items( Schema::number() ),
] );

Enums:

$schema = Schema::for( 'sort', [
    'order' => Schema::string()->enum( ['asc', 'desc'] )->required(),
] );

// Or from a BackedEnum:
$schema = Schema::for( 'sort', [
    'order' => Schema::string()->enum( SortOrder::class )->required(),
] );

Strict mode and no additional properties (for providers that support it, e.g. OpenAI):

$schema = Schema::for( 'search', [
    'query' => Schema::string()->required(),
] )->strict()->withoutAdditionalProperties();

Union types allow a value to match any of several types (JSON Schema anyOf):

$schema = Schema::for( 'result', [
    'value' => Schema::anyOf( [
        Schema::string(),
        Schema::object( [
            'code' => Schema::integer()->required(),
            'message' => Schema::string()->required(),
        ] ),
    ] )->description( 'Either a plain string or an error object' )->required(),
] );

Prisma adapts each anyOf branch for the target provider: object branches are closed for OpenAI, Anthropic and Cohere. Google Gemini receives the JSON Schema without OpenAPI filtering. The schema builder supports anyOf; it does not provide a oneOf type.

Reusable definitions let you declare a sub-schema once and reference it from multiple places (JSON Schema $defs and $ref). Register a definition with def() and point to it with Schema::ref():

$schema = Schema::for( 'order', [
    'billing'  => Schema::ref( 'Address' )->required(),
    'shipping' => Schema::ref( 'Address' )->required(),
] )->def( 'Address', Schema::object( [
    'street' => Schema::string()->required(),
    'city'   => Schema::string()->required(),
] ) );

Schema::ref( 'Address' ) resolves to the pointer #/$defs/Address; a value already starting with # is used verbatim. Definitions are adapted to the target provider just like inline schemas (objects are closed for OpenAI/Anthropic/Cohere; Google Gemini receives the JSON Schema directly). For providers without native schema support, such as Bedrock, they are passed through in the prompt as-is.

From arrays

If you already have a JSON Schema array, use Schema::fromArray():

$schema = Schema::fromArray( 'search', [
    'type' => 'object',
    'properties' => [
        'query' => ['type' => 'string', 'description' => 'Search query'],
        'limit' => ['type' => 'integer'],
    ],
    'required' => ['query'],
] );

Type reference

Scalar, array and object types support description(), required(), nullable(), title() and enum(). Union and reference types preserve description(), title() and the containing object's required() flag. To allow null in a union, include a nullable branch; apply enums to individual branches or the referenced definition.

Factory method Type Additional methods
Schema::string() String min(), max(), pattern(), format(), default()
Schema::integer() Integer min(), max(), multipleOf(), default()
Schema::number() Number (float) min(), max(), multipleOf(), default()
Schema::boolean() Boolean default()
Schema::array() Array items(), min(), max(), unique(), default()
Schema::object() Object withoutAdditionalProperties(), default(), def()
Schema::anyOf() Union (anyOf) add(), default()
Schema::ref() Reference ($ref)

Tools

Tools enable LLMs to call functions during text generation. Prisma supports both custom tools (executed locally) and provider tools (executed server-side by the LLM provider).

Creating tools

Create tools using the Tools facade:

From scratch:

use Aimeos\Prisma\Schema\Schema;
use Aimeos\Prisma\Tools;

$tool = Tools::make( 'search', 'Search the web', Schema::for( 'search', [
    'query' => Schema::string()->description( 'Search query' )->required(),
] ), fn( $args ) => file_get_contents( 'https://api.example.com/search?q=' . $args['query'] ) );

From a Laravel AI / MCP tool:

$tool = Tools::laravel( new MyLaravelTool() );
// or pass the fully qualified class name (resolved via the Laravel container):
$tool = Tools::laravel( MyLaravelTool::class );

The tool must extend \Laravel\Mcp\Server\Tool (MCP) or implement \Laravel\Ai\Contracts\Tool (AI). When a class name is given, the instance is resolved through the Laravel container (app()), so constructor dependencies are injected. MCP tools are executed via handle(); AI tools via __invoke() or handle().

From a Symfony #[AsTool] class:

$tool = Tools::symfony( MySymfonyTool::class );
// or with a specific tool name when the class has multiple #[AsTool] attributes:
$tool = Tools::symfony( MySymfonyTool::class, 'tool-name' );

Using tools with a provider:

use Aimeos\Prisma\Prisma;
use Aimeos\Prisma\Schema\Schema;
use Aimeos\Prisma\Tools;

$tool = Tools::make( 'weather', 'Get current weather', Schema::for( 'weather', [
    'city' => Schema::string()->description( 'City name' )->required(),
] ), fn( $args ) => json_encode( ['temp' => '22°C', 'city' => $args['city']] ) );

$response = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->withTools( [$tool] )
    ->withMaxSteps( 5 )
    ->write( 'What is the weather in Berlin?' );

withMaxSteps() controls the maximum number of tool-loop steps performed (default is 25). Raise it for workflows that need more tool calls, or lower it to cap cost.

Note: Tool handlers can return any value. Strings are passed through as-is; all other return types (arrays, objects, numbers) are automatically JSON-encoded.

Tool choice:

withToolChoice() controls whether the model must use tools:

Constant Description
Provider::AUTO Model decides (default)
Provider::REQUIRED Must use a tool
Provider::NONE No tools
use Aimeos\Prisma\Providers\Base as Provider;

$provider->withToolChoice( Provider::REQUIRED );

The choice applies to the first tool-loop step. Supported choices vary: Deepseek, Openrouter, Requesty and Z.AI only send AUTO; Groq and xAI omit NONE.

Limiting tool calls:

$tool = Tools::make( ... )->max( 3 ); // This specific tool can only be called 3 times per request

Provider tools

Provider tools are built-in tools executed server-side by the LLM provider (e.g., web search, code execution). They don't require local function handlers. Create them using Tools::provider():

use Aimeos\Prisma\Prisma;
use Aimeos\Prisma\Tools;

$response = Prisma::text()
    ->using( 'anthropic', ['api_key' => 'xxx'] )
    ->withTools( [
        Tools::provider( 'web_search' ),
        Tools::provider( 'code_execution' ),
    ] )
    ->write( 'Search for the latest PHP version and write code to check it' );

Available provider tools:

Tool name Providers
web_search Anthropic, OpenAI, Google Gemini, Vertexai, Mistral, xAI, OpenRouter, Alibaba, Z.AI
web_search_premium Mistral
code_execution Anthropic, OpenAI, Google Gemini, Vertexai, Mistral, xAI
web_fetch Anthropic, Google Gemini, Vertexai
file_search OpenAI
image_generation Mistral
document_library Mistral

Provider tool names not supported by the chosen provider are silently ignored. Providers without any provider tool support (e.g. Bedrock, Cohere, Deepseek, Perplexity) ignore all provider tools.

OpenAI omits web_search for structure() in both native and JSON modes.

Custom and provider tools can be mixed in a single withTools() call:

$response = Prisma::text()
    ->using( 'anthropic', ['api_key' => 'xxx'] )
    ->withTools( [
        $customTool,
        Tools::provider( 'web_search' ),
        Tools::provider( 'code_execution' ),
    ] )
    ->withMaxSteps( 5 )
    ->write( 'Search and analyze' );

Pass provider-specific options using with():

Tools::provider( 'web_search' )->with( [
    'allowed_domains' => ['example.com', 'docs.example.com'],
    'blocked_domains' => ['spam.com'],
] );

Unknown or unsupported options are silently ignored by each provider.

Normalized options (translated automatically per provider):

Option Description Supported by
allowed_domains Only include results from these domains Anthropic, OpenAI, OpenRouter
blocked_domains Exclude results from these domains Anthropic, xAI, OpenRouter
search_context_size Search depth: "low", "medium", "high" OpenAI, xAI
user_location User location object for localized results OpenAI, Anthropic

Provider-specific options:

Option Provider Tool Description
->max( N ) Anthropic web_search, web_fetch, code_execution Maps the tool call limit to max_uses; with() does not pass this option through
search_engine OpenRouter web_search "auto", "native", "exa"
container OpenAI code_execution Container config (['type' => 'auto'])
vector_store_ids OpenAI file_search Vector store IDs to search
max_num_results OpenAI file_search Max results returned
library_ids Mistral document_library Document library IDs

Tool state

The configured call limit is available via limit():

$tool = Tools::make( ... )->max( 3 );

$tool->limit(); // 3 — configured maximum calls

The remaining budget is tracked per request, not on the tool itself: every write() / stream() / structure() call starts fresh, so a tool capped at 3 can be called up to 3 times in each request. Every executed call counts against the budget, including calls whose handler throws. Once the budget is exhausted within a request, further calls to that tool return an error to the model.

Error handling

By default, when a tool handler throws an exception, the error message is returned to the model as "Error: {message}" instead of propagating the exception. You can override this with a custom error handler using failed():

$tool = Tools::make( 'search', 'Search the web', $schema, fn( $args ) => doSearch( $args ) )
    ->failed( function( \Throwable $e, array $arguments ) : string {
        Log::error( 'Tool failed', ['error' => $e->getMessage(), 'args' => $arguments] );
        return 'Search is currently unavailable, please try a different approach.';
    } );

The handler receives the thrown exception and the original arguments, and must return a string that is sent back to the model.

Concurrent tools

Tools can be marked as concurrent so they are eligible to run in parallel when the configured concurrency strategy supports it:

$schema = Schema::for( 'tool' );

$search = Tools::make( 'search', 'Search the web', $schema, fn( $args ) => '...' )->concurrent();
$weather = Tools::make( 'weather', 'Get weather', $schema, fn( $args ) => '...' )->concurrent();
$save = Tools::make( 'save', 'Save to database', $schema, fn( $args ) => '...' ); // sequential (default)

When the LLM calls multiple tools in a single step, all runnable calls are handed to the configured concurrency strategy in the model's call order. A custom strategy is responsible for checking $step->tool()->isConcurrent() and keeping other tools sequential. You can also disable concurrency again:

$tool->concurrent( false );

Concurrency strategy:

Prisma uses the Sequential strategy by default, which runs every step one after another. To run concurrent tools in parallel, provide your own strategy (see below). You can also set the strategy explicitly:

use Aimeos\Prisma\Tools\Concurrency\Sequential;

$response = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->withConcurrency( new Sequential() )
    ->withTools( [$search, $weather] )
    ->write( 'Search and get weather for Berlin' );

Custom concurrency strategy:

Implement the Concurrency interface to use your own execution strategy (e.g., async I/O, thread pools, or framework-specific solutions):

use Aimeos\Prisma\Tools\Concurrency\Concurrency;
use Aimeos\Prisma\Tools\Step;

class CustomConcurrency implements Concurrency
{
    public function run( array $steps ) : array
    {
        foreach( $steps as $step )
        {
            if( $tool = $step->tool() )
            {
                $step->complete( $tool( $step->arguments() ) );
            }
        }

        return $steps;
    }
}

This example executes sequentially. Each $steps entry is a Step object with tool(), arguments(), id(), name(), and result(). Complete the supplied objects with $step->complete(); the tool loop reads those same instances.

Note: Read-only tools that don't modify state should be marked as concurrent.

Decorating tools

Use the Decorator abstract class to wrap tools with additional behavior:

use Aimeos\Prisma\Tools\Adapter\Decorator;
use Aimeos\Prisma\Tools\Adapter\Adapter;

class LoggingTool extends Decorator
{
    private $logger;

    public function __construct( Adapter $adapter, $logger )
    {
        parent::__construct( $adapter );
        $this->logger = $logger;
    }

    public function __invoke( array $arguments ) : string
    {
        $this->logger->info( 'Tool called: ' . $this->name(), $arguments );
        return parent::__invoke( $arguments );
    }
}

$tool = new LoggingTool( Tools::make( 'search', 'Search', $schema, fn( $args ) => '...' ), $logger );

Decorators delegate all Adapter interface methods to the wrapped tool. Override any adapter method to add custom behavior.

Audio API

demix

Separate an audio file into its individual tracks.

public function demix( Audio $audio, int $stems, array $options = [] ) : FileResponse
  • @param Audio $audio Input audio object
  • @param int $stems Number of stems to separate into (e.g. 2 for vocals and accompaniment)
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Audio file response

Supported options:

  • AudioPod

denoise

Remove noise from an audio file.

public function denoise( Audio $audio, array $options = [] ) : FileResponse
  • @param Audio $audio Input audio object
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Audio file response

Supported options:

describe

Describe the content of an audio file.

public function describe( Audio $audio, ?string $lang = null, array $options = [] ) : TextResponse
  • @param Audio $audio Input audio object
  • @param string|null $lang ISO language code the description should be generated in
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Response text

Supported options:

  • Google Gemini
  • Groq
  • Mistral
  • OpenAI
  • Openrouter

revoice

Exchange the voice in an audio file.

public function revoice( Audio $audio, string $voice, array $options = [] ) : FileResponse;
  • @param Audio $audio Input audio object
  • @param string $voice Voice name or identifier
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Audio file response

Supported options:

speak

Converts text to speech.

public function speak( string $text, ?string $voice = null, array $options = [] ) : FileResponse;
  • @param string $text Text to be converted to speech
  • @param string|null $voice Voice identifier for speech synthesis
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Audio file response

Supported options:

transcribe

Converts speech to text.

public function transcribe( Audio $audio, ?string $lang = null, array $options = [] ) : TextResponse
  • @param Audio $audio Input audio object
  • @param string|null $lang ISO language code of the audio content
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Transcription text response

Supported options:

Note: Z.AI audio transcriptions currently support only mono (single-channel) input files.

Image API

Most methods require an image object as input which contains a reference to the image that should be processed. This object can be created by:

use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.php', 'image/png' );
$image = Image::fromLocalPath( 'path/to/image.png', 'image/png' );
$image = Image::fromBinary( 'PNG...', 'image/png' );
$image = Image::fromBase64( 'UE5H...', 'image/png' );
$image = Image::fromStream( $stream, 'image/png' );

// Laravel only:
$image = Image::fromStoragePath( 'path/to/image.png', 'public', 'image/png' );

fromStream() retains a forward-only resource until conversion is needed. See the custom provider guide for ownership details.

The mimeType parameter is optional. If omitted, Prisma detects it when requested; URL-backed files use a probe of the first 255 bytes. fromUrl() also accepts an optional third argument, bool $strict = true.

Note: It's best to use fromUrl() if possible because all other formats (binary and base64) can be derived from the URL content but URLs can't be created from binary/base64 data.

background

Replace image background with a background described by the prompt.

public function background( Image $image, string $prompt, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param string $prompt Prompt describing the new background
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->background( $image, 'Golden sunset on a caribbean beach' );

$image = $fileResponse->binary();

describe

Describe the content of an image.

public function describe( Image $image, ?string $lang = null, array $options = [] ) : TextResponse
  • @param Image $image Input image object
  • @param string|null $lang ISO language code the description should be generated in
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Response text

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$textResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->describe( $image, 'de' );

$text = $textResponse->text();

detext

Remove all text from the image.

public function detext( Image $image, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->detext( $image );

$image = $fileResponse->binary();

erase

Erase parts of the image.

public function erase( Image $image, Image $mask, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param Image $mask Mask image object
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

The mask must be an image with black parts (#000000) to keep and white parts (#FFFFFF) to remove.

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );
$mask = Image::fromBinary( 'PNG...' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->erase( $image, $mask );

$image = $fileResponse->binary();

imagine

Generate an image from the prompt.

public function imagine( string $prompt, array $images = [], array $options = [] ) : FileResponse
  • @param string $prompt Prompt describing the image
  • @param array<int, \Aimeos\Prisma\Files\Image> $images List of reference image objects (provider-dependent)
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->imagine( 'Futuristic robot looking at a dashboard' );

$image = $fileResponse->binary();

Ideogram supports generating PNG images with transparency using transparent => true:

$fileResponse = Prisma::image()
    ->using( 'ideogram', ['api_key' => 'xxx'] )
    ->imagine( 'A watercolor sunflower', [], [
        'transparent' => true,
        'aspect_ratio' => '1x1',
        'output_resolution' => '2K',
    ] );

This selects Ideogram V4 transparent generation. Supported options are aspect_ratio (including AUTO), output_resolution (1K, 2K, 4K, or 8K), rendering_speed (TURBO, DEFAULT, or QUALITY), and enable_copyright_detection. Use output_resolution for the output size; the ordinary resolution option is unavailable on this endpoint. Reference images and other options, including V3 style controls and seed, throw BadRequestException when transparent is true. To use reference images, generate normally and then call isolate() on the result. Omitting transparent or setting it to false keeps the existing V4/V3 routing.

Ideogram V4 generation also supports async => true, independently of transparent. Omitting async or setting it to false keeps synchronous behavior. Both modes return a FileResponse. The async routes are V4 generation and V4 transparent generation, with the same generation options as their synchronous counterparts.

$provider = Prisma::image()->using( 'ideogram', [
    'api_key' => 'xxx',
    'poll_timeout' => 300,
] );

// Submit both jobs before accessing their files so generation can overlap.
$first = $provider->imagine( 'A watercolor landscape', [], ['async' => true] );
$second = $provider->imagine( 'A sunflower sticker', [], ['async' => true, 'transparent' => true] );

$generationId = $first->meta()['generation_id']; // Available immediately after submission
$ready = $first->ready(); // One status request; no polling loop or sleep
$landscape = $first->binary(); // Waits for completion if needed, then downloads the image
$sticker = $second->binary();

Each response polls its own generation ID. File access, including url(), first(), files(), and iteration, waits for completion; completed responses do not poll again. Submissions and individual status requests still use blocking HTTP. Waiting polls every two seconds, with a default 900-second deadline. Configure poll_timeout in the provider configuration in seconds; 0 disables the deadline. Failed generations, malformed results, and polling timeouts throw PrismaException. Completed metadata includes the generation ID, status, image metadata, and usage cost when supplied by Ideogram.

async => true throws BadRequestException for requests that require V3 reference/style options and for methods other than imagine(), including repaint() with or without transparency. These calls are rejected before submission.

inpaint

Edit an image by inpainting an area defined by a mask according to a prompt.

public function inpaint( Image $image, Image $mask, string $prompt, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param Image $mask Input mask image object
  • @param string $prompt Prompt describing the changes
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

The mask must be an image with black parts (#000000) to keep and white parts (#FFFFFF) to edit.

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );
$mask = Image::fromBinary( 'PNG...' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->inpaint( $image, $mask, 'add a pink flamingo' );

$image = $fileResponse->binary();

isolate

Remove the image background.

public function isolate( Image $image, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->isolate( $image );

$image = $fileResponse->binary();

recognize

Recognizes the text in the given image (OCR).

public function recognize( Image $image, array $options = [] ) : TextResponse;
  • @param Image $image Input image object
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Response text object

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$textResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->recognize( $image );

$text = $textResponse->text();

relocate

Place the foreground object on a new background.

public function relocate( Image $image, Image $bgimage, array $options = [] ) : FileResponse
  • @param Image $image Input image with foreground object
  • @param Image $bgimage Background image
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );
$bgimage = Image::fromUrl( 'https://example.com/background.png' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->relocate( $image, $bgimage );

$image = $fileResponse->binary();

repaint

Repaint an image according to the prompt.

public function repaint( Image $image, string $prompt, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param string $prompt Prompt describing the changes
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->repaint( $image, 'Use a van Goch style' );

$image = $fileResponse->binary();

Ideogram can also repaint an image with transparent output:

$fileResponse = Prisma::image()
    ->using( 'ideogram', ['api_key' => 'xxx'] )
    ->repaint( $image, 'Make the petals blue', ['transparent' => true] );

This uses Ideogram's edit endpoint, with transparent mapped to transparent_background=true on the request. Supported options are aspect_ratio, resolution, magic_prompt, num_images, and seed; aspect_ratio and resolution cannot be combined. Other options, including image_weight, rendering speed, and style/character references, throw BadRequestException when transparent is true. Omitting transparent or setting it to false keeps the existing V4/V3 remix routing. The returned PNG bytes retain transparency when accessed through binary().

uncrop

Extend/outpaint the image.

public function uncrop( Image $image,  int $top, int $right, int $bottom, int $left, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param int $top Number of pixels to extend to the top
  • @param int $right Number of pixels to extend to the right
  • @param int $bottom Number of pixels to extend to the bottom
  • @param int $left Number of pixels to extend to the left
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->uncrop( $image, 100, 200, 0, 50 );

$image = $fileResponse->binary();

upscale

Scale up the image.

public function upscale( Image $image, int $factor, array $options = [] ) : FileResponse
  • @param Image $image Input image object
  • @param int $factor Upscaling factor between 2 and the maximum value supported by the provider
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Response file

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$image = Image::fromUrl( 'https://example.com/image.png' );

$fileResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->upscale( $image, 4 );

$image = $fileResponse->binary();

vectorize

Creates embedding vectors of the images' content.

public function vectorize( array $images, ?int $size = null, array $options = [] ) : VectorResponse
  • @param array<int, \Aimeos\Prisma\Files\Image> $images List of input image objects
  • @param int|null $size Size of the resulting vector or null for provider default
  • @param array<string, mixed> $options Provider specific options
  • @return VectorResponse Response vector object

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use \Aimeos\Prisma\Files\Image;

$images = [
    Image::fromUrl( 'https://example.com/image.png' ),
    Image::fromUrl( 'https://example.com/image2.png' ),
];

$vectorResponse = Prisma::image()
    ->using( '<provider>', ['api_key' => 'xxx'])
    ->vectorize( $images, 512 );

$vectors = $vectorResponse->vectors();

Text API

stream

Generate text from the given prompt and stream it in text chunks. The returned TextResponse is backed by a live stream: iterate TextResponse::stream() to consume each chunk as it arrives. The text accessors (text(), texts(), first(), output()) and iterating the response drain the stream for you, so you can also ignore the live chunks and use the response like a non-streamed one. Streaming uses the provider's streaming variant of write(), with the same tools, system prompts, conversation history and options. Mistral with provider tools runs its Agents API eagerly and then yields the complete answer as one chunk.

Consume the stream before reading body metadata. usage(), steps(), meta(), citations(), reason() and structured() are only populated after the stream has been consumed - either iterate stream() to completion or call one of the text accessors first (e.g. text()/output()). Read before the stream is drained, they return empty/default values. rateLimit() is the exception: it comes from the response headers and is available immediately, as are HTTP/auth errors, which surface from the stream() call itself rather than during iteration.

public function stream( string $prompt, array $files = [], array $options = [] ) : TextResponse
  • @param string $prompt Input prompt for text generation
  • @param array<int, File> $files Files for multimodal input (images, audio, documents)
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Streamed response text

Iterating $response->stream() yields:

  • a string for every streamed text delta, and
  • a Step for every executed tool call - once before it runs (done() === false) and once after it completed (done() === true). A tool rejected because of its call limit, invalid arguments or denied approval is not executed and is reported once (completed).

The stream is single-pass and the same Step instance is reused for both notifications, so read done() / result() inside the loop (a stored reference reflects the final state).

Supported providers:

Example:

use Aimeos\Prisma\Prisma;

$textResponse = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->ensure( 'stream' )
    ->stream( 'Summarize the benefits of renewable energy' );

foreach( $textResponse->stream() as $delta ) {
    echo $delta; // print each text chunk as it arrives
}

$full = $textResponse->output(); // all collected text, including text across tool-loop steps
$usage = $textResponse->usage(); // token usage

Multi-turn conversation:

Pass the earlier turns with withMessages(); the current prompt is appended as the next user message.

use Aimeos\Prisma\Prisma;

$textResponse = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->withMessages( [
        ['role' => 'user', 'content' => 'Recommend a laptop'],
        ['role' => 'assistant', 'content' => 'Sure - what is your budget?'],
    ] )
    ->ensure( 'stream' )
    ->stream( 'Around $1500' );

foreach( $textResponse->stream() as $delta ) {
    echo $delta;
}

Streaming with tools:

use Aimeos\Prisma\Prisma;
use Aimeos\Prisma\Tools\Step;

$textResponse = Prisma::text()
    ->using( 'anthropic', ['api_key' => 'xxx'] )
    ->withTools( [$weatherTool] )
    ->ensure( 'stream' )
    ->stream( 'What is the weather in Berlin?' );

foreach( $textResponse->stream() as $chunk ) {
    if( !$chunk instanceof Step ) {
        echo $chunk;                                                        // text delta
    } elseif( $chunk->done() ) {
        printf( "\n[%s -> %s]\n", $chunk->name(), $chunk->result() );       // tool result
    } else {
        printf( "\n[calling %s(%s)]\n", $chunk->name(), json_encode( $chunk->arguments() ) ); // tool call
    }
}

$steps = $textResponse->steps(); // executed tool steps, same as write()

Performance: the loop body runs once per chunk, synchronously in the read loop. For high-frequency sinks (broadcast, WebSocket, database), coalesce deltas - buffer them and flush every ~50ms or every N characters - instead of doing a round trip per chunk.

Laravel SSE (response()->eventStream()):

Because stream() returns an iterable response, it plugs straight into Laravel's native SSE helper - just delegate to its generator:

use Aimeos\Prisma\Prisma;

Route::get( '/chat', function () {
    $response = Prisma::text()
        ->using( 'openai', config( 'services.openai' ) )
        ->ensure( 'stream' )
        ->stream( 'Summarize the benefits of renewable energy' );

    return response()->eventStream( function () use ( $response ) {
        foreach( $response->stream() as $chunk ) {
            if( is_string( $chunk ) ) {
                yield $chunk;
            }
        }
    } );
} );

structure

Generate structured output from the given prompt and schema. The response JSON is parsed and available via the structured() method on the response object.

public function structure( string $prompt, Schema $schema, array $files = [], array $options = [] ) : TextResponse
  • @param string $prompt Input prompt for structured text generation
  • @param Schema $schema Schema definition for the structured output
  • @param array<int, File> $files Files for multimodal input (images, audio, documents)
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Response text with structured data

Supply prior conversation turns with withMessages(); the current $prompt is appended as the final user message.

Supported options:

Example:

use Aimeos\Prisma\Prisma;
use Aimeos\Prisma\Schema\Schema;

$schema = Schema::for( 'person', [
    'name' => Schema::string(),
    'age' => Schema::integer(),
] );

$textResponse = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->ensure( 'structure' )
    ->structure( 'Extract the person from: John is 30 years old', $schema );

$data = $textResponse->structured(); // ['name' => 'John', 'age' => 30]
$json = $textResponse->text(); // '{"name":"John","age":30}'

Output mode:

Providers with mode selection default to sending the schema through their native structured-output API. Pass ['mode' => 'json'] to embed the schema in the prompt and parse the JSON response instead; ['mode' => 'structured'] selects native mode explicitly. Other values throw BadRequestException on these providers. Schema::strict() controls the strict flag where supported and defaults to false.

Bedrock, Deepseek and Ollama always use JSON mode and ignore the option. Cohere also ignores mode, but always sends response_format.json_schema. Mistral uses prompt-based JSON through Chat Completions when custom tools are configured and ignores mode on that path.

$textResponse = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'] )
    ->ensure( 'structure' )
    ->structure( 'Extract the person', $schema, [], ['mode' => 'json'] );

Warning: structured() is the model's output parsed as-is — always treat it as untrusted. Guard two separate things:

  • Shape — it is not validated against your schema. Native strict mode is provider-enforced, but JSON mode (['mode' => 'json'], and the JSON-only providers above) gives no guarantee the result matches the schema. Check it with $schema->validate( $data ) (returns [] when valid).
  • Values — even a schema-conformant result contains model-generated text. validate() verifies types and constraints, not safety, so never drop a value straight into SQL, a shell command, a file path, or markup. Use bound parameters, escaping, or allow-lists, exactly as you would for any user input.
$data = $textResponse->structured();
$errors = $schema->validate( $data ); // [] when valid
if( $errors ) {
    // reject, retry, or handle the mismatch
}

translate

Translate one or more texts from one language to another.

public function translate( array $texts, string $to, ?string $from = null, ?string $context = null, array $options = [] ) : TextResponse
  • @param array<string> $texts Input texts to be translated
  • @param string $to ISO language code to translate the text into
  • @param string|null $from ISO language code of the input text (optional, auto-detected if omitted)
  • @param string|null $context Context for the translation (optional)
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Response text

Supported options:

Example:

use Aimeos\Prisma\Prisma;

$textResponse = Prisma::text()
    ->using( 'deepl', ['api_key' => 'xxx'])
    ->ensure( 'translate' )
    ->translate( ['Hello', 'World'], 'de', 'en' );

$texts = $textResponse->texts(); // ['Hallo', 'Welt']

vectorize

Creates embedding vectors of the texts' content.

public function vectorize( array $texts, ?int $size = null, array $options = [] ) : VectorResponse
  • @param array<int, string> $texts List of input texts
  • @param int|null $size Size of the resulting vector or null for provider default
  • @param array<string, mixed> $options Provider specific options
  • @return VectorResponse Response vector object

Supported options:

Example:

use Aimeos\Prisma\Prisma;

$vectorResponse = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'])
    ->ensure( 'vectorize' )
    ->vectorize( ['The quick brown fox', 'jumps over the lazy dog'], 256 );

$vectors = $vectorResponse->vectors(); // one embedding vector per input text

write

Generate text from the given prompt with optional multimodal file inputs (images, audio, documents).

public function write( string $prompt, array $files = [], array $options = [] ) : TextResponse
  • @param string $prompt Input prompt for text generation
  • @param array<int, File> $files Files for multimodal input (images, audio, documents)
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Response text

Supply prior conversation turns with withMessages(); the current $prompt is appended as the final user message.

Supported options:

Example:

use Aimeos\Prisma\Prisma;

$textResponse = Prisma::text()
    ->using( 'openai', ['api_key' => 'xxx'])
    ->ensure( 'write' )
    ->write( 'Summarize the benefits of renewable energy' );

$texts = $textResponse->texts(); // ['Renewable energy offers...']

Video API

imagine

Generate a video from a prompt and optional input media.

public function imagine( string $prompt, array $media = [], array $options = [] ) : FileResponse
  • @param string $prompt Description of the video to generate
  • @param array $media Semantic media roles: start, end, and references
  • @param array<string, mixed> $options Common and provider-specific options
  • @return FileResponse Generated video response
use Aimeos\Prisma\Files\Image;
use Aimeos\Prisma\Prisma;

$video = Prisma::video()
    ->using( 'runway', ['api_key' => 'xxx'] )
    ->ensure( 'imagine' )
    ->imagine(
        'A paper boat crossing a rain-filled city street',
        [
            'start' => Image::fromUrl( 'https://example.com/start.png' ),
            'end' => Image::fromUrl( 'https://example.com/end.png' ),
            'references' => [],
        ],
        [
            'duration' => 5,
            'aspectRatio' => '16:9',
            'resolution' => '720p',
            'audio' => true,
        ]
    );

$url = $video->first()?->url();

The common media roles are:

  • start: An Image used as the first frame
  • end: An Image used as the last frame
  • references: A list of Audio, Image, or Video reference files

Providers have different media capabilities. Unsupported file types, orphaned end frames, and conflicting media combinations are omitted silently. When a provider cannot combine frame interpolation with references, start/end takes precedence. An audio-only reference set is also omitted when the provider requires an image or video reference. Openrouter forwards end frames even without a start frame and forwards audio-only references; the selected model determines whether they are accepted.

Provider start end references
Alibaba Wan image image image, video, audio; frame mode may use one driving audio
Bedrock Nova Reel image - -
BytePlus Seedance image image image, video, audio; audio requires a visual reference
Google Omni image - image
Google Veo image image image
Luma Ray image image -
MiniMax Hailuo image image image
Openrouter image image image, video, audio
Runway image image -
xAI Grok Imagine image - image

Common options are duration (seconds), aspectRatio, resolution, audio, count, seed, and loop. A provider maps the options it supports and ignores the rest. Provider-native options can be supplied in the same array. Openrouter uses generate_audio for audio generation; the common audio option is ignored.

Most video generation jobs are asynchronous. Accessing first(), files(), binary(), or iterating the response waits and polls until the provider finishes. Use ready() to perform one non-blocking status poll; providers returning video data immediately are ready without polling.

Alibaba and Openrouter video polling stop after 900 seconds by default. Set poll_timeout in the provider configuration to another number of seconds, or to 0 to disable the deadline. For changes to fromAsync() in custom providers, see the migration note.

Supported options:

Amazon Nova Reel also requires an S3 destination in the provider configuration: ['api_key' => 'xxx', 's3_uri' => 's3://bucket/prefix'].

describe

Describe the content of a video file.

public function describe( Video $video, ?string $lang = null, array $options = [] ) : TextResponse
  • @param Video $video Input video object
  • @param string|null $lang ISO language code the description should be generated in
  • @param array<string, mixed> $options Provider specific options
  • @return TextResponse Response text

Supported options:

extend

Continue a video according to the prompt.

public function extend( Video $video, string $prompt, array $options = [] ) : FileResponse
  • @param Video $video Input video object
  • @param string $prompt Prompt describing the continuation
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Extended video response
use Aimeos\Prisma\Files\Video;
use Aimeos\Prisma\Prisma;

$source = Video::fromUrl( 'https://example.com/video.mp4', 'video/mp4' );

$video = Prisma::video()
    ->using( 'xai', ['api_key' => 'xxx'] )
    ->ensure( 'extend' )
    ->extend( $source, 'The camera pulls back to reveal the city skyline', [
        'duration' => 6,
    ] );

$url = $video->first()?->url();

Most extension jobs are asynchronous and use the same lazy polling behavior as imagine(). Providers ignore options they don't support. duration is supported by all three providers, but Alibaba interprets it as the total output duration while xAI interprets it as the duration of the new continuation. BytePlus also supports direction with forward (default) or backward.

Supported options:

repaint

Repaint a video according to the prompt.

public function repaint( Video $video, string $prompt, array $media = [], array $options = [] ) : FileResponse
  • @param Video $video Input video object
  • @param string $prompt Prompt describing the changes
  • @param array $media Semantic media role references
  • @param array<string, mixed> $options Common and provider-specific options
  • @return FileResponse Repainted video response
use Aimeos\Prisma\Files\Image;
use Aimeos\Prisma\Files\Video;
use Aimeos\Prisma\Prisma;

$source = Video::fromUrl( 'https://example.com/video.mp4', 'video/mp4' );
$reference = Image::fromUrl( 'https://example.com/style.png', 'image/png' );

$video = Prisma::video()
    ->using( 'runway', ['api_key' => 'xxx'] )
    ->ensure( 'repaint' )
    ->repaint( $source, 'Use the colors and clothing from the reference image', [
        'references' => [$reference],
    ] );

$url = $video->first()?->url();

Most repaint jobs are asynchronous and use the same lazy polling behavior as imagine(). Unsupported reference types and references beyond provider limits are omitted silently. Calls using the third argument for options remain supported.

Provider references
Alibaba Wan image, up to 4
BytePlus Seedance image, video, audio
Google Omni image
Luma Ray image keyframes
Runway image keyframes, up to 5
xAI Grok Imagine -

Luma uses the first reference at frame 0 by default. Multiple Luma references require keyframeIndexes, for example [0, 48]. Runway distributes references across the video by default; normalized positions can be supplied using referencePositions, for example [0.0, 1.0].

Supported options:

uncrop

Extend/outpaint a video frame according to the prompt.

public function uncrop( Video $video, string $prompt, float $top, float $right, float $bottom, float $left, array $options = [] ) : FileResponse
  • @param Video $video Input video object
  • @param string $prompt Prompt describing the extended scene
  • @param float $top Fraction of the source height to add at the top
  • @param float $right Fraction of the source width to add at the right
  • @param float $bottom Fraction of the source height to add at the bottom
  • @param float $left Fraction of the source width to add at the left
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Extended video response
use Aimeos\Prisma\Files\Video;
use Aimeos\Prisma\Prisma;

$source = Video::fromUrl( 'https://example.com/video.mp4', 'video/mp4' );

$video = Prisma::video()
    ->using( 'luma', ['api_key' => 'xxx'] )
    ->ensure( 'uncrop' )
    ->uncrop(
        $source,
        'Extend the flower garden naturally beyond the frame',
        0,
        0.25,
        0,
        0.25,
        ['resolution' => '720p']
    );

$url = $video->first()?->url();

Each edge value is a fraction of the corresponding source dimension and is limited to the range from 0 to 1. Alibaba maps these values to its outpainting scales. Luma maps them to the source rectangle of a video_reframe request. At least one edge must be greater than zero. Non-finite values are rejected and unsupported options are ignored.

Alibaba supports prompt_extend, seed, and watermark. Luma supports aspectRatio and resolution (360p, 540p, 720p, or 1080p); availability of 1080p reframing depends on the Luma account.

Supported options:

upscale

Scale up a video.

public function upscale( Video $video, int $factor, array $options = [] ) : FileResponse
  • @param Video $video Input video object
  • @param int $factor Upscaling factor between 2 and the maximum value supported by the provider
  • @param array<string, mixed> $options Provider specific options
  • @return FileResponse Upscaled video response
use Aimeos\Prisma\Files\Video;
use Aimeos\Prisma\Prisma;

$source = Video::fromUrl( 'https://example.com/video.mp4', 'video/mp4' );

$video = Prisma::video()
    ->using( 'runway', ['api_key' => 'xxx'] )
    ->ensure( 'upscale' )
    ->upscale( $source, 4, [
        'resolution' => '4k',
        'flavor' => 'natural',
    ] );

$url = $video->first()?->url();

Runway maps factors of four or more to 4k and lower factors to 2k. Set resolution to 720p, 1k, 2k, or 4k to select the exact output tier. Other supported options are creativity, sharpen, and smartGrain (integers from 0 to 100), flavor (natural or vivid), and fpsBoost (boolean). Unsupported values and options are ignored. Upscaling is asynchronous and uses the same lazy polling behavior as other video operations.

Supported options: