1use serde::{Deserialize, Serialize};
4
5use super::{Message, Plugin, Provider, ReasoningConfig, ResponseFormat, Tool, ToolChoice};
6
7#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
9pub struct ChatCompletionRequest {
10 pub model: String,
12 pub messages: Vec<Message>,
14
15 #[serde(skip_serializing_if = "Option::is_none", default)]
17 pub temperature: Option<f64>,
18 #[serde(skip_serializing_if = "Option::is_none", default)]
20 pub top_p: Option<f64>,
21 #[serde(skip_serializing_if = "Option::is_none", default)]
23 pub top_k: Option<u32>,
24 #[serde(skip_serializing_if = "Option::is_none", default)]
26 pub max_tokens: Option<u32>,
27 #[serde(skip_serializing_if = "Option::is_none", default)]
30 pub stream: Option<bool>,
31 #[serde(skip_serializing_if = "Option::is_none", default)]
33 pub stop: Option<serde_json::Value>,
34 #[serde(skip_serializing_if = "Option::is_none", default)]
36 pub seed: Option<i64>,
37 #[serde(skip_serializing_if = "Option::is_none", default)]
39 pub frequency_penalty: Option<f64>,
40 #[serde(skip_serializing_if = "Option::is_none", default)]
42 pub presence_penalty: Option<f64>,
43 #[serde(skip_serializing_if = "Option::is_none", default)]
45 pub repetition_penalty: Option<f64>,
46 #[serde(skip_serializing_if = "Option::is_none", default)]
48 pub logit_bias: Option<serde_json::Value>,
49 #[serde(skip_serializing_if = "Option::is_none", default)]
51 pub logprobs: Option<bool>,
52 #[serde(skip_serializing_if = "Option::is_none", default)]
54 pub top_logprobs: Option<u32>,
55 #[serde(skip_serializing_if = "Option::is_none", default)]
57 pub min_p: Option<f64>,
58 #[serde(skip_serializing_if = "Option::is_none", default)]
60 pub top_a: Option<f64>,
61
62 #[serde(skip_serializing_if = "Option::is_none", default)]
64 pub tools: Option<Vec<Tool>>,
65 #[serde(skip_serializing_if = "Option::is_none", default)]
67 pub tool_choice: Option<ToolChoice>,
68 #[serde(skip_serializing_if = "Option::is_none", default)]
70 pub response_format: Option<ResponseFormat>,
71 #[serde(skip_serializing_if = "Option::is_none", default)]
73 pub provider: Option<Provider>,
74 #[serde(skip_serializing_if = "Option::is_none", default)]
76 pub reasoning: Option<ReasoningConfig>,
77 #[serde(skip_serializing_if = "Option::is_none", default)]
79 pub transforms: Option<Vec<String>>,
80 #[serde(skip_serializing_if = "Option::is_none", default)]
82 pub plugins: Option<Vec<Plugin>>,
83 #[serde(skip_serializing_if = "Option::is_none", default)]
85 pub usage: Option<serde_json::Value>,
86 #[serde(skip_serializing_if = "Option::is_none", default)]
88 pub user: Option<String>,
89}
90
91impl ChatCompletionRequest {
92 pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
94 Self {
95 model: model.into(),
96 messages,
97 ..Default::default()
98 }
99 }
100
101 pub fn with_tools(mut self, tools: Vec<Tool>) -> Self {
103 self.tools = Some(tools);
104 self
105 }
106
107 pub fn with_tool_choice(mut self, choice: ToolChoice) -> Self {
109 self.tool_choice = Some(choice);
110 self
111 }
112
113 pub fn with_response_format(mut self, format: ResponseFormat) -> Self {
115 self.response_format = Some(format);
116 self
117 }
118
119 pub fn with_json_schema(
122 self,
123 name: impl Into<String>,
124 strict: bool,
125 schema: serde_json::Value,
126 ) -> Self {
127 self.with_response_format(ResponseFormat::json_schema(name, strict, schema))
128 }
129
130 pub fn with_json_mode(self) -> Self {
133 self.with_response_format(ResponseFormat::json_object())
134 }
135
136 pub fn with_transforms<S, I>(mut self, transforms: I) -> Self
139 where
140 S: Into<String>,
141 I: IntoIterator<Item = S>,
142 {
143 self.transforms = Some(transforms.into_iter().map(Into::into).collect());
144 self
145 }
146
147 pub fn with_provider(mut self, provider: Provider) -> Self {
149 self.provider = Some(provider);
150 self
151 }
152
153 fn provider_mut(&mut self) -> &mut Provider {
154 self.provider.get_or_insert_with(Provider::default)
155 }
156
157 pub fn with_provider_order<S, I>(mut self, order: I) -> Self
159 where
160 S: Into<String>,
161 I: IntoIterator<Item = S>,
162 {
163 self.provider_mut().order = Some(order.into_iter().map(Into::into).collect());
164 self
165 }
166
167 pub fn with_provider_sort(mut self, sort: impl Into<String>) -> Self {
169 self.provider_mut().sort = Some(sort.into());
170 self
171 }
172
173 pub fn with_only_providers<S, I>(mut self, only: I) -> Self
175 where
176 S: Into<String>,
177 I: IntoIterator<Item = S>,
178 {
179 self.provider_mut().only = Some(only.into_iter().map(Into::into).collect());
180 self
181 }
182
183 pub fn with_ignore_providers<S, I>(mut self, ignore: I) -> Self
185 where
186 S: Into<String>,
187 I: IntoIterator<Item = S>,
188 {
189 self.provider_mut().ignore = Some(ignore.into_iter().map(Into::into).collect());
190 self
191 }
192
193 pub fn with_quantizations<S, I>(mut self, q: I) -> Self
195 where
196 S: Into<String>,
197 I: IntoIterator<Item = S>,
198 {
199 self.provider_mut().quantizations = Some(q.into_iter().map(Into::into).collect());
200 self
201 }
202
203 pub fn with_max_price(mut self, price: serde_json::Value) -> Self {
205 self.provider_mut().max_price = Some(price);
206 self
207 }
208
209 pub fn with_data_collection(mut self, policy: impl Into<String>) -> Self {
211 self.provider_mut().data_collection = Some(policy.into());
212 self
213 }
214
215 pub fn with_require_parameters(mut self, required: bool) -> Self {
217 self.provider_mut().require_parameters = Some(required);
218 self
219 }
220
221 pub fn with_allow_fallbacks(mut self, allow: bool) -> Self {
223 self.provider_mut().allow_fallbacks = Some(allow);
224 self
225 }
226
227 pub fn with_zdr(mut self, zdr: bool) -> Self {
229 self.provider_mut().zdr = Some(zdr);
230 self
231 }
232
233 pub fn with_nitro(self) -> Self {
236 self.with_provider_sort("throughput")
237 }
238
239 pub fn with_floor(self) -> Self {
242 self.with_provider_sort("price")
243 }
244
245 pub fn with_reasoning(mut self, reasoning: ReasoningConfig) -> Self {
247 self.reasoning = Some(reasoning);
248 self
249 }
250
251 fn reasoning_mut(&mut self) -> &mut ReasoningConfig {
252 self.reasoning.get_or_insert_with(ReasoningConfig::default)
253 }
254
255 pub fn with_reasoning_effort(mut self, effort: impl Into<String>) -> Self {
257 self.reasoning_mut().effort = Some(effort.into());
258 self
259 }
260
261 pub fn with_reasoning_max_tokens(mut self, max_tokens: u32) -> Self {
263 self.reasoning_mut().max_tokens = Some(max_tokens);
264 self
265 }
266
267 pub fn with_reasoning_exclude(mut self, exclude: bool) -> Self {
269 self.reasoning_mut().exclude = Some(exclude);
270 self
271 }
272
273 pub fn with_plugins(mut self, plugins: Vec<Plugin>) -> Self {
275 self.plugins = Some(plugins);
276 self
277 }
278
279 pub fn with_web_search(mut self) -> Self {
283 self.plugins
284 .get_or_insert_with(Vec::new)
285 .push(Plugin::web());
286 self
287 }
288}
289
290#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
292pub struct CompletionRequest {
293 pub model: String,
295 pub prompt: String,
297
298 #[serde(skip_serializing_if = "Option::is_none", default)]
300 pub temperature: Option<f64>,
301 #[serde(skip_serializing_if = "Option::is_none", default)]
303 pub top_p: Option<f64>,
304 #[serde(skip_serializing_if = "Option::is_none", default)]
306 pub max_tokens: Option<u32>,
307 #[serde(skip_serializing_if = "Option::is_none", default)]
309 pub stream: Option<bool>,
310 #[serde(skip_serializing_if = "Option::is_none", default)]
312 pub stop: Option<serde_json::Value>,
313 #[serde(skip_serializing_if = "Option::is_none", default)]
315 pub seed: Option<i64>,
316 #[serde(skip_serializing_if = "Option::is_none", default)]
318 pub frequency_penalty: Option<f64>,
319 #[serde(skip_serializing_if = "Option::is_none", default)]
321 pub presence_penalty: Option<f64>,
322 #[serde(skip_serializing_if = "Option::is_none", default)]
324 pub provider: Option<Provider>,
325 #[serde(skip_serializing_if = "Option::is_none", default)]
327 pub transforms: Option<Vec<String>>,
328 #[serde(skip_serializing_if = "Option::is_none", default)]
330 pub plugins: Option<Vec<Plugin>>,
331 #[serde(skip_serializing_if = "Option::is_none", default)]
333 pub user: Option<String>,
334}
335
336impl CompletionRequest {
337 pub fn new(model: impl Into<String>, prompt: impl Into<String>) -> Self {
339 Self {
340 model: model.into(),
341 prompt: prompt.into(),
342 ..Default::default()
343 }
344 }
345
346 pub fn with_transforms<S, I>(mut self, transforms: I) -> Self
349 where
350 S: Into<String>,
351 I: IntoIterator<Item = S>,
352 {
353 self.transforms = Some(transforms.into_iter().map(Into::into).collect());
354 self
355 }
356
357 pub fn with_provider(mut self, provider: Provider) -> Self {
359 self.provider = Some(provider);
360 self
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367 use crate::types::{FunctionDef, Tool, ToolChoice};
368 use pretty_assertions::assert_eq;
369 use serde_json::json;
370
371 #[test]
372 fn with_tools_serializes_only_set_fields() {
373 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")])
374 .with_tools(vec![Tool::function(FunctionDef::new(
375 "f",
376 json!({"type":"object"}),
377 ))])
378 .with_tool_choice(ToolChoice::required());
379 let v = serde_json::to_value(&req).unwrap();
380 assert_eq!(
381 v,
382 json!({
383 "model": "x/y",
384 "messages": [{"role":"user","content":"hi"}],
385 "tools": [{
386 "type": "function",
387 "function": {"name":"f","parameters":{"type":"object"}}
388 }],
389 "tool_choice": "required"
390 })
391 );
392 }
393
394 #[test]
395 fn tool_choice_function_serializes() {
396 let v = serde_json::to_value(ToolChoice::function("get_weather")).unwrap();
397 assert_eq!(
398 v,
399 json!({"type":"function","function":{"name":"get_weather"}})
400 );
401 }
402
403 #[test]
404 fn with_json_mode_serializes() {
405 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")]).with_json_mode();
406 let v = serde_json::to_value(&req).unwrap();
407 assert_eq!(v["response_format"], json!({"type":"json_object"}));
408 }
409
410 #[test]
411 fn with_transforms_serializes_array() {
412 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")])
413 .with_transforms(["middle-out"]);
414 let v = serde_json::to_value(&req).unwrap();
415 assert_eq!(v["transforms"], json!(["middle-out"]));
416 }
417
418 #[test]
419 fn with_transforms_empty_disables_defaults() {
420 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")])
421 .with_transforms(Vec::<String>::new());
422 let v = serde_json::to_value(&req).unwrap();
423 assert_eq!(v["transforms"], json!([]));
424 }
425
426 #[test]
427 fn with_provider_helpers_compose_into_one_object() {
428 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")])
429 .with_provider_order(["openai", "anthropic"])
430 .with_only_providers(["openai"])
431 .with_zdr(true)
432 .with_nitro();
433 let v = serde_json::to_value(&req).unwrap();
434 assert_eq!(
435 v["provider"],
436 json!({
437 "order": ["openai", "anthropic"],
438 "only": ["openai"],
439 "zdr": true,
440 "sort": "throughput"
441 })
442 );
443 }
444
445 #[test]
446 fn with_web_search_serializes_default_plugin() {
447 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")]).with_web_search();
448 let v = serde_json::to_value(&req).unwrap();
449 assert_eq!(v["plugins"], json!([{"id":"web"}]));
450 }
451
452 #[test]
453 fn with_plugins_custom_config_serializes() {
454 use crate::types::{Plugin, WebPluginConfig};
455 let plugin = Plugin::web_with(
456 WebPluginConfig::new()
457 .with_max_results(3)
458 .with_search_prompt("Cite sources.")
459 .with_engine("native"),
460 );
461 let req =
462 ChatCompletionRequest::new("x/y", vec![Message::user("hi")]).with_plugins(vec![plugin]);
463 let v = serde_json::to_value(&req).unwrap();
464 assert_eq!(
465 v["plugins"],
466 json!([{
467 "id":"web",
468 "max_results":3,
469 "search_prompt":"Cite sources.",
470 "engine":"native"
471 }])
472 );
473 }
474
475 #[test]
476 fn with_reasoning_helpers_compose() {
477 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")])
478 .with_reasoning_effort("high")
479 .with_reasoning_max_tokens(512)
480 .with_reasoning_exclude(false);
481 let v = serde_json::to_value(&req).unwrap();
482 assert_eq!(
483 v["reasoning"],
484 json!({"effort":"high","max_tokens":512,"exclude":false})
485 );
486 }
487
488 #[test]
489 fn completion_with_transforms_serializes_array() {
490 let req = CompletionRequest::new("x/y", "hello").with_transforms(["middle-out"]);
491 let v = serde_json::to_value(&req).unwrap();
492 assert_eq!(v["transforms"], json!(["middle-out"]));
493 }
494
495 #[test]
496 fn with_json_schema_serializes_strict_named_schema() {
497 let req = ChatCompletionRequest::new("x/y", vec![Message::user("hi")]).with_json_schema(
498 "answer",
499 true,
500 json!({"type":"object","properties":{"x":{"type":"number"}}}),
501 );
502 let v = serde_json::to_value(&req).unwrap();
503 assert_eq!(
504 v["response_format"],
505 json!({
506 "type": "json_schema",
507 "json_schema": {
508 "name": "answer",
509 "schema": {"type":"object","properties":{"x":{"type":"number"}}},
510 "strict": true
511 }
512 })
513 );
514 }
515}