1use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum Role {
17 System,
19 User,
21 Assistant,
23 Tool,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33#[serde(untagged)]
34pub enum Content {
35 Text(String),
37 Parts(Vec<ContentPart>),
39}
40
41impl Content {
42 pub fn text(s: impl Into<String>) -> Self {
44 Self::Text(s.into())
45 }
46}
47
48impl<T: Into<String>> From<T> for Content {
49 fn from(s: T) -> Self {
50 Self::Text(s.into())
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56#[serde(tag = "type", rename_all = "snake_case")]
57pub enum ContentPart {
58 Text {
60 text: String,
62 },
63 ImageUrl {
65 image_url: ImageUrl,
67 },
68}
69
70impl ContentPart {
71 pub fn text(s: impl Into<String>) -> Self {
73 Self::Text { text: s.into() }
74 }
75
76 pub fn image(url: impl Into<String>) -> Self {
78 Self::ImageUrl {
79 image_url: ImageUrl {
80 url: url.into(),
81 detail: None,
82 },
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub struct ImageUrl {
90 pub url: String,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub detail: Option<String>,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99pub struct Message {
100 pub role: Role,
102 #[serde(skip_serializing_if = "Option::is_none")]
105 pub content: Option<Content>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub tool_calls: Option<Vec<ToolCall>>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub tool_call_id: Option<String>,
112}
113
114impl Message {
115 pub fn system(content: impl Into<Content>) -> Self {
117 Self::simple(Role::System, content)
118 }
119
120 pub fn user(content: impl Into<Content>) -> Self {
122 Self::simple(Role::User, content)
123 }
124
125 pub fn assistant(content: impl Into<Content>) -> Self {
127 Self::simple(Role::Assistant, content)
128 }
129
130 pub fn tool(tool_call_id: impl Into<String>, content: impl Into<Content>) -> Self {
132 Self {
133 role: Role::Tool,
134 content: Some(content.into()),
135 tool_calls: None,
136 tool_call_id: Some(tool_call_id.into()),
137 }
138 }
139
140 fn simple(role: Role, content: impl Into<Content>) -> Self {
141 Self {
142 role,
143 content: Some(content.into()),
144 tool_calls: None,
145 tool_call_id: None,
146 }
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152#[serde(tag = "type", rename_all = "snake_case")]
153pub enum Tool {
154 Function {
156 function: FunctionDef,
158 },
159}
160
161impl Tool {
162 pub fn function(
165 name: impl Into<String>,
166 description: impl Into<String>,
167 parameters: serde_json::Value,
168 ) -> Self {
169 Self::Function {
170 function: FunctionDef {
171 name: name.into(),
172 description: Some(description.into()),
173 parameters: Some(parameters),
174 },
175 }
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct FunctionDef {
182 pub name: String,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub description: Option<String>,
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub parameters: Option<serde_json::Value>,
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub struct ToolCall {
195 pub id: String,
197 #[serde(rename = "type", default = "default_tool_type")]
199 pub kind: String,
200 pub function: FunctionCall,
202}
203
204fn default_tool_type() -> String {
205 "function".to_string()
206}
207
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210pub struct FunctionCall {
211 pub name: String,
213 pub arguments: String,
215}
216
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219#[serde(untagged)]
220pub enum ToolChoice {
221 Mode(String),
223 Named {
225 #[serde(rename = "type")]
227 kind: String,
228 function: NamedFunction,
230 },
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
235pub struct NamedFunction {
236 pub name: String,
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242#[serde(tag = "type", rename_all = "snake_case")]
243pub enum ResponseFormat {
244 Text,
246 JsonObject,
248 JsonSchema {
250 json_schema: JsonSchema,
252 },
253}
254
255impl ResponseFormat {
256 pub fn json_schema(name: impl Into<String>, schema: serde_json::Value) -> Self {
258 Self::JsonSchema {
259 json_schema: JsonSchema {
260 name: name.into(),
261 schema,
262 strict: Some(true),
263 },
264 }
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270pub struct JsonSchema {
271 pub name: String,
273 pub schema: serde_json::Value,
275 #[serde(skip_serializing_if = "Option::is_none")]
277 pub strict: Option<bool>,
278}
279
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282pub struct ChatRequest {
283 pub model: String,
285 pub messages: Vec<Message>,
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub stream: Option<bool>,
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub temperature: Option<f32>,
293 #[serde(skip_serializing_if = "Option::is_none")]
295 pub max_tokens: Option<u32>,
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub tools: Option<Vec<Tool>>,
299 #[serde(skip_serializing_if = "Option::is_none")]
301 pub tool_choice: Option<ToolChoice>,
302 #[serde(skip_serializing_if = "Option::is_none")]
304 pub response_format: Option<ResponseFormat>,
305}
306
307impl ChatRequest {
308 pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
310 Self {
311 model: model.into(),
312 messages,
313 stream: None,
314 temperature: None,
315 max_tokens: None,
316 tools: None,
317 tool_choice: None,
318 response_format: None,
319 }
320 }
321
322 #[must_use]
324 pub fn temperature(mut self, t: f32) -> Self {
325 self.temperature = Some(t);
326 self
327 }
328
329 #[must_use]
331 pub fn max_tokens(mut self, n: u32) -> Self {
332 self.max_tokens = Some(n);
333 self
334 }
335
336 #[must_use]
338 pub fn tools(mut self, tools: Vec<Tool>) -> Self {
339 self.tools = Some(tools);
340 self
341 }
342
343 #[must_use]
345 pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
346 self.tool_choice = Some(choice);
347 self
348 }
349
350 #[must_use]
352 pub fn response_format(mut self, format: ResponseFormat) -> Self {
353 self.response_format = Some(format);
354 self
355 }
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
360pub struct Usage {
361 pub prompt_tokens: u32,
363 pub completion_tokens: u32,
365 pub total_tokens: u32,
367}
368
369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
371pub struct ChatResponse {
372 pub id: String,
374 pub model: String,
376 pub choices: Vec<ResponseChoice>,
378 #[serde(default)]
380 pub usage: Option<Usage>,
381}
382
383impl ChatResponse {
384 pub fn content(&self) -> Option<&str> {
386 self.choices
387 .first()
388 .and_then(|c| c.message.content.as_ref())
389 .and_then(|c| match c {
390 Content::Text(s) => Some(s.as_str()),
391 Content::Parts(_) => None,
392 })
393 }
394
395 pub fn tool_calls(&self) -> &[ToolCall] {
397 self.choices
398 .first()
399 .and_then(|c| c.message.tool_calls.as_deref())
400 .unwrap_or(&[])
401 }
402}
403
404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
406pub struct ResponseChoice {
407 pub index: u32,
409 pub message: Message,
411 #[serde(default)]
413 pub finish_reason: Option<String>,
414}
415
416#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
424pub struct ChatChunk {
425 #[serde(default)]
427 pub id: String,
428 pub choices: Vec<ChunkChoice>,
430 #[serde(default)]
432 pub usage: Option<Usage>,
433}
434
435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
437pub struct ChunkChoice {
438 pub index: u32,
440 pub delta: Delta,
442 #[serde(default)]
444 pub finish_reason: Option<String>,
445}
446
447#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
449pub struct Delta {
450 #[serde(default)]
452 pub role: Option<String>,
453 #[serde(default)]
455 pub content: Option<String>,
456 #[serde(default)]
458 pub tool_calls: Option<Vec<ToolCallDelta>>,
459}
460
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
463pub struct ToolCallDelta {
464 pub index: u32,
466 #[serde(default)]
468 pub id: Option<String>,
469 #[serde(default)]
471 pub function: Option<FunctionDelta>,
472}
473
474#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
476pub struct FunctionDelta {
477 #[serde(default)]
479 pub name: Option<String>,
480 #[serde(default)]
482 pub arguments: Option<String>,
483}