Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b2df3ff
feat(perplexity): add config
joao-salomao Nov 16, 2025
94a5ded
feat(perplexity): add Perplexity factory to PrismManager
joao-salomao Nov 16, 2025
d081205
feat(perplexity): add structured and text handlers
joao-salomao Nov 16, 2025
ceb3c55
test(perplexity): add basic tests
joao-salomao Nov 16, 2025
4e76dcc
refactor(perplexity): add todos
joao-salomao Nov 16, 2025
ac18eeb
refactor(perplexity): create media mappers
joao-salomao Nov 20, 2025
cdbe3fc
refactor(perplexity): create messages mapper
joao-salomao Nov 20, 2025
07d539f
refactor(perplexity): move Http handling logic to a concern
joao-salomao Nov 20, 2025
8be1ef8
refactor(perplexity): use HandleHttpRequests concern
joao-salomao Nov 20, 2025
4c157d2
test(perplexity): testing document mapper
joao-salomao Nov 20, 2025
249ab95
test(perplexity): testing image mapper
joao-salomao Nov 20, 2025
b0baf95
refactor(perplexity): add ExtractsStructuredOutput trait for parsing …
joao-salomao Nov 22, 2025
43eb7d6
feat(perplexity): add ExtractsReasoning trait for extracting reasonin…
joao-salomao Nov 22, 2025
a65bd50
refactor(perplexity): merge ExtractsReasoning trait with ExtractsAddi…
joao-salomao Nov 22, 2025
12b656b
refactor(perplexity): remove todo
joao-salomao Nov 22, 2025
99ad084
feat(perplexity): validating allowed image mime types
joao-salomao Nov 22, 2025
dbb341a
feat(perplexity): validating allowed document mime types
joao-salomao Nov 22, 2025
0002b21
fix(perplexity): document mapper to only send the encoded bytes witho…
joao-salomao Nov 23, 2025
e5545db
feature(perplexity): using the native structure output with the respo…
joao-salomao Nov 23, 2025
ec05f23
docs(perplexity): create docs page for perplexity
joao-salomao Nov 23, 2025
5e37c86
docs(perplexity): add support table and links
joao-salomao Nov 23, 2025
c7add2b
feature(perplexity): add streaming handler
joao-salomao Nov 23, 2025
7644066
refactor(perplexity): move finish reason to a concern
joao-salomao Nov 23, 2025
9e4ce7c
refactor(perplexity): remove unused method
joao-salomao Nov 23, 2025
0a4d2eb
docs(perplexity): add link to Perplexity streaming docs
joao-salomao Nov 23, 2025
9a638af
Formatting
sixlive Jan 26, 2026
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
4 changes: 4 additions & 0 deletions config/prism.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,9 @@
'x_title' => env('OPENROUTER_SITE_X_TITLE', null),
],
],
'perplexity' => [
'api_key' => env('PERPLEXITY_API_KEY', ''),
'url' => env('PERPLEXITY_URL', 'https://api.perplexity.ai'),
],
],
];
4 changes: 4 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ export default defineConfig({
text: "XAI",
link: "/providers/xai",
},
{
text: "Perplexity",
link: "/providers/perplexity",
},
],
},
{
Expand Down
8 changes: 7 additions & 1 deletion docs/components/ExceptionSupport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,13 @@ export default {
rateLimited: Supported,
overloaded: Unsupported,
tooLarge: Unsupported,
},
},
{
name: "Perplexity",
rateLimited: Supported,
overloaded: Unsupported,
tooLarge: Unsupported,
},
],
};
},
Expand Down
12 changes: 12 additions & 0 deletions docs/components/ProviderSupport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,18 @@ export default {
documents: Unsupported,
moderation: Unsupported,
},
{
name: "Perplexity",
text: Supported,
streaming: Supported,
structured: Supported,
embeddings: Unsupported,
image: Unsupported,
"speech-to-text": Unsupported,
"text-to-speech": Unsupported,
tools: Unsupported,
documents: Unsupported,
},
],
};
},
Expand Down
1 change: 1 addition & 0 deletions docs/getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ We currently offer first-party support for these leading AI providers:
- [Ollama](/providers/ollama.md)
- [OpenAI](/providers/openai.md)
- [xAI](/providers/xai.md)
- [Perplexity](/providers/perplexity.md)

Each provider brings its own strengths to the table, and Prism makes it easy to use them all through a consistent, elegant interface.

Expand Down
62 changes: 62 additions & 0 deletions docs/providers/perplexity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Perplexity
## Configuration

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

## Documents

