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