Skip to main content

self_llm/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Role of a message participant.
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum Role {
7    System,
8    User,
9    Assistant,
10}
11
12/// A part of message content.
13#[derive(Debug, Clone)]
14pub enum ContentPart {
15    Text(String),
16    Reasoning(String),
17    Image(ImageSource),
18    ToolUse(ToolUse),
19    ToolResult(ToolResult),
20}
21
22/// Source data for an image.
23#[derive(Debug, Clone)]
24pub enum ImageSource {
25    Url(String),
26    Base64 { media_type: String, data: String },
27}
28
29/// A tool invocation returned by the model.
30#[derive(Debug, Clone)]
31pub struct ToolUse {
32    pub id: String,
33    pub name: String,
34    pub input: serde_json::Value,
35}
36
37/// Result of a tool invocation, sent back to the model.
38#[derive(Debug, Clone)]
39pub struct ToolResult {
40    pub tool_use_id: String,
41    pub content: String,
42    pub is_error: bool,
43}
44
45/// A single message in the conversation.
46#[derive(Debug, Clone)]
47pub struct Message {
48    pub role: Role,
49    pub content: Vec<ContentPart>,
50}
51
52impl Message {
53    pub fn system(text: impl Into<String>) -> Self {
54        Self {
55            role: Role::System,
56            content: vec![ContentPart::Text(text.into())],
57        }
58    }
59
60    pub fn user(text: impl Into<String>) -> Self {
61        Self {
62            role: Role::User,
63            content: vec![ContentPart::Text(text.into())],
64        }
65    }
66
67    pub fn assistant(text: impl Into<String>) -> Self {
68        Self {
69            role: Role::Assistant,
70            content: vec![ContentPart::Text(text.into())],
71        }
72    }
73
74    pub fn assistant_with_reasoning(reasoning: impl Into<String>, text: impl Into<String>) -> Self {
75        Self {
76            role: Role::Assistant,
77            content: vec![
78                ContentPart::Reasoning(reasoning.into()),
79                ContentPart::Text(text.into()),
80            ],
81        }
82    }
83
84    pub fn user_with_image(text: impl Into<String>, image: ImageSource) -> Self {
85        Self {
86            role: Role::User,
87            content: vec![ContentPart::Text(text.into()), ContentPart::Image(image)],
88        }
89    }
90
91    pub fn tool_results(results: Vec<ToolResult>) -> Self {
92        Self {
93            role: Role::User,
94            content: results.into_iter().map(ContentPart::ToolResult).collect(),
95        }
96    }
97}
98
99/// Strategy for tool selection.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum ToolChoice {
102    /// Let the model decide whether to call tools.
103    Auto,
104    /// Never call tools.
105    None,
106    /// Force the model to call at least one tool (OpenAI: "required", Anthropic: "any").
107    Required,
108    /// Force the model to call a specific tool by name.
109    Specific(String),
110}
111
112/// A tool definition that the model can call.
113#[derive(Debug, Clone)]
114pub struct Tool {
115    pub name: String,
116    pub description: String,
117    /// JSON Schema describing the tool parameters.
118    pub parameters: serde_json::Value,
119}
120
121/// A chat completion request.
122#[derive(Debug, Clone)]
123pub struct ChatRequest {
124    pub model: String,
125    pub messages: Vec<Message>,
126    pub max_tokens: Option<u32>,
127    pub temperature: Option<f32>,
128    pub top_p: Option<f32>,
129    pub tools: Option<Vec<Tool>>,
130    /// Strategy for tool selection.
131    pub tool_choice: Option<ToolChoice>,
132    /// Whether to allow parallel tool calls.
133    /// OpenAI: `parallel_tool_calls`; Anthropic: `tool_choice.disable_parallel_tool_use`.
134    pub parallel_tool_calls: Option<bool>,
135    /// Custom stop sequences.
136    /// OpenAI: `stop`; Anthropic: `stop_sequences`.
137    pub stop_sequences: Option<Vec<String>>,
138    /// Top-k sampling parameter (Anthropic only; ignored for OpenAI).
139    pub top_k: Option<u32>,
140    /// End-user identifier for abuse detection.
141    /// OpenAI: `user`; Anthropic: `metadata.user_id`.
142    pub user: Option<String>,
143    /// Budget tokens for extended thinking (Anthropic).
144    pub budget_tokens: Option<u32>,
145    /// Enable prompt caching.
146    /// OpenAI: sets `store: true`; Anthropic: sets top-level `cache_control`.
147    pub prompt_cache: Option<bool>,
148}
149
150impl ChatRequest {
151    pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
152        Self {
153            model: model.into(),
154            messages,
155            max_tokens: None,
156            temperature: None,
157            top_p: None,
158            tools: None,
159            tool_choice: None,
160            parallel_tool_calls: None,
161            stop_sequences: None,
162            top_k: None,
163            user: None,
164            budget_tokens: None,
165            prompt_cache: None,
166        }
167    }
168
169    pub fn max_tokens(mut self, n: u32) -> Self {
170        self.max_tokens = Some(n);
171        self
172    }
173
174    pub fn temperature(mut self, t: f32) -> Self {
175        self.temperature = Some(t);
176        self
177    }
178
179    pub fn top_p(mut self, p: f32) -> Self {
180        self.top_p = Some(p);
181        self
182    }
183
184    pub fn tools(mut self, tools: Vec<Tool>) -> Self {
185        self.tools = Some(tools);
186        self
187    }
188
189    pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
190        self.tool_choice = Some(choice);
191        self
192    }
193
194    pub fn parallel_tool_calls(mut self, enabled: bool) -> Self {
195        self.parallel_tool_calls = Some(enabled);
196        self
197    }
198
199    pub fn stop_sequences(mut self, sequences: Vec<String>) -> Self {
200        self.stop_sequences = Some(sequences);
201        self
202    }
203
204    pub fn top_k(mut self, k: u32) -> Self {
205        self.top_k = Some(k);
206        self
207    }
208
209    pub fn user(mut self, user: impl Into<String>) -> Self {
210        self.user = Some(user.into());
211        self
212    }
213
214    pub fn budget_tokens(mut self, n: u32) -> Self {
215        self.budget_tokens = Some(n);
216        self
217    }
218
219    pub fn prompt_cache(mut self, enabled: bool) -> Self {
220        self.prompt_cache = Some(enabled);
221        self
222    }
223}
224
225/// A chat completion response.
226#[derive(Debug, Clone)]
227pub struct ChatResponse {
228    pub id: String,
229    pub model: String,
230    pub content: Vec<ContentPart>,
231    pub usage: Option<Usage>,
232    pub stop_reason: StopReason,
233}
234
235impl ChatResponse {
236    /// Returns the first text content, if any.
237    pub fn text(&self) -> Option<&str> {
238        self.content.iter().find_map(|p| match p {
239            ContentPart::Text(t) => Some(t.as_str()),
240            _ => None,
241        })
242    }
243
244    /// Returns the first reasoning content, if any.
245    pub fn reasoning(&self) -> Option<&str> {
246        self.content.iter().find_map(|p| match p {
247            ContentPart::Reasoning(t) => Some(t.as_str()),
248            _ => None,
249        })
250    }
251
252    /// Returns all tool-use parts from the response.
253    pub fn tool_uses(&self) -> Vec<&ToolUse> {
254        self.content
255            .iter()
256            .filter_map(|p| match p {
257                ContentPart::ToolUse(t) => Some(t),
258                _ => None,
259            })
260            .collect()
261    }
262}
263
264/// Token usage information.
265#[derive(Debug, Clone)]
266pub struct Usage {
267    pub input_tokens: u32,
268    pub output_tokens: u32,
269    /// Tokens read from cache (prompt cache hit).
270    pub cache_read_input_tokens: Option<u32>,
271    /// Tokens written to cache (prompt cache miss / creation).
272    pub cache_creation_input_tokens: Option<u32>,
273}
274
275/// Reason why the model stopped generating.
276#[derive(Debug, Clone, PartialEq, Eq)]
277pub enum StopReason {
278    EndTurn,
279    MaxTokens,
280    ToolUse,
281    Unknown(String),
282}
283
284/// An event from a streaming chat response.
285#[derive(Debug, Clone)]
286pub enum StreamEvent {
287    /// A chunk of text content.
288    ContentDelta(String),
289    /// A chunk of reasoning content.
290    ReasoningDelta(String),
291    /// Start of a new tool call.
292    ToolCallStart {
293        index: usize,
294        id: String,
295        name: String,
296    },
297    /// Incremental arguments JSON for a tool call.
298    ToolCallDelta {
299        index: usize,
300        arguments_delta: String,
301    },
302    /// Token usage information.
303    Usage(Usage),
304    /// Generation finished.
305    Done(StopReason),
306}