Skip to main content

liter_llm/types/
chat.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use super::common::{
6    AssistantMessage, ChatCompletionTool, Message, ResponseFormat, StopSequence, ToolChoice, ToolType, Usage,
7};
8use crate::cost;
9
10// ─── Finish Reason ────────────────────────────────────────────────────────────
11
12/// Why a choice stopped generating tokens.
13#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum FinishReason {
16    #[default]
17    Stop,
18    Length,
19    ToolCalls,
20    ContentFilter,
21    /// Deprecated legacy finish reason; retained for API compatibility.
22    #[serde(rename = "function_call")]
23    FunctionCall,
24    /// Catch-all for unknown finish reasons returned by non-OpenAI providers.
25    ///
26    /// Note: this intentionally does **not** carry the original string (e.g.
27    /// `Other(String)`).  Using `#[serde(other)]` requires a unit variant, and
28    /// switching to `#[serde(untagged)]` would change deserialization semantics
29    /// for all variants.  The original value can be recovered by inspecting the
30    /// raw JSON if needed.
31    #[serde(other)]
32    Other,
33}
34
35// ─── Reasoning Effort ────────────────────────────────────────────────────────
36
37/// Controls how much reasoning effort the model should use.
38#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "lowercase")]
40pub enum ReasoningEffort {
41    Low,
42    #[default]
43    Medium,
44    High,
45}
46
47// ─── Request ─────────────────────────────────────────────────────────────────
48
49#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
50#[serde(deny_unknown_fields)]
51pub struct ChatCompletionRequest {
52    pub model: String,
53    pub messages: Vec<Message>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub temperature: Option<f64>,
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub top_p: Option<f64>,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub n: Option<u32>,
60    /// Whether to stream the response.
61    ///
62    /// Managed by the client layer — do not set directly.
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub stream: Option<bool>,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub stop: Option<StopSequence>,
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub max_tokens: Option<u64>,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub presence_penalty: Option<f64>,
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub frequency_penalty: Option<f64>,
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    /// Token bias map.  Uses `BTreeMap` (sorted keys) for deterministic
75    /// serialization order — important when hashing or signing requests.
76    pub logit_bias: Option<BTreeMap<String, f64>>,
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub user: Option<String>,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub tools: Option<Vec<ChatCompletionTool>>,
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub tool_choice: Option<ToolChoice>,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub parallel_tool_calls: Option<bool>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub response_format: Option<ResponseFormat>,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub stream_options: Option<StreamOptions>,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub seed: Option<i64>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub reasoning_effort: Option<ReasoningEffort>,
93    /// Provider-specific extra parameters merged into the request body.
94    /// Use for guardrails, safety settings, grounding config, etc.
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub extra_body: Option<serde_json::Value>,
97}
98
99#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
100#[serde(deny_unknown_fields)]
101pub struct StreamOptions {
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub include_usage: Option<bool>,
104}
105
106// ─── Response ────────────────────────────────────────────────────────────────
107
108#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
109pub struct ChatCompletionResponse {
110    pub id: String,
111    /// Always `"chat.completion"` from OpenAI-compatible APIs.  Stored as a
112    /// plain `String` so non-standard provider values do not break deserialization.
113    pub object: String,
114    pub created: u64,
115    pub model: String,
116    pub choices: Vec<Choice>,
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub usage: Option<Usage>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub system_fingerprint: Option<String>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub service_tier: Option<String>,
123}
124
125impl ChatCompletionResponse {
126    /// Estimate the cost of this response based on embedded pricing data.
127    ///
128    /// Returns `None` if:
129    /// - the `model` field is not present in the embedded pricing registry, or
130    /// - the `usage` field is absent from the response.
131    ///
132    /// # Example
133    ///
134    /// ```rust,ignore
135    /// let cost = response.estimated_cost();
136    /// if let Some(usd) = cost {
137    ///     println!("Request cost: ${usd:.6}");
138    /// }
139    /// ```
140    #[must_use]
141    pub fn estimated_cost(&self) -> Option<f64> {
142        let usage = self.usage.as_ref()?;
143        cost::completion_cost(&self.model, usage.prompt_tokens, usage.completion_tokens)
144    }
145}
146
147#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
148pub struct Choice {
149    pub index: u32,
150    pub message: AssistantMessage,
151    pub finish_reason: Option<FinishReason>,
152}
153
154// ─── Stream Chunk ────────────────────────────────────────────────────────────
155
156#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
157pub struct ChatCompletionChunk {
158    pub id: String,
159    /// Always `"chat.completion.chunk"` from OpenAI-compatible APIs.  Stored
160    /// as a plain `String` so non-standard provider values do not fail parsing.
161    pub object: String,
162    pub created: u64,
163    pub model: String,
164    pub choices: Vec<StreamChoice>,
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub usage: Option<Usage>,
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub system_fingerprint: Option<String>,
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub service_tier: Option<String>,
171}
172
173#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
174pub struct StreamChoice {
175    pub index: u32,
176    pub delta: StreamDelta,
177    pub finish_reason: Option<FinishReason>,
178}
179
180#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
181pub struct StreamDelta {
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub role: Option<String>,
184    #[serde(default, skip_serializing_if = "Option::is_none")]
185    pub content: Option<String>,
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub tool_calls: Option<Vec<StreamToolCall>>,
188    /// Deprecated legacy function_call delta; retained for API compatibility.
189    #[serde(default, skip_serializing_if = "Option::is_none")]
190    pub function_call: Option<StreamFunctionCall>,
191    #[serde(default, skip_serializing_if = "Option::is_none")]
192    pub refusal: Option<String>,
193}
194
195#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
196pub struct StreamToolCall {
197    pub index: u32,
198    #[serde(default, skip_serializing_if = "Option::is_none")]
199    pub id: Option<String>,
200    #[serde(default, skip_serializing_if = "Option::is_none", rename = "type")]
201    pub call_type: Option<ToolType>,
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub function: Option<StreamFunctionCall>,
204}
205
206#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
207pub struct StreamFunctionCall {
208    #[serde(default, skip_serializing_if = "Option::is_none")]
209    pub name: Option<String>,
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub arguments: Option<String>,
212}