1use serde_json::Value;
4
5use super::message::ChatMessage;
6use super::reasoning::ReasoningConfig;
7
8#[derive(Clone, Debug)]
10pub enum ResponseFormat {
11 JsonObject,
12 JsonSchema { name: String, schema: Value },
13}
14
15impl ResponseFormat {
16 pub fn to_api_value(&self) -> Value {
17 match self {
18 ResponseFormat::JsonObject => {
19 serde_json::json!({ "type": "json_object" })
20 }
21 ResponseFormat::JsonSchema { name, schema } => {
22 serde_json::json!({
23 "type": "json_schema",
24 "json_schema": {
25 "name": name,
26 "schema": schema,
27 }
28 })
29 }
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct ChatRequest {
37 pub messages: Vec<ChatMessage>,
39
40 pub tools: Vec<Value>,
42
43 pub reasoning: Option<ReasoningConfig>,
45
46 pub response_format: Option<ResponseFormat>,
48
49 pub model: Option<String>,
58}
59
60impl ChatRequest {
61 pub fn new(messages: Vec<ChatMessage>) -> Self {
63 Self {
64 messages,
65 tools: Vec::new(),
66 reasoning: None,
67 response_format: None,
68 model: None,
69 }
70 }
71
72 pub fn with_model(mut self, model: impl Into<String>) -> Self {
74 self.model = Some(model.into());
75 self
76 }
77
78 pub fn with_tools(mut self, tools: Vec<Value>) -> Self {
80 self.tools = tools;
81 self
82 }
83
84 pub fn with_reasoning(mut self, reasoning: ReasoningConfig) -> Self {
86 self.reasoning = Some(reasoning);
87 self
88 }
89
90 pub fn with_response_format(mut self, format: ResponseFormat) -> Self {
92 self.response_format = Some(format);
93 self
94 }
95}
96
97#[cfg(test)]
98mod response_format_tests {
99 use super::*;
100
101 #[test]
102 fn json_object_to_api_value() {
103 assert_eq!(
104 ResponseFormat::JsonObject.to_api_value(),
105 serde_json::json!({ "type": "json_object" })
106 );
107 }
108
109 #[test]
110 fn json_schema_to_api_value() {
111 let schema = serde_json::json!({"type": "object"});
112 let value = ResponseFormat::JsonSchema {
113 name: "answer".to_string(),
114 schema: schema.clone(),
115 }
116 .to_api_value();
117
118 assert_eq!(value["type"], "json_schema");
119 assert_eq!(value["json_schema"]["name"], "answer");
120 assert_eq!(value["json_schema"]["schema"], schema);
121 }
122
123 #[test]
124 fn with_response_format_stores_the_format() {
125 let req = ChatRequest::new(vec![ChatMessage::user("hi")])
126 .with_response_format(ResponseFormat::JsonObject);
127 assert!(matches!(
128 req.response_format,
129 Some(ResponseFormat::JsonObject)
130 ));
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn chat_request_new() {
140 let req = ChatRequest::new(vec![ChatMessage::user("hello")]);
141 assert_eq!(req.messages.len(), 1);
142 assert!(req.tools.is_empty());
143 assert!(req.reasoning.is_none());
144 assert!(req.response_format.is_none());
145 assert!(req.model.is_none());
146 }
147
148 #[test]
149 fn chat_request_with_model() {
150 let req = ChatRequest::new(vec![ChatMessage::user("hello")]).with_model("mimo-v2.5-pro");
151 assert_eq!(req.model.as_deref(), Some("mimo-v2.5-pro"));
152 }
153
154 #[test]
155 fn chat_request_with_tools() {
156 let tools = vec![serde_json::json!({"type": "function", "function": {"name": "test"}})];
157 let req = ChatRequest::new(vec![ChatMessage::user("hello")]).with_tools(tools.clone());
158 assert_eq!(req.tools.len(), 1);
159 assert_eq!(req.tools[0], tools[0]);
160 }
161
162 #[test]
163 fn chat_request_with_reasoning() {
164 let reasoning = ReasoningConfig {
165 enabled: Some(true),
166 budget_tokens: Some(4096),
167 effort: None,
168 };
169 let req = ChatRequest::new(vec![ChatMessage::user("hello")]).with_reasoning(reasoning);
170 assert!(req.reasoning.is_some());
171 assert_eq!(req.reasoning.as_ref().unwrap().budget_tokens, Some(4096));
172 }
173
174 #[test]
175 fn chat_request_builder_chain() {
176 let tools = vec![serde_json::json!({"name": "tool1"})];
177 let reasoning = ReasoningConfig {
178 enabled: Some(true),
179 ..Default::default()
180 };
181 let req = ChatRequest::new(vec![ChatMessage::user("hello")])
182 .with_tools(tools)
183 .with_reasoning(reasoning);
184 assert_eq!(req.tools.len(), 1);
185 assert!(req.reasoning.is_some());
186 }
187}