Sonar models support document analysis through file uploads. You can provide files either as URLs to publicly accessible documents or as base64 encoded bytes. Ask questions about document content, get summaries, extract information, and perform detailed analysis of uploaded files in multiple formats including PDF, DOC, DOCX, TXT, and RTF.
- The maximum file size is 50MB. Files larger than this limit will not be processed
- Ensure provided HTTPS URLs are publicly accessible
Check it out the [documentation for more details](https://docs.perplexity.ai/guides/file-attachments)

## Images
Sonar models support image analysis through direct image uploads. You can include images in your API requests to support multi-modal conversations alongside text. Images can be provided either as base64 encoded strings within a data URI or as standard HTTPS URLs.
- When using base64 encoding, the API currently only supports images up to 50 MB per image
- Supported formats for base64 encoded images: PNG (image/png), JPEG (image/jpeg), WEBP (image/webp), and GIF (image/gif)
- When using an HTTPS URL, the model will attempt to fetch the image from the provided URL. Ensure the URL is publicly accessible.

## Considerations
### Message Order

- Message order matters. Perplexity is strict about the message order being:

1. `SystemMessage`
2. `UserMessage`
3. `AssistantMessage`

### Additional fields
Perplexity outputs additional fields in the response, such as `citations`, `search_results`, and the `reasoning` that is extracted from the model response. These fields are exposed in the response object
via the property `additionalFields`. e.g `$response->additionalFields['citations']`.

### Structured Output

Perplexity supports two types of structured outputs: JSON Schema and Regex; but currently Prism only supports JSON Schema.

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

```php
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();
```
1 change: 1 addition & 0 deletions src/Enums/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ enum Provider: string
case Gemini = 'gemini';
case VoyageAI = 'voyageai';
case ElevenLabs = 'elevenlabs';
case Perplexity = 'perplexity';
}
6 changes: 6 additions & 0 deletions src/PrismManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Prism\Prism\Providers\Ollama\Ollama;
use Prism\Prism\Providers\OpenAI\OpenAI;
use Prism\Prism\Providers\OpenRouter\OpenRouter;
use Prism\Prism\Providers\Perplexity\Perplexity;
use Prism\Prism\Providers\Provider;
use Prism\Prism\Providers\VoyageAI\VoyageAI;
use Prism\Prism\Providers\XAI\XAI;
Expand Down Expand Up @@ -150,6 +151,11 @@
);
}

protected function createPerplexityProvider(array $config): Perplexity

Check failure on line 154 in src/PrismManager.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Prism\Prism\PrismManager::createPerplexityProvider() has parameter $config with no value type specified in iterable type array.
{
return new Perplexity(apiKey: $config['api_key'] ?? '', url: $config['url'] ?? '');
}

/**
* @param array<string, mixed> $config
*/
Expand Down
42 changes: 42 additions & 0 deletions src/Providers/Perplexity/Concerns/ExtractsAdditionalContent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Prism\Prism\Providers\Perplexity\Concerns;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

trait ExtractsAdditionalContent
{
/**
* @return array<string, mixed>
*
* @throws \JsonException
*/
protected function extractsAdditionalContent(array $data): array
{
return Arr::whereNotNull([
'citations' => data_get($data, 'citations'),
'search_results' => data_get($data, 'search_results'),
'reasoning' => $this->extractsReasoning(data_get($data, 'choices.{last}.message.content')),
]);
}

/***
* It extracts the reasoning part from a given string content which might include the reasoning tags.
* e.g <think>reasoning</think> or ```json ... ```
*
* @param string $content
*
* @return string|null
* @throws \JsonException
*/
protected function extractsReasoning(string $content): ?string
{
$str = Str::of($content);
if (! $str->contains('<think>')) {
return null;
}

return $str->between('<think>', '</think>')->trim()->toString();
}
}
18 changes: 18 additions & 0 deletions src/Providers/Perplexity/Concerns/ExtractsFinishReason.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Prism\Prism\Providers\Perplexity\Concerns;

use Prism\Prism\Enums\FinishReason;

trait ExtractsFinishReason
{
protected function extractsFinishReason(array $data): FinishReason
{
$rawFinishReason = data_get($data, 'choices.0.finish_reason');

return match ($rawFinishReason) {
'stop' => FinishReason::Stop,
default => FinishReason::Unknown,
};
}
}
17 changes: 17 additions & 0 deletions src/Providers/Perplexity/Concerns/ExtractsMeta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Prism\Prism\Providers\Perplexity\Concerns;

