Skip to content

feat: Support perplexity citations #574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Resources/Chat.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function create(array $parameters): CreateResponse

$payload = Payload::create('chat/completions', $parameters);

/** @var Response<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}> $response */
/** @var Response<array{id: string, object: string, created: int, model: string, citations?: string[], system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}> $response */
$response = $this->transporter->requestObject($payload);

return CreateResponse::from($response->data(), $response->meta());
Expand Down
5 changes: 4 additions & 1 deletion src/Responses/Chat/CreateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ private function __construct(
public readonly string $object,
public readonly int $created,
public readonly string $model,
public readonly ?array $citations,
public readonly ?string $systemFingerprint,
public readonly array $choices,
public readonly ?CreateResponseUsage $usage,
Expand All @@ -41,7 +42,7 @@ private function __construct(
/**
* Acts as static factory, and returns a new Response instance.
*
* @param array{id?: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, annotations?: array<int, array{type: string, url_citation: array{start_index: int, end_index: int, title: string, url: string}}>, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage?: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}, completion_tokens_details?:array{audio_tokens?:int, reasoning_tokens:int, accepted_prediction_tokens:int, rejected_prediction_tokens:int}}} $attributes
* @param array{id?: string, object: string, created: int, model: string, citations?: string[], system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, annotations?: array<int, array{type: string, url_citation: array{start_index: int, end_index: int, title: string, url: string}}>, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage?: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}, completion_tokens_details?:array{audio_tokens?:int, reasoning_tokens:int, accepted_prediction_tokens:int, rejected_prediction_tokens:int}}} $attributes
*/
public static function from(array $attributes, MetaInformation $meta): self
{
Expand All @@ -54,6 +55,7 @@ public static function from(array $attributes, MetaInformation $meta): self
$attributes['object'],
$attributes['created'],
$attributes['model'],
$attributes['citations'] ?? null,
$attributes['system_fingerprint'] ?? null,
$choices,
isset($attributes['usage']) ? CreateResponseUsage::from($attributes['usage']) : null,
Expand All @@ -71,6 +73,7 @@ public function toArray(): array
'object' => $this->object,
'created' => $this->created,
'model' => $this->model,
'citations' => $this->citations,
'system_fingerprint' => $this->systemFingerprint,
'choices' => array_map(
static fn (CreateResponseChoice $result): array => $result->toArray(),
Expand Down
14 changes: 10 additions & 4 deletions src/Responses/Chat/CreateResponseUsage.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ private function __construct(
public readonly ?int $completionTokens,
public readonly int $totalTokens,
public readonly ?CreateResponseUsagePromptTokensDetails $promptTokensDetails,
public readonly ?CreateResponseUsageCompletionTokensDetails $completionTokensDetails
public readonly ?CreateResponseUsageCompletionTokensDetails $completionTokensDetails,
public readonly ?string $searchContextSize,
) {}

/**
* @param array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}, completion_tokens_details?:array{audio_tokens?:int, reasoning_tokens:int, accepted_prediction_tokens:int, rejected_prediction_tokens:int}} $attributes
* @param array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}, completion_tokens_details?:array{audio_tokens?:int, reasoning_tokens:int, accepted_prediction_tokens:int, rejected_prediction_tokens:int}, search_context_size: string|null} $attributes
*/
public static function from(array $attributes): self
{
Expand All @@ -24,12 +25,13 @@ public static function from(array $attributes): self
$attributes['completion_tokens'] ?? null,
$attributes['total_tokens'],
isset($attributes['prompt_tokens_details']) ? CreateResponseUsagePromptTokensDetails::from($attributes['prompt_tokens_details']) : null,
isset($attributes['completion_tokens_details']) ? CreateResponseUsageCompletionTokensDetails::from($attributes['completion_tokens_details']) : null
isset($attributes['completion_tokens_details']) ? CreateResponseUsageCompletionTokensDetails::from($attributes['completion_tokens_details']) : null,
$attributes['search_context_size'] ?? null,
);
}

/**
* @return array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}}
* @return array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}, search_context_size: string|null}
*/
public function toArray(): array
{
Expand All @@ -47,6 +49,10 @@ public function toArray(): array
$result['completion_tokens_details'] = $this->completionTokensDetails->toArray();
}

if ($this->searchContextSize) {
$result['search_context_size'] = $this->searchContextSize;
}

return $result;
}
}
40 changes: 37 additions & 3 deletions tests/Fixtures/Chat.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,40 @@ function chatCompletionWithSystemFingerprint(): array
];
}

