1use crate::{
7 chat::{FunctionTool, ParameterProperty, ParametersSchema, ReasoningEffort, Tool, ToolChoice},
8 error::LLMError,
9 LLMProvider,
10};
11use std::{collections::HashMap, marker::PhantomData};
12
13pub type ValidatorFn = dyn Fn(&str) -> Result<(), String> + Send + Sync + 'static;
16
17#[derive(Debug, Clone)]
19pub enum LLMBackend {
20 OpenAI,
22 Anthropic,
24 Ollama,
26 DeepSeek,
28 XAI,
30 Phind,
32 Google,
34 Groq,
36 AzureOpenAI,
38 OpenRouter,
40}
41
42impl std::str::FromStr for LLMBackend {
69 type Err = LLMError;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 match s.to_lowercase().as_str() {
73 "openai" => Ok(LLMBackend::OpenAI),
74 "anthropic" => Ok(LLMBackend::Anthropic),
75 "ollama" => Ok(LLMBackend::Ollama),
76 "deepseek" => Ok(LLMBackend::DeepSeek),
77 "xai" => Ok(LLMBackend::XAI),
78 "phind" => Ok(LLMBackend::Phind),
79 "google" => Ok(LLMBackend::Google),
80 "groq" => Ok(LLMBackend::Groq),
81 "azure-openai" => Ok(LLMBackend::AzureOpenAI),
82 "openrouter" => Ok(LLMBackend::OpenRouter),
83 _ => Err(LLMError::InvalidRequest(format!(
84 "Unknown LLM backend: {s}"
85 ))),
86 }
87 }
88}
89
90pub struct LLMBuilder<L: LLMProvider> {
95 pub(crate) backend: PhantomData<L>,
97 pub(crate) api_key: Option<String>,
99 pub(crate) base_url: Option<String>,
101 pub model: Option<String>,
103 pub max_tokens: Option<u32>,
105 pub temperature: Option<f32>,
107 pub system: Option<String>,
109 pub(crate) timeout_seconds: Option<u64>,
111 pub top_p: Option<f32>,
113 pub(crate) top_k: Option<u32>,
115 pub(crate) embedding_encoding_format: Option<String>,
117 pub(crate) embedding_dimensions: Option<u32>,
119 pub(crate) validator: Option<Box<ValidatorFn>>,
121 pub(crate) validator_attempts: usize,
123 pub(crate) tool_choice: Option<ToolChoice>,
125 pub(crate) enable_parallel_tool_use: Option<bool>,
127 pub(crate) reasoning: Option<bool>,
129 pub(crate) reasoning_effort: Option<String>,
131 pub(crate) reasoning_budget_tokens: Option<u32>,
133 pub(crate) api_version: Option<String>,
135 pub(crate) deployment_id: Option<String>,
137 #[allow(dead_code)]
139 pub(crate) voice: Option<String>,
140 #[allow(dead_code)]
142 pub(crate) normalize_response: Option<bool>,
143}
144
145impl<L: LLMProvider> Default for LLMBuilder<L> {
146 fn default() -> Self {
147 Self {
148 backend: PhantomData,
149 api_key: None,
150 base_url: None,
151 model: None,
152 max_tokens: None,
153 temperature: None,
154 system: None,
155 timeout_seconds: None,
156 top_p: None,
157 top_k: None,
158 embedding_encoding_format: None,
159 embedding_dimensions: None,
160 validator: None,
161 validator_attempts: 0,
162 tool_choice: None,
163 enable_parallel_tool_use: None,
164 reasoning: None,
165 reasoning_effort: None,
166 reasoning_budget_tokens: None,
167 api_version: None,
168 deployment_id: None,
169 voice: None,
170 normalize_response: None,
171 }
172 }
173}
174
175impl<L: LLMProvider> LLMBuilder<L> {
176 pub fn new() -> Self {
178 Self::default()
179 }
180
181 pub fn api_key(mut self, key: impl Into<String>) -> Self {
183 self.api_key = Some(key.into());
184 self
185 }
186
187 pub fn base_url(mut self, url: impl Into<String>) -> Self {
189 self.base_url = Some(url.into());
190 self
191 }
192
193 pub fn model(mut self, model: impl Into<String>) -> Self {
195 self.model = Some(model.into());
196 self
197 }
198
199 pub fn max_tokens(mut self, max_tokens: u32) -> Self {
201 self.max_tokens = Some(max_tokens);
202 self
203 }
204
205 pub fn temperature(mut self, temperature: f32) -> Self {
207 self.temperature = Some(temperature);
208 self
209 }
210
211 pub fn system(mut self, system: impl Into<String>) -> Self {
213 self.system = Some(system.into());
214 self
215 }
216
217 pub fn reasoning_effort(mut self, reasoning_effort: ReasoningEffort) -> Self {
219 self.reasoning_effort = Some(reasoning_effort.to_string());
220 self
221 }
222
223 pub fn reasoning(mut self, reasoning: bool) -> Self {
225 self.reasoning = Some(reasoning);
226 self
227 }
228
229 pub fn reasoning_budget_tokens(mut self, reasoning_budget_tokens: u32) -> Self {
231 self.reasoning_budget_tokens = Some(reasoning_budget_tokens);
232 self
233 }
234
235 pub fn timeout_seconds(mut self, timeout_seconds: u64) -> Self {
237 self.timeout_seconds = Some(timeout_seconds);
238 self
239 }
240
241 pub fn top_p(mut self, top_p: f32) -> Self {
243 self.top_p = Some(top_p);
244 self
245 }
246
247 pub fn top_k(mut self, top_k: u32) -> Self {
249 self.top_k = Some(top_k);
250 self
251 }
252
253 pub fn embedding_encoding_format(
255 mut self,
256 embedding_encoding_format: impl Into<String>,
257 ) -> Self {
258 self.embedding_encoding_format = Some(embedding_encoding_format.into());
259 self
260 }
261
262 pub fn embedding_dimensions(mut self, embedding_dimensions: u32) -> Self {
264 self.embedding_dimensions = Some(embedding_dimensions);
265 self
266 }
267
268 pub fn validator<F>(mut self, f: F) -> Self
274 where
275 F: Fn(&str) -> Result<(), String> + Send + Sync + 'static,
276 {
277 self.validator = Some(Box::new(f));
278 self
279 }
280
281 pub fn validator_attempts(mut self, attempts: usize) -> Self {
287 self.validator_attempts = attempts;
288 self
289 }
290
291 pub fn enable_parallel_tool_use(mut self, enable: bool) -> Self {
293 self.enable_parallel_tool_use = Some(enable);
294 self
295 }
296
297 pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
300 self.tool_choice = Some(choice);
301 self
302 }
303
304 pub fn disable_tools(mut self) -> Self {
306 self.tool_choice = Some(ToolChoice::None);
307 self
308 }
309
310 pub fn api_version(mut self, api_version: impl Into<String>) -> Self {
312 self.api_version = Some(api_version.into());
313 self
314 }
315
316 pub fn deployment_id(mut self, deployment_id: impl Into<String>) -> Self {
318 self.deployment_id = Some(deployment_id.into());
319 self
320 }
321}
322
323#[allow(dead_code)]
325pub struct ParamBuilder {
326 name: String,
327 property_type: String,
328 description: String,
329 items: Option<Box<ParameterProperty>>,
330 enum_list: Option<Vec<String>>,
331}
332
333impl ParamBuilder {
334 pub fn new(name: impl Into<String>) -> Self {
336 Self {
337 name: name.into(),
338 property_type: "string".to_string(),
339 description: String::new(),
340 items: None,
341 enum_list: None,
342 }
343 }
344
345 pub fn type_of(mut self, type_str: impl Into<String>) -> Self {
347 self.property_type = type_str.into();
348 self
349 }
350
351 pub fn description(mut self, desc: impl Into<String>) -> Self {
353 self.description = desc.into();
354 self
355 }
356
357 pub fn items(mut self, item_property: ParameterProperty) -> Self {
359 self.items = Some(Box::new(item_property));
360 self
361 }
362
363 pub fn enum_values(mut self, values: Vec<String>) -> Self {
365 self.enum_list = Some(values);
366 self
367 }
368
369 #[allow(dead_code)]
371 fn build(self) -> (String, ParameterProperty) {
372 (
373 self.name,
374 ParameterProperty {
375 property_type: self.property_type,
376 description: self.description,
377 items: self.items,
378 enum_list: self.enum_list,
379 },
380 )
381 }
382}
383
384#[allow(dead_code)]
386pub struct FunctionBuilder {
387 name: String,
388 description: String,
389 parameters: Vec<ParamBuilder>,
390 required: Vec<String>,
391 raw_schema: Option<serde_json::Value>,
392}
393
394impl FunctionBuilder {
395 pub fn new(name: impl Into<String>) -> Self {
397 Self {
398 name: name.into(),
399 description: String::new(),
400 parameters: Vec::new(),
401 required: Vec::new(),
402 raw_schema: None,
403 }
404 }
405
406 pub fn description(mut self, desc: impl Into<String>) -> Self {
408 self.description = desc.into();
409 self
410 }
411
412 pub fn param(mut self, param: ParamBuilder) -> Self {
414 self.parameters.push(param);
415 self
416 }
417
418 pub fn required(mut self, param_names: Vec<String>) -> Self {
420 self.required = param_names;
421 self
422 }
423
424 pub fn json_schema(mut self, schema: serde_json::Value) -> Self {
428 self.raw_schema = Some(schema);
429 self
430 }
431
432 #[allow(dead_code)]
434 fn build(self) -> Tool {
435 let parameters_value = if let Some(schema) = self.raw_schema {
436 schema
437 } else {
438 let mut properties = HashMap::new();
439 for param in self.parameters {
440 let (name, prop) = param.build();
441 properties.insert(name, prop);
442 }
443
444 serde_json::to_value(ParametersSchema {
445 schema_type: "object".to_string(),
446 properties,
447 required: self.required,
448 })
449 .unwrap_or_else(|_| serde_json::Value::Object(serde_json::Map::new()))
450 };
451
452 Tool {
453 tool_type: "function".to_string(),
454 function: FunctionTool {
455 name: self.name,
456 description: self.description,
457 parameters: parameters_value,
458 },
459 }
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use crate::chat::{ChatMessage, ChatResponse, StructuredOutputFormat};
467 use crate::error::LLMError;
468 use serde_json::json;
469 use std::str::FromStr;
470
471 #[test]
472 fn test_llm_backend_from_str() {
473 assert!(matches!(
474 LLMBackend::from_str("openai").unwrap(),
475 LLMBackend::OpenAI
476 ));
477 assert!(matches!(
478 LLMBackend::from_str("OpenAI").unwrap(),
479 LLMBackend::OpenAI
480 ));
481 assert!(matches!(
482 LLMBackend::from_str("OPENAI").unwrap(),
483 LLMBackend::OpenAI
484 ));
485 assert!(matches!(
486 LLMBackend::from_str("anthropic").unwrap(),
487 LLMBackend::Anthropic
488 ));
489 assert!(matches!(
490 LLMBackend::from_str("ollama").unwrap(),
491 LLMBackend::Ollama
492 ));
493 assert!(matches!(
494 LLMBackend::from_str("deepseek").unwrap(),
495 LLMBackend::DeepSeek
496 ));
497 assert!(matches!(
498 LLMBackend::from_str("xai").unwrap(),
499 LLMBackend::XAI
500 ));
501 assert!(matches!(
502 LLMBackend::from_str("phind").unwrap(),
503 LLMBackend::Phind
504 ));
505 assert!(matches!(
506 LLMBackend::from_str("google").unwrap(),
507 LLMBackend::Google
508 ));
509 assert!(matches!(
510 LLMBackend::from_str("groq").unwrap(),
511 LLMBackend::Groq
512 ));
513 assert!(matches!(
514 LLMBackend::from_str("azure-openai").unwrap(),
515 LLMBackend::AzureOpenAI
516 ));
517
518 let result = LLMBackend::from_str("invalid");
519 assert!(result.is_err());
520 assert!(result
521 .unwrap_err()
522 .to_string()
523 .contains("Unknown LLM backend"));
524 }
525
526 #[test]
527 fn test_param_builder_new() {
528 let builder = ParamBuilder::new("test_param");
529 assert_eq!(builder.name, "test_param");
530 assert_eq!(builder.property_type, "string");
531 assert_eq!(builder.description, "");
532 assert!(builder.items.is_none());
533 assert!(builder.enum_list.is_none());
534 }
535
536 #[test]
537 fn test_param_builder_fluent_interface() {
538 let builder = ParamBuilder::new("test_param")
539 .type_of("integer")
540 .description("A test parameter")
541 .enum_values(vec!["option1".to_string(), "option2".to_string()]);
542
543 assert_eq!(builder.name, "test_param");
544 assert_eq!(builder.property_type, "integer");
545 assert_eq!(builder.description, "A test parameter");
546 assert_eq!(
547 builder.enum_list,
548 Some(vec!["option1".to_string(), "option2".to_string()])
549 );
550 }
551
552 #[test]
553 fn test_param_builder_with_items() {
554 let item_property = ParameterProperty {
555 property_type: "string".to_string(),
556 description: "Array item".to_string(),
557 items: None,
558 enum_list: None,
559 };
560
561 let builder = ParamBuilder::new("array_param")
562 .type_of("array")
563 .description("An array parameter")
564 .items(item_property);
565
566 assert_eq!(builder.name, "array_param");
567 assert_eq!(builder.property_type, "array");
568 assert_eq!(builder.description, "An array parameter");
569 assert!(builder.items.is_some());
570 }
571
572 #[test]
573 fn test_param_builder_build() {
574 let builder = ParamBuilder::new("test_param")
575 .type_of("string")
576 .description("A test parameter");
577
578 let (name, property) = builder.build();
579 assert_eq!(name, "test_param");
580 assert_eq!(property.property_type, "string");
581 assert_eq!(property.description, "A test parameter");
582 }
583
584 #[test]
585 fn test_function_builder_new() {
586 let builder = FunctionBuilder::new("test_function");
587 assert_eq!(builder.name, "test_function");
588 assert_eq!(builder.description, "");
589 assert!(builder.parameters.is_empty());
590 assert!(builder.required.is_empty());
591 assert!(builder.raw_schema.is_none());
592 }
593
594 #[test]
595 fn test_function_builder_fluent_interface() {
596 let param = ParamBuilder::new("name")
597 .type_of("string")
598 .description("Name");
599 let builder = FunctionBuilder::new("test_function")
600 .description("A test function")
601 .param(param)
602 .required(vec!["name".to_string()]);
603
604 assert_eq!(builder.name, "test_function");
605 assert_eq!(builder.description, "A test function");
606 assert_eq!(builder.parameters.len(), 1);
607 assert_eq!(builder.required, vec!["name".to_string()]);
608 }
609
610 #[test]
611 fn test_function_builder_with_json_schema() {
612 let schema = json!({
613 "type": "object",
614 "properties": {
615 "name": {"type": "string"},
616 "age": {"type": "integer"}
617 },
618 "required": ["name", "age"]
619 });
620
621 let builder = FunctionBuilder::new("test_function").json_schema(schema.clone());
622 assert_eq!(builder.raw_schema, Some(schema));
623 }
624
625 #[test]
626 fn test_function_builder_build_with_parameters() {
627 let param = ParamBuilder::new("name").type_of("string");
628 let tool = FunctionBuilder::new("test_function")
629 .description("A test function")
630 .param(param)
631 .required(vec!["name".to_string()])
632 .build();
633
634 assert_eq!(tool.tool_type, "function");
635 assert_eq!(tool.function.name, "test_function");
636 assert_eq!(tool.function.description, "A test function");
637 assert!(tool.function.parameters.is_object());
638 }
639
640 #[test]
641 fn test_function_builder_build_with_raw_schema() {
642 let schema = json!({
643 "type": "object",
644 "properties": {
645 "name": {"type": "string"}
646 }
647 });
648
649 let tool = FunctionBuilder::new("test_function")
650 .json_schema(schema.clone())
651 .build();
652
653 assert_eq!(tool.function.parameters, schema);
654 }
655
656 struct MockLLMProvider;
658
659 #[async_trait::async_trait]
660 impl crate::chat::ChatProvider for MockLLMProvider {
661 async fn chat(
662 &self,
663 _messages: &[ChatMessage],
664 _tools: Option<&[Tool]>,
665 _json_schema: Option<StructuredOutputFormat>,
666 ) -> Result<Box<dyn ChatResponse>, LLMError> {
667 unimplemented!()
668 }
669 }
670
671 #[async_trait::async_trait]
672 impl crate::completion::CompletionProvider for MockLLMProvider {
673 async fn complete(
674 &self,
675 _req: &crate::completion::CompletionRequest,
676 _json_schema: Option<crate::chat::StructuredOutputFormat>,
677 ) -> Result<crate::completion::CompletionResponse, LLMError> {
678 unimplemented!()
679 }
680 }
681
682 #[async_trait::async_trait]
683 impl crate::embedding::EmbeddingProvider for MockLLMProvider {
684 async fn embed(&self, _text: Vec<String>) -> Result<Vec<Vec<f32>>, LLMError> {
685 unimplemented!()
686 }
687 }
688
689 #[async_trait::async_trait]
690 impl crate::models::ModelsProvider for MockLLMProvider {}
691
692 impl crate::LLMProvider for MockLLMProvider {}
693
694 #[test]
695 fn test_llm_builder_new() {
696 let builder = LLMBuilder::<MockLLMProvider>::new();
697 assert!(builder.api_key.is_none());
698 assert!(builder.base_url.is_none());
699 assert!(builder.model.is_none());
700 assert!(builder.max_tokens.is_none());
701 assert!(builder.temperature.is_none());
702 assert!(builder.system.is_none());
703 assert!(builder.timeout_seconds.is_none());
704 assert!(builder.tool_choice.is_none());
705 }
706
707 #[test]
708 fn test_llm_builder_default() {
709 let builder = LLMBuilder::<MockLLMProvider>::default();
710 assert!(builder.api_key.is_none());
711 assert!(builder.base_url.is_none());
712 assert!(builder.model.is_none());
713 assert_eq!(builder.validator_attempts, 0);
714 }
715
716 #[test]
717 fn test_llm_builder_api_key() {
718 let builder = LLMBuilder::<MockLLMProvider>::new().api_key("test_key");
719 assert_eq!(builder.api_key, Some("test_key".to_string()));
720 }
721
722 #[test]
723 fn test_llm_builder_base_url() {
724 let builder = LLMBuilder::<MockLLMProvider>::new().base_url("https://api.example.com");
725 assert_eq!(
726 builder.base_url,
727 Some("https://api.example.com".to_string())
728 );
729 }
730
731 #[test]
732 fn test_llm_builder_model() {
733 let builder = LLMBuilder::<MockLLMProvider>::new().model("gpt-4");
734 assert_eq!(builder.model, Some("gpt-4".to_string()));
735 }
736
737 #[test]
738 fn test_llm_builder_max_tokens() {
739 let builder = LLMBuilder::<MockLLMProvider>::new().max_tokens(1000);
740 assert_eq!(builder.max_tokens, Some(1000));
741 }
742
743 #[test]
744 fn test_llm_builder_temperature() {
745 let builder = LLMBuilder::<MockLLMProvider>::new().temperature(0.7);
746 assert_eq!(builder.temperature, Some(0.7));
747 }
748
749 #[test]
750 fn test_llm_builder_system() {
751 let builder = LLMBuilder::<MockLLMProvider>::new().system("You are a helpful assistant");
752 assert_eq!(
753 builder.system,
754 Some("You are a helpful assistant".to_string())
755 );
756 }
757
758 #[test]
759 fn test_llm_builder_reasoning_effort() {
760 let builder = LLMBuilder::<MockLLMProvider>::new()
761 .reasoning_effort(crate::chat::ReasoningEffort::High);
762 assert_eq!(builder.reasoning_effort, Some("high".to_string()));
763 }
764
765 #[test]
766 fn test_llm_builder_reasoning() {
767 let builder = LLMBuilder::<MockLLMProvider>::new().reasoning(true);
768 assert_eq!(builder.reasoning, Some(true));
769 }
770
771 #[test]
772 fn test_llm_builder_reasoning_budget_tokens() {
773 let builder = LLMBuilder::<MockLLMProvider>::new().reasoning_budget_tokens(5000);
774 assert_eq!(builder.reasoning_budget_tokens, Some(5000));
775 }
776
777 #[test]
778 fn test_llm_builder_timeout_seconds() {
779 let builder = LLMBuilder::<MockLLMProvider>::new().timeout_seconds(30);
780 assert_eq!(builder.timeout_seconds, Some(30));
781 }
782
783 #[test]
784 fn test_llm_builder_top_p() {
785 let builder = LLMBuilder::<MockLLMProvider>::new().top_p(0.9);
786 assert_eq!(builder.top_p, Some(0.9));
787 }
788
789 #[test]
790 fn test_llm_builder_top_k() {
791 let builder = LLMBuilder::<MockLLMProvider>::new().top_k(50);
792 assert_eq!(builder.top_k, Some(50));
793 }
794
795 #[test]
796 fn test_llm_builder_embedding_encoding_format() {
797 let builder = LLMBuilder::<MockLLMProvider>::new().embedding_encoding_format("float");
798 assert_eq!(builder.embedding_encoding_format, Some("float".to_string()));
799 }
800
801 #[test]
802 fn test_llm_builder_embedding_dimensions() {
803 let builder = LLMBuilder::<MockLLMProvider>::new().embedding_dimensions(1536);
804 assert_eq!(builder.embedding_dimensions, Some(1536));
805 }
806
807 #[test]
808 fn test_llm_builder_validator() {
809 let builder = LLMBuilder::<MockLLMProvider>::new().validator(|response| {
810 if response.contains("error") {
811 Err("Response contains error".to_string())
812 } else {
813 Ok(())
814 }
815 });
816 assert!(builder.validator.is_some());
817 }
818
819 #[test]
820 fn test_llm_builder_validator_attempts() {
821 let builder = LLMBuilder::<MockLLMProvider>::new().validator_attempts(3);
822 assert_eq!(builder.validator_attempts, 3);
823 }
824
825 #[test]
826 fn test_llm_builder_enable_parallel_tool_use() {
827 let builder = LLMBuilder::<MockLLMProvider>::new().enable_parallel_tool_use(true);
828 assert_eq!(builder.enable_parallel_tool_use, Some(true));
829 }
830
831 #[test]
832 fn test_llm_builder_tool_choice() {
833 let builder = LLMBuilder::<MockLLMProvider>::new().tool_choice(ToolChoice::Auto);
834 assert!(matches!(builder.tool_choice, Some(ToolChoice::Auto)));
835 }
836
837 #[test]
838 fn test_llm_builder_disable_tools() {
839 let builder = LLMBuilder::<MockLLMProvider>::new().disable_tools();
840 assert!(matches!(builder.tool_choice, Some(ToolChoice::None)));
841 }
842
843 #[test]
844 fn test_llm_builder_api_version() {
845 let builder = LLMBuilder::<MockLLMProvider>::new().api_version("2023-05-15");
846 assert_eq!(builder.api_version, Some("2023-05-15".to_string()));
847 }
848
849 #[test]
850 fn test_llm_builder_deployment_id() {
851 let builder = LLMBuilder::<MockLLMProvider>::new().deployment_id("my-deployment");
852 assert_eq!(builder.deployment_id, Some("my-deployment".to_string()));
853 }
854
855 #[test]
856 fn test_llm_builder_chaining() {
857 let builder = LLMBuilder::<MockLLMProvider>::new()
858 .api_key("test_key")
859 .model("gpt-4")
860 .max_tokens(2000)
861 .temperature(0.8)
862 .system("You are helpful")
863 .timeout_seconds(60)
864 .top_p(0.95)
865 .top_k(40)
866 .embedding_encoding_format("float")
867 .embedding_dimensions(1536)
868 .validator_attempts(5)
869 .reasoning(true)
870 .reasoning_budget_tokens(10000)
871 .api_version("2023-05-15")
872 .deployment_id("test-deployment");
873
874 assert_eq!(builder.api_key, Some("test_key".to_string()));
875 assert_eq!(builder.model, Some("gpt-4".to_string()));
876 assert_eq!(builder.max_tokens, Some(2000));
877 assert_eq!(builder.temperature, Some(0.8));
878 assert_eq!(builder.system, Some("You are helpful".to_string()));
879 assert_eq!(builder.timeout_seconds, Some(60));
880 assert_eq!(builder.top_p, Some(0.95));
881 assert_eq!(builder.top_k, Some(40));
882 assert_eq!(builder.embedding_encoding_format, Some("float".to_string()));
883 assert_eq!(builder.embedding_dimensions, Some(1536));
884 assert_eq!(builder.validator_attempts, 5);
885 assert_eq!(builder.reasoning, Some(true));
886 assert_eq!(builder.reasoning_budget_tokens, Some(10000));
887 assert_eq!(builder.api_version, Some("2023-05-15".to_string()));
888 assert_eq!(builder.deployment_id, Some("test-deployment".to_string()));
889 }
890}