1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::id::{ModelName, ProviderId};
7use crate::tool_types::{ToolCall, ToolChoice, ToolSpec};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct ChatRequest {
12 pub model: ModelName,
14 pub messages: Vec<Message>,
16 pub tools: Vec<ToolSpec>,
18 pub tool_choice: ToolChoice,
20 pub response_format: Option<ResponseFormat>,
22 pub temperature: Option<f32>,
24 pub top_p: Option<f32>,
26 pub max_output_tokens: Option<u32>,
28 pub stop: Vec<String>,
30 pub metadata: Value,
32}
33
34impl ChatRequest {
35 #[must_use]
37 pub fn new(model: ModelName) -> Self {
38 Self {
39 model,
40 messages: Vec::new(),
41 tools: Vec::new(),
42 tool_choice: ToolChoice::default(),
43 response_format: None,
44 temperature: None,
45 top_p: None,
46 max_output_tokens: None,
47 stop: Vec::new(),
48 metadata: Value::Null,
49 }
50 }
51
52 #[must_use]
54 pub fn with_message(mut self, message: Message) -> Self {
55 self.messages.push(message);
56 self
57 }
58
59 #[must_use]
61 pub fn with_user_text(self, text: impl Into<String>) -> Self {
62 self.with_message(Message::user_text(text))
63 }
64
65 #[must_use]
67 pub fn with_tool(mut self, tool: ToolSpec) -> Self {
68 self.tools.push(tool);
69 self
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75#[serde(rename_all = "snake_case", tag = "role")]
76#[non_exhaustive]
77pub enum Message {
78 System {
80 content: Vec<ContentPart>,
82 },
83 User {
85 content: Vec<ContentPart>,
87 },
88 Assistant {
90 content: Vec<ContentPart>,
92 tool_calls: Vec<ToolCall>,
94 },
95 Tool {
97 tool_call_id: String,
99 name: String,
101 content: Vec<ContentPart>,
103 },
104}
105
106impl Message {
107 #[must_use]
109 pub fn system_text(text: impl Into<String>) -> Self {
110 Self::System {
111 content: vec![ContentPart::text(text)],
112 }
113 }
114
115 #[must_use]
117 pub fn user_text(text: impl Into<String>) -> Self {
118 Self::User {
119 content: vec![ContentPart::text(text)],
120 }
121 }
122
123 #[must_use]
125 pub fn assistant_text(text: impl Into<String>) -> Self {
126 Self::Assistant {
127 content: vec![ContentPart::text(text)],
128 tool_calls: Vec::new(),
129 }
130 }
131
132 #[must_use]
134 pub fn tool_text(
135 tool_call_id: impl Into<String>,
136 name: impl Into<String>,
137 text: impl Into<String>,
138 ) -> Self {
139 Self::Tool {
140 tool_call_id: tool_call_id.into(),
141 name: name.into(),
142 content: vec![ContentPart::text(text)],
143 }
144 }
145
146 #[must_use]
148 pub fn tool_calls(&self) -> &[ToolCall] {
149 match self {
150 Self::Assistant { tool_calls, .. } => tool_calls.as_slice(),
151 _ => &[],
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158#[serde(rename_all = "snake_case", tag = "type")]
159#[non_exhaustive]
160pub enum ContentPart {
161 Text {
163 text: String,
165 },
166 Json {
168 value: Value,
170 },
171 ImageUrl {
173 url: String,
175 mime_type: Option<String>,
177 },
178}
179
180impl ContentPart {
181 #[must_use]
183 pub fn text(text: impl Into<String>) -> Self {
184 Self::Text { text: text.into() }
185 }
186
187 #[must_use]
189 pub fn json(value: Value) -> Self {
190 Self::Json { value }
191 }
192
193 #[must_use]
195 pub fn image_url(url: impl Into<String>, mime_type: Option<String>) -> Self {
196 Self::ImageUrl {
197 url: url.into(),
198 mime_type,
199 }
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
205#[serde(rename_all = "snake_case", tag = "type")]
206#[non_exhaustive]
207pub enum ResponseFormat {
208 Text,
210 JsonObject,
212 JsonSchema {
214 name: String,
216 schema: Value,
218 strict: bool,
220 },
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
225pub struct ChatResponse {
226 pub provider: ProviderId,
228 pub model: ModelName,
230 pub message: Message,
232 pub finish_reason: FinishReason,
234 pub usage: Option<TokenUsage>,
236 pub raw: Option<Value>,
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242#[serde(rename_all = "snake_case")]
243#[non_exhaustive]
244pub enum FinishReason {
245 Stop,
247 ToolCalls,
249 Length,
251 ContentFilter,
253 Error,
255 Unknown(String),
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
261pub struct TokenUsage {
262 pub input_tokens: u64,
264 pub output_tokens: u64,
266 pub total_tokens: u64,
268}
269
270impl TokenUsage {
271 #[must_use]
273 pub const fn new(input_tokens: u64, output_tokens: u64) -> Self {
274 Self {
275 input_tokens,
276 output_tokens,
277 total_tokens: input_tokens + output_tokens,
278 }
279 }
280}