1use serde::{Deserialize, Serialize};
4use std::time::Instant;
5use tracing::{debug, info, warn};
6
7use super::{serialize_for_log, ModeCore};
8use crate::config::Config;
9use crate::error::{AppResult, ToolError};
10use crate::langbase::{LangbaseClient, Message, PipeRequest};
11use crate::modes::ReasoningMode;
12use crate::prompts::AUTO_ROUTER_PROMPT;
13use crate::storage::{Invocation, SqliteStorage, Storage};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct AutoParams {
18 pub content: String,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub hints: Option<Vec<String>>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub session_id: Option<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct AutoResult {
31 pub recommended_mode: ReasoningMode,
33 pub confidence: f64,
35 pub rationale: String,
37 pub complexity: f64,
39 pub alternative_modes: Vec<ModeRecommendation>,
41 #[serde(default)]
43 pub fallback_used: bool,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub original_invalid_mode: Option<String>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ModeRecommendation {
52 pub mode: ReasoningMode,
54 pub confidence: f64,
56 pub rationale: String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62struct AutoResponse {
63 recommended_mode: String,
64 confidence: f64,
65 rationale: String,
66 #[serde(default = "default_complexity")]
67 complexity: f64,
68 #[serde(default)]
69 metadata: Option<serde_json::Value>,
70}
71
72fn default_complexity() -> f64 {
73 0.5
74}
75
76impl AutoResponse {
77 fn from_completion(completion: &str) -> Result<Self, ToolError> {
79 serde_json::from_str::<AutoResponse>(completion).map_err(|e| {
80 let preview: String = completion.chars().take(200).collect();
81 ToolError::ParseFailed {
82 mode: "auto".to_string(),
83 message: format!("JSON parse error: {} | Response preview: {}", e, preview),
84 }
85 })
86 }
87}
88
89#[derive(Clone)]
91pub struct AutoMode {
92 core: ModeCore,
94 pipe_name: String,
96}
97
98impl AutoMode {
99 pub fn new(storage: SqliteStorage, langbase: LangbaseClient, config: &Config) -> Self {
101 Self {
102 core: ModeCore::new(storage, langbase),
103 pipe_name: config
104 .pipes
105 .auto
106 .clone()
107 .unwrap_or_else(|| "mode-router-v1".to_string()),
108 }
109 }
110
111 pub async fn route(&self, params: AutoParams) -> AppResult<AutoResult> {
113 let start = Instant::now();
114
115 debug!(content_len = params.content.len(), "Auto-routing content");
116
117 if let Some(result) = self.local_heuristics(¶ms) {
119 info!(
120 mode = %result.recommended_mode,
121 confidence = result.confidence,
122 source = "heuristics",
123 "Auto-routing completed via heuristics"
124 );
125 return Ok(result);
126 }
127
128 let messages = self.build_messages(¶ms);
130
131 let mut invocation = Invocation::new(
133 "reasoning.auto",
134 serialize_for_log(¶ms, "reasoning.auto input"),
135 )
136 .with_pipe(&self.pipe_name);
137
138 if let Some(session_id) = ¶ms.session_id {
139 invocation = invocation.with_session(session_id);
140 }
141
142 let request = PipeRequest::new(&self.pipe_name, messages);
144 let response = match self.core.langbase().call_pipe(request).await {
145 Ok(resp) => resp,
146 Err(e) => {
147 let latency = start.elapsed().as_millis() as i64;
148 invocation = invocation.failure(e.to_string(), latency);
149 if let Err(log_err) = self.core.storage().log_invocation(&invocation).await {
150 warn!(
151 error = %log_err,
152 tool = %invocation.tool_name,
153 "Failed to log invocation - audit trail incomplete"
154 );
155 }
156 return Err(e.into());
157 }
158 };
159
160 let auto_response = AutoResponse::from_completion(&response.completion)?;
162
163 let (recommended_mode, fallback_used, original_invalid_mode) =
165 match auto_response.recommended_mode.parse() {
166 Ok(mode) => (mode, false, None),
167 Err(_) => {
168 warn!(
169 invalid_mode = %auto_response.recommended_mode,
170 "Invalid mode returned by auto-router, falling back to Linear"
171 );
172 (
173 ReasoningMode::Linear,
174 true,
175 Some(auto_response.recommended_mode.clone()),
176 )
177 }
178 };
179
180 let alternatives = self.generate_alternatives(&auto_response);
182
183 let latency = start.elapsed().as_millis() as i64;
184
185 if fallback_used {
187 invocation = invocation.with_fallback("invalid_mode_parse");
188 }
189
190 invocation = invocation.success(
191 serialize_for_log(&auto_response, "reasoning.auto output"),
192 latency,
193 );
194 if let Err(log_err) = self.core.storage().log_invocation(&invocation).await {
195 warn!(
196 error = %log_err,
197 tool = %invocation.tool_name,
198 "Failed to log invocation - audit trail incomplete"
199 );
200 }
201
202 info!(
203 mode = %recommended_mode,
204 confidence = auto_response.confidence,
205 complexity = auto_response.complexity,
206 fallback_used = fallback_used,
207 latency_ms = latency,
208 "Auto-routing completed"
209 );
210
211 Ok(AutoResult {
212 recommended_mode,
213 confidence: auto_response.confidence,
214 rationale: auto_response.rationale,
215 complexity: auto_response.complexity,
216 alternative_modes: alternatives,
217 fallback_used,
218 original_invalid_mode,
219 })
220 }
221
222 fn local_heuristics(&self, params: &AutoParams) -> Option<AutoResult> {
224 let content_lower = params.content.to_lowercase();
225
226 if params.content.len() < 50 {
228 return Some(AutoResult {
229 recommended_mode: ReasoningMode::Linear,
230 confidence: 0.9,
231 rationale: "Short content is best handled with linear reasoning".to_string(),
232 complexity: 0.2,
233 alternative_modes: vec![],
234 fallback_used: false,
235 original_invalid_mode: None,
236 });
237 }
238
239 if content_lower.contains("evaluate")
241 || content_lower.contains("assess")
242 || content_lower.contains("review quality")
243 || content_lower.contains("critique")
244 {
245 return Some(AutoResult {
246 recommended_mode: ReasoningMode::Reflection,
247 confidence: 0.85,
248 rationale: "Content contains evaluation/assessment keywords".to_string(),
249 complexity: 0.5,
250 alternative_modes: vec![ModeRecommendation {
251 mode: ReasoningMode::Linear,
252 confidence: 0.6,
253 rationale: "Could also use linear for structured evaluation".to_string(),
254 }],
255 fallback_used: false,
256 original_invalid_mode: None,
257 });
258 }
259
260 if content_lower.contains("creative")
262 || content_lower.contains("brainstorm")
263 || content_lower.contains("novel")
264 || content_lower.contains("unconventional")
265 {
266 return Some(AutoResult {
267 recommended_mode: ReasoningMode::Divergent,
268 confidence: 0.85,
269 rationale: "Content requires creative/divergent thinking".to_string(),
270 complexity: 0.6,
271 alternative_modes: vec![ModeRecommendation {
272 mode: ReasoningMode::Tree,
273 confidence: 0.5,
274 rationale: "Could explore multiple creative paths with tree mode".to_string(),
275 }],
276 fallback_used: false,
277 original_invalid_mode: None,
278 });
279 }
280
281 if content_lower.contains("options")
283 || content_lower.contains("alternatives")
284 || content_lower.contains("compare")
285 || content_lower.contains("trade-offs")
286 {
287 return Some(AutoResult {
288 recommended_mode: ReasoningMode::Tree,
289 confidence: 0.8,
290 rationale: "Content requires exploring multiple options".to_string(),
291 complexity: 0.5,
292 alternative_modes: vec![ModeRecommendation {
293 mode: ReasoningMode::Divergent,
294 confidence: 0.4,
295 rationale: "Could also use divergent for creative alternatives".to_string(),
296 }],
297 fallback_used: false,
298 original_invalid_mode: None,
299 });
300 }
301
302 if content_lower.contains("complex system")
304 || content_lower.contains("interconnected")
305 || content_lower.contains("multi-step")
306 || content_lower.contains("graph")
307 {
308 return Some(AutoResult {
309 recommended_mode: ReasoningMode::Got,
310 confidence: 0.75,
311 rationale: "Content suggests complex graph-based reasoning".to_string(),
312 complexity: 0.8,
313 alternative_modes: vec![ModeRecommendation {
314 mode: ReasoningMode::Tree,
315 confidence: 0.6,
316 rationale: "Tree mode could also handle multi-path exploration".to_string(),
317 }],
318 fallback_used: false,
319 original_invalid_mode: None,
320 });
321 }
322
323 None
324 }
325
326 fn generate_alternatives(&self, response: &AutoResponse) -> Vec<ModeRecommendation> {
328 let mut alternatives = Vec::new();
329
330 if response.complexity < 0.3 {
332 if response.recommended_mode != "linear" {
333 alternatives.push(ModeRecommendation {
334 mode: ReasoningMode::Linear,
335 confidence: 0.7,
336 rationale: "Low complexity could be handled linearly".to_string(),
337 });
338 }
339 } else if response.complexity > 0.7 {
340 if response.recommended_mode != "got" {
341 alternatives.push(ModeRecommendation {
342 mode: ReasoningMode::Got,
343 confidence: 0.6,
344 rationale: "High complexity might benefit from graph exploration".to_string(),
345 });
346 }
347 if response.recommended_mode != "tree" {
348 alternatives.push(ModeRecommendation {
349 mode: ReasoningMode::Tree,
350 confidence: 0.5,
351 rationale: "Multi-path exploration could also work".to_string(),
352 });
353 }
354 } else {
355 if response.recommended_mode != "tree" {
357 alternatives.push(ModeRecommendation {
358 mode: ReasoningMode::Tree,
359 confidence: 0.5,
360 rationale: "Moderate complexity could use branching".to_string(),
361 });
362 }
363 }
364
365 alternatives
366 }
367
368 fn build_messages(&self, params: &AutoParams) -> Vec<Message> {
370 let mut messages = Vec::new();
371
372 messages.push(Message::system(AUTO_ROUTER_PROMPT));
373
374 let mut user_message = format!(
376 "Analyze this content and recommend the best reasoning mode:\n\n{}",
377 params.content
378 );
379
380 if let Some(hints) = ¶ms.hints {
382 user_message.push_str(&format!(
383 "\n\nHints about the problem:\n- {}",
384 hints.join("\n- ")
385 ));
386 }
387
388 messages.push(Message::user(user_message));
389
390 messages
391 }
392}
393
394impl AutoParams {
395 pub fn new(content: impl Into<String>) -> Self {
397 Self {
398 content: content.into(),
399 hints: None,
400 session_id: None,
401 }
402 }
403
404 pub fn with_hints(mut self, hints: Vec<String>) -> Self {
406 self.hints = Some(hints);
407 self
408 }
409
410 pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
412 self.session_id = Some(session_id.into());
413 self
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
426 fn test_auto_params_new() {
427 let params = AutoParams::new("Test content");
428 assert_eq!(params.content, "Test content");
429 assert!(params.hints.is_none());
430 assert!(params.session_id.is_none());
431 }
432
433 #[test]
434 fn test_auto_params_with_hints() {
435 let params = AutoParams::new("Content").with_hints(vec!["hint1".to_string()]);
436 assert_eq!(params.hints, Some(vec!["hint1".to_string()]));
437 }
438
439 #[test]
440 fn test_auto_params_with_session() {
441 let params = AutoParams::new("Content").with_session("sess-123");
442 assert_eq!(params.session_id, Some("sess-123".to_string()));
443 }
444
445 #[test]
446 fn test_auto_params_builder_chain() {
447 let params = AutoParams::new("Content")
448 .with_hints(vec!["hint1".to_string(), "hint2".to_string()])
449 .with_session("sess-abc");
450
451 assert_eq!(params.content, "Content");
452 assert_eq!(params.hints.as_ref().unwrap().len(), 2);
453 assert_eq!(params.session_id, Some("sess-abc".to_string()));
454 }
455
456 #[test]
457 fn test_auto_params_serialize() {
458 let params = AutoParams::new("Test").with_hints(vec!["h1".to_string()]);
459 let json = serde_json::to_string(¶ms).unwrap();
460 assert!(json.contains("Test"));
461 assert!(json.contains("hints"));
462 }
463
464 #[test]
465 fn test_auto_params_deserialize() {
466 let json = r#"{"content": "Test content", "hints": ["hint1", "hint2"]}"#;
467 let params: AutoParams = serde_json::from_str(json).unwrap();
468 assert_eq!(params.content, "Test content");
469 assert_eq!(params.hints.as_ref().unwrap().len(), 2);
470 }
471
472 #[test]
473 fn test_auto_params_deserialize_minimal() {
474 let json = r#"{"content": "Minimal"}"#;
475 let params: AutoParams = serde_json::from_str(json).unwrap();
476 assert_eq!(params.content, "Minimal");
477 assert!(params.hints.is_none());
478 assert!(params.session_id.is_none());
479 }
480
481 #[test]
486 fn test_auto_response_from_json() {
487 let json = r#"{"recommended_mode": "tree", "confidence": 0.9, "rationale": "Multiple paths", "complexity": 0.6}"#;
488 let resp = AutoResponse::from_completion(json).unwrap();
489 assert_eq!(resp.recommended_mode, "tree");
490 assert_eq!(resp.confidence, 0.9);
491 assert_eq!(resp.complexity, 0.6);
492 }
493
494 #[test]
495 fn test_auto_response_from_plain_text_returns_error() {
496 let text = "Invalid JSON";
497 let result = AutoResponse::from_completion(text);
499 assert!(result.is_err());
500 let err = result.unwrap_err();
501 assert!(matches!(err, crate::error::ToolError::ParseFailed { .. }));
502 }
503
504 #[test]
505 fn test_auto_response_with_metadata() {
506 let json = r#"{
507 "recommended_mode": "divergent",
508 "confidence": 0.85,
509 "rationale": "Creative task",
510 "complexity": 0.7,
511 "metadata": {"source": "test"}
512 }"#;
513 let resp = AutoResponse::from_completion(json).unwrap();
514 assert_eq!(resp.recommended_mode, "divergent");
515 assert!(resp.metadata.is_some());
516 }
517
518 #[test]
519 fn test_auto_response_default_complexity() {
520 let json = r#"{"recommended_mode": "linear", "confidence": 0.8, "rationale": "Test"}"#;
521 let resp = AutoResponse::from_completion(json).unwrap();
522 assert_eq!(resp.complexity, 0.5); }
524
525 #[test]
526 fn test_auto_response_all_modes() {
527 let modes = vec![
528 ("linear", ReasoningMode::Linear),
529 ("tree", ReasoningMode::Tree),
530 ("divergent", ReasoningMode::Divergent),
531 ("reflection", ReasoningMode::Reflection),
532 ("got", ReasoningMode::Got),
533 ];
534
535 for (mode_str, _expected_mode) in modes {
536 let json = format!(
537 r#"{{"recommended_mode": "{}", "confidence": 0.8, "rationale": "Test"}}"#,
538 mode_str
539 );
540 let resp = AutoResponse::from_completion(&json).unwrap();
541 assert_eq!(resp.recommended_mode, mode_str);
542 }
543 }
544
545 #[test]
546 fn test_auto_response_valid_json() {
547 let json = r#"{"recommended_mode": "tree", "confidence": 0.9, "rationale": "Test"}"#;
548 let result = AutoResponse::from_completion(json);
549 assert!(result.is_ok());
550 let resp = result.unwrap();
551 assert_eq!(resp.recommended_mode, "tree");
552 }
553
554 #[test]
555 fn test_auto_response_error_message() {
556 let invalid_json = "{ broken json";
557 let result = AutoResponse::from_completion(invalid_json);
558 assert!(result.is_err());
559 let err_str = result.unwrap_err().to_string();
560 assert!(err_str.contains("Parse error in auto mode"));
561 assert!(err_str.contains("broken json"));
562 }
563
564 #[test]
569 fn test_auto_result_serialize() {
570 let result = AutoResult {
571 recommended_mode: ReasoningMode::Tree,
572 confidence: 0.85,
573 rationale: "Multiple paths needed".to_string(),
574 complexity: 0.6,
575 alternative_modes: vec![ModeRecommendation {
576 mode: ReasoningMode::Divergent,
577 confidence: 0.6,
578 rationale: "Could also be creative".to_string(),
579 }],
580 fallback_used: false,
581 original_invalid_mode: None,
582 };
583 let json = serde_json::to_string(&result).unwrap();
584 assert!(json.contains("tree"));
585 assert!(json.contains("0.85"));
586 assert!(json.contains("alternative_modes"));
587 }
588
589 #[test]
590 fn test_auto_result_deserialize() {
591 let json = r#"{
592 "recommended_mode": "linear",
593 "confidence": 0.9,
594 "rationale": "Simple task",
595 "complexity": 0.2,
596 "alternative_modes": []
597 }"#;
598 let result: AutoResult = serde_json::from_str(json).unwrap();
599 assert_eq!(result.recommended_mode, ReasoningMode::Linear);
600 assert_eq!(result.confidence, 0.9);
601 assert!(result.alternative_modes.is_empty());
602 }
603
604 #[test]
605 fn test_auto_result_with_alternatives() {
606 let result = AutoResult {
607 recommended_mode: ReasoningMode::Got,
608 confidence: 0.75,
609 rationale: "Complex system".to_string(),
610 complexity: 0.8,
611 alternative_modes: vec![
612 ModeRecommendation {
613 mode: ReasoningMode::Tree,
614 confidence: 0.6,
615 rationale: "Alt 1".to_string(),
616 },
617 ModeRecommendation {
618 mode: ReasoningMode::Divergent,
619 confidence: 0.4,
620 rationale: "Alt 2".to_string(),
621 },
622 ],
623 fallback_used: false,
624 original_invalid_mode: None,
625 };
626 let json = serde_json::to_string(&result).unwrap();
627 let parsed: AutoResult = serde_json::from_str(&json).unwrap();
628 assert_eq!(parsed.alternative_modes.len(), 2);
629 }
630
631 #[test]
632 fn test_auto_result_fallback_fields_default() {
633 let json = r#"{
635 "recommended_mode": "linear",
636 "confidence": 0.9,
637 "rationale": "test",
638 "complexity": 0.5,
639 "alternative_modes": []
640 }"#;
641 let result: AutoResult = serde_json::from_str(json).unwrap();
642 assert!(!result.fallback_used);
643 assert!(result.original_invalid_mode.is_none());
644 }
645
646 #[test]
647 fn test_auto_result_with_fallback() {
648 let result = AutoResult {
650 recommended_mode: ReasoningMode::Linear,
651 confidence: 0.5,
652 rationale: "Fallback due to invalid mode".to_string(),
653 complexity: 0.5,
654 alternative_modes: vec![],
655 fallback_used: true,
656 original_invalid_mode: Some("invalid_mode_xyz".to_string()),
657 };
658 let json = serde_json::to_string(&result).unwrap();
659 assert!(json.contains("\"fallback_used\":true"));
660 assert!(json.contains("\"original_invalid_mode\":\"invalid_mode_xyz\""));
661 }
662
663 #[test]
664 fn test_auto_result_fallback_not_serialized_when_none() {
665 let result = AutoResult {
667 recommended_mode: ReasoningMode::Linear,
668 confidence: 0.9,
669 rationale: "test".to_string(),
670 complexity: 0.5,
671 alternative_modes: vec![],
672 fallback_used: false,
673 original_invalid_mode: None,
674 };
675 let json = serde_json::to_string(&result).unwrap();
676 assert!(!json.contains("original_invalid_mode"));
678 assert!(json.contains("\"fallback_used\":false"));
680 }
681
682 #[test]
683 fn test_auto_result_fallback_round_trip() {
684 let original = AutoResult {
686 recommended_mode: ReasoningMode::Linear,
687 confidence: 0.5,
688 rationale: "Fallback test".to_string(),
689 complexity: 0.5,
690 alternative_modes: vec![],
691 fallback_used: true,
692 original_invalid_mode: Some("bad_mode".to_string()),
693 };
694 let json = serde_json::to_string(&original).unwrap();
695 let parsed: AutoResult = serde_json::from_str(&json).unwrap();
696 assert!(parsed.fallback_used);
697 assert_eq!(parsed.original_invalid_mode, Some("bad_mode".to_string()));
698 }
699
700 #[test]
705 fn test_mode_recommendation_serialize() {
706 let rec = ModeRecommendation {
707 mode: ReasoningMode::Tree,
708 confidence: 0.8,
709 rationale: "Test".to_string(),
710 };
711 let json = serde_json::to_string(&rec).unwrap();
712 assert!(json.contains("tree"));
713 }
714
715 #[test]
716 fn test_mode_recommendation_deserialize() {
717 let json = r#"{"mode": "reflection", "confidence": 0.7, "rationale": "Needs evaluation"}"#;
718 let rec: ModeRecommendation = serde_json::from_str(json).unwrap();
719 assert_eq!(rec.mode, ReasoningMode::Reflection);
720 assert_eq!(rec.confidence, 0.7);
721 }
722
723 #[test]
724 fn test_mode_recommendation_all_modes() {
725 let modes = vec![
726 ReasoningMode::Linear,
727 ReasoningMode::Tree,
728 ReasoningMode::Divergent,
729 ReasoningMode::Reflection,
730 ReasoningMode::Got,
731 ];
732
733 for mode in modes {
734 let rec = ModeRecommendation {
735 mode,
736 confidence: 0.5,
737 rationale: "Test".to_string(),
738 };
739 let json = serde_json::to_string(&rec).unwrap();
740 let parsed: ModeRecommendation = serde_json::from_str(&json).unwrap();
741 assert_eq!(parsed.mode, mode);
742 }
743 }
744
745 #[test]
750 fn test_default_complexity() {
751 assert_eq!(default_complexity(), 0.5);
752 }
753
754 #[test]
759 fn test_reasoning_mode_from_string() {
760 assert_eq!(
761 "linear".parse::<ReasoningMode>().unwrap(),
762 ReasoningMode::Linear
763 );
764 assert_eq!(
765 "tree".parse::<ReasoningMode>().unwrap(),
766 ReasoningMode::Tree
767 );
768 assert_eq!(
769 "divergent".parse::<ReasoningMode>().unwrap(),
770 ReasoningMode::Divergent
771 );
772 assert_eq!(
773 "reflection".parse::<ReasoningMode>().unwrap(),
774 ReasoningMode::Reflection
775 );
776 assert_eq!("got".parse::<ReasoningMode>().unwrap(), ReasoningMode::Got);
777 }
778
779 #[test]
780 fn test_reasoning_mode_invalid_string() {
781 assert!("invalid".parse::<ReasoningMode>().is_err());
782 assert!("unknown".parse::<ReasoningMode>().is_err());
783 assert!("".parse::<ReasoningMode>().is_err());
784 }
785
786 #[test]
791 fn test_auto_params_empty_content() {
792 let params = AutoParams::new("");
793 assert_eq!(params.content, "");
794 }
795
796 #[test]
797 fn test_auto_params_empty_hints() {
798 let params = AutoParams::new("Content").with_hints(vec![]);
799 assert_eq!(params.hints, Some(vec![]));
800 }
801
802 #[test]
803 fn test_auto_response_zero_confidence() {
804 let json =
805 r#"{"recommended_mode": "linear", "confidence": 0.0, "rationale": "No confidence"}"#;
806 let resp = AutoResponse::from_completion(json).unwrap();
807 assert_eq!(resp.confidence, 0.0);
808 }
809
810 #[test]
811 fn test_auto_response_max_confidence() {
812 let json =
813 r#"{"recommended_mode": "linear", "confidence": 1.0, "rationale": "Full confidence"}"#;
814 let resp = AutoResponse::from_completion(json).unwrap();
815 assert_eq!(resp.confidence, 1.0);
816 }
817
818 #[test]
819 fn test_auto_result_empty_alternatives() {
820 let result = AutoResult {
821 recommended_mode: ReasoningMode::Linear,
822 confidence: 0.9,
823 rationale: "Simple".to_string(),
824 complexity: 0.1,
825 alternative_modes: vec![],
826 fallback_used: false,
827 original_invalid_mode: None,
828 };
829 assert!(result.alternative_modes.is_empty());
830 }
831
832 #[test]
833 fn test_mode_recommendation_zero_confidence() {
834 let rec = ModeRecommendation {
835 mode: ReasoningMode::Linear,
836 confidence: 0.0,
837 rationale: "Low confidence alt".to_string(),
838 };
839 assert_eq!(rec.confidence, 0.0);
840 }
841
842 #[test]
847 fn test_auto_params_round_trip() {
848 let original = AutoParams::new("Complex content")
849 .with_hints(vec!["hint1".to_string(), "hint2".to_string()])
850 .with_session("sess-xyz");
851
852 let json = serde_json::to_string(&original).unwrap();
853 let parsed: AutoParams = serde_json::from_str(&json).unwrap();
854
855 assert_eq!(parsed.content, original.content);
856 assert_eq!(parsed.hints, original.hints);
857 assert_eq!(parsed.session_id, original.session_id);
858 }
859
860 #[test]
861 fn test_auto_result_round_trip() {
862 let original = AutoResult {
863 recommended_mode: ReasoningMode::Divergent,
864 confidence: 0.87,
865 rationale: "Creative exploration needed".to_string(),
866 complexity: 0.65,
867 alternative_modes: vec![ModeRecommendation {
868 mode: ReasoningMode::Tree,
869 confidence: 0.55,
870 rationale: "Could branch".to_string(),
871 }],
872 fallback_used: false,
873 original_invalid_mode: None,
874 };
875
876 let json = serde_json::to_string(&original).unwrap();
877 let parsed: AutoResult = serde_json::from_str(&json).unwrap();
878
879 assert_eq!(parsed.recommended_mode, original.recommended_mode);
880 assert_eq!(parsed.confidence, original.confidence);
881 assert_eq!(parsed.rationale, original.rationale);
882 assert_eq!(parsed.complexity, original.complexity);
883 assert_eq!(parsed.fallback_used, original.fallback_used);
884 assert_eq!(parsed.original_invalid_mode, original.original_invalid_mode);
885 assert_eq!(parsed.alternative_modes.len(), 1);
886 }
887
888 #[test]
893 fn test_local_heuristics_short_content() {
894 let mode = create_test_mode();
895 let params = AutoParams::new("Short text");
896 let result = mode.local_heuristics(¶ms).unwrap();
897 assert_eq!(result.recommended_mode, ReasoningMode::Linear);
898 assert_eq!(result.confidence, 0.9);
899 assert_eq!(result.complexity, 0.2);
900 }
901
902 #[test]
903 fn test_local_heuristics_evaluate_keyword() {
904 let mode = create_test_mode();
905 let params =
906 AutoParams::new("Please evaluate this solution for correctness and efficiency");
907 let result = mode.local_heuristics(¶ms).unwrap();
908 assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
909 assert_eq!(result.confidence, 0.85);
910 assert_eq!(result.alternative_modes.len(), 1);
911 }
912
913 #[test]
914 fn test_local_heuristics_assess_keyword() {
915 let mode = create_test_mode();
916 let params = AutoParams::new("I need you to assess the quality of this implementation");
917 let result = mode.local_heuristics(¶ms).unwrap();
918 assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
919 }
920
921 #[test]
922 fn test_local_heuristics_review_quality_keyword() {
923 let mode = create_test_mode();
924 let params = AutoParams::new(
925 "Can you review quality of the architecture design and implementation patterns?",
926 );
927 let result = mode.local_heuristics(¶ms).unwrap();
928 assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
929 }
930
931 #[test]
932 fn test_local_heuristics_critique_keyword() {
933 let mode = create_test_mode();
934 let params = AutoParams::new("Please critique this approach to see if it works effectively and meets our requirements");
935 let result = mode.local_heuristics(¶ms).unwrap();
936 assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
937 }
938
939 #[test]
940 fn test_local_heuristics_creative_keyword() {
941 let mode = create_test_mode();
942 let params = AutoParams::new("We need a creative solution to this unique problem that differs from existing approaches");
943 let result = mode.local_heuristics(¶ms).unwrap();
944 assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
945 assert_eq!(result.confidence, 0.85);
946 }
947
948 #[test]
949 fn test_local_heuristics_brainstorm_keyword() {
950 let mode = create_test_mode();
951 let params = AutoParams::new(
952 "Let's brainstorm ideas for improving user engagement and retention metrics",
953 );
954 let result = mode.local_heuristics(¶ms).unwrap();
955 assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
956 }
957
958 #[test]
959 fn test_local_heuristics_novel_keyword() {
960 let mode = create_test_mode();
961 let params = AutoParams::new(
962 "We need novel approaches to solve this challenge in the competitive market",
963 );
964 let result = mode.local_heuristics(¶ms).unwrap();
965 assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
966 }
967
968 #[test]
969 fn test_local_heuristics_unconventional_keyword() {
970 let mode = create_test_mode();
971 let params = AutoParams::new(
972 "Looking for unconventional strategies to differentiate our product offering",
973 );
974 let result = mode.local_heuristics(¶ms).unwrap();
975 assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
976 }
977
978 #[test]
979 fn test_local_heuristics_options_keyword() {
980 let mode = create_test_mode();
981 let params = AutoParams::new("What options do we have for implementing authentication?");
982 let result = mode.local_heuristics(¶ms).unwrap();
983 assert_eq!(result.recommended_mode, ReasoningMode::Tree);
984 assert_eq!(result.confidence, 0.8);
985 }
986
987 #[test]
988 fn test_local_heuristics_alternatives_keyword() {
989 let mode = create_test_mode();
990 let params = AutoParams::new("Consider alternatives to the current database design");
991 let result = mode.local_heuristics(¶ms).unwrap();
992 assert_eq!(result.recommended_mode, ReasoningMode::Tree);
993 }
994
995 #[test]
996 fn test_local_heuristics_compare_keyword() {
997 let mode = create_test_mode();
998 let params = AutoParams::new(
999 "Compare different approaches to API versioning and list their pros and cons",
1000 );
1001 let result = mode.local_heuristics(¶ms).unwrap();
1002 assert_eq!(result.recommended_mode, ReasoningMode::Tree);
1003 }
1004
1005 #[test]
1006 fn test_local_heuristics_tradeoffs_keyword() {
1007 let mode = create_test_mode();
1008 let params = AutoParams::new(
1009 "Analyze the trade-offs between consistency and availability in distributed systems",
1010 );
1011 let result = mode.local_heuristics(¶ms).unwrap();
1012 assert_eq!(result.recommended_mode, ReasoningMode::Tree);
1013 }
1014
1015 #[test]
1016 fn test_local_heuristics_complex_system_keyword() {
1017 let mode = create_test_mode();
1018 let params = AutoParams::new(
1019 "We have a complex system with many interdependencies that need careful analysis",
1020 );
1021 let result = mode.local_heuristics(¶ms).unwrap();
1022 assert_eq!(result.recommended_mode, ReasoningMode::Got);
1023 assert_eq!(result.confidence, 0.75);
1024 assert_eq!(result.complexity, 0.8);
1025 }
1026
1027 #[test]
1028 fn test_local_heuristics_interconnected_keyword() {
1029 let mode = create_test_mode();
1030 let params = AutoParams::new(
1031 "These services are highly interconnected across multiple domains and boundaries",
1032 );
1033 let result = mode.local_heuristics(¶ms).unwrap();
1034 assert_eq!(result.recommended_mode, ReasoningMode::Got);
1035 }
1036
1037 #[test]
1038 fn test_local_heuristics_multistep_keyword() {
1039 let mode = create_test_mode();
1040 let params = AutoParams::new(
1041 "This requires a multi-step deployment process with various dependencies and stages",
1042 );
1043 let result = mode.local_heuristics(¶ms).unwrap();
1044 assert_eq!(result.recommended_mode, ReasoningMode::Got);
1045 }
1046
1047 #[test]
1048 fn test_local_heuristics_graph_keyword() {
1049 let mode = create_test_mode();
1050 let params = AutoParams::new("Analyze this graph of dependencies between services and components in our architecture");
1051 let result = mode.local_heuristics(¶ms).unwrap();
1052 assert_eq!(result.recommended_mode, ReasoningMode::Got);
1053 }
1054
1055 #[test]
1056 fn test_local_heuristics_no_match() {
1057 let mode = create_test_mode();
1058 let params = AutoParams::new("Just a regular query without special keywords that would trigger heuristics and patterns");
1059 let result = mode.local_heuristics(¶ms);
1060 assert!(result.is_none());
1061 }
1062
1063 #[test]
1064 fn test_local_heuristics_case_insensitive() {
1065 let mode = create_test_mode();
1066 let params = AutoParams::new(
1067 "BRAINSTORM new IDEAS for this CREATIVE project with innovative solutions",
1068 );
1069 let result = mode.local_heuristics(¶ms).unwrap();
1070 assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
1071 }
1072
1073 #[test]
1078 fn test_generate_alternatives_low_complexity() {
1079 let mode = create_test_mode();
1080 let response = AutoResponse {
1081 recommended_mode: "tree".to_string(),
1082 confidence: 0.8,
1083 rationale: "Test".to_string(),
1084 complexity: 0.2, metadata: None,
1086 };
1087 let alternatives = mode.generate_alternatives(&response);
1088 assert!(!alternatives.is_empty());
1089 assert_eq!(alternatives[0].mode, ReasoningMode::Linear);
1090 assert_eq!(alternatives[0].confidence, 0.7);
1091 }
1092
1093 #[test]
1094 fn test_generate_alternatives_low_complexity_already_linear() {
1095 let mode = create_test_mode();
1096 let response = AutoResponse {
1097 recommended_mode: "linear".to_string(),
1098 confidence: 0.9,
1099 rationale: "Simple".to_string(),
1100 complexity: 0.1,
1101 metadata: None,
1102 };
1103 let alternatives = mode.generate_alternatives(&response);
1104 assert!(alternatives.is_empty() || alternatives[0].mode != ReasoningMode::Linear);
1106 }
1107
1108 #[test]
1109 fn test_generate_alternatives_high_complexity() {
1110 let mode = create_test_mode();
1111 let response = AutoResponse {
1112 recommended_mode: "linear".to_string(),
1113 confidence: 0.7,
1114 rationale: "Test".to_string(),
1115 complexity: 0.8, metadata: None,
1117 };
1118 let alternatives = mode.generate_alternatives(&response);
1119 assert!(!alternatives.is_empty());
1120 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1122 }
1123
1124 #[test]
1125 fn test_generate_alternatives_high_complexity_already_got() {
1126 let mode = create_test_mode();
1127 let response = AutoResponse {
1128 recommended_mode: "got".to_string(),
1129 confidence: 0.9,
1130 rationale: "Complex".to_string(),
1131 complexity: 0.9,
1132 metadata: None,
1133 };
1134 let alternatives = mode.generate_alternatives(&response);
1135 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1137 }
1138
1139 #[test]
1140 fn test_generate_alternatives_medium_complexity() {
1141 let mode = create_test_mode();
1142 let response = AutoResponse {
1143 recommended_mode: "linear".to_string(),
1144 confidence: 0.8,
1145 rationale: "Test".to_string(),
1146 complexity: 0.5, metadata: None,
1148 };
1149 let alternatives = mode.generate_alternatives(&response);
1150 assert!(!alternatives.is_empty());
1151 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1153 }
1154
1155 #[test]
1156 fn test_generate_alternatives_medium_complexity_already_tree() {
1157 let mode = create_test_mode();
1158 let response = AutoResponse {
1159 recommended_mode: "tree".to_string(),
1160 confidence: 0.85,
1161 rationale: "Branching".to_string(),
1162 complexity: 0.5,
1163 metadata: None,
1164 };
1165 let alternatives = mode.generate_alternatives(&response);
1166 assert!(
1168 alternatives.is_empty() || !alternatives.iter().any(|a| a.mode == ReasoningMode::Tree)
1169 );
1170 }
1171
1172 #[test]
1177 fn test_build_messages_basic() {
1178 let mode = create_test_mode();
1179 let params = AutoParams::new("Test content for analysis");
1180 let messages = mode.build_messages(¶ms);
1181
1182 assert_eq!(messages.len(), 2);
1183 assert!(messages[0].content.contains("reasoning mode"));
1185 assert!(messages[1].content.contains("Test content for analysis"));
1187 }
1188
1189 #[test]
1190 fn test_build_messages_with_hints() {
1191 let mode = create_test_mode();
1192 let params = AutoParams::new("Complex problem").with_hints(vec![
1193 "performance critical".to_string(),
1194 "needs scalability".to_string(),
1195 ]);
1196 let messages = mode.build_messages(¶ms);
1197
1198 assert_eq!(messages.len(), 2);
1199 assert!(messages[1].content.contains("Hints about the problem"));
1200 assert!(messages[1].content.contains("performance critical"));
1201 assert!(messages[1].content.contains("needs scalability"));
1202 }
1203
1204 #[test]
1205 fn test_build_messages_with_single_hint() {
1206 let mode = create_test_mode();
1207 let params = AutoParams::new("Question").with_hints(vec!["security".to_string()]);
1208 let messages = mode.build_messages(¶ms);
1209
1210 assert!(messages[1].content.contains("Hints"));
1211 assert!(messages[1].content.contains("security"));
1212 }
1213
1214 #[test]
1215 fn test_build_messages_with_empty_hints() {
1216 let mode = create_test_mode();
1217 let params = AutoParams::new("Question").with_hints(vec![]);
1218 let messages = mode.build_messages(¶ms);
1219
1220 assert!(messages[1].content.contains("Hints"));
1222 }
1223
1224 #[test]
1225 fn test_build_messages_no_hints() {
1226 let mode = create_test_mode();
1227 let params = AutoParams::new("Simple question");
1228 let messages = mode.build_messages(¶ms);
1229
1230 assert_eq!(messages.len(), 2);
1231 assert!(!messages[1].content.contains("Hints"));
1232 }
1233
1234 #[test]
1235 fn test_build_messages_long_content() {
1236 let mode = create_test_mode();
1237 let long_content = "A".repeat(5000);
1238 let params = AutoParams::new(long_content.clone());
1239 let messages = mode.build_messages(¶ms);
1240
1241 assert_eq!(messages.len(), 2);
1242 assert!(messages[1].content.contains(&long_content));
1243 }
1244
1245 #[test]
1246 fn test_build_messages_special_characters_in_content() {
1247 let mode = create_test_mode();
1248 let params = AutoParams::new("Special: \n\t\"quotes\" and <brackets>");
1249 let messages = mode.build_messages(¶ms);
1250
1251 assert_eq!(messages.len(), 2);
1252 assert!(messages[1].content.contains("Special:"));
1253 }
1254
1255 #[test]
1256 fn test_build_messages_special_characters_in_hints() {
1257 let mode = create_test_mode();
1258 let params = AutoParams::new("Question").with_hints(vec![
1259 "hint with \"quotes\"".to_string(),
1260 "hint with \nnewlines".to_string(),
1261 ]);
1262 let messages = mode.build_messages(¶ms);
1263
1264 assert!(messages[1].content.contains("Hints"));
1265 assert!(messages[1].content.contains("quotes"));
1266 }
1267
1268 #[test]
1273 fn test_auto_mode_new_creates_instance() {
1274 let mode = create_test_mode();
1275 assert_eq!(mode.pipe_name, "mode-router-v1");
1277 }
1278
1279 #[test]
1280 fn test_auto_mode_new_with_custom_pipe() {
1281 use crate::config::{
1282 Config, DatabaseConfig, ErrorHandlingConfig, LangbaseConfig, LogFormat, LoggingConfig,
1283 PipeConfig, RequestConfig,
1284 };
1285 use std::path::PathBuf;
1286
1287 let langbase_config = LangbaseConfig {
1288 api_key: "test-key".to_string(),
1289 base_url: "https://api.langbase.com".to_string(),
1290 };
1291
1292 let pipes = PipeConfig {
1293 auto: Some("custom-auto-pipe".to_string()),
1294 ..Default::default()
1295 };
1296
1297 let config = Config {
1298 langbase: langbase_config.clone(),
1299 database: DatabaseConfig {
1300 path: PathBuf::from(":memory:"),
1301 max_connections: 5,
1302 },
1303 logging: LoggingConfig {
1304 level: "info".to_string(),
1305 format: LogFormat::Pretty,
1306 },
1307 request: RequestConfig::default(),
1308 pipes,
1309 error_handling: ErrorHandlingConfig::default(),
1310 };
1311
1312 let rt = tokio::runtime::Runtime::new().unwrap();
1313 let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1314 let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1315
1316 let mode = AutoMode::new(storage, langbase, &config);
1317 assert_eq!(mode.pipe_name, "custom-auto-pipe");
1318 }
1319
1320 #[test]
1321 fn test_auto_mode_new_without_custom_pipe_uses_default() {
1322 use crate::config::{
1323 Config, DatabaseConfig, LangbaseConfig, LogFormat, LoggingConfig, PipeConfig,
1324 RequestConfig,
1325 };
1326 use std::path::PathBuf;
1327
1328 let langbase_config = LangbaseConfig {
1329 api_key: "test-key".to_string(),
1330 base_url: "https://api.langbase.com".to_string(),
1331 };
1332
1333 let pipes = PipeConfig {
1334 auto: None, ..Default::default()
1336 };
1337
1338 let config = Config {
1339 langbase: langbase_config.clone(),
1340 database: DatabaseConfig {
1341 path: PathBuf::from(":memory:"),
1342 max_connections: 5,
1343 },
1344 logging: LoggingConfig {
1345 level: "info".to_string(),
1346 format: LogFormat::Pretty,
1347 },
1348 request: RequestConfig::default(),
1349 pipes,
1350 error_handling: crate::config::ErrorHandlingConfig::default(),
1351 };
1352
1353 let rt = tokio::runtime::Runtime::new().unwrap();
1354 let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1355 let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1356
1357 let mode = AutoMode::new(storage, langbase, &config);
1358 assert_eq!(mode.pipe_name, "mode-router-v1");
1359 }
1360
1361 #[test]
1366 fn test_auto_response_from_empty_json_returns_error() {
1367 let json = "{}";
1368 let result = AutoResponse::from_completion(json);
1370 assert!(result.is_err());
1371 }
1372
1373 #[test]
1374 fn test_auto_response_from_json_missing_rationale_returns_error() {
1375 let json = r#"{"recommended_mode": "tree", "confidence": 0.8}"#;
1376 let result = AutoResponse::from_completion(json);
1378 assert!(result.is_err());
1379 }
1380
1381 #[test]
1382 fn test_auto_response_from_json_null_metadata() {
1383 let json = r#"{
1384 "recommended_mode": "reflection",
1385 "confidence": 0.82,
1386 "rationale": "Test",
1387 "complexity": 0.5,
1388 "metadata": null
1389 }"#;
1390 let resp = AutoResponse::from_completion(json).unwrap();
1391 assert_eq!(resp.recommended_mode, "reflection");
1392 assert!(resp.metadata.is_none());
1393 }
1394
1395 #[test]
1396 fn test_auto_response_from_json_empty_string_mode() {
1397 let json = r#"{"recommended_mode": "", "confidence": 0.8, "rationale": "Test"}"#;
1398 let resp = AutoResponse::from_completion(json).unwrap();
1399 assert_eq!(resp.recommended_mode, "");
1400 }
1401
1402 #[test]
1403 fn test_auto_response_from_json_invalid_mode_string() {
1404 let json =
1405 r#"{"recommended_mode": "invalid_mode", "confidence": 0.8, "rationale": "Test"}"#;
1406 let resp = AutoResponse::from_completion(json).unwrap();
1407 assert_eq!(resp.recommended_mode, "invalid_mode");
1408 }
1409
1410 #[test]
1411 fn test_auto_response_from_json_negative_confidence() {
1412 let json = r#"{"recommended_mode": "linear", "confidence": -0.5, "rationale": "Test"}"#;
1413 let resp = AutoResponse::from_completion(json).unwrap();
1414 assert_eq!(resp.confidence, -0.5); }
1416
1417 #[test]
1418 fn test_auto_response_from_json_over_one_confidence() {
1419 let json = r#"{"recommended_mode": "linear", "confidence": 1.5, "rationale": "Test"}"#;
1420 let resp = AutoResponse::from_completion(json).unwrap();
1421 assert_eq!(resp.confidence, 1.5); }
1423
1424 #[test]
1425 fn test_auto_response_from_json_negative_complexity() {
1426 let json = r#"{"recommended_mode": "linear", "confidence": 0.8, "rationale": "Test", "complexity": -0.3}"#;
1427 let resp = AutoResponse::from_completion(json).unwrap();
1428 assert_eq!(resp.complexity, -0.3);
1429 }
1430
1431 #[test]
1432 fn test_auto_response_from_json_over_one_complexity() {
1433 let json = r#"{"recommended_mode": "linear", "confidence": 0.8, "rationale": "Test", "complexity": 1.5}"#;
1434 let resp = AutoResponse::from_completion(json).unwrap();
1435 assert_eq!(resp.complexity, 1.5);
1436 }
1437
1438 #[test]
1439 fn test_auto_response_from_long_preview_text_returns_error() {
1440 let long_text = "Not JSON: ".to_owned() + &"a".repeat(300);
1441 let result = AutoResponse::from_completion(&long_text);
1443 assert!(result.is_err());
1444 }
1445
1446 #[test]
1447 fn test_auto_response_from_backtracking_mode() {
1448 let json = r#"{"recommended_mode": "backtracking", "confidence": 0.85, "rationale": "Needs backtracking"}"#;
1449 let resp = AutoResponse::from_completion(json).unwrap();
1450 assert_eq!(resp.recommended_mode, "backtracking");
1451 }
1452
1453 #[test]
1458 fn test_local_heuristics_exactly_50_chars() {
1459 let mode = create_test_mode();
1460 let params = AutoParams::new("A".repeat(50));
1461 let result = mode.local_heuristics(¶ms);
1462 assert!(result.is_none());
1464 }
1465
1466 #[test]
1467 fn test_local_heuristics_49_chars() {
1468 let mode = create_test_mode();
1469 let params = AutoParams::new("A".repeat(49));
1470 let result = mode.local_heuristics(¶ms).unwrap();
1471 assert_eq!(result.recommended_mode, ReasoningMode::Linear);
1472 }
1473
1474 #[test]
1475 fn test_local_heuristics_multiple_keywords_first_wins() {
1476 let mode = create_test_mode();
1477 let params = AutoParams::new("Please evaluate this creative solution with novel ideas");
1480 let result = mode.local_heuristics(¶ms).unwrap();
1481 assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
1482 }
1483
1484 #[test]
1485 fn test_local_heuristics_mixed_case_keywords() {
1486 let mode = create_test_mode();
1487 let params = AutoParams::new(
1489 "EVALUATE this CREATIVE approach with NOVEL IDEAS and more context here",
1490 );
1491 let result = mode.local_heuristics(¶ms).unwrap();
1492 assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
1494 }
1495
1496 #[test]
1497 fn test_local_heuristics_keyword_in_middle() {
1498 let mode = create_test_mode();
1499 let params = AutoParams::new("The system has many interconnected parts that need analysis");
1500 let result = mode.local_heuristics(¶ms).unwrap();
1501 assert_eq!(result.recommended_mode, ReasoningMode::Got);
1502 }
1503
1504 #[test]
1505 fn test_local_heuristics_partial_keyword_no_match() {
1506 let mode = create_test_mode();
1507 let params = AutoParams::new("We are creating a new system for data processing");
1509 let result = mode.local_heuristics(¶ms);
1510 assert!(result.is_some());
1512 }
1513
1514 #[test]
1515 fn test_local_heuristics_whitespace_around_keywords() {
1516 let mode = create_test_mode();
1517 let params =
1519 AutoParams::new(" evaluate this approach with significant context padding ");
1520 let result = mode.local_heuristics(¶ms).unwrap();
1521 assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
1522 }
1523
1524 #[test]
1525 fn test_local_heuristics_backtracking_not_detected() {
1526 let mode = create_test_mode();
1527 let params = AutoParams::new(
1529 "This requires backtracking to find the solution with additional context",
1530 );
1531 let result = mode.local_heuristics(¶ms);
1532 assert!(result.is_none());
1534 }
1535
1536 #[test]
1537 fn test_local_heuristics_empty_string() {
1538 let mode = create_test_mode();
1539 let params = AutoParams::new("");
1540 let result = mode.local_heuristics(¶ms).unwrap();
1541 assert_eq!(result.recommended_mode, ReasoningMode::Linear);
1542 assert_eq!(result.complexity, 0.2);
1543 }
1544
1545 #[test]
1546 fn test_local_heuristics_single_char() {
1547 let mode = create_test_mode();
1548 let params = AutoParams::new("x");
1549 let result = mode.local_heuristics(¶ms).unwrap();
1550 assert_eq!(result.recommended_mode, ReasoningMode::Linear);
1551 }
1552
1553 #[test]
1558 fn test_generate_alternatives_boundary_low_complexity() {
1559 let mode = create_test_mode();
1560 let response = AutoResponse {
1561 recommended_mode: "tree".to_string(),
1562 confidence: 0.8,
1563 rationale: "Test".to_string(),
1564 complexity: 0.29, metadata: None,
1566 };
1567 let alternatives = mode.generate_alternatives(&response);
1568 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Linear));
1569 }
1570
1571 #[test]
1572 fn test_generate_alternatives_boundary_high_complexity() {
1573 let mode = create_test_mode();
1574 let response = AutoResponse {
1575 recommended_mode: "linear".to_string(),
1576 confidence: 0.7,
1577 rationale: "Test".to_string(),
1578 complexity: 0.71, metadata: None,
1580 };
1581 let alternatives = mode.generate_alternatives(&response);
1582 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1583 }
1584
1585 #[test]
1586 fn test_generate_alternatives_exactly_0_3_complexity() {
1587 let mode = create_test_mode();
1588 let response = AutoResponse {
1589 recommended_mode: "tree".to_string(),
1590 confidence: 0.8,
1591 rationale: "Test".to_string(),
1592 complexity: 0.3, metadata: None,
1594 };
1595 let alternatives = mode.generate_alternatives(&response);
1596 assert!(
1598 alternatives.is_empty()
1599 || !alternatives.iter().any(|a| a.mode == ReasoningMode::Linear)
1600 );
1601 }
1602
1603 #[test]
1604 fn test_generate_alternatives_exactly_0_7_complexity() {
1605 let mode = create_test_mode();
1606 let response = AutoResponse {
1607 recommended_mode: "linear".to_string(),
1608 confidence: 0.8,
1609 rationale: "Test".to_string(),
1610 complexity: 0.7, metadata: None,
1612 };
1613 let alternatives = mode.generate_alternatives(&response);
1614 assert!(
1616 alternatives.is_empty() || !alternatives.iter().any(|a| a.mode == ReasoningMode::Got)
1617 );
1618 }
1619
1620 #[test]
1621 fn test_generate_alternatives_high_complexity_both_alternatives() {
1622 let mode = create_test_mode();
1623 let response = AutoResponse {
1624 recommended_mode: "linear".to_string(),
1625 confidence: 0.7,
1626 rationale: "Test".to_string(),
1627 complexity: 0.9,
1628 metadata: None,
1629 };
1630 let alternatives = mode.generate_alternatives(&response);
1631 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1633 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1634 }
1635
1636 #[test]
1637 fn test_generate_alternatives_recommended_divergent() {
1638 let mode = create_test_mode();
1639 let response = AutoResponse {
1640 recommended_mode: "divergent".to_string(),
1641 confidence: 0.8,
1642 rationale: "Test".to_string(),
1643 complexity: 0.5,
1644 metadata: None,
1645 };
1646 let alternatives = mode.generate_alternatives(&response);
1647 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1649 }
1650
1651 #[test]
1652 fn test_generate_alternatives_recommended_reflection() {
1653 let mode = create_test_mode();
1654 let response = AutoResponse {
1655 recommended_mode: "reflection".to_string(),
1656 confidence: 0.8,
1657 rationale: "Test".to_string(),
1658 complexity: 0.5,
1659 metadata: None,
1660 };
1661 let alternatives = mode.generate_alternatives(&response);
1662 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1664 }
1665
1666 #[test]
1667 fn test_generate_alternatives_zero_complexity() {
1668 let mode = create_test_mode();
1669 let response = AutoResponse {
1670 recommended_mode: "got".to_string(),
1671 confidence: 0.8,
1672 rationale: "Test".to_string(),
1673 complexity: 0.0,
1674 metadata: None,
1675 };
1676 let alternatives = mode.generate_alternatives(&response);
1677 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Linear));
1679 }
1680
1681 #[test]
1682 fn test_generate_alternatives_max_complexity() {
1683 let mode = create_test_mode();
1684 let response = AutoResponse {
1685 recommended_mode: "linear".to_string(),
1686 confidence: 0.8,
1687 rationale: "Test".to_string(),
1688 complexity: 1.0,
1689 metadata: None,
1690 };
1691 let alternatives = mode.generate_alternatives(&response);
1692 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1694 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1695 }
1696
1697 #[test]
1698 fn test_generate_alternatives_backtracking_mode() {
1699 let mode = create_test_mode();
1700 let response = AutoResponse {
1701 recommended_mode: "backtracking".to_string(),
1702 confidence: 0.8,
1703 rationale: "Test".to_string(),
1704 complexity: 0.5,
1705 metadata: None,
1706 };
1707 let alternatives = mode.generate_alternatives(&response);
1708 assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1710 }
1711
1712 #[test]
1717 fn test_auto_result_complexity_boundaries() {
1718 let result = AutoResult {
1719 recommended_mode: ReasoningMode::Linear,
1720 confidence: 0.9,
1721 rationale: "Test".to_string(),
1722 complexity: 0.0,
1723 alternative_modes: vec![],
1724 fallback_used: false,
1725 original_invalid_mode: None,
1726 };
1727 assert_eq!(result.complexity, 0.0);
1728
1729 let result2 = AutoResult {
1730 recommended_mode: ReasoningMode::Linear,
1731 confidence: 0.9,
1732 rationale: "Test".to_string(),
1733 complexity: 1.0,
1734 alternative_modes: vec![],
1735 fallback_used: false,
1736 original_invalid_mode: None,
1737 };
1738 assert_eq!(result2.complexity, 1.0);
1739 }
1740
1741 #[test]
1742 fn test_auto_result_many_alternatives() {
1743 let result = AutoResult {
1744 recommended_mode: ReasoningMode::Linear,
1745 confidence: 0.9,
1746 rationale: "Test".to_string(),
1747 complexity: 0.5,
1748 alternative_modes: vec![
1749 ModeRecommendation {
1750 mode: ReasoningMode::Tree,
1751 confidence: 0.8,
1752 rationale: "Alt 1".to_string(),
1753 },
1754 ModeRecommendation {
1755 mode: ReasoningMode::Divergent,
1756 confidence: 0.7,
1757 rationale: "Alt 2".to_string(),
1758 },
1759 ModeRecommendation {
1760 mode: ReasoningMode::Reflection,
1761 confidence: 0.6,
1762 rationale: "Alt 3".to_string(),
1763 },
1764 ],
1765 fallback_used: false,
1766 original_invalid_mode: None,
1767 };
1768 assert_eq!(result.alternative_modes.len(), 3);
1769 }
1770
1771 #[test]
1772 fn test_mode_recommendation_all_reasoning_modes() {
1773 let modes = vec![
1774 (ReasoningMode::Linear, "linear"),
1775 (ReasoningMode::Tree, "tree"),
1776 (ReasoningMode::Divergent, "divergent"),
1777 (ReasoningMode::Reflection, "reflection"),
1778 (ReasoningMode::Got, "got"),
1779 (ReasoningMode::Backtracking, "backtracking"),
1780 ];
1781
1782 for (mode, expected_str) in modes {
1783 let rec = ModeRecommendation {
1784 mode,
1785 confidence: 0.5,
1786 rationale: "Test".to_string(),
1787 };
1788 let json = serde_json::to_string(&rec).unwrap();
1789 assert!(json.contains(expected_str));
1790
1791 let parsed: ModeRecommendation = serde_json::from_str(&json).unwrap();
1792 assert_eq!(parsed.mode, mode);
1793 }
1794 }
1795
1796 #[test]
1797 fn test_auto_params_very_long_hints() {
1798 let long_hints: Vec<String> = (0..100).map(|i| format!("Hint number {}", i)).collect();
1799 let params = AutoParams::new("Content").with_hints(long_hints.clone());
1800 assert_eq!(params.hints.as_ref().unwrap().len(), 100);
1801 }
1802
1803 #[test]
1804 fn test_auto_params_empty_string_hints() {
1805 let params =
1806 AutoParams::new("Content").with_hints(vec!["".to_string(), "valid".to_string()]);
1807 let hints = params.hints.unwrap();
1808 assert_eq!(hints.len(), 2);
1809 assert_eq!(hints[0], "");
1810 assert_eq!(hints[1], "valid");
1811 }
1812
1813 #[test]
1814 fn test_auto_params_unicode_hints() {
1815 let params = AutoParams::new("Content")
1816 .with_hints(vec!["Unicode: δΈη".to_string(), "Emoji: π".to_string()]);
1817 let hints = params.hints.unwrap();
1818 assert!(hints[0].contains("δΈη"));
1819 assert!(hints[1].contains("π"));
1820 }
1821
1822 #[test]
1823 fn test_auto_params_newlines_in_hints() {
1824 let params = AutoParams::new("Content").with_hints(vec!["Multi\nline\nhint".to_string()]);
1825 assert!(params.hints.unwrap()[0].contains('\n'));
1826 }
1827
1828 fn create_test_mode() -> AutoMode {
1833 use crate::config::{
1834 Config, DatabaseConfig, ErrorHandlingConfig, LangbaseConfig, LogFormat, LoggingConfig,
1835 PipeConfig, RequestConfig,
1836 };
1837 use std::path::PathBuf;
1838
1839 let langbase_config = LangbaseConfig {
1840 api_key: "test-key".to_string(),
1841 base_url: "https://api.langbase.com".to_string(),
1842 };
1843
1844 let config = Config {
1845 langbase: langbase_config.clone(),
1846 database: DatabaseConfig {
1847 path: PathBuf::from(":memory:"),
1848 max_connections: 5,
1849 },
1850 logging: LoggingConfig {
1851 level: "info".to_string(),
1852 format: LogFormat::Pretty,
1853 },
1854 request: RequestConfig::default(),
1855 pipes: PipeConfig::default(),
1856 error_handling: ErrorHandlingConfig::default(),
1857 };
1858
1859 let rt = tokio::runtime::Runtime::new().unwrap();
1861 let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1862 let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1863
1864 AutoMode::new(storage, langbase, &config)
1865 }
1866}