use Prism\Prism\ValueObjects\Meta;

trait ExtractsMeta
{
protected function extractsMeta(array $data): Meta
{
return new Meta(
id: data_get($data, 'id'),
model: data_get($data, 'model'),
rateLimits: [],
);
}
}
42 changes: 42 additions & 0 deletions src/Providers/Perplexity/Concerns/ExtractsStructuredOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Prism\Prism\Providers\Perplexity\Concerns;

use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

trait ExtractsStructuredOutput
{
/***
* It extracts structured JSON output from a given string content which might include the reasoning tags or code block formatting.
* e.g <think>...</think> or ```json ... ```
*
* @param string $content
*
* @return array
* @throws \JsonException
*/
protected function parseStructuredOutput(string $content): array
{
$str = Str::of($content)
->trim()
->when(
static fn (Stringable $str) => $str->contains('</think>'),
static fn (Stringable $str) => $str->after('</think>')->trim()
)
->when(
static fn (Stringable $str) => $str->startsWith('```json'),
static fn (Stringable $str) => $str->after('```json')->trim()
)
->when(
static fn (Stringable $str) => $str->startsWith('```'),
static fn (Stringable $str) => $str->substr(3)->trim()
)
->when(
static fn (Stringable $str) => $str->endsWith('```'),
static fn (Stringable $str) => $str->substr(0, $str->length('UTF-8') - 3)->trim()
);

return json_decode($str, associative: true, flags: JSON_THROW_ON_ERROR);
}
}
16 changes: 16 additions & 0 deletions src/Providers/Perplexity/Concerns/ExtractsUsage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Prism\Prism\Providers\Perplexity\Concerns;

use Prism\Prism\ValueObjects\Usage;

trait ExtractsUsage
{
protected function extractUsage(array $data): Usage
{
return new Usage(
promptTokens: data_get($data, 'usage.prompt_tokens'),
completionTokens: data_get($data, 'usage.completion_tokens'),
);
}
}
71 changes: 71 additions & 0 deletions src/Providers/Perplexity/Concerns/HandlesHttpRequests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Providers\Perplexity\Concerns;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Arr;
use Prism\Prism\Contracts\PrismRequest;
use Prism\Prism\Providers\Perplexity\Maps\MessagesMapper;
use Prism\Prism\Structured\Request as StructuredRequest;

trait HandlesHttpRequests
{
protected function sendRequest(PendingRequest $client, PrismRequest $request, bool $stream = false): Response
{
return $client->post(
'/chat/completions',
$this->buildHttpRequestPayload($request, $stream)
);
}

/**
* @return array<string, mixed>
*
* @throws \Exception
*/
protected function buildHttpRequestPayload(PrismRequest $request, bool $stream = false): array
{
$responseFormat = null;

if ($request->is(StructuredRequest::class)) {
$responseFormat = Arr::whereNotNull([
'type' => 'json_schema',
'json_schema' => [
'schema' => $request->schema()->toArray(),
],
]);
}

return array_merge([
'model' => $request->model(),
'messages' => (new MessagesMapper($request->messages()))->toPayload(),
'max_tokens' => $request->maxTokens(),
'stream' => $stream,
], Arr::whereNotNull([
'response_format' => $responseFormat,
'temperature' => $request->temperature(),
'top_p' => $request->topP(),
'top_k' => $request->providerOptions('top_k'),
'reasoning_effort' => $request->providerOptions('reasoning_effort'),
'web_search_options' => $request->providerOptions('web_search_options'),
'search_mode' => $request->providerOptions('search_mode'),
'language_preference' => $request->providerOptions('language_preference'),
'search_domain_filter' => $request->providerOptions('search_domain_filter'),
'return_images' => $request->providerOptions('return_images'),
'return_related_questions' => $request->providerOptions('return_related_questions'),
'search_recency_filter' => $request->providerOptions('search_recency_filter'),
'search_after_date_filter' => $request->providerOptions('search_after_date_filter'),
'search_before_date_filter' => $request->providerOptions('search_before_date_filter'),
'last_updated_after_filter' => $request->providerOptions('last_updated_after_filter'),
'last_updated_before_filter' => $request->providerOptions('last_updated_before_filter'),
'presence_penalty' => $request->providerOptions('presence_penalty'),
'frequency_penalty' => $request->providerOptions('frequency_penalty'),
'disable_search' => $request->providerOptions('disable_search'),
'enable_search_classifier' => $request->providerOptions('enable_search_classifier'),
'media_response' => $request->providerOptions('media_response'),
]));
}
}
Loading
Loading