openai_ergonomic/responses/
chat.rs

1//! Chat completion response types and helpers.
2
3use openai_client_base::models::{
4    ChatCompletionMessageToolCallsInner, ChatCompletionResponseMessage,
5    CreateChatCompletionResponse, CreateChatCompletionResponseChoicesInner,
6};
7
8/// Extension trait for chat completion responses.
9pub trait ChatCompletionResponseExt {
10    /// Get the content of the first choice, if available.
11    fn content(&self) -> Option<&str>;
12
13    /// Get the tool calls from the first choice, if available.
14    fn tool_calls(&self) -> Vec<&ChatCompletionMessageToolCallsInner>;
15
16    /// Check if the response has tool calls.
17    fn has_tool_calls(&self) -> bool;
18
19    /// Get the first choice from the response.
20    fn first_choice(&self) -> Option<&CreateChatCompletionResponseChoicesInner>;
21
22    /// Get the message from the first choice.
23    fn first_message(&self) -> Option<&ChatCompletionResponseMessage>;
24
25    /// Check if the response was refused.
26    fn is_refusal(&self) -> bool;
27
28    /// Get the refusal message if the response was refused.
29    fn refusal(&self) -> Option<&str>;
30
31    /// Get the finish reason for the first choice.
32    fn finish_reason(&self) -> Option<String>;
33}
34
35impl ChatCompletionResponseExt for CreateChatCompletionResponse {
36    fn content(&self) -> Option<&str> {
37        self.choices
38            .first()
39            .and_then(|choice| choice.message.content.as_deref())
40    }
41
42    fn tool_calls(&self) -> Vec<&ChatCompletionMessageToolCallsInner> {
43        self.choices
44            .first()
45            .and_then(|choice| choice.message.tool_calls.as_ref())
46            .map(|calls| calls.iter().collect())
47            .unwrap_or_default()
48    }
49
50    fn has_tool_calls(&self) -> bool {
51        !self.tool_calls().is_empty()
52    }
53
54    fn first_choice(&self) -> Option<&CreateChatCompletionResponseChoicesInner> {
55        self.choices.first()
56    }
57
58    fn first_message(&self) -> Option<&ChatCompletionResponseMessage> {
59        self.first_choice().map(|choice| choice.message.as_ref())
60    }
61
62    fn is_refusal(&self) -> bool {
63        self.first_message()
64            .and_then(|msg| msg.refusal.as_ref())
65            .is_some()
66    }
67
68    fn refusal(&self) -> Option<&str> {
69        self.first_message()
70            .and_then(|msg| msg.refusal.as_ref())
71            .map(std::string::String::as_str)
72    }
73
74    fn finish_reason(&self) -> Option<String> {
75        use openai_client_base::models::create_chat_completion_response_choices_inner::FinishReason;
76        self.first_choice()
77            .map(|choice| match &choice.finish_reason {
78                FinishReason::Stop => "stop".to_string(),
79                FinishReason::Length => "length".to_string(),
80                FinishReason::ToolCalls => "tool_calls".to_string(),
81                FinishReason::ContentFilter => "content_filter".to_string(),
82                FinishReason::FunctionCall => "function_call".to_string(),
83            })
84    }
85}
86
87/// Extension trait for tool calls.
88pub trait ToolCallExt {
89    /// Get the tool call ID.
90    fn id(&self) -> &str;
91
92    /// Get the function name from the tool call.
93    fn function_name(&self) -> &str;
94
95    /// Get the function arguments as a string.
96    fn function_arguments(&self) -> &str;
97
98    /// Parse the function arguments as JSON.
99    fn parse_arguments<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error>;
100}
101
102impl ToolCallExt for ChatCompletionMessageToolCallsInner {
103    fn id(&self) -> &str {
104        match self {
105            ChatCompletionMessageToolCallsInner::ChatCompletionMessageToolCall(tool_call) => {
106                &tool_call.id
107            }
108            ChatCompletionMessageToolCallsInner::ChatCompletionMessageCustomToolCall(tool_call) => {
109                &tool_call.id
110            }
111        }
112    }
113
114    fn function_name(&self) -> &str {
115        match self {
116            ChatCompletionMessageToolCallsInner::ChatCompletionMessageToolCall(tool_call) => {
117                &tool_call.function.name
118            }
119            ChatCompletionMessageToolCallsInner::ChatCompletionMessageCustomToolCall(_) => {
120                // Custom tool calls don't have function names in the same way
121                ""
122            }
123        }
124    }
125
126    fn function_arguments(&self) -> &str {
127        match self {
128            ChatCompletionMessageToolCallsInner::ChatCompletionMessageToolCall(tool_call) => {
129                &tool_call.function.arguments
130            }
131            ChatCompletionMessageToolCallsInner::ChatCompletionMessageCustomToolCall(_) => {
132                // Custom tool calls don't have function arguments in the same way
133                ""
134            }
135        }
136    }
137
138    fn parse_arguments<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
139        serde_json::from_str(self.function_arguments())
140    }
141}
142
143// Re-export types for convenience
144pub use openai_client_base::models::{
145    ChatCompletionResponseMessage as ChatMessage,
146    CreateChatCompletionResponse as ChatCompletionResponse,
147    CreateChatCompletionResponseChoicesInner as ChatChoice,
148};
149
150// Re-export the FunctionCall type with a more ergonomic alias
151pub use openai_client_base::models::{
152    ChatCompletionMessageToolCallFunction as FunctionCall,
153    ChatCompletionResponseMessageFunctionCall,
154};
155
156// Re-export ToolCall with an alias
157pub use openai_client_base::models::ChatCompletionMessageToolCallsInner as ToolCall;