api_openai_compatible/components/chat.rs
1//! Wire types for `OpenAI` chat completion requests and responses.
2//!
3//! These types serialise to and deserialise from the JSON shapes described in
4//! the `OpenAI` chat completions API reference. Absent optional fields are
5//! omitted from the wire representation (`skip_serializing_if = "Option::is_none"`).
6
7mod private
8{
9 use serde::{ Serialize, Deserialize };
10 use former::Former;
11
12 // ------------------------------------------------------------------ //
13 // Role
14 // ------------------------------------------------------------------ //
15
16 /// Role of a participant in a chat conversation.
17 ///
18 /// Serialises to lowercase strings as required by the `OpenAI` wire protocol
19 /// (e.g. `Role::User` → `"user"`).
20 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq, Eq ) ]
21 #[ serde( rename_all = "lowercase" ) ]
22 pub enum Role
23 {
24 /// System-level instructions provided before the conversation begins.
25 System,
26
27 /// Message from the human user.
28 User,
29
30 /// Response generated by the AI assistant.
31 Assistant,
32
33 /// Result message from a tool/function call execution.
34 Tool,
35 }
36
37 // ------------------------------------------------------------------ //
38 // ToolCall / FunctionCall
39 // ------------------------------------------------------------------ //
40
41 /// A function invocation requested by the assistant.
42 ///
43 /// When the model decides to call a tool it includes one or more `ToolCall`
44 /// objects in the assistant message's `tool_calls` field.
45 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
46 pub struct ToolCall
47 {
48 /// Unique identifier for this invocation (used to correlate tool results).
49 pub id : String,
50
51 /// Always `"function"` for function-calling tools.
52 #[ serde( rename = "type" ) ]
53 pub tool_type : String,
54
55 /// Name and serialised arguments for the function to invoke.
56 pub function : FunctionCall,
57 }
58
59 /// Name and arguments for a specific function invocation.
60 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
61 pub struct FunctionCall
62 {
63 /// Registered function name.
64 pub name : String,
65
66 /// JSON-encoded arguments string (not a `Value` — must be re-parsed by caller).
67 pub arguments : String,
68 }
69
70 // ------------------------------------------------------------------ //
71 // Message
72 // ------------------------------------------------------------------ //
73
74 /// A single message in a chat conversation.
75 ///
76 /// All fields except `role` are optional; `None` fields are omitted from
77 /// serialised JSON (`skip_serializing_if = "Option::is_none"`).
78 ///
79 /// # Examples
80 ///
81 /// ```
82 /// # #[ cfg( feature = "enabled" ) ]
83 /// # {
84 /// use api_openai_compatible::Message;
85 ///
86 /// let sys = Message::system( "You are a helpful assistant." );
87 /// let user = Message::user( "What is 2 + 2?" );
88 /// # }
89 /// ```
90 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
91 pub struct Message
92 {
93 /// Who sent this message.
94 pub role : Role,
95
96 /// Text content of the message.
97 #[ serde( skip_serializing_if = "Option::is_none" ) ]
98 pub content : Option< String >,
99
100 /// Tool invocations requested by the assistant (assistant role only).
101 #[ serde( skip_serializing_if = "Option::is_none" ) ]
102 pub tool_calls : Option< Vec< ToolCall > >,
103
104 /// ID of the `ToolCall` this message responds to (tool role only).
105 #[ serde( skip_serializing_if = "Option::is_none" ) ]
106 pub tool_call_id : Option< String >,
107 }
108
109 impl Message
110 {
111 /// Creates a system-role message.
112 ///
113 /// # Examples
114 ///
115 /// ```
116 /// # #[ cfg( feature = "enabled" ) ]
117 /// # {
118 /// use api_openai_compatible::Message;
119 ///
120 /// let msg = Message::system( "You are a helpful coding assistant." );
121 /// # }
122 /// ```
123 #[ inline ]
124 pub fn system( content : impl Into< String > ) -> Self
125 {
126 Self
127 {
128 role : Role::System,
129 content : Some( content.into() ),
130 tool_calls : None,
131 tool_call_id : None,
132 }
133 }
134
135 /// Creates a user-role message.
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// # #[ cfg( feature = "enabled" ) ]
141 /// # {
142 /// use api_openai_compatible::Message;
143 ///
144 /// let msg = Message::user( "Explain monads." );
145 /// # }
146 /// ```
147 #[ inline ]
148 pub fn user( content : impl Into< String > ) -> Self
149 {
150 Self
151 {
152 role : Role::User,
153 content : Some( content.into() ),
154 tool_calls : None,
155 tool_call_id : None,
156 }
157 }
158
159 /// Creates an assistant-role message.
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// # #[ cfg( feature = "enabled" ) ]
165 /// # {
166 /// use api_openai_compatible::Message;
167 ///
168 /// let msg = Message::assistant( "A monad is a design pattern…" );
169 /// # }
170 /// ```
171 #[ inline ]
172 pub fn assistant( content : impl Into< String > ) -> Self
173 {
174 Self
175 {
176 role : Role::Assistant,
177 content : Some( content.into() ),
178 tool_calls : None,
179 tool_call_id : None,
180 }
181 }
182
183 /// Creates a tool-result message.
184 ///
185 /// # Arguments
186 ///
187 /// * `tool_call_id` — Must match the `id` of the `ToolCall` being answered.
188 /// * `content` — Serialised result from the tool execution.
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// # #[ cfg( feature = "enabled" ) ]
194 /// # {
195 /// use api_openai_compatible::Message;
196 ///
197 /// let msg = Message::tool( "call_abc123", r#"{"temperature":22}"# );
198 /// # }
199 /// ```
200 #[ inline ]
201 pub fn tool
202 (
203 tool_call_id : impl Into< String >,
204 content : impl Into< String >,
205 )
206 -> Self
207 {
208 Self
209 {
210 role : Role::Tool,
211 content : Some( content.into() ),
212 tool_calls : None,
213 tool_call_id : Some( tool_call_id.into() ),
214 }
215 }
216 }
217
218 // ------------------------------------------------------------------ //
219 // Tool / Function definitions
220 // ------------------------------------------------------------------ //
221
222 /// A function tool definition passed in the `tools` array of a request.
223 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq, Former ) ]
224 pub struct Tool
225 {
226 /// Always `"function"` for function-type tools.
227 #[ serde( rename = "type" ) ]
228 pub tool_type : String,
229
230 /// Function specification.
231 pub function : Function,
232 }
233
234 impl Tool
235 {
236 /// Convenience constructor for a function-type tool.
237 ///
238 /// # Examples
239 ///
240 /// ```
241 /// # #[ cfg( feature = "enabled" ) ]
242 /// # {
243 /// use api_openai_compatible::Tool;
244 ///
245 /// let tool = Tool::function(
246 /// "get_weather",
247 /// "Get current weather for a location",
248 /// serde_json::json!({ "type": "object", "properties": {} }),
249 /// );
250 /// # }
251 /// ```
252 #[ inline ]
253 pub fn function
254 (
255 name : impl Into< String >,
256 description : impl Into< String >,
257 parameters : serde_json::Value,
258 )
259 -> Self
260 {
261 Self
262 {
263 tool_type : "function".to_string(),
264 function : Function
265 {
266 name : name.into(),
267 description : description.into(),
268 parameters,
269 },
270 }
271 }
272 }
273
274 /// Function specification (name, description, JSON Schema parameters).
275 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq, Former ) ]
276 pub struct Function
277 {
278 /// Registered function name.
279 pub name : String,
280
281 /// Human-readable description used by the model to decide when to call.
282 pub description : String,
283
284 /// JSON Schema describing the expected parameters.
285 pub parameters : serde_json::Value,
286 }
287
288 // ------------------------------------------------------------------ //
289 // Usage
290 // ------------------------------------------------------------------ //
291
292 /// Token usage statistics returned in every completion response.
293 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq, Eq ) ]
294 pub struct Usage
295 {
296 /// Number of tokens in the prompt (input).
297 pub prompt_tokens : u32,
298
299 /// Number of tokens in the completion (output).
300 pub completion_tokens : u32,
301
302 /// Total tokens consumed (`prompt_tokens + completion_tokens`).
303 pub total_tokens : u32,
304 }
305
306 // ------------------------------------------------------------------ //
307 // ChatCompletionRequest
308 // ------------------------------------------------------------------ //
309
310 /// Request body for the `POST chat/completions` endpoint.
311 ///
312 /// Uses the `Former` builder pattern for ergonomic construction.
313 ///
314 /// # Examples
315 ///
316 /// ```
317 /// # #[ cfg( feature = "enabled" ) ]
318 /// # {
319 /// use api_openai_compatible::{ ChatCompletionRequest, Message };
320 ///
321 /// let req = ChatCompletionRequest::former()
322 /// .model( "gpt-4o".to_string() )
323 /// .messages( vec![ Message::user( "Hello!" ) ] )
324 /// .max_tokens( 100_u32 )
325 /// .form();
326 /// # }
327 /// ```
328 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq, Former ) ]
329 pub struct ChatCompletionRequest
330 {
331 /// Model identifier (e.g. `"gpt-4o"`, `"grok-2-1212"`).
332 pub model : String,
333
334 /// Ordered list of conversation messages.
335 pub messages : Vec< Message >,
336
337 /// Sampling temperature in `[0.0, 2.0]`. Higher = more random.
338 #[ serde( skip_serializing_if = "Option::is_none" ) ]
339 pub temperature : Option< f32 >,
340
341 /// Maximum tokens to generate in the completion.
342 #[ serde( skip_serializing_if = "Option::is_none" ) ]
343 pub max_tokens : Option< u32 >,
344
345 /// Nucleus sampling threshold in `[0.0, 1.0]`.
346 #[ serde( skip_serializing_if = "Option::is_none" ) ]
347 pub top_p : Option< f32 >,
348
349 /// Frequency penalty in `[0.0, 2.0]` (reduces token repetition).
350 #[ serde( skip_serializing_if = "Option::is_none" ) ]
351 pub frequency_penalty : Option< f32 >,
352
353 /// Presence penalty in `[0.0, 2.0]` (encourages topic diversity).
354 #[ serde( skip_serializing_if = "Option::is_none" ) ]
355 pub presence_penalty : Option< f32 >,
356
357 /// When `true`, the response is streamed as Server-Sent Events.
358 #[ serde( skip_serializing_if = "Option::is_none" ) ]
359 pub stream : Option< bool >,
360
361 /// Tool definitions available for function calling.
362 #[ serde( skip_serializing_if = "Option::is_none" ) ]
363 pub tools : Option< Vec< Tool > >,
364 }
365
366 // ------------------------------------------------------------------ //
367 // ChatCompletionResponse / Choice
368 // ------------------------------------------------------------------ //
369
370 /// Response body from the `POST chat/completions` endpoint.
371 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
372 pub struct ChatCompletionResponse
373 {
374 /// Opaque completion identifier (e.g. `"chatcmpl-abc123"`).
375 pub id : String,
376
377 /// Object type — always `"chat.completion"`.
378 pub object : String,
379
380 /// Unix timestamp of when the completion was created.
381 pub created : u64,
382
383 /// Model that generated this completion.
384 pub model : String,
385
386 /// One or more completion choices (`n` defaults to 1).
387 pub choices : Vec< Choice >,
388
389 /// Token usage statistics for billing.
390 pub usage : Usage,
391 }
392
393 /// One completion alternative within a `ChatCompletionResponse`.
394 #[ derive( Debug, Serialize, Deserialize, Clone, PartialEq ) ]
395 pub struct Choice
396 {
397 /// Zero-based index of this choice.
398 pub index : u32,
399
400 /// The generated message.
401 pub message : Message,
402
403 /// Reason the generation stopped (`"stop"`, `"length"`, `"tool_calls"`).
404 pub finish_reason : Option< String >,
405 }
406}
407
408crate::mod_interface!
409{
410 exposed use
411 {
412 Role,
413 ToolCall,
414 FunctionCall,
415 Message,
416 Tool,
417 Function,
418 Usage,
419 ChatCompletionRequest,
420 ChatCompletionResponse,
421 Choice,
422 };
423}