1use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum Role {
9 System,
11 Developer,
13 User,
15 Assistant,
17 Tool,
19 Function,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct ChatMessage {
26 pub role: Role,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub content: Option<String>,
32
33 #[serde(default, alias = "reasoning", skip_serializing_if = "Option::is_none")]
35 pub reasoning_content: Option<String>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub refusal: Option<String>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub name: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub tool_calls: Option<Vec<ToolCall>>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub tool_call_id: Option<String>,
52}
53
54impl ChatMessage {
55 pub fn system(content: impl Into<String>) -> Self {
57 Self {
58 role: Role::System,
59 content: Some(content.into()),
60 reasoning_content: None,
61 refusal: None,
62 name: None,
63 tool_calls: None,
64 tool_call_id: None,
65 }
66 }
67
68 pub fn developer(content: impl Into<String>) -> Self {
70 Self {
71 role: Role::Developer,
72 content: Some(content.into()),
73 reasoning_content: None,
74 refusal: None,
75 name: None,
76 tool_calls: None,
77 tool_call_id: None,
78 }
79 }
80
81 pub fn user(content: impl Into<String>) -> Self {
83 Self {
84 role: Role::User,
85 content: Some(content.into()),
86 reasoning_content: None,
87 refusal: None,
88 name: None,
89 tool_calls: None,
90 tool_call_id: None,
91 }
92 }
93
94 pub fn assistant(content: impl Into<String>) -> Self {
96 Self {
97 role: Role::Assistant,
98 content: Some(content.into()),
99 reasoning_content: None,
100 refusal: None,
101 name: None,
102 tool_calls: None,
103 tool_call_id: None,
104 }
105 }
106
107 pub fn assistant_with_reasoning(
109 content: impl Into<String>,
110 reasoning: impl Into<String>,
111 ) -> Self {
112 Self {
113 role: Role::Assistant,
114 content: Some(content.into()),
115 reasoning_content: Some(reasoning.into()),
116 refusal: None,
117 name: None,
118 tool_calls: None,
119 tool_call_id: None,
120 }
121 }
122
123 pub fn assistant_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
125 Self {
126 role: Role::Assistant,
127 content: None,
128 reasoning_content: None,
129 refusal: None,
130 name: None,
131 tool_calls: Some(tool_calls),
132 tool_call_id: None,
133 }
134 }
135
136 pub fn tool(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
138 Self {
139 role: Role::Tool,
140 content: Some(content.into()),
141 reasoning_content: None,
142 refusal: None,
143 name: None,
144 tool_calls: None,
145 tool_call_id: Some(tool_call_id.into()),
146 }
147 }
148
149 pub fn function(name: impl Into<String>, content: impl Into<String>) -> Self {
151 Self {
152 role: Role::Function,
153 content: Some(content.into()),
154 reasoning_content: None,
155 refusal: None,
156 name: Some(name.into()),
157 tool_calls: None,
158 tool_call_id: None,
159 }
160 }
161
162 pub fn name(mut self, name: impl Into<String>) -> Self {
164 self.name = Some(name.into());
165 self
166 }
167
168 pub fn refusal(mut self, refusal: impl Into<String>) -> Self {
170 self.refusal = Some(refusal.into());
171 self
172 }
173
174 pub fn content(mut self, content: impl Into<String>) -> Self {
176 self.content = Some(content.into());
177 self
178 }
179
180 pub fn reasoning_content(mut self, reasoning: impl Into<String>) -> Self {
182 self.reasoning_content = Some(reasoning.into());
183 self
184 }
185
186 pub fn tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
188 self.tool_calls = if tool_calls.is_empty() {
189 None
190 } else {
191 Some(tool_calls)
192 };
193 self
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199pub struct ToolCall {
200 pub id: String,
202
203 #[serde(rename = "type")]
205 pub call_type: String,
206
207 pub function: FunctionCall,
209}
210
211impl ToolCall {
212 pub fn function(
214 id: impl Into<String>,
215 name: impl Into<String>,
216 arguments: impl Into<String>,
217 ) -> Self {
218 Self {
219 id: id.into(),
220 call_type: "function".to_string(),
221 function: FunctionCall::new(name, arguments),
222 }
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228pub struct FunctionCall {
229 pub name: String,
231
232 pub arguments: String,
234}
235
236impl FunctionCall {
237 pub fn new(name: impl Into<String>, arguments: impl Into<String>) -> Self {
239 Self {
240 name: name.into(),
241 arguments: arguments.into(),
242 }
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248pub struct ToolDefinition {
249 #[serde(rename = "type")]
251 pub tool_type: String,
252
253 pub function: FunctionDefinition,
255}
256
257impl ToolDefinition {
258 pub fn function(
260 name: impl Into<String>,
261 description: Option<String>,
262 parameters: serde_json::Value,
263 ) -> Self {
264 Self {
265 tool_type: "function".to_string(),
266 function: FunctionDefinition {
267 name: name.into(),
268 description,
269 parameters,
270 strict: None,
271 },
272 }
273 }
274
275 pub fn strict_function(
277 name: impl Into<String>,
278 description: Option<String>,
279 parameters: serde_json::Value,
280 ) -> Self {
281 Self {
282 tool_type: "function".to_string(),
283 function: FunctionDefinition {
284 name: name.into(),
285 description,
286 parameters,
287 strict: Some(true),
288 },
289 }
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
295pub struct FunctionDefinition {
296 pub name: String,
298
299 #[serde(skip_serializing_if = "Option::is_none")]
301 pub description: Option<String>,
302
303 pub parameters: serde_json::Value,
305
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub strict: Option<bool>,
309}
310
311impl FunctionDefinition {
312 pub fn new(name: impl Into<String>, parameters: serde_json::Value) -> Self {
314 Self {
315 name: name.into(),
316 description: None,
317 parameters,
318 strict: None,
319 }
320 }
321
322 pub fn description(mut self, description: impl Into<String>) -> Self {
324 self.description = Some(description.into());
325 self
326 }
327
328 pub fn strict(mut self, strict: bool) -> Self {
330 self.strict = Some(strict);
331 self
332 }
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct ChatCompletionRequest {
338 pub model: String,
340
341 pub messages: Vec<ChatMessage>,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub temperature: Option<f32>,
347
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub top_p: Option<f32>,
351
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub max_tokens: Option<u32>,
355
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub max_completion_tokens: Option<u32>,
359
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub stream_options: Option<serde_json::Value>,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub stream: Option<bool>,
367
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub tools: Option<Vec<ToolDefinition>>,
371
372 #[serde(skip_serializing_if = "Option::is_none")]
374 pub parallel_tool_calls: Option<bool>,
375
376 #[serde(skip_serializing_if = "Option::is_none")]
378 pub tool_choice: Option<serde_json::Value>,
379
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub response_format: Option<serde_json::Value>,
383
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub stop: Option<Vec<String>>,
387
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub presence_penalty: Option<f32>,
391
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub frequency_penalty: Option<f32>,
395
396 #[serde(skip_serializing_if = "Option::is_none")]
398 pub seed: Option<i64>,
399
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub user: Option<String>,
403}
404
405impl ChatCompletionRequest {
406 pub fn new(model: impl Into<String>, messages: Vec<ChatMessage>) -> Self {
408 Self {
409 model: model.into(),
410 messages,
411 temperature: None,
412 top_p: None,
413 max_tokens: None,
414 max_completion_tokens: None,
415 stream_options: None,
416 stream: None,
417 tools: None,
418 parallel_tool_calls: None,
419 tool_choice: None,
420 response_format: None,
421 stop: None,
422 presence_penalty: None,
423 frequency_penalty: None,
424 seed: None,
425 user: None,
426 }
427 }
428
429 pub fn temperature(mut self, temperature: f32) -> Self {
431 self.temperature = Some(temperature);
432 self
433 }
434
435 pub fn top_p(mut self, top_p: f32) -> Self {
437 self.top_p = Some(top_p);
438 self
439 }
440
441 pub fn max_tokens(mut self, max_tokens: u32) -> Self {
443 self.max_tokens = Some(max_tokens);
444 self
445 }
446
447 pub fn max_completion_tokens(mut self, max_completion_tokens: u32) -> Self {
449 self.max_completion_tokens = Some(max_completion_tokens);
450 self
451 }
452
453 pub fn include_usage(mut self) -> Self {
455 self.stream_options = Some(serde_json::json!({ "include_usage": true }));
456 self
457 }
458
459 pub fn stream(mut self, stream: bool) -> Self {
461 self.stream = Some(stream);
462 self
463 }
464
465 pub fn tools(mut self, tools: Vec<ToolDefinition>) -> Self {
467 self.tools = if tools.is_empty() { None } else { Some(tools) };
468 self
469 }
470
471 pub fn parallel_tool_calls(mut self, parallel: bool) -> Self {
473 self.parallel_tool_calls = Some(parallel);
474 self
475 }
476
477 pub fn stop(mut self, stop: Vec<String>) -> Self {
479 self.stop = Some(stop);
480 self
481 }
482
483 pub fn stop_sequence(mut self, stop: impl Into<String>) -> Self {
485 let mut seqs = self.stop.unwrap_or_default();
486 seqs.push(stop.into());
487 self.stop = Some(seqs);
488 self
489 }
490
491 pub fn json_mode(mut self) -> Self {
493 self.response_format = Some(serde_json::json!({ "type": "json_object" }));
494 self
495 }
496
497 pub fn presence_penalty(mut self, presence_penalty: f32) -> Self {
499 self.presence_penalty = Some(presence_penalty);
500 self
501 }
502
503 pub fn frequency_penalty(mut self, frequency_penalty: f32) -> Self {
505 self.frequency_penalty = Some(frequency_penalty);
506 self
507 }
508
509 pub fn seed(mut self, seed: i64) -> Self {
511 self.seed = Some(seed);
512 self
513 }
514
515 pub fn user(mut self, user: impl Into<String>) -> Self {
517 self.user = Some(user.into());
518 self
519 }
520
521 pub fn tool_choice(mut self, tool_choice: serde_json::Value) -> Self {
523 self.tool_choice = Some(tool_choice);
524 self
525 }
526
527 pub fn response_format(mut self, response_format: serde_json::Value) -> Self {
529 self.response_format = Some(response_format);
530 self
531 }
532}
533
534#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
536pub struct Usage {
537 pub prompt_tokens: u32,
539
540 pub completion_tokens: u32,
542
543 pub total_tokens: u32,
545}
546
547#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
549pub struct ChatChoice {
550 pub index: u32,
552
553 pub message: ChatMessage,
555
556 #[serde(default)]
558 pub finish_reason: Option<String>,
559}
560
561#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
563pub struct ChatCompletionResponse {
564 pub id: String,
566
567 #[serde(default)]
569 pub object: Option<String>,
570
571 pub created: u64,
573
574 pub model: String,
576
577 pub choices: Vec<ChatChoice>,
579
580 #[serde(default)]
582 pub usage: Option<Usage>,
583}
584
585#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
587pub struct ChatChunkDelta {
588 #[serde(default)]
590 pub role: Option<Role>,
591
592 #[serde(default)]
594 pub content: Option<String>,
595
596 #[serde(default, alias = "reasoning")]
598 pub reasoning_content: Option<String>,
599
600 #[serde(default)]
602 pub refusal: Option<String>,
603
604 #[serde(default)]
606 pub tool_calls: Option<Vec<ChunkToolCall>>,
607}
608
609#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
611pub struct ChunkToolCall {
612 pub index: u32,
614
615 #[serde(default)]
617 pub id: Option<String>,
618
619 #[serde(rename = "type", default)]
621 pub call_type: Option<String>,
622
623 #[serde(default)]
625 pub function: Option<ChunkFunctionCall>,
626}
627
628#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
630pub struct ChunkFunctionCall {
631 #[serde(default)]
633 pub name: Option<String>,
634
635 #[serde(default)]
637 pub arguments: Option<String>,
638}
639
640#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
642pub struct ChatChunkChoice {
643 pub index: u32,
645
646 #[serde(default)]
648 pub delta: ChatChunkDelta,
649
650 #[serde(default)]
652 pub finish_reason: Option<String>,
653}
654
655#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
657pub struct ChatCompletionChunk {
658 pub id: String,
660
661 #[serde(default)]
663 pub object: Option<String>,
664
665 #[serde(default)]
667 pub created: u64,
668
669 #[serde(default)]
671 pub model: String,
672
673 #[serde(default)]
675 pub choices: Vec<ChatChunkChoice>,
676
677 #[serde(default)]
679 pub usage: Option<Usage>,
680}
681
682#[derive(Debug, Clone, PartialEq, Eq)]
684pub enum EmbeddingInput {
685 Single(String),
687 Multiple(Vec<String>),
689}
690
691impl From<&str> for EmbeddingInput {
692 fn from(s: &str) -> Self {
693 Self::Single(s.to_string())
694 }
695}
696
697impl From<String> for EmbeddingInput {
698 fn from(s: String) -> Self {
699 Self::Single(s)
700 }
701}
702
703impl From<Vec<String>> for EmbeddingInput {
704 fn from(v: Vec<String>) -> Self {
705 Self::Multiple(v)
706 }
707}
708
709impl From<Vec<&str>> for EmbeddingInput {
710 fn from(v: Vec<&str>) -> Self {
711 Self::Multiple(v.into_iter().map(String::from).collect())
712 }
713}
714
715impl From<&[&str]> for EmbeddingInput {
716 fn from(s: &[&str]) -> Self {
717 Self::Multiple(s.iter().copied().map(String::from).collect())
718 }
719}
720
721impl Serialize for EmbeddingInput {
722 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
723 where
724 S: Serializer,
725 {
726 match self {
727 Self::Single(text) => serializer.serialize_str(text),
728 Self::Multiple(texts) => texts.serialize(serializer),
729 }
730 }
731}
732
733impl<'de> Deserialize<'de> for EmbeddingInput {
734 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
735 where
736 D: Deserializer<'de>,
737 {
738 #[derive(Deserialize)]
739 #[serde(untagged)]
740 enum Helper {
741 Single(String),
742 Multiple(Vec<String>),
743 }
744 match Helper::deserialize(deserializer)? {
745 Helper::Single(s) => Ok(Self::Single(s)),
746 Helper::Multiple(v) => Ok(Self::Multiple(v)),
747 }
748 }
749}
750
751#[derive(Debug, Clone, Serialize, Deserialize)]
753pub struct EmbeddingRequest {
754 pub model: String,
756
757 pub input: EmbeddingInput,
759
760 #[serde(skip_serializing_if = "Option::is_none")]
762 pub dimensions: Option<u32>,
763
764 #[serde(skip_serializing_if = "Option::is_none")]
766 pub user: Option<String>,
767}
768
769impl EmbeddingRequest {
770 pub fn new(model: impl Into<String>, input: impl Into<EmbeddingInput>) -> Self {
772 Self {
773 model: model.into(),
774 input: input.into(),
775 dimensions: None,
776 user: None,
777 }
778 }
779
780 pub fn dimensions(mut self, dims: u32) -> Self {
782 self.dimensions = Some(dims);
783 self
784 }
785}
786
787#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
789pub struct EmbeddingData {
790 pub index: u32,
792
793 pub object: String,
795
796 pub embedding: Vec<f32>,
798}
799
800#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
802pub struct EmbeddingResponse {
803 pub object: String,
805
806 pub data: Vec<EmbeddingData>,
808
809 pub model: String,
811
812 #[serde(default)]
814 pub usage: Option<Usage>,
815}
816
817#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
819pub struct ModelInfo {
820 pub id: String,
822
823 #[serde(default)]
825 pub object: Option<String>,
826
827 #[serde(default)]
829 pub created: Option<u64>,
830
831 #[serde(default)]
833 pub owned_by: Option<String>,
834}
835
836#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
838pub struct ListModelsResponse {
839 #[serde(default)]
841 pub object: Option<String>,
842
843 pub data: Vec<ModelInfo>,
845}
846
847#[cfg(test)]
848mod tests {
849 use super::*;
850
851 #[test]
852 fn test_chat_message_constructors_and_serialization() {
853 let msg = ChatMessage::system("System prompt");
854 assert_eq!(msg.role, Role::System);
855 let json = serde_json::to_string(&msg).unwrap();
856 assert!(json.contains("\"role\":\"system\""));
857 assert!(json.contains("\"content\":\"System prompt\""));
858
859 let user_msg = ChatMessage::user("Hello");
860 assert_eq!(user_msg.role, Role::User);
861
862 let tool_msg = ChatMessage::tool("call_123", "{\"result\": 42}");
863 assert_eq!(tool_msg.role, Role::Tool);
864 assert_eq!(tool_msg.tool_call_id.as_deref(), Some("call_123"));
865
866 let assistant_refusal = ChatMessage::assistant("Cannot comply")
867 .name("safety_agent")
868 .refusal("I cannot assist with that request.");
869 assert_eq!(assistant_refusal.name.as_deref(), Some("safety_agent"));
870 assert_eq!(
871 assistant_refusal.refusal.as_deref(),
872 Some("I cannot assist with that request.")
873 );
874 let refusal_json = serde_json::to_string(&assistant_refusal).unwrap();
875 assert!(refusal_json.contains("\"name\":\"safety_agent\""));
876 assert!(refusal_json.contains("\"refusal\":\"I cannot assist with that request.\""));
877
878 let chained_assistant = ChatMessage::assistant("Here is the tool invocation:")
879 .reasoning_content("Let me check the weather.")
880 .tool_calls(vec![ToolCall::function("call_1", "get_weather", "{}")]);
881 assert_eq!(
882 chained_assistant.reasoning_content.as_deref(),
883 Some("Let me check the weather.")
884 );
885 assert_eq!(chained_assistant.tool_calls.as_ref().unwrap().len(), 1);
886 }
887
888 #[test]
889 fn test_chat_completion_request_builder() {
890 let tool = ToolDefinition::function(
891 "get_weather",
892 Some("Fetch weather for location".to_string()),
893 serde_json::json!({
894 "type": "object",
895 "properties": {
896 "location": {"type": "string"}
897 },
898 "required": ["location"]
899 }),
900 );
901
902 let req = ChatCompletionRequest::new(
903 "gpt-4o-mini",
904 vec![ChatMessage::user("What is the weather?")],
905 )
906 .temperature(0.5)
907 .top_p(0.9)
908 .max_tokens(100)
909 .tools(vec![tool])
910 .json_mode()
911 .stop(vec!["\n".to_string()]);
912
913 let json = serde_json::to_value(&req).unwrap();
914 assert_eq!(json["model"], "gpt-4o-mini");
915 assert_eq!(json["temperature"], 0.5);
916 assert_eq!(json["max_tokens"], 100);
917 assert_eq!(json["tools"][0]["function"]["name"], "get_weather");
918 assert_eq!(json["response_format"]["type"], "json_object");
919 assert_eq!(json["stop"][0], "\n");
920
921 let empty_tools_req =
922 ChatCompletionRequest::new("gpt-4o-mini", vec![ChatMessage::user("Hi")])
923 .tools(Vec::new());
924 let empty_json = serde_json::to_value(&empty_tools_req).unwrap();
925 assert!(empty_json.get("tools").is_none());
926 }
927
928 #[test]
929 fn test_chat_completion_response_deserialization() {
930 let raw = r#"{
931 "id": "chatcmpl-123",
932 "object": "chat.completion",
933 "created": 1677652288,
934 "model": "gpt-4o-mini",
935 "choices": [{
936 "index": 0,
937 "message": {
938 "role": "assistant",
939 "content": "Hello there!"
940 },
941 "finish_reason": "stop"
942 }],
943 "usage": {
944 "prompt_tokens": 9,
945 "completion_tokens": 12,
946 "total_tokens": 21
947 }
948 }"#;
949
950 let res: ChatCompletionResponse = serde_json::from_str(raw).unwrap();
951 assert_eq!(res.id, "chatcmpl-123");
952 assert_eq!(res.choices.len(), 1);
953 assert_eq!(res.choices[0].finish_reason.as_deref(), Some("stop"));
954 assert_eq!(
955 res.choices[0].message.content.as_deref(),
956 Some("Hello there!")
957 );
958 assert_eq!(res.usage.unwrap().total_tokens, 21);
959 }
960
961 #[test]
962 fn test_chat_completion_chunk_deserialization() {
963 let raw = r#"{
964 "id": "chatcmpl-chunk-1",
965 "object": "chat.completion.chunk",
966 "created": 1677652288,
967 "model": "gpt-4o",
968 "choices": [{
969 "index": 0,
970 "delta": {
971 "role": "assistant",
972 "content": "part"
973 },
974 "finish_reason": null
975 }]
976 }"#;
977
978 let chunk: ChatCompletionChunk = serde_json::from_str(raw).unwrap();
979 assert_eq!(chunk.id, "chatcmpl-chunk-1");
980 assert_eq!(chunk.choices[0].delta.content.as_deref(), Some("part"));
981 assert_eq!(chunk.choices[0].delta.role, Some(Role::Assistant));
982 }
983
984 #[test]
985 fn test_embedding_request_and_response() {
986 let req_single =
987 EmbeddingRequest::new("text-embedding-3-small", "test text").dimensions(512);
988 let val_single = serde_json::to_value(&req_single).unwrap();
989 assert_eq!(val_single["input"], "test text");
990 assert_eq!(val_single["dimensions"], 512);
991
992 let req_multi = EmbeddingRequest::new(
993 "text-embedding-3-small",
994 vec!["item1".to_string(), "item2".to_string()],
995 );
996 let val_multi = serde_json::to_value(&req_multi).unwrap();
997 assert_eq!(val_multi["input"][0], "item1");
998 assert_eq!(val_multi["input"][1], "item2");
999
1000 let req_slice_vec =
1001 EmbeddingRequest::new("text-embedding-3-small", vec!["slice1", "slice2"]);
1002 let val_slice_vec = serde_json::to_value(&req_slice_vec).unwrap();
1003 assert_eq!(val_slice_vec["input"][0], "slice1");
1004 assert_eq!(val_slice_vec["input"][1], "slice2");
1005
1006 let items: &[&str] = &["item_a", "item_b"];
1007 let req_slice = EmbeddingRequest::new("text-embedding-3-small", items);
1008 let val_slice = serde_json::to_value(&req_slice).unwrap();
1009 assert_eq!(val_slice["input"][0], "item_a");
1010 assert_eq!(val_slice["input"][1], "item_b");
1011
1012 let raw_res = r#"{
1013 "object": "list",
1014 "data": [
1015 {
1016 "object": "embedding",
1017 "index": 0,
1018 "embedding": [0.1, -0.2, 0.3]
1019 }
1020 ],
1021 "model": "text-embedding-3-small",
1022 "usage": {
1023 "prompt_tokens": 5,
1024 "total_tokens": 5,
1025 "completion_tokens": 0
1026 }
1027 }"#;
1028 let res: EmbeddingResponse = serde_json::from_str(raw_res).unwrap();
1029 assert_eq!(res.data.len(), 1);
1030 assert_eq!(res.data[0].embedding, vec![0.1, -0.2, 0.3]);
1031 }
1032
1033 #[test]
1034 fn test_models_list_deserialization() {
1035 let raw = r#"{
1036 "object": "list",
1037 "data": [
1038 {
1039 "id": "gpt-4o",
1040 "object": "model",
1041 "created": 1700000000,
1042 "owned_by": "openai"
1043 }
1044 ]
1045 }"#;
1046 let res: ListModelsResponse = serde_json::from_str(raw).unwrap();
1047 assert_eq!(res.data.len(), 1);
1048 assert_eq!(res.data[0].id, "gpt-4o");
1049 }
1050
1051 #[test]
1052 fn test_function_definition_and_role_serialization() {
1053 let msg = ChatMessage::function("calc", "42");
1054 let val = serde_json::to_value(&msg).unwrap();
1055 assert_eq!(val["role"], "function");
1056 assert_eq!(val["name"], "calc");
1057 assert_eq!(val["content"], "42");
1058
1059 let tool_def = ToolDefinition::strict_function(
1060 "get_weather",
1061 Some("Fetch current weather".to_string()),
1062 serde_json::json!({
1063 "type": "object",
1064 "properties": { "location": { "type": "string" } },
1065 "required": ["location"],
1066 "additionalProperties": false
1067 }),
1068 );
1069 let val_tool = serde_json::to_value(&tool_def).unwrap();
1070 assert_eq!(val_tool["type"], "function");
1071 assert_eq!(val_tool["function"]["name"], "get_weather");
1072 assert_eq!(val_tool["function"]["strict"], true);
1073 }
1074
1075 #[test]
1076 fn test_reasoning_content_and_request_builder_methods() {
1077 let msg = ChatMessage::assistant_with_reasoning("The answer is 42", "Let me compute 6 * 7");
1078 let val = serde_json::to_value(&msg).unwrap();
1079 assert_eq!(val["role"], "assistant");
1080 assert_eq!(val["content"], "The answer is 42");
1081 assert_eq!(val["reasoning_content"], "Let me compute 6 * 7");
1082
1083 let raw_chunk = r#"{
1084 "id": "chunk-r1",
1085 "created": 12345,
1086 "model": "deepseek-r1",
1087 "choices": [{
1088 "index": 0,
1089 "delta": {
1090 "content": null,
1091 "reasoning": "step 1"
1092 }
1093 }]
1094 }"#;
1095 let chunk: ChatCompletionChunk = serde_json::from_str(raw_chunk).unwrap();
1096 assert_eq!(
1097 chunk.choices[0].delta.reasoning_content.as_deref(),
1098 Some("step 1")
1099 );
1100
1101 let raw_non_streaming_reasoning = r#"{
1102 "role": "assistant",
1103 "content": "Result",
1104 "reasoning": "thought process"
1105 }"#;
1106 let non_streaming_msg: ChatMessage =
1107 serde_json::from_str(raw_non_streaming_reasoning).unwrap();
1108 assert_eq!(
1109 non_streaming_msg.reasoning_content.as_deref(),
1110 Some("thought process")
1111 );
1112
1113 let raw_chunk_omitted_delta = r#"{
1114 "id": "chunk-term",
1115 "choices": [{
1116 "index": 0,
1117 "finish_reason": "stop"
1118 }]
1119 }"#;
1120 let chunk_term: ChatCompletionChunk =
1121 serde_json::from_str(raw_chunk_omitted_delta).unwrap();
1122 assert_eq!(chunk_term.choices[0].finish_reason.as_deref(), Some("stop"));
1123 assert_eq!(chunk_term.choices[0].delta.content, None);
1124 assert_eq!(chunk_term.model, "");
1125
1126 let raw_chunk_usage_only = r#"{
1127 "id": "chunk-usage",
1128 "usage": {
1129 "prompt_tokens": 5,
1130 "completion_tokens": 10,
1131 "total_tokens": 15
1132 }
1133 }"#;
1134 let chunk_usage: ChatCompletionChunk = serde_json::from_str(raw_chunk_usage_only).unwrap();
1135 assert!(chunk_usage.choices.is_empty());
1136 assert_eq!(chunk_usage.usage.unwrap().total_tokens, 15);
1137
1138 let tool_call = ToolCall::function("call_1", "get_stock", r#"{"symbol":"AAPL"}"#);
1139 assert_eq!(tool_call.id, "call_1");
1140 assert_eq!(tool_call.call_type, "function");
1141 assert_eq!(tool_call.function.name, "get_stock");
1142
1143 let req = ChatCompletionRequest::new("gpt-4o", vec![ChatMessage::user("Hello")])
1144 .presence_penalty(0.5)
1145 .frequency_penalty(-0.2)
1146 .seed(42)
1147 .user("user_123")
1148 .parallel_tool_calls(true)
1149 .stop_sequence("END")
1150 .tool_choice(serde_json::json!("auto"))
1151 .response_format(serde_json::json!({ "type": "text" }));
1152 let val_req = serde_json::to_value(&req).unwrap();
1153 assert_eq!(val_req["presence_penalty"], 0.5);
1154 assert!((val_req["frequency_penalty"].as_f64().unwrap() - -0.2).abs() < 1e-6);
1155 assert_eq!(val_req["seed"], 42);
1156 assert_eq!(val_req["user"], "user_123");
1157 assert_eq!(val_req["parallel_tool_calls"], true);
1158 assert_eq!(val_req["stop"][0], "END");
1159 assert_eq!(val_req["tool_choice"], "auto");
1160 assert_eq!(val_req["response_format"]["type"], "text");
1161 }
1162}