Skip to main content

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}