mcp_langbase_reasoning/modes/
detection.rs

1//! Detection mode - bias and fallacy detection in reasoning.
2//!
3//! This module provides cognitive bias and logical fallacy detection:
4//! - Bias detection (confirmation bias, anchoring, etc.)
5//! - Fallacy detection (formal and informal)
6//! - Storage persistence for detected issues
7//! - Integration with thought analysis
8
9use serde::{Deserialize, Serialize};
10use std::time::Instant;
11use tracing::info;
12
13use super::ModeCore;
14use crate::config::Config;
15use crate::error::{AppResult, ToolError};
16use crate::langbase::{
17    BiasDetectionResponse, FallacyDetectionResponse, LangbaseClient, Message, PipeRequest,
18};
19use crate::prompts::{BIAS_DETECTION_PROMPT, FALLACY_DETECTION_PROMPT};
20use crate::storage::{Detection, DetectionType, SqliteStorage, Storage};
21
22// ============================================================================
23// Bias Detection
24// ============================================================================
25
26/// Parameters for bias detection
27#[derive(Debug, Clone, Deserialize)]
28pub struct DetectBiasesParams {
29    /// Content to analyze for biases
30    pub content: Option<String>,
31    /// ID of an existing thought to analyze
32    pub thought_id: Option<String>,
33    /// Session ID for context and persistence
34    pub session_id: Option<String>,
35    /// Specific bias types to check (optional)
36    pub check_types: Option<Vec<String>>,
37}
38
39/// Result of bias detection
40#[derive(Debug, Clone, Serialize)]
41pub struct DetectBiasesResult {
42    /// Detected biases
43    pub detections: Vec<Detection>,
44    /// Number of detections
45    pub detection_count: usize,
46    /// Length of analyzed content
47    pub analyzed_content_length: usize,
48    /// Overall assessment
49    pub overall_assessment: Option<String>,
50    /// Reasoning quality score (0.0-1.0)
51    pub reasoning_quality: Option<f64>,
52}
53
54// ============================================================================
55// Fallacy Detection
56// ============================================================================
57
58/// Parameters for fallacy detection
59#[derive(Debug, Clone, Deserialize)]
60pub struct DetectFallaciesParams {
61    /// Content to analyze for fallacies
62    pub content: Option<String>,
63    /// ID of an existing thought to analyze
64    pub thought_id: Option<String>,
65    /// Session ID for context and persistence
66    pub session_id: Option<String>,
67    /// Check for formal logical fallacies (default: true)
68    #[serde(default = "default_true")]
69    pub check_formal: bool,
70    /// Check for informal logical fallacies (default: true)
71    #[serde(default = "default_true")]
72    pub check_informal: bool,
73}
74
75fn default_true() -> bool {
76    true
77}
78
79/// Result of fallacy detection
80#[derive(Debug, Clone, Serialize)]
81pub struct DetectFallaciesResult {
82    /// Detected fallacies
83    pub detections: Vec<Detection>,
84    /// Number of detections
85    pub detection_count: usize,
86    /// Length of analyzed content
87    pub analyzed_content_length: usize,
88    /// Overall assessment
89    pub overall_assessment: Option<String>,
90    /// Argument validity score (0.0-1.0)
91    pub argument_validity: Option<f64>,
92}
93
94// ============================================================================
95// Detection Mode
96// ============================================================================
97
98/// Detection mode handler for bias and fallacy detection.
99#[derive(Clone)]
100pub struct DetectionMode {
101    /// Core infrastructure (storage and langbase client).
102    core: ModeCore,
103    /// Consolidated pipe name for all detection operations (prompts passed dynamically).
104    detection_pipe: String,
105}
106
107impl DetectionMode {
108    /// Create a new detection mode handler
109    pub fn new(storage: SqliteStorage, langbase: LangbaseClient, config: &Config) -> Self {
110        let detection_pipe = config
111            .pipes
112            .detection
113            .as_ref()
114            .and_then(|d| d.pipe.clone())
115            .unwrap_or_else(|| "detection-v1".to_string());
116
117        info!(
118            pipe = %detection_pipe,
119            from_env = config.pipes.detection.as_ref().and_then(|d| d.pipe.as_ref()).is_some(),
120            "DetectionMode initialized with pipe"
121        );
122
123        Self {
124            core: ModeCore::new(storage, langbase),
125            detection_pipe,
126        }
127    }
128
129    /// Detect biases in content or a thought
130    pub async fn detect_biases(&self, params: DetectBiasesParams) -> AppResult<DetectBiasesResult> {
131        let start = Instant::now();
132
133        // Validate and resolve content
134        let (analysis_content, thought_id) = self
135            .resolve_content(
136                params.content.as_deref(),
137                params.thought_id.as_deref(),
138                "detect_biases",
139            )
140            .await?;
141
142        // Build messages for Langbase
143        let mut messages = vec![Message::system(BIAS_DETECTION_PROMPT)];
144
145        // Add specific bias types to check if provided
146        if let Some(check_types) = &params.check_types {
147            if !check_types.is_empty() {
148                messages.push(Message::user(format!(
149                    "Focus specifically on detecting these bias types: {}\n\nContent to analyze:\n{}",
150                    check_types.join(", "),
151                    analysis_content
152                )));
153            } else {
154                messages.push(Message::user(format!(
155                    "Analyze the following content for cognitive biases:\n\n{}",
156                    analysis_content
157                )));
158            }
159        } else {
160            messages.push(Message::user(format!(
161                "Analyze the following content for cognitive biases:\n\n{}",
162                analysis_content
163            )));
164        }
165
166        // Call Langbase pipe
167        let request = PipeRequest::new(&self.detection_pipe, messages);
168        let response = self.core.langbase().call_pipe(request).await?;
169
170        // Parse response
171        let bias_response = BiasDetectionResponse::from_completion(&response.completion);
172
173        // Convert to Detection structs and persist
174        let mut detections = Vec::new();
175        for detected in &bias_response.detections {
176            let mut detection = Detection::new(
177                DetectionType::Bias,
178                &detected.bias_type,
179                detected.severity,
180                detected.confidence,
181                &detected.explanation,
182            );
183
184            if let Some(session_id) = &params.session_id {
185                detection = detection.with_session(session_id);
186            }
187            if let Some(tid) = &thought_id {
188                detection = detection.with_thought(tid);
189            }
190            if let Some(remediation) = &detected.remediation {
191                detection = detection.with_remediation(remediation);
192            }
193            if let Some(excerpt) = &detected.excerpt {
194                detection = detection.with_metadata(serde_json::json!({ "excerpt": excerpt }));
195            }
196
197            // Persist to storage
198            self.core.storage().create_detection(&detection).await?;
199            detections.push(detection);
200        }
201
202        let latency = start.elapsed().as_millis();
203        info!(
204            detection_count = detections.len(),
205            latency_ms = latency,
206            "Bias detection completed"
207        );
208
209        Ok(DetectBiasesResult {
210            detections,
211            detection_count: bias_response.detections.len(),
212            analyzed_content_length: analysis_content.len(),
213            overall_assessment: Some(bias_response.overall_assessment),
214            reasoning_quality: Some(bias_response.reasoning_quality),
215        })
216    }
217
218    /// Detect fallacies in content or a thought
219    pub async fn detect_fallacies(
220        &self,
221        params: DetectFallaciesParams,
222    ) -> AppResult<DetectFallaciesResult> {
223        let start = Instant::now();
224
225        // Validate check flags
226        if !params.check_formal && !params.check_informal {
227            return Err(ToolError::Validation {
228                field: "check_formal/check_informal".to_string(),
229                reason: "At least one of check_formal or check_informal must be true".to_string(),
230            }
231            .into());
232        }
233
234        // Validate and resolve content
235        let (analysis_content, thought_id) = self
236            .resolve_content(
237                params.content.as_deref(),
238                params.thought_id.as_deref(),
239                "detect_fallacies",
240            )
241            .await?;
242
243        info!(
244            check_formal = %params.check_formal,
245            check_informal = %params.check_informal,
246            "Detecting fallacies"
247        );
248
249        // Build messages for Langbase
250        let mut messages = vec![Message::system(FALLACY_DETECTION_PROMPT)];
251
252        // Build instruction based on what types to check
253        let check_instruction = match (params.check_formal, params.check_informal) {
254            (true, true) => "Check for both formal and informal logical fallacies.",
255            (true, false) => "Focus only on formal logical fallacies (structural errors).",
256            (false, true) => "Focus only on informal logical fallacies (content/context errors).",
257            (false, false) => unreachable!(), // Already validated above
258        };
259
260        messages.push(Message::user(format!(
261            "{}\n\nContent to analyze:\n{}",
262            check_instruction, analysis_content
263        )));
264
265        // Call Langbase pipe
266        let request = PipeRequest::new(&self.detection_pipe, messages);
267        let response = self.core.langbase().call_pipe(request).await?;
268
269        // Parse response
270        let fallacy_response = FallacyDetectionResponse::from_completion(&response.completion);
271
272        // Convert to Detection structs and persist
273        let mut detections = Vec::new();
274        for detected in &fallacy_response.detections {
275            // Filter based on check_formal/check_informal params
276            let is_formal = detected.category.to_lowercase() == "formal";
277            if (is_formal && !params.check_formal) || (!is_formal && !params.check_informal) {
278                continue;
279            }
280
281            let mut detection = Detection::new(
282                DetectionType::Fallacy,
283                &detected.fallacy_type,
284                detected.severity,
285                detected.confidence,
286                &detected.explanation,
287            );
288
289            if let Some(session_id) = &params.session_id {
290                detection = detection.with_session(session_id);
291            }
292            if let Some(tid) = &thought_id {
293                detection = detection.with_thought(tid);
294            }
295            if let Some(remediation) = &detected.remediation {
296                detection = detection.with_remediation(remediation);
297            }
298
299            // Store category and excerpt in metadata
300            let mut meta = serde_json::Map::new();
301            meta.insert("category".to_string(), serde_json::json!(detected.category));
302            if let Some(excerpt) = &detected.excerpt {
303                meta.insert("excerpt".to_string(), serde_json::json!(excerpt));
304            }
305            detection = detection.with_metadata(serde_json::Value::Object(meta));
306
307            // Persist to storage
308            self.core.storage().create_detection(&detection).await?;
309            detections.push(detection);
310        }
311
312        let latency = start.elapsed().as_millis();
313        info!(
314            detection_count = detections.len(),
315            latency_ms = latency,
316            "Fallacy detection completed"
317        );
318
319        Ok(DetectFallaciesResult {
320            detections,
321            detection_count: fallacy_response.detections.len(),
322            analyzed_content_length: analysis_content.len(),
323            overall_assessment: Some(fallacy_response.overall_assessment),
324            argument_validity: Some(fallacy_response.argument_validity),
325        })
326    }
327
328    /// Resolve content from either direct content or thought ID
329    async fn resolve_content(
330        &self,
331        content: Option<&str>,
332        thought_id: Option<&str>,
333        operation: &str,
334    ) -> AppResult<(String, Option<String>)> {
335        match (content, thought_id) {
336            (Some(content), _) => Ok((content.to_string(), thought_id.map(|s| s.to_string()))),
337            (None, Some(thought_id)) => {
338                let thought = self
339                    .core
340                    .storage()
341                    .get_thought(thought_id)
342                    .await?
343                    .ok_or_else(|| {
344                        ToolError::Session(format!("Thought not found: {}", thought_id))
345                    })?;
346                Ok((thought.content, Some(thought_id.to_string())))
347            }
348            (None, None) => Err(ToolError::Validation {
349                field: "content/thought_id".to_string(),
350                reason: format!(
351                    "Either 'content' or 'thought_id' must be provided for {}",
352                    operation
353                ),
354            }
355            .into()),
356        }
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    #[test]
365    fn test_detect_biases_params_deserialize() {
366        let json = r#"{"content": "Test content"}"#;
367        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
368        assert_eq!(params.content, Some("Test content".to_string()));
369        assert!(params.thought_id.is_none());
370        assert!(params.session_id.is_none());
371        assert!(params.check_types.is_none());
372    }
373
374    #[test]
375    fn test_detect_biases_params_with_check_types() {
376        let json = r#"{"content": "Test", "check_types": ["confirmation_bias", "anchoring"]}"#;
377        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
378        assert_eq!(
379            params.check_types,
380            Some(vec![
381                "confirmation_bias".to_string(),
382                "anchoring".to_string()
383            ])
384        );
385    }
386
387    #[test]
388    fn test_detect_fallacies_params_defaults() {
389        let json = r#"{"content": "Test content"}"#;
390        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
391        assert!(params.check_formal);
392        assert!(params.check_informal);
393    }
394
395    #[test]
396    fn test_detect_fallacies_params_custom_checks() {
397        let json = r#"{"content": "Test", "check_formal": false, "check_informal": true}"#;
398        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
399        assert!(!params.check_formal);
400        assert!(params.check_informal);
401    }
402
403    #[test]
404    fn test_detect_biases_result_serialize() {
405        let result = DetectBiasesResult {
406            detections: vec![],
407            detection_count: 0,
408            analyzed_content_length: 100,
409            overall_assessment: Some("Good reasoning".to_string()),
410            reasoning_quality: Some(0.85),
411        };
412        let json = serde_json::to_string(&result).unwrap();
413        assert!(json.contains("detection_count"));
414        assert!(json.contains("0.85"));
415    }
416
417    #[test]
418    fn test_detect_fallacies_result_serialize() {
419        let result = DetectFallaciesResult {
420            detections: vec![],
421            detection_count: 2,
422            analyzed_content_length: 200,
423            overall_assessment: Some("Some issues found".to_string()),
424            argument_validity: Some(0.7),
425        };
426        let json = serde_json::to_string(&result).unwrap();
427        assert!(json.contains("detection_count"));
428        assert!(json.contains("0.7"));
429    }
430
431    #[test]
432    fn test_default_true_function() {
433        assert!(default_true());
434    }
435
436    #[test]
437    fn test_detect_biases_params_all_fields() {
438        let json = r#"{
439            "content": "Test content",
440            "thought_id": "thought-123",
441            "session_id": "session-456",
442            "check_types": ["confirmation_bias"]
443        }"#;
444        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
445        assert_eq!(params.content, Some("Test content".to_string()));
446        assert_eq!(params.thought_id, Some("thought-123".to_string()));
447        assert_eq!(params.session_id, Some("session-456".to_string()));
448        assert_eq!(
449            params.check_types,
450            Some(vec!["confirmation_bias".to_string()])
451        );
452    }
453
454    #[test]
455    fn test_detect_biases_params_empty_check_types() {
456        let json = r#"{"content": "Test", "check_types": []}"#;
457        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
458        assert_eq!(params.check_types, Some(vec![]));
459    }
460
461    #[test]
462    fn test_detect_biases_params_only_thought_id() {
463        let json = r#"{"thought_id": "thought-789"}"#;
464        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
465        assert!(params.content.is_none());
466        assert_eq!(params.thought_id, Some("thought-789".to_string()));
467        assert!(params.session_id.is_none());
468        assert!(params.check_types.is_none());
469    }
470
471    #[test]
472    fn test_detect_fallacies_params_all_fields() {
473        let json = r#"{
474            "content": "Test content",
475            "thought_id": "thought-123",
476            "session_id": "session-456",
477            "check_formal": true,
478            "check_informal": false
479        }"#;
480        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
481        assert_eq!(params.content, Some("Test content".to_string()));
482        assert_eq!(params.thought_id, Some("thought-123".to_string()));
483        assert_eq!(params.session_id, Some("session-456".to_string()));
484        assert!(params.check_formal);
485        assert!(!params.check_informal);
486    }
487
488    #[test]
489    fn test_detect_fallacies_params_only_thought_id() {
490        let json = r#"{"thought_id": "thought-789"}"#;
491        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
492        assert!(params.content.is_none());
493        assert_eq!(params.thought_id, Some("thought-789".to_string()));
494        assert!(params.session_id.is_none());
495        assert!(params.check_formal); // default
496        assert!(params.check_informal); // default
497    }
498
499    #[test]
500    fn test_detect_biases_result_round_trip() {
501        let original = DetectBiasesResult {
502            detections: vec![],
503            detection_count: 3,
504            analyzed_content_length: 250,
505            overall_assessment: Some("Multiple biases detected".to_string()),
506            reasoning_quality: Some(0.65),
507        };
508        let json = serde_json::to_string(&original).unwrap();
509        let deserialized: serde_json::Value = serde_json::from_str(&json).unwrap();
510
511        assert_eq!(deserialized["detection_count"], 3);
512        assert_eq!(deserialized["analyzed_content_length"], 250);
513        assert_eq!(deserialized["reasoning_quality"], 0.65);
514    }
515
516    #[test]
517    fn test_detect_fallacies_result_round_trip() {
518        let original = DetectFallaciesResult {
519            detections: vec![],
520            detection_count: 1,
521            analyzed_content_length: 150,
522            overall_assessment: Some("One fallacy detected".to_string()),
523            argument_validity: Some(0.9),
524        };
525        let json = serde_json::to_string(&original).unwrap();
526        let deserialized: serde_json::Value = serde_json::from_str(&json).unwrap();
527
528        assert_eq!(deserialized["detection_count"], 1);
529        assert_eq!(deserialized["analyzed_content_length"], 150);
530        assert_eq!(deserialized["argument_validity"], 0.9);
531    }
532
533    #[test]
534    fn test_detect_biases_result_with_none_values() {
535        let result = DetectBiasesResult {
536            detections: vec![],
537            detection_count: 0,
538            analyzed_content_length: 50,
539            overall_assessment: None,
540            reasoning_quality: None,
541        };
542        let json = serde_json::to_string(&result).unwrap();
543        assert!(json.contains("detection_count"));
544        assert!(json.contains("\"overall_assessment\":null"));
545        assert!(json.contains("\"reasoning_quality\":null"));
546    }
547
548    #[test]
549    fn test_detect_fallacies_result_with_none_values() {
550        let result = DetectFallaciesResult {
551            detections: vec![],
552            detection_count: 0,
553            analyzed_content_length: 75,
554            overall_assessment: None,
555            argument_validity: None,
556        };
557        let json = serde_json::to_string(&result).unwrap();
558        assert!(json.contains("detection_count"));
559        assert!(json.contains("\"overall_assessment\":null"));
560        assert!(json.contains("\"argument_validity\":null"));
561    }
562
563    // ========================================================================
564    // Edge Cases & Boundary Values
565    // ========================================================================
566
567    #[test]
568    fn test_detect_biases_params_empty_content() {
569        let json = r#"{"content": ""}"#;
570        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
571        assert_eq!(params.content, Some("".to_string()));
572    }
573
574    #[test]
575    fn test_detect_biases_params_unicode_content() {
576        let json = r#"{"content": "测试 🎉 тест"}"#;
577        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
578        assert_eq!(params.content, Some("测试 🎉 тест".to_string()));
579    }
580
581    #[test]
582    fn test_detect_biases_params_long_content() {
583        let long_text = "a".repeat(10000);
584        let json = serde_json::json!({
585            "content": long_text
586        });
587        let params: DetectBiasesParams = serde_json::from_value(json).unwrap();
588        assert_eq!(params.content.unwrap().len(), 10000);
589    }
590
591    #[test]
592    fn test_detect_biases_params_special_characters() {
593        let json = r#"{"content": "Line1\nLine2\t\"quoted\""}"#;
594        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
595        let content = params.content.unwrap();
596        assert!(content.contains("Line1"));
597        assert!(content.contains("Line2"));
598    }
599
600    #[test]
601    fn test_detect_fallacies_params_empty_content() {
602        let json = r#"{"content": ""}"#;
603        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
604        assert_eq!(params.content, Some("".to_string()));
605    }
606
607    #[test]
608    fn test_detect_fallacies_params_unicode_content() {
609        let json = r#"{"content": "Test 日本語"}"#;
610        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
611        assert_eq!(params.content, Some("Test 日本語".to_string()));
612    }
613
614    #[test]
615    fn test_detect_biases_result_boundary_scores() {
616        let result = DetectBiasesResult {
617            detections: vec![],
618            detection_count: 0,
619            analyzed_content_length: 0,
620            overall_assessment: Some("".to_string()),
621            reasoning_quality: Some(0.0),
622        };
623        let json = serde_json::to_string(&result).unwrap();
624        assert!(json.contains("0.0"));
625
626        let result = DetectBiasesResult {
627            detections: vec![],
628            detection_count: 999,
629            analyzed_content_length: 999999,
630            overall_assessment: Some("Max".to_string()),
631            reasoning_quality: Some(1.0),
632        };
633        let json = serde_json::to_string(&result).unwrap();
634        assert!(json.contains("1.0"));
635        assert!(json.contains("999999"));
636    }
637
638    #[test]
639    fn test_detect_fallacies_result_boundary_scores() {
640        let result = DetectFallaciesResult {
641            detections: vec![],
642            detection_count: 0,
643            analyzed_content_length: 0,
644            overall_assessment: Some("".to_string()),
645            argument_validity: Some(0.0),
646        };
647        let json = serde_json::to_string(&result).unwrap();
648        assert!(json.contains("0.0"));
649
650        let result = DetectFallaciesResult {
651            detections: vec![],
652            detection_count: 500,
653            analyzed_content_length: 100000,
654            overall_assessment: Some("Max".to_string()),
655            argument_validity: Some(1.0),
656        };
657        let json = serde_json::to_string(&result).unwrap();
658        assert!(json.contains("1.0"));
659        assert!(json.contains("100000"));
660    }
661
662    // ========================================================================
663    // Clone & Debug Tests
664    // ========================================================================
665
666    #[test]
667    fn test_detect_biases_params_clone() {
668        let params = DetectBiasesParams {
669            content: Some("Test".to_string()),
670            thought_id: Some("t1".to_string()),
671            session_id: Some("s1".to_string()),
672            check_types: Some(vec!["bias1".to_string()]),
673        };
674        let cloned = params.clone();
675        assert_eq!(params.content, cloned.content);
676        assert_eq!(params.thought_id, cloned.thought_id);
677        assert_eq!(params.session_id, cloned.session_id);
678        assert_eq!(params.check_types, cloned.check_types);
679    }
680
681    #[test]
682    fn test_detect_fallacies_params_clone() {
683        let params = DetectFallaciesParams {
684            content: Some("Test".to_string()),
685            thought_id: Some("t1".to_string()),
686            session_id: Some("s1".to_string()),
687            check_formal: true,
688            check_informal: false,
689        };
690        let cloned = params.clone();
691        assert_eq!(params.content, cloned.content);
692        assert_eq!(params.check_formal, cloned.check_formal);
693        assert_eq!(params.check_informal, cloned.check_informal);
694    }
695
696    #[test]
697    fn test_detect_biases_result_clone() {
698        let result = DetectBiasesResult {
699            detections: vec![],
700            detection_count: 5,
701            analyzed_content_length: 100,
702            overall_assessment: Some("Test".to_string()),
703            reasoning_quality: Some(0.75),
704        };
705        let cloned = result.clone();
706        assert_eq!(result.detection_count, cloned.detection_count);
707        assert_eq!(
708            result.analyzed_content_length,
709            cloned.analyzed_content_length
710        );
711        assert_eq!(result.reasoning_quality, cloned.reasoning_quality);
712    }
713
714    #[test]
715    fn test_detect_fallacies_result_clone() {
716        let result = DetectFallaciesResult {
717            detections: vec![],
718            detection_count: 3,
719            analyzed_content_length: 200,
720            overall_assessment: Some("Test".to_string()),
721            argument_validity: Some(0.8),
722        };
723        let cloned = result.clone();
724        assert_eq!(result.detection_count, cloned.detection_count);
725        assert_eq!(result.argument_validity, cloned.argument_validity);
726    }
727
728    #[test]
729    fn test_detect_biases_params_debug() {
730        let params = DetectBiasesParams {
731            content: Some("Test".to_string()),
732            thought_id: None,
733            session_id: None,
734            check_types: None,
735        };
736        let debug_str = format!("{:?}", params);
737        assert!(debug_str.contains("DetectBiasesParams"));
738        assert!(debug_str.contains("Test"));
739    }
740
741    #[test]
742    fn test_detect_fallacies_params_debug() {
743        let params = DetectFallaciesParams {
744            content: Some("Test".to_string()),
745            thought_id: None,
746            session_id: None,
747            check_formal: true,
748            check_informal: true,
749        };
750        let debug_str = format!("{:?}", params);
751        assert!(debug_str.contains("DetectFallaciesParams"));
752        assert!(debug_str.contains("Test"));
753    }
754
755    #[test]
756    fn test_detect_biases_result_debug() {
757        let result = DetectBiasesResult {
758            detections: vec![],
759            detection_count: 1,
760            analyzed_content_length: 50,
761            overall_assessment: Some("Test".to_string()),
762            reasoning_quality: Some(0.9),
763        };
764        let debug_str = format!("{:?}", result);
765        assert!(debug_str.contains("DetectBiasesResult"));
766    }
767
768    #[test]
769    fn test_detect_fallacies_result_debug() {
770        let result = DetectFallaciesResult {
771            detections: vec![],
772            detection_count: 2,
773            analyzed_content_length: 100,
774            overall_assessment: Some("Test".to_string()),
775            argument_validity: Some(0.85),
776        };
777        let debug_str = format!("{:?}", result);
778        assert!(debug_str.contains("DetectFallaciesResult"));
779    }
780
781    // ========================================================================
782    // JSON Parsing - Invalid/Malformed Input
783    // ========================================================================
784
785    #[test]
786    fn test_detect_biases_params_invalid_json() {
787        let json = r#"{"content": "Test", invalid}"#;
788        let result: Result<DetectBiasesParams, _> = serde_json::from_str(json);
789        assert!(result.is_err());
790    }
791
792    #[test]
793    fn test_detect_fallacies_params_invalid_json() {
794        let json = r#"{"content": malformed"#;
795        let result: Result<DetectFallaciesParams, _> = serde_json::from_str(json);
796        assert!(result.is_err());
797    }
798
799    #[test]
800    fn test_detect_biases_params_missing_all_fields() {
801        let json = r#"{}"#;
802        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
803        assert!(params.content.is_none());
804        assert!(params.thought_id.is_none());
805        assert!(params.session_id.is_none());
806        assert!(params.check_types.is_none());
807    }
808
809    #[test]
810    fn test_detect_fallacies_params_missing_all_optional_fields() {
811        let json = r#"{}"#;
812        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
813        assert!(params.content.is_none());
814        assert!(params.thought_id.is_none());
815        assert!(params.session_id.is_none());
816        assert!(params.check_formal); // defaults to true
817        assert!(params.check_informal); // defaults to true
818    }
819
820    #[test]
821    fn test_detect_biases_params_null_values() {
822        let json =
823            r#"{"content": null, "thought_id": null, "session_id": null, "check_types": null}"#;
824        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
825        assert!(params.content.is_none());
826        assert!(params.thought_id.is_none());
827        assert!(params.session_id.is_none());
828        assert!(params.check_types.is_none());
829    }
830
831    #[test]
832    fn test_detect_fallacies_params_null_values() {
833        let json = r#"{"content": null, "thought_id": null, "session_id": null}"#;
834        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
835        assert!(params.content.is_none());
836        assert!(params.thought_id.is_none());
837        assert!(params.session_id.is_none());
838    }
839
840    // ========================================================================
841    // Multiple check_types Variations
842    // ========================================================================
843
844    #[test]
845    fn test_detect_biases_params_single_check_type() {
846        let json = r#"{"content": "Test", "check_types": ["anchoring"]}"#;
847        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
848        assert_eq!(params.check_types, Some(vec!["anchoring".to_string()]));
849    }
850
851    #[test]
852    fn test_detect_biases_params_many_check_types() {
853        let json =
854            r#"{"content": "Test", "check_types": ["bias1", "bias2", "bias3", "bias4", "bias5"]}"#;
855        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
856        assert_eq!(params.check_types.as_ref().unwrap().len(), 5);
857    }
858
859    #[test]
860    fn test_detect_biases_params_check_types_with_spaces() {
861        let json = r#"{"content": "Test", "check_types": ["confirmation bias", "anchoring bias"]}"#;
862        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
863        assert_eq!(
864            params.check_types,
865            Some(vec![
866                "confirmation bias".to_string(),
867                "anchoring bias".to_string()
868            ])
869        );
870    }
871
872    // ========================================================================
873    // Serialization Consistency
874    // ========================================================================
875
876    #[test]
877    fn test_detect_biases_result_serialization_field_names() {
878        let result = DetectBiasesResult {
879            detections: vec![],
880            detection_count: 1,
881            analyzed_content_length: 100,
882            overall_assessment: Some("Test".to_string()),
883            reasoning_quality: Some(0.5),
884        };
885        let json = serde_json::to_string(&result).unwrap();
886        assert!(json.contains("detections"));
887        assert!(json.contains("detection_count"));
888        assert!(json.contains("analyzed_content_length"));
889        assert!(json.contains("overall_assessment"));
890        assert!(json.contains("reasoning_quality"));
891    }
892
893    #[test]
894    fn test_detect_fallacies_result_serialization_field_names() {
895        let result = DetectFallaciesResult {
896            detections: vec![],
897            detection_count: 1,
898            analyzed_content_length: 100,
899            overall_assessment: Some("Test".to_string()),
900            argument_validity: Some(0.5),
901        };
902        let json = serde_json::to_string(&result).unwrap();
903        assert!(json.contains("detections"));
904        assert!(json.contains("detection_count"));
905        assert!(json.contains("analyzed_content_length"));
906        assert!(json.contains("overall_assessment"));
907        assert!(json.contains("argument_validity"));
908    }
909
910    // ========================================================================
911    // Boolean Combinations for Fallacy Detection
912    // ========================================================================
913
914    #[test]
915    fn test_detect_fallacies_params_both_false() {
916        let json = r#"{"content": "Test", "check_formal": false, "check_informal": false}"#;
917        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
918        assert!(!params.check_formal);
919        assert!(!params.check_informal);
920    }
921
922    #[test]
923    fn test_detect_fallacies_params_only_formal_true() {
924        let json = r#"{"content": "Test", "check_formal": true, "check_informal": false}"#;
925        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
926        assert!(params.check_formal);
927        assert!(!params.check_informal);
928    }
929
930    #[test]
931    fn test_detect_fallacies_params_only_informal_true() {
932        let json = r#"{"content": "Test", "check_formal": false, "check_informal": true}"#;
933        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
934        assert!(!params.check_formal);
935        assert!(params.check_informal);
936    }
937
938    #[test]
939    fn test_detect_fallacies_params_both_true() {
940        let json = r#"{"content": "Test", "check_formal": true, "check_informal": true}"#;
941        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
942        assert!(params.check_formal);
943        assert!(params.check_informal);
944    }
945
946    // ========================================================================
947    // Large Data Tests
948    // ========================================================================
949
950    #[test]
951    fn test_detect_biases_result_large_detection_count() {
952        let result = DetectBiasesResult {
953            detections: vec![],
954            detection_count: 1000,
955            analyzed_content_length: 50000,
956            overall_assessment: Some("Many detections".to_string()),
957            reasoning_quality: Some(0.3),
958        };
959        let json = serde_json::to_string(&result).unwrap();
960        assert!(json.contains("1000"));
961        assert!(json.contains("50000"));
962    }
963
964    #[test]
965    fn test_detect_fallacies_result_large_detection_count() {
966        let result = DetectFallaciesResult {
967            detections: vec![],
968            detection_count: 2000,
969            analyzed_content_length: 100000,
970            overall_assessment: Some("Critical issues".to_string()),
971            argument_validity: Some(0.1),
972        };
973        let json = serde_json::to_string(&result).unwrap();
974        assert!(json.contains("2000"));
975        assert!(json.contains("100000"));
976    }
977
978    // ========================================================================
979    // Float Precision Tests
980    // ========================================================================
981
982    #[test]
983    fn test_detect_biases_result_float_precision() {
984        let result = DetectBiasesResult {
985            detections: vec![],
986            detection_count: 1,
987            analyzed_content_length: 100,
988            overall_assessment: Some("Test".to_string()),
989            reasoning_quality: Some(0.123456789),
990        };
991        let json = serde_json::to_string(&result).unwrap();
992        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
993        let quality = parsed["reasoning_quality"].as_f64().unwrap();
994        assert!((quality - 0.123456789).abs() < 1e-9);
995    }
996
997    #[test]
998    fn test_detect_fallacies_result_float_precision() {
999        let result = DetectFallaciesResult {
1000            detections: vec![],
1001            detection_count: 1,
1002            analyzed_content_length: 100,
1003            overall_assessment: Some("Test".to_string()),
1004            argument_validity: Some(0.987654321),
1005        };
1006        let json = serde_json::to_string(&result).unwrap();
1007        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1008        let validity = parsed["argument_validity"].as_f64().unwrap();
1009        assert!((validity - 0.987654321).abs() < 1e-9);
1010    }
1011
1012    // ========================================================================
1013    // Default Function Test (already exists but adding more comprehensive check)
1014    // ========================================================================
1015
1016    #[test]
1017    fn test_default_true_is_consistent() {
1018        assert!(default_true());
1019        assert_eq!(default_true(), default_true());
1020    }
1021
1022    // ========================================================================
1023    // Empty String Tests
1024    // ========================================================================
1025
1026    #[test]
1027    fn test_detect_biases_params_empty_string_fields() {
1028        let json = r#"{"content": "", "thought_id": "", "session_id": ""}"#;
1029        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
1030        assert_eq!(params.content, Some("".to_string()));
1031        assert_eq!(params.thought_id, Some("".to_string()));
1032        assert_eq!(params.session_id, Some("".to_string()));
1033    }
1034
1035    #[test]
1036    fn test_detect_fallacies_params_empty_string_fields() {
1037        let json = r#"{"content": "", "thought_id": "", "session_id": ""}"#;
1038        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
1039        assert_eq!(params.content, Some("".to_string()));
1040        assert_eq!(params.thought_id, Some("".to_string()));
1041        assert_eq!(params.session_id, Some("".to_string()));
1042    }
1043
1044    #[test]
1045    fn test_detect_biases_result_empty_assessment() {
1046        let result = DetectBiasesResult {
1047            detections: vec![],
1048            detection_count: 0,
1049            analyzed_content_length: 0,
1050            overall_assessment: Some("".to_string()),
1051            reasoning_quality: Some(0.5),
1052        };
1053        let json = serde_json::to_string(&result).unwrap();
1054        assert!(json.contains(r#""overall_assessment":"""#));
1055    }
1056
1057    #[test]
1058    fn test_detect_fallacies_result_empty_assessment() {
1059        let result = DetectFallaciesResult {
1060            detections: vec![],
1061            detection_count: 0,
1062            analyzed_content_length: 0,
1063            overall_assessment: Some("".to_string()),
1064            argument_validity: Some(0.5),
1065        };
1066        let json = serde_json::to_string(&result).unwrap();
1067        assert!(json.contains(r#""overall_assessment":"""#));
1068    }
1069
1070    // ========================================================================
1071    // Mixed Valid/Invalid Fields
1072    // ========================================================================
1073
1074    #[test]
1075    fn test_detect_biases_params_mixed_some_none() {
1076        let json =
1077            r#"{"content": "Test", "thought_id": null, "session_id": "s1", "check_types": null}"#;
1078        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
1079        assert_eq!(params.content, Some("Test".to_string()));
1080        assert!(params.thought_id.is_none());
1081        assert_eq!(params.session_id, Some("s1".to_string()));
1082        assert!(params.check_types.is_none());
1083    }
1084
1085    #[test]
1086    fn test_detect_fallacies_params_mixed_some_none() {
1087        let json = r#"{"content": null, "thought_id": "t1", "session_id": null}"#;
1088        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
1089        assert!(params.content.is_none());
1090        assert_eq!(params.thought_id, Some("t1".to_string()));
1091        assert!(params.session_id.is_none());
1092    }
1093
1094    // ========================================================================
1095    // Negative and Out-of-Range Score Tests
1096    // ========================================================================
1097
1098    #[test]
1099    fn test_detect_biases_result_negative_score() {
1100        let result = DetectBiasesResult {
1101            detections: vec![],
1102            detection_count: 0,
1103            analyzed_content_length: 0,
1104            overall_assessment: None,
1105            reasoning_quality: Some(-0.5),
1106        };
1107        let json = serde_json::to_string(&result).unwrap();
1108        assert!(json.contains("-0.5"));
1109    }
1110
1111    #[test]
1112    fn test_detect_fallacies_result_over_one_score() {
1113        let result = DetectFallaciesResult {
1114            detections: vec![],
1115            detection_count: 0,
1116            analyzed_content_length: 0,
1117            overall_assessment: None,
1118            argument_validity: Some(1.5),
1119        };
1120        let json = serde_json::to_string(&result).unwrap();
1121        assert!(json.contains("1.5"));
1122    }
1123
1124    // ========================================================================
1125    // Very Long String Tests
1126    // ========================================================================
1127
1128    #[test]
1129    fn test_detect_biases_result_very_long_assessment() {
1130        let long_text = "a".repeat(50000);
1131        let result = DetectBiasesResult {
1132            detections: vec![],
1133            detection_count: 0,
1134            analyzed_content_length: 50000,
1135            overall_assessment: Some(long_text),
1136            reasoning_quality: Some(0.5),
1137        };
1138        let json = serde_json::to_string(&result).unwrap();
1139        assert!(json.len() > 50000);
1140    }
1141
1142    #[test]
1143    fn test_detect_fallacies_result_very_long_assessment() {
1144        let long_text = "b".repeat(30000);
1145        let result = DetectFallaciesResult {
1146            detections: vec![],
1147            detection_count: 0,
1148            analyzed_content_length: 30000,
1149            overall_assessment: Some(long_text),
1150            argument_validity: Some(0.5),
1151        };
1152        let json = serde_json::to_string(&result).unwrap();
1153        assert!(json.len() > 30000);
1154    }
1155
1156    // ========================================================================
1157    // Whitespace Tests
1158    // ========================================================================
1159
1160    #[test]
1161    fn test_detect_biases_params_whitespace_content() {
1162        let json = r#"{"content": "   \n\t   "}"#;
1163        let params: DetectBiasesParams = serde_json::from_str(json).unwrap();
1164        assert_eq!(params.content, Some("   \n\t   ".to_string()));
1165    }
1166
1167    #[test]
1168    fn test_detect_fallacies_params_whitespace_content() {
1169        let json = r#"{"content": "\t\n\r  "}"#;
1170        let params: DetectFallaciesParams = serde_json::from_str(json).unwrap();
1171        assert_eq!(params.content, Some("\t\n\r  ".to_string()));
1172    }
1173
1174    // ========================================================================
1175    // Type Mismatch Tests (should fail)
1176    // ========================================================================
1177
1178    #[test]
1179    fn test_detect_biases_params_wrong_type_check_types() {
1180        let json = r#"{"content": "Test", "check_types": "not_an_array"}"#;
1181        let result: Result<DetectBiasesParams, _> = serde_json::from_str(json);
1182        assert!(result.is_err());
1183    }
1184
1185    #[test]
1186    fn test_detect_fallacies_params_wrong_type_check_formal() {
1187        let json = r#"{"content": "Test", "check_formal": "true"}"#;
1188        let result: Result<DetectFallaciesParams, _> = serde_json::from_str(json);
1189        assert!(result.is_err());
1190    }
1191
1192    #[test]
1193    fn test_detect_biases_result_wrong_type_detection_count() {
1194        let json = r#"{"detections": [], "detection_count": "5", "analyzed_content_length": 100}"#;
1195        let result: Result<serde_json::Value, _> = serde_json::from_str(json);
1196        assert!(result.is_ok()); // JSON parsing succeeds but won't match struct type
1197    }
1198}