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