function chatCompletionWithCitations(): array
{
return [
"id" => "80a3200c-98d0-4d29-97d0-4766130d7e4d",
"object" => "chat.completion",
"created" => 1746182677,
"model" => "sonar",
"citations" => [
"https://hennessey.com/seo/technical/what-are-the-benefits-of-technical-seo/",
"https://www.cloudflare.com/learning/performance/how-website-speed-boosts-seo/",
"https://www.semrush.com/blog/technical-seo/",
"https://thriveagency.com/news/mastering-technical-seo-for-website-performance/",
"https://elearninginfographics.com/the-importance-of-prioritizing-technical-seo-for-improved-website-performance/"
],
"choices" => [
[
"index" => 0,
"message" => [
"role" => "assistant",
"content" => "Technical SEO plays a crucial role in boosting website performance by enhancing various aspects that contribute to better search engine rankings and user experience. Here's how technical SEO impacts website performance, based on expert insights and case studies:\n\n## Key Benefits of Technical SEO\n\n1. **Website Speed**: Technical SEO improves site speed, which is a critical factor for both user experience and search engine rankings. Faster websites retain visitors, reduce bounce rates, and improve the chances of higher rankings in search results[2][5]. Techniques like compressing images, using Content Delivery Networks (CDNs), and minifying code can enhance speed[5].\n\n2. **Mobile Compatibility**: Ensuring that a website is mobile-friendly is vital, as Google prioritizes mobile-first indexing. Technical SEO helps websites adapt to mobile screens, providing a seamless experience across devices[1][5].\n\n3. **Crawlability and Indexing**: Technical SEO optimizes crawlability and indexing by guiding search engine bots through the site more efficiently. This involves creating XML sitemaps, using proper URL structures, and implementing canonical tags to ensure that content is indexed correctly, leading to better search engine visibility[3][5].\n\n4. **Security and Navigation**: Technical SEO also emphasizes website security and navigation. Ensuring that a site is secure (HTTPS) and easy to navigate improves user trust and engagement, which can positively influence search engine rankings[1][5].\n\n## Expert Views\n\nExperts generally agree that technical SEO is essential for maintaining high website performance. It not only ensures that search engines can crawl and index content effectively but also enhances the overall user experience, which is crucial for converting traffic into leads and customers[1][3]. \n\n## Case Studies\n\nWhile specific case studies are not provided in the search results, it is common for businesses to see significant improvements in organic traffic and user engagement when they implement comprehensive technical SEO strategies. Improvements in website speed, mobile compatibility, and crawlability typically lead to increased visibility in search results and better user retention rates.\n\nIn summary, technical SEO is indispensable for boosting website performance by optimizing for search engines and enhancing user experience, which are both critical factors in achieving higher search engine rankings and driving organic traffic."
],
'logprobs' => null,
'finish_reason' => null,
]
],
"usage" => [
"prompt_tokens" => 15,
"completion_tokens" => 438,
"total_tokens" => 453,
"search_context_size" => "low"
],
];
}

/**
* @return array<string, mixed>
*/
Expand Down Expand Up @@ -639,21 +673,21 @@ function chatCompletionStreamVisionContentChunk(): array
*/
function chatCompletionStream()
{
return fopen(__DIR__.'/Streams/ChatCompletionCreate.txt', 'r');
return fopen(__DIR__ . '/Streams/ChatCompletionCreate.txt', 'r');
}

/**
* @return resource
*/
function chatCompletionStreamPing()
{
return fopen(__DIR__.'/Streams/ChatCompletionPing.txt', 'r');
return fopen(__DIR__ . '/Streams/ChatCompletionPing.txt', 'r');
}

/**
* @return resource
*/
function chatCompletionStreamError()
{
return fopen(__DIR__.'/Streams/ChatCompletionCreateError.txt', 'r');
return fopen(__DIR__ . '/Streams/ChatCompletionCreateError.txt', 'r');
}
39 changes: 39 additions & 0 deletions tests/Resources/Chat.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,45 @@
->toBeInstanceOf(MetaInformation::class);
});

