data_for_seo/api/ai_optimization/llm_responses.rs
1//! LLM Responses sub-API, shared by every provider.
2//!
3//! See <https://docs.dataforseo.com/v3/ai_optimization/chat_gpt/llm_responses/overview/>.
4
5use crate::entity::{
6 AiOptimizationLlmResponse, AiOptimizationLlmResponseModel, AiOptimizationTaskReady,
7};
8use crate::{DataForSeoApiResponse, DataForSeoClient};
9use serde::{Deserialize, Serialize};
10
11/// LLM Responses endpoints for a single provider (`chat_gpt`, `claude`, `gemini`).
12///
13/// These providers support both the Standard (`task_post`/`task_get`) and Live
14/// methods. Perplexity is Live-only and exposed separately.
15pub struct AiOptimizationApiLlmResponses<'a> {
16 pub(crate) client: &'a DataForSeoClient,
17 pub(crate) provider: &'static str,
18}
19
20impl AiOptimizationApiLlmResponses<'_> {
21 /// Lists the models available for this provider.
22 ///
23 /// `GET /v3/ai_optimization/{provider}/llm_responses/models`
24 /// See <https://docs.dataforseo.com/v3/ai_optimization/chat_gpt/llm_responses/models/>.
25 pub async fn models(&self) -> DataForSeoApiResponse<AiOptimizationLlmResponseModel> {
26 self.client
27 .http_get(&format!(
28 "/v3/ai_optimization/{}/llm_responses/models",
29 self.provider
30 ))
31 .await
32 }
33
34 /// Generates a response synchronously.
35 ///
36 /// `POST /v3/ai_optimization/{provider}/llm_responses/live`
37 /// See <https://docs.dataforseo.com/v3/ai_optimization/chat_gpt/llm_responses/live/>.
38 pub async fn live(
39 &self,
40 data: Vec<AiOptimizationLlmResponsesPost>,
41 ) -> DataForSeoApiResponse<AiOptimizationLlmResponse> {
42 self.client
43 .http_post(
44 &format!("/v3/ai_optimization/{}/llm_responses/live", self.provider),
45 &data,
46 )
47 .await
48 }
49
50 /// Queues a response task (Standard method).
51 ///
52 /// `POST /v3/ai_optimization/{provider}/llm_responses/task_post`
53 /// See <https://docs.dataforseo.com/v3/ai_optimization/chat_gpt/llm_responses/task_post/>.
54 pub async fn task_post(
55 &self,
56 data: Vec<AiOptimizationLlmResponsesPost>,
57 ) -> DataForSeoApiResponse<AiOptimizationLlmResponse> {
58 self.client
59 .http_post(
60 &format!(
61 "/v3/ai_optimization/{}/llm_responses/task_post",
62 self.provider
63 ),
64 &data,
65 )
66 .await
67 }
68
69 /// Lists tasks that have completed and are ready to collect.
70 ///
71 /// `GET /v3/ai_optimization/{provider}/llm_responses/tasks_ready`
72 /// See <https://docs.dataforseo.com/v3/ai_optimization/chat_gpt/llm_responses/tasks_ready/>.
73 pub async fn tasks_ready(&self) -> DataForSeoApiResponse<AiOptimizationTaskReady> {
74 self.client
75 .http_get(&format!(
76 "/v3/ai_optimization/{}/llm_responses/tasks_ready",
77 self.provider
78 ))
79 .await
80 }
81
82 /// Collects the result of a completed task by its `id`.
83 ///
84 /// `GET /v3/ai_optimization/{provider}/llm_responses/task_get/{id}`
85 /// See <https://docs.dataforseo.com/v3/ai_optimization/chat_gpt/llm_responses/task_get/>.
86 pub async fn task_get(&self, id: &str) -> DataForSeoApiResponse<AiOptimizationLlmResponse> {
87 self.client
88 .http_get(&format!(
89 "/v3/ai_optimization/{}/llm_responses/task_get/{}",
90 self.provider, id
91 ))
92 .await
93 }
94}
95
96/// Perplexity LLM Responses endpoints (Live-only).
97///
98/// See <https://docs.dataforseo.com/v3/ai_optimization/perplexity/llm_responses/overview/>.
99pub struct AiOptimizationApiPerplexityLlmResponses<'a> {
100 pub(crate) client: &'a DataForSeoClient,
101}
102
103impl AiOptimizationApiPerplexityLlmResponses<'_> {
104 /// Lists the Perplexity models available.
105 ///
106 /// `GET /v3/ai_optimization/perplexity/llm_responses/models`
107 pub async fn models(&self) -> DataForSeoApiResponse<AiOptimizationLlmResponseModel> {
108 self.client
109 .http_get("/v3/ai_optimization/perplexity/llm_responses/models")
110 .await
111 }
112
113 /// Generates a Perplexity response synchronously.
114 ///
115 /// `POST /v3/ai_optimization/perplexity/llm_responses/live`
116 /// See <https://docs.dataforseo.com/v3/ai_optimization/perplexity/llm_responses/live/>.
117 pub async fn live(
118 &self,
119 data: Vec<AiOptimizationLlmResponsesPost>,
120 ) -> DataForSeoApiResponse<AiOptimizationLlmResponse> {
121 self.client
122 .http_post("/v3/ai_optimization/perplexity/llm_responses/live", &data)
123 .await
124 }
125}
126
127/// A single turn of a conversation passed via `message_chain`.
128#[derive(Debug, Default, Serialize, Deserialize, Clone)]
129pub struct AiOptimizationLlmMessage {
130 /// Role of the message author (e.g. `"user"`, `"assistant"`).
131 pub role: String,
132 /// Text content of the message.
133 pub message: String,
134}
135
136impl AiOptimizationLlmMessage {
137 /// Creates a message with the given role and content.
138 pub fn new(role: &str, message: &str) -> AiOptimizationLlmMessage {
139 AiOptimizationLlmMessage {
140 role: role.to_string(),
141 message: message.to_string(),
142 }
143 }
144}
145
146/// Request body for the LLM Responses `live` and `task_post` endpoints.
147///
148/// `postback_url` and `pingback_url` apply only to `task_post`.
149#[derive(Debug, Default, Serialize, Deserialize, Clone)]
150pub struct AiOptimizationLlmResponsesPost {
151 /// The question or task to send to the model (max 500 characters).
152 pub user_prompt: String,
153 /// Model to use; a base name selects the latest version.
154 pub model_name: String,
155 /// Maximum tokens to generate (16–4096, default: 2048).
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub max_output_tokens: Option<i64>,
158 /// Sampling temperature (0–2, default: 0.94; unsupported by reasoning models).
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub temperature: Option<f64>,
161 /// Nucleus sampling probability (0–1, default: 0.92; mutually exclusive with `temperature`).
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub top_p: Option<f64>,
164 /// Whether the model may access the web (default: false).
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub web_search: Option<bool>,
167 /// Whether to force the model to use web search (default: false).
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub force_web_search: Option<bool>,
170 /// ISO country code to localize web search.
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub web_search_country_iso_code: Option<String>,
173 /// City name to localize web search.
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub web_search_city: Option<String>,
176 /// System instructions steering the model (max 500 characters).
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub system_message: Option<String>,
179 /// Prior conversation turns to include (max 10).
180 #[serde(skip_serializing_if = "Option::is_none")]
181 pub message_chain: Option<Vec<AiOptimizationLlmMessage>>,
182 /// URL to POST the result to when the task completes (`task_post` only).
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub postback_url: Option<String>,
185 /// URL to ping when the task completes (`task_post` only).
186 #[serde(skip_serializing_if = "Option::is_none")]
187 pub pingback_url: Option<String>,
188 /// User-defined task identifier (max 255 characters).
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub tag: Option<String>,
191}
192
193impl AiOptimizationLlmResponsesPost {
194 /// Creates a request with the given prompt and model.
195 pub fn new(user_prompt: &str, model_name: &str) -> AiOptimizationLlmResponsesPost {
196 AiOptimizationLlmResponsesPost {
197 user_prompt: user_prompt.to_string(),
198 model_name: model_name.to_string(),
199 ..Default::default()
200 }
201 }
202}