Skip to main content

rig_core/providers/perplexity/
extension.rs

1//! Perplexity's typed reply extras. Perplexity has no typed request
2//! option: its Sonar Chat API was retired on 2026-09-27, so its request
3//! fields are left to `additional_params` and its replies are read from
4//! recordings.
5//!
6//! ```
7//! use rig_core::completion::CompletionResponse;
8//! use rig_core::providers::perplexity::extension::PerplexityExt;
9//!
10//! fn sources(reply: &CompletionResponse) -> Vec<String> {
11//!     reply
12//!         .extras_lossy::<PerplexityExt>()
13//!         .citations
14//!         .unwrap_or_default()
15//! }
16//! # let _ = sources;
17//! ```
18
19use serde::{Deserialize, Serialize};
20use serde_json::Value;
21
22use crate::completion::provider_options::reply_field;
23use crate::completion::{ExtensionOptions, ProviderExtension, ReplyExtras};
24use crate::message::Api;
25
26/// Perplexity's extension marker.
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28pub struct PerplexityExt;
29
30impl ProviderExtension for PerplexityExt {
31    const PROVIDER: &'static str = super::PROVIDER_NAME;
32    type Options = PerplexityOptions;
33    type Extras = PerplexityExtras;
34}
35
36/// Perplexity's request options: none.
37#[non_exhaustive]
38#[derive(Clone, Debug, Default, PartialEq, Serialize)]
39pub struct PerplexityOptions {}
40
41impl PerplexityOptions {
42    /// No option set.
43    pub fn new() -> Self {
44        Self::default()
45    }
46}
47
48impl ExtensionOptions for PerplexityOptions {
49    type Ext = PerplexityExt;
50}
51
52/// Perplexity's reply fields. Each is `None` when the reply lacks it.
53#[non_exhaustive]
54#[derive(Clone, Debug, Default, PartialEq)]
55pub struct PerplexityExtras {
56    /// The URLs the answer cites, in citation order.
57    pub citations: Option<Vec<String>>,
58    /// The search results the answer drew on.
59    pub search_results: Option<Vec<SearchResult>>,
60    /// The images the search returned.
61    pub images: Option<Vec<Value>>,
62    /// Follow-up questions Perplexity suggests.
63    pub related_questions: Option<Vec<String>>,
64    /// What the request cost.
65    pub cost: Option<PerplexityCost>,
66    /// How much search context went into the prompt.
67    pub search_context_size: Option<String>,
68}
69
70/// One search result.
71#[non_exhaustive]
72#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
73pub struct SearchResult {
74    /// The page title.
75    #[serde(default)]
76    pub title: Option<String>,
77    /// The page URL.
78    #[serde(default)]
79    pub url: Option<String>,
80    /// The publication date.
81    #[serde(default)]
82    pub date: Option<String>,
83    /// When the page last changed.
84    #[serde(default)]
85    pub last_updated: Option<String>,
86    /// The excerpt the answer drew on.
87    #[serde(default)]
88    pub snippet: Option<String>,
89    /// Where the result came from, such as `web`.
90    #[serde(default)]
91    pub source: Option<String>,
92}
93
94/// What a Perplexity request cost, in USD.
95#[non_exhaustive]
96#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize)]
97pub struct PerplexityCost {
98    /// For the prompt tokens.
99    #[serde(default)]
100    pub input_tokens_cost: Option<f64>,
101    /// For the completion tokens.
102    #[serde(default)]
103    pub output_tokens_cost: Option<f64>,
104    /// Per request.
105    #[serde(default)]
106    pub request_cost: Option<f64>,
107    /// In all.
108    #[serde(default)]
109    pub total_cost: Option<f64>,
110}
111
112impl ReplyExtras for PerplexityExtras {
113    fn from_reply(_api: &Api, raw: &Value) -> Result<Self, serde_json::Error> {
114        Ok(Self {
115            citations: reply_field(raw, "/citations")?,
116            search_results: reply_field(raw, "/search_results")?,
117            images: reply_field(raw, "/images")?,
118            related_questions: reply_field(raw, "/related_questions")?,
119            cost: reply_field(raw, "/usage/cost")?,
120            search_context_size: reply_field(raw, "/usage/search_context_size")?,
121        })
122    }
123}
124
125#[cfg(test)]
126mod tests;