test('create perplexity', function () {
$client = mockClient('POST', 'chat/completions', [
"model" => "sonar",
"messages" => [["role" => "user","content" => "Does technical SEO boost website performance? What do case studies and experts say?"]],
], \OpenAI\ValueObjects\Transporter\Response::from(chatCompletionWithCitations(), metaHeaders()));

$result = $client->chat()->create([
"model" => "sonar",
"messages" => [["role" => "user","content" => "Does technical SEO boost website performance? What do case studies and experts say?"]],
]);

expect($result)
->toBeInstanceOf(CreateResponse::class)
->id->toBe('80a3200c-98d0-4d29-97d0-4766130d7e4d')
->object->toBe('chat.completion')
->created->toBe(1746182677)
->model->toBe('sonar')
->choices->toBeArray()->toHaveCount(1)
->choices->each->toBeInstanceOf(CreateResponseChoice::class)
->usage->toBeInstanceOf(CreateResponseUsage::class);

expect($result->choices[0])
->message->role->toBe('assistant')
->message->content->toBe("Technical SEO plays a crucial role in boosting website performance by enhancing various aspects that contribute to better search engine rankings and user experience. Here's how technical SEO impacts website performance, based on expert insights and case studies:\n\n## Key Benefits of Technical SEO\n\n1. **Website Speed**: Technical SEO improves site speed, which is a critical factor for both user experience and search engine rankings. Faster websites retain visitors, reduce bounce rates, and improve the chances of higher rankings in search results[2][5]. Techniques like compressing images, using Content Delivery Networks (CDNs), and minifying code can enhance speed[5].\n\n2. **Mobile Compatibility**: Ensuring that a website is mobile-friendly is vital, as Google prioritizes mobile-first indexing. Technical SEO helps websites adapt to mobile screens, providing a seamless experience across devices[1][5].\n\n3. **Crawlability and Indexing**: Technical SEO optimizes crawlability and indexing by guiding search engine bots through the site more efficiently. This involves creating XML sitemaps, using proper URL structures, and implementing canonical tags to ensure that content is indexed correctly, leading to better search engine visibility[3][5].\n\n4. **Security and Navigation**: Technical SEO also emphasizes website security and navigation. Ensuring that a site is secure (HTTPS) and easy to navigate improves user trust and engagement, which can positively influence search engine rankings[1][5].\n\n## Expert Views\n\nExperts generally agree that technical SEO is essential for maintaining high website performance. It not only ensures that search engines can crawl and index content effectively but also enhances the overall user experience, which is crucial for converting traffic into leads and customers[1][3]. \n\n## Case Studies\n\nWhile specific case studies are not provided in the search results, it is common for businesses to see significant improvements in organic traffic and user engagement when they implement comprehensive technical SEO strategies. Improvements in website speed, mobile compatibility, and crawlability typically lead to increased visibility in search results and better user retention rates.\n\nIn summary, technical SEO is indispensable for boosting website performance by optimizing for search engines and enhancing user experience, which are both critical factors in achieving higher search engine rankings and driving organic traffic.")
->index->toBe(0)
->logprobs->toBeNull()
->finishReason->toBeNull();

expect($result->usage)
->promptTokens->toBe(15)
->completionTokens->toBe(438)
->totalTokens->toBe(453)
->searchContextSize
->toBe('low');

expect($result->meta())
->toBeInstanceOf(MetaInformation::class);
});

test('create throws an exception if stream option is true', function () {
OpenAI::client('foo')->chat()->create([
'model' => 'gpt-3.5-turbo',
Expand Down
21 changes: 21 additions & 0 deletions tests/Responses/Chat/CreateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@
->toBe(chatCompletionWithSystemFingerprint());
});

test('from with citations', function () {
$completion = CreateResponse::from(chatCompletionWithCitations(), meta());

expect($completion)
->toBeInstanceOf(CreateResponse::class)
->id->toBe('80a3200c-98d0-4d29-97d0-4766130d7e4d')
->object->toBe('chat.completion')
->created->toBe(1746182677)
->model->toBe('sonar')
->systemFingerprint->toBeNull()
->citations->toBeArray()->toHaveCount(5)
->choices->toBeArray()->toHaveCount(1)
->choices->each->toBeInstanceOf(CreateResponseChoice::class)
->usage->toBeInstanceOf(CreateResponseUsage::class)
->meta()->toBeInstanceOf(MetaInformation::class);

expect($completion->toArray())
->toBeArray()
->toEqual(chatCompletionWithCitations());
});

test('from function response', function () {
$completion = CreateResponse::from(chatCompletionWithFunction(), meta());

Expand Down
Loading