Providers

Code language

View this guide and its examples in your language.

Perplexity

Important

Prism uses Perplexity's Agent API (POST /v1/agent). Review the model mappings and request behavior below when migrating from Sonar.

Migrating from Sonar#

Your model strings keep working. Perplexity replaced model slugs with presets, and Prism translates the retired slugs for you:

You pass Prism sends
sonar preset: fast
sonar-pro preset: low
sonar-reasoning preset: low (slug retired upstream)
sonar-reasoning-pro preset: low
sonar-deep-research preset: medium

A preset name given directly (fast, low, medium, high, xhigh, wide-research) is passed through, and anything else is treated as a real model id — openai/gpt-5.6-sol is sent as model, not guessed at as a preset.

Overriding a preset#

sonar-deep-research maps to medium. To select a different tier, set the preset explicitly:

Prism::text()
    ->using(Provider::Perplexity, 'sonar-deep-research')
    ->withProviderOptions(['preset' => 'high']);

withSystemPrompt() replaces the preset's own prompt#

withSystemPrompt() sets the Agent API's instructions field, replacing the preset's built-in system prompt rather than appending to it.

Prism only sends instructions when you actually set a system prompt, so presets keep their own by default.

Failures arrive as HTTP 200#

An incomplete, failed or cancelled run can return HTTP 200. For text, structured and streaming requests, Prism throws PrismRunException, which extends PrismException; it does not return partial output as a successful response.

For streams, catch the exception around iteration of asStream(). Text deltas already yielded are provisional; an unsuccessful terminal event throws before TextCompleteEvent or StreamEndEvent is emitted. Its run snapshot remains available through the same accessors below. An incomplete run does not imply a token limit: inspect incompleteReason() rather than assuming a larger token budget will resolve it.

An unrecognized non-progress stream status ends with FinishReason::Unknown; it does not confirm successful completion. A rate_limit_exceeded error keeps its PrismRateLimitedException classification, including on a terminal run snapshot, so callers can retain their backoff handling.

Use code() to distinguish run_incomplete, run_failed and run_cancelled without parsing the exception message. The exception provides:

Accessor Result
status() Provider run status, or null if unavailable
runId() Provider run ID, or null if absent
incompleteReason() incomplete_details.reason, or null if absent
incompleteDetails() Full provider incomplete-details array
output() Partial output items, including citation annotations and search-result sources
citations() Inline citation annotations in provider order, without deduplication; source items remain in output()
usage() Normalized Usage, or null if usage was not reported
usageDetails() Full provider usage ledger, including cost and tool-call breakdowns

httpStatus is 200; PHP's numeric getCode() remains 200 for compatibility. Use code() for the run outcome. Usage records resources spent, not successful completion: your application decides whether to bill an unsuccessful run.

Partial content is available only through explicit accessors, not appended to the message or copied into the public responseBody field. Treat returned diagnostics as sensitive data and apply your retention and access policies; do not log or serialize the complete exception indiscriminately.

What comes back#

$response->additionalContent['search_results'];   // structured sources, not prose
$response->additionalContent['fetch_url_results'];
$response->additionalContent['resolved_model'];   // which model the preset actually used

resolved_model identifies the model selected by the preset. Use it when interpreting usage and applying your application's provider policies.

An empty search_results on a completed run is normal — a preset may answer without searching — so do not treat a missing source list as an error.

Cost is reported, not estimated#

cost contains the provider-reported request cost:

$response->usage->cost;   // e.g. 0.005, or null if the response carried none

Only two Prism providers do this — Perplexity and OpenRouter. Everywhere else cost is null and you have to derive it. A 0.0 here is a genuine answer, not a missing one.

The full provider usage ledger, including its cost breakdown, is available in $response->additionalContent['usage'] when reported.

Note

tools: [] does not disable a preset's built-in tools. Perplexity exposes no public field that does.

Configuration#

'perplexity' => [
    'api_key' => env('PERPLEXITY_API_KEY', ''),
    'url' => env('PERPLEXITY_URL', 'https://api.perplexity.ai'),
]

Server tools and request options#

Passing custom tools with withTools() raises PrismException. Perplexity executes its own server tools; request those through the tools provider option:

use Prism\Prism\Facades\Prism;

$response = Prism::text()
    ->using('perplexity', 'low')
    ->withPrompt('Find recent PHP documentation about enums.')
    ->withProviderOptions([
        'tools' => [['type' => 'web_search']],
        'search_domain_filter' => ['php.net'],
        'search_recency_filter' => 'month',
    ])
    ->asText();

The search-filter keys search_domain_filter, search_recency_filter, search_after_date_filter, search_before_date_filter, last_updated_after_filter and last_updated_before_filter are moved into the web_search tool's filters. Prism adds that tool when needed. Filters explicitly set on a tool take precedence over translated request filters. A tool's filters value must be an array.

Supported request options also include background, max_steps, models, previous_response_id, store, skills, metadata and language_preference. reasoning_effort maps to reasoning.effort; an explicit reasoning object takes precedence. These are Agent API options, separate from Prism's local withMaxSteps() tool-loop limit. For background-job retrieval, cancellation and polling, see Prism Perplexity.

search_mode is refused when supplied. return_images and return_related_questions are refused unless their value is exactly false; those two no-op values are accepted and omitted. Remove unsupported Sonar options or express the request through a preset, server tool or structured prompt.

Documents#

This integration sends text messages to the Perplexity Agent API. It does not send Document attachments. Extract document text in your application and include it in the prompt, or use a provider that supports document parts.

Images#

This integration does not send Image attachments. Use a provider that supports image parts when your prompt needs visual input.

Considerations#

Message Order#

Use withSystemPrompt() for Agent API instructions. User and assistant messages retain their order in input; a single user message is sent as a string. Tool-result messages have no client-tool continuation on this provider and are not sent.

Additional fields#

Read provider-specific fields from $response->additionalContent. Available keys include search_results, fetch_url_results, resolved_model, response_id, response_status, annotations, usage and reasoning. Citation annotations are in annotations; there is no top-level citations key. Check whether a key is present before reading it.

Token Usage#

usage.input_tokens maps to promptTokens and usage.output_tokens maps to completionTokens, with the older prompt_tokens/completion_tokens keys used as fallbacks. Cache and thought token fields remain null. usage.cost.total_cost maps to Usage::$cost, including a reported zero; absence leaves cost null. Read the full ledger from additionalContent['usage'] rather than deriving a bill from token counts alone.

Structured Output#

Prism sends response_format: json_schema with your schema to the Agent API. Regex output constraints are not exposed by this integration.

Here's an example of how to use JSON Schema for structured output:

use Prism\Prism\Enums\Provider;
use Prism\Prism\Facades\Prism;
use Prism\Prism\Schema\ObjectSchema;
use Prism\Prism\Schema\StringSchema;

$response = Prism::structured()
    ->withSchema(new ObjectSchema(
        'weather_report',
        'Weather forecast with recommendations',
        [
            new StringSchema('forecast', 'The weather forecast'),
            new StringSchema('recommendation', 'Clothing recommendation')
        ],
        ['forecast', 'recommendation']
    ))
    ->using(Provider::Perplexity, 'sonar-pro')
    ->withPrompt('What\'s the weather like and what should I wear?')
    ->asStructured();