reasonkit/thinktool/
socratic.rs

1//! # Socratic Questioning Engine
2//!
3//! Implements the Socratic method for deep inquiry and BrutalHonesty critique.
4//!
5//! ## Scientific Foundation
6//!
7//! Based on Socratic pedagogy and epistemic inquiry:
8//! - Elenchus (cross-examination) to expose contradictions
9//! - Maieutics (midwifery of ideas) to draw out understanding
10//! - Aporia (puzzlement) as a path to deeper understanding
11//!
12//! ## The Socratic Method
13//!
14//! ```text
15//! CLAIM → CLARIFY → CHALLENGE → CONSEQUENCE → QUESTION ASSUMPTIONS
16//!    ↓        ↓          ↓            ↓               ↓
17//!  What?    Define    Evidence?   Implications?   Why believe?
18//! ```
19//!
20//! ## Question Categories (Paul-Elder Framework)
21//!
22//! 1. Clarification questions
23//! 2. Probing assumptions
24//! 3. Probing reasons/evidence
25//! 4. Viewpoint/perspective questions
26//! 5. Probing implications/consequences
27//! 6. Questions about the question
28//!
29//! ## Usage
30//!
31//! ```rust,ignore
32//! use reasonkit::thinktool::socratic::{SocraticEngine, SocraticConfig};
33//!
34//! let engine = SocraticEngine::new(SocraticConfig::default());
35//! let result = engine.examine(claim).await?;
36//! ```
37
38use serde::{Deserialize, Serialize};
39
40/// Configuration for Socratic questioning
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SocraticConfig {
43    /// Question categories to use
44    pub categories: Vec<QuestionCategory>,
45    /// Minimum questions per category
46    pub min_questions_per_category: usize,
47    /// Maximum total questions
48    pub max_questions: usize,
49    /// Depth of follow-up (nested questioning)
50    pub follow_up_depth: usize,
51    /// Enable aporia detection (finding genuine puzzlement)
52    pub detect_aporia: bool,
53    /// BrutalHonesty mode (more aggressive questioning)
54    pub brutal_honesty: bool,
55}
56
57impl Default for SocraticConfig {
58    fn default() -> Self {
59        Self {
60            categories: vec![
61                QuestionCategory::Clarification,
62                QuestionCategory::Assumptions,
63                QuestionCategory::Evidence,
64                QuestionCategory::Viewpoints,
65                QuestionCategory::Implications,
66                QuestionCategory::MetaQuestions,
67            ],
68            min_questions_per_category: 1,
69            max_questions: 12,
70            follow_up_depth: 2,
71            detect_aporia: true,
72            brutal_honesty: false,
73        }
74    }
75}
76
77impl SocraticConfig {
78    /// BrutalHonesty-optimized configuration
79    pub fn brutal_honesty() -> Self {
80        Self {
81            categories: vec![
82                QuestionCategory::Clarification,
83                QuestionCategory::Assumptions,
84                QuestionCategory::Evidence,
85                QuestionCategory::Viewpoints,
86                QuestionCategory::Implications,
87                QuestionCategory::MetaQuestions,
88                QuestionCategory::DevilsAdvocate,
89                QuestionCategory::SteelMan,
90            ],
91            min_questions_per_category: 2,
92            max_questions: 20,
93            follow_up_depth: 3,
94            detect_aporia: true,
95            brutal_honesty: true,
96        }
97    }
98
99    /// Quick examination
100    pub fn quick() -> Self {
101        Self {
102            categories: vec![
103                QuestionCategory::Clarification,
104                QuestionCategory::Assumptions,
105                QuestionCategory::Evidence,
106            ],
107            min_questions_per_category: 1,
108            max_questions: 6,
109            follow_up_depth: 1,
110            detect_aporia: false,
111            brutal_honesty: false,
112        }
113    }
114}
115
116/// Categories of Socratic questions (Paul-Elder Framework extended)
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
118pub enum QuestionCategory {
119    /// What do you mean by...? Can you clarify?
120    Clarification,
121    /// What are you assuming? Why assume that?
122    Assumptions,
123    /// What evidence supports this? How do you know?
124    Evidence,
125    /// How would others view this? What alternatives exist?
126    Viewpoints,
127    /// What follows from this? What are the consequences?
128    Implications,
129    /// Why is this question important? What's the deeper question?
130    MetaQuestions,
131    /// What's the strongest counter-argument?
132    DevilsAdvocate,
133    /// What's the strongest version of this argument?
134    SteelMan,
135}
136
137impl QuestionCategory {
138    /// Get description of this category
139    pub fn description(&self) -> &'static str {
140        match self {
141            Self::Clarification => "Questions that seek to understand exactly what is meant",
142            Self::Assumptions => "Questions that probe underlying assumptions and presuppositions",
143            Self::Evidence => "Questions that examine reasons, evidence, and justification",
144            Self::Viewpoints => "Questions that explore alternative perspectives",
145            Self::Implications => "Questions that investigate consequences and implications",
146            Self::MetaQuestions => "Questions about the question itself",
147            Self::DevilsAdvocate => "Questions that challenge by taking the opposing view",
148            Self::SteelMan => "Questions that strengthen the argument to test its limits",
149        }
150    }
151
152    /// Example questions in this category
153    pub fn examples(&self) -> Vec<&'static str> {
154        match self {
155            Self::Clarification => vec![
156                "What exactly do you mean by...?",
157                "Can you give me an example?",
158                "How does this relate to...?",
159                "Can you put that another way?",
160            ],
161            Self::Assumptions => vec![
162                "What are you assuming here?",
163                "Why do you assume that's true?",
164                "What if that assumption were wrong?",
165                "What would have to be true for this to hold?",
166            ],
167            Self::Evidence => vec![
168                "What evidence supports this?",
169                "How do you know this is true?",
170                "What would convince you otherwise?",
171                "What's the source of this belief?",
172            ],
173            Self::Viewpoints => vec![
174                "How would [X] see this?",
175                "What's the opposing view?",
176                "Are there alternative explanations?",
177                "Who might disagree and why?",
178            ],
179            Self::Implications => vec![
180                "What follows from this?",
181                "If this is true, what else must be true?",
182                "What are the practical consequences?",
183                "How does this affect...?",
184            ],
185            Self::MetaQuestions => vec![
186                "Why is this question important?",
187                "Is this the right question to ask?",
188                "What's the deeper issue here?",
189                "How would we know if we've answered this?",
190            ],
191            Self::DevilsAdvocate => vec![
192                "What's the strongest argument against this?",
193                "How could this fail?",
194                "What would a critic say?",
195                "What evidence would disprove this?",
196            ],
197            Self::SteelMan => vec![
198                "What's the strongest form of this argument?",
199                "How could this be made more defensible?",
200                "What additional evidence would strengthen this?",
201                "What objections does this already address?",
202            ],
203        }
204    }
205}
206
207/// A single Socratic question
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct SocraticQuestion {
210    /// Unique identifier
211    pub id: usize,
212    /// The question text
213    pub question: String,
214    /// Question category
215    pub category: QuestionCategory,
216    /// Follow-up to which question (None if root)
217    pub follows_up: Option<usize>,
218    /// Depth level (0 = root question)
219    pub depth: usize,
220    /// Expected type of answer
221    pub answer_type: AnswerType,
222    /// Whether this question was answered
223    pub answered: bool,
224    /// The answer if provided
225    pub answer: Option<String>,
226}
227
228/// Expected type of answer
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230pub enum AnswerType {
231    /// A definition or clarification
232    Definition,
233    /// Evidence or justification
234    Evidence,
235    /// An explanation or reason
236    Explanation,
237    /// Acknowledgment of uncertainty
238    Uncertainty,
239    /// A counter-example
240    CounterExample,
241    /// A reformulation
242    Reformulation,
243    /// A concession
244    Concession,
245}
246
247/// State of aporia (productive puzzlement)
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct Aporia {
250    /// Description of the puzzlement
251    pub description: String,
252    /// What beliefs are in tension?
253    pub tension: Vec<String>,
254    /// Questions that led to aporia
255    pub triggering_questions: Vec<usize>,
256    /// Potential paths forward
257    pub potential_resolutions: Vec<String>,
258    /// Is this a genuine philosophical puzzle or just confusion?
259    pub genuine_puzzle: bool,
260}
261
262/// Result of Socratic examination
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct SocraticResult {
265    /// Original claim being examined
266    pub claim: String,
267    /// All questions asked
268    pub questions: Vec<SocraticQuestion>,
269    /// Questions by category
270    pub category_coverage: Vec<(QuestionCategory, usize)>,
271    /// Discovered aporias
272    pub aporias: Vec<Aporia>,
273    /// Key insights from examination
274    pub insights: Vec<String>,
275    /// Exposed weaknesses
276    pub weaknesses: Vec<String>,
277    /// Hidden assumptions discovered
278    pub hidden_assumptions: Vec<String>,
279    /// Revised understanding
280    pub revised_claim: Option<String>,
281    /// Confidence in original claim after examination
282    pub post_examination_confidence: f32,
283}
284
285impl SocraticResult {
286    /// Was aporia reached?
287    pub fn reached_aporia(&self) -> bool {
288        self.aporias.iter().any(|a| a.genuine_puzzle)
289    }
290
291    /// Questions answered ratio
292    pub fn answer_rate(&self) -> f32 {
293        if self.questions.is_empty() {
294            return 0.0;
295        }
296        self.questions.iter().filter(|q| q.answered).count() as f32 / self.questions.len() as f32
297    }
298
299    /// Average depth of questioning
300    pub fn avg_depth(&self) -> f32 {
301        if self.questions.is_empty() {
302            return 0.0;
303        }
304        self.questions.iter().map(|q| q.depth as f32).sum::<f32>() / self.questions.len() as f32
305    }
306
307    /// Format examination summary
308    pub fn format_summary(&self) -> String {
309        let answered = self.questions.iter().filter(|q| q.answered).count();
310        format!(
311            "Socratic Examination: {} questions ({}/{} answered), {} assumptions exposed, {} weaknesses found, confidence: {:.0}%",
312            self.questions.len(),
313            answered,
314            self.questions.len(),
315            self.hidden_assumptions.len(),
316            self.weaknesses.len(),
317            self.post_examination_confidence * 100.0
318        )
319    }
320}
321
322/// Prompt templates for Socratic questioning
323pub struct SocraticPrompts;
324
325impl SocraticPrompts {
326    /// Generate initial questions for a claim
327    pub fn examine_claim(claim: &str, categories: &[QuestionCategory]) -> String {
328        let category_guidance: String = categories
329            .iter()
330            .map(|c| format!("- {:?}: {}", c, c.description()))
331            .collect::<Vec<_>>()
332            .join("\n");
333
334        format!(
335            r#"Apply the SOCRATIC METHOD to examine this claim.
336
337CLAIM: {claim}
338
339Generate probing questions in these categories:
340{category_guidance}
341
342For each question:
3431. State the question clearly
3442. Indicate the category
3453. Explain what the question seeks to uncover
3464. Suggest what type of answer would be satisfactory
347
348Generate at least one question per category.
349Be genuinely curious. Seek to understand, not to defeat.
350
351Format:
352QUESTION 1:
353- Category: [category]
354- Question: [the question]
355- Purpose: [what this seeks to uncover]
356- Answer type expected: [definition/evidence/explanation/etc.]
357
358QUESTION 2:
359..."#,
360            claim = claim,
361            category_guidance = category_guidance
362        )
363    }
364
365    /// Generate follow-up questions based on an answer
366    pub fn follow_up(original_question: &str, answer: &str, depth: usize, brutal: bool) -> String {
367        let mode = if brutal {
368            "Be RUTHLESSLY PROBING. Do not accept vague answers. Push for precision."
369        } else {
370            "Be genuinely curious. Seek deeper understanding."
371        };
372
373        format!(
374            r#"Generate follow-up Socratic questions.
375
376ORIGINAL QUESTION: {original_question}
377
378ANSWER PROVIDED: {answer}
379
380CURRENT DEPTH: {depth}
381
382{mode}
383
384Based on this answer:
3851. What remains unclear or undefined?
3862. What assumptions does this answer make?
3873. What evidence supports this answer?
3884. What are the implications of this answer?
3895. What contradictions or tensions exist?
390
391Generate 2-3 follow-up questions that probe deeper.
392
393Format:
394FOLLOW-UP 1:
395- Question: [the question]
396- Category: [what type of question]
397- Purpose: [what this seeks to uncover]
398
399FOLLOW-UP 2:
400..."#,
401            original_question = original_question,
402            answer = answer,
403            depth = depth,
404            mode = mode
405        )
406    }
407
408    /// Identify aporia (productive puzzlement)
409    pub fn detect_aporia(claim: &str, questions: &[String], answers: &[String]) -> String {
410        let qa_pairs: String = questions
411            .iter()
412            .zip(answers.iter())
413            .enumerate()
414            .map(|(i, (q, a))| format!("Q{}: {}\nA{}: {}", i + 1, q, i + 1, a))
415            .collect::<Vec<_>>()
416            .join("\n\n");
417
418        format!(
419            r#"Analyze this Socratic examination for APORIA (genuine puzzlement).
420
421ORIGINAL CLAIM: {claim}
422
423EXAMINATION:
424{qa_pairs}
425
426Aporia occurs when:
4271. Genuinely held beliefs come into conflict
4282. The examination reveals we don't know what we thought we knew
4293. There's no easy resolution without abandoning a cherished belief
430
431Analyze:
4321. Are there tensions between beliefs expressed?
4332. Has any belief been undermined by the questioning?
4343. Is there genuine puzzlement, or just confusion?
4354. What are the potential paths forward?
436
437Format:
438APORIA_DETECTED: [yes/no]
439TENSION: [describe the conflicting beliefs]
440GENUINE_PUZZLE: [is this a real philosophical puzzle or just confusion?]
441POTENTIAL_RESOLUTIONS:
4421. [resolution option 1]
4432. [resolution option 2]
444
445INSIGHTS:
446- [key insight 1]
447- [key insight 2]"#,
448            claim = claim,
449            qa_pairs = qa_pairs
450        )
451    }
452
453    /// BrutalHonesty Socratic examination
454    pub fn brutal_honesty_examine(claim: &str) -> String {
455        format!(
456            r#"Apply BRUTAL HONESTY Socratic examination to this claim.
457
458CLAIM: {claim}
459
460You are the relentless questioner. Your job is to:
4611. EXPOSE every hidden assumption
4622. CHALLENGE every piece of evidence
4633. FIND every weakness
4644. REVEAL every bias
4655. TEST every implication
466
467Ask the HARDEST questions:
468
469CLARIFICATION (be pedantic):
470- What EXACTLY do you mean by each term?
471- How is this not just [alternative interpretation]?
472
473ASSUMPTIONS (be suspicious):
474- What are you ASSUMING that could be completely wrong?
475- What would have to be true that you haven't established?
476
477EVIDENCE (be skeptical):
478- What HARD EVIDENCE supports this?
479- Why should we believe that evidence?
480- What contrary evidence exists?
481
482IMPLICATIONS (be thorough):
483- If this is true, what ELSE must be true?
484- What are the uncomfortable consequences?
485
486DEVIL'S ADVOCATE:
487- What's the STRONGEST case against this?
488- How would your smartest critic respond?
489
490Generate at least 10 probing questions.
491Do not pull punches. Seek truth, not comfort."#,
492            claim = claim
493        )
494    }
495
496    /// Synthesize insights from examination
497    pub fn synthesize(claim: &str, questions: &[String], insights: &[String]) -> String {
498        let questions_formatted: String = questions
499            .iter()
500            .enumerate()
501            .map(|(i, q)| format!("{}. {}", i + 1, q))
502            .collect::<Vec<_>>()
503            .join("\n");
504
505        let insights_formatted: String = insights
506            .iter()
507            .enumerate()
508            .map(|(i, insight)| format!("{}. {}", i + 1, insight))
509            .collect::<Vec<_>>()
510            .join("\n");
511
512        format!(
513            r#"Synthesize the Socratic examination.
514
515ORIGINAL CLAIM: {claim}
516
517QUESTIONS ASKED:
518{questions_formatted}
519
520INSIGHTS DISCOVERED:
521{insights_formatted}
522
523Provide:
5241. REVISED_CLAIM: A more defensible version (if needed)
5252. HIDDEN_ASSUMPTIONS: Assumptions that were exposed
5263. WEAKNESSES: Vulnerabilities in the original claim
5274. STRENGTHS: What remains solid after examination
5285. CONFIDENCE: How confident can we be after this examination? (0-100%)
529
530Format as structured output."#,
531            claim = claim,
532            questions_formatted = questions_formatted,
533            insights_formatted = insights_formatted
534        )
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541
542    #[test]
543    fn test_config_default() {
544        let config = SocraticConfig::default();
545        assert!(!config.brutal_honesty);
546        assert!(config.categories.contains(&QuestionCategory::Clarification));
547        assert!(config.categories.contains(&QuestionCategory::Assumptions));
548    }
549
550    #[test]
551    fn test_brutal_honesty_config() {
552        let config = SocraticConfig::brutal_honesty();
553        assert!(config.brutal_honesty);
554        assert!(config
555            .categories
556            .contains(&QuestionCategory::DevilsAdvocate));
557        assert!(config.categories.contains(&QuestionCategory::SteelMan));
558        assert!(config.max_questions >= 15);
559    }
560
561    #[test]
562    fn test_question_categories() {
563        let cat = QuestionCategory::Assumptions;
564        assert!(cat.description().contains("assumptions"));
565        assert!(!cat.examples().is_empty());
566    }
567
568    #[test]
569    fn test_socratic_result() {
570        let result = SocraticResult {
571            claim: "All swans are white".into(),
572            questions: vec![
573                SocraticQuestion {
574                    id: 0,
575                    question: "What do you mean by 'all'?".into(),
576                    category: QuestionCategory::Clarification,
577                    follows_up: None,
578                    depth: 0,
579                    answer_type: AnswerType::Definition,
580                    answered: true,
581                    answer: Some("Every swan that exists".into()),
582                },
583                SocraticQuestion {
584                    id: 1,
585                    question: "Have you observed all swans?".into(),
586                    category: QuestionCategory::Evidence,
587                    follows_up: Some(0),
588                    depth: 1,
589                    answer_type: AnswerType::Evidence,
590                    answered: true,
591                    answer: Some("No, but all I've seen are white".into()),
592                },
593            ],
594            category_coverage: vec![
595                (QuestionCategory::Clarification, 1),
596                (QuestionCategory::Evidence, 1),
597            ],
598            aporias: vec![],
599            insights: vec!["Claim is based on limited observation".into()],
600            weaknesses: vec!["Cannot verify claim about unobserved swans".into()],
601            hidden_assumptions: vec!["Future swans will be like past swans".into()],
602            revised_claim: Some("All observed swans have been white".into()),
603            post_examination_confidence: 0.4,
604        };
605
606        assert_eq!(result.answer_rate(), 1.0);
607        assert!(!result.reached_aporia());
608        assert!(result.format_summary().contains("2/2 answered"));
609    }
610
611    #[test]
612    fn test_aporia() {
613        let aporia = Aporia {
614            description: "We believe in free will but also causation".into(),
615            tension: vec![
616                "Free will requires uncaused choices".into(),
617                "All events are caused".into(),
618            ],
619            triggering_questions: vec![3, 5],
620            potential_resolutions: vec!["Compatibilism".into(), "Libertarian free will".into()],
621            genuine_puzzle: true,
622        };
623
624        assert!(aporia.genuine_puzzle);
625        assert_eq!(aporia.tension.len(), 2);
626    }
627
628    #[test]
629    fn test_avg_depth() {
630        let result = SocraticResult {
631            claim: "Test".into(),
632            questions: vec![
633                SocraticQuestion {
634                    id: 0,
635                    question: "Q1".into(),
636                    category: QuestionCategory::Clarification,
637                    follows_up: None,
638                    depth: 0,
639                    answer_type: AnswerType::Definition,
640                    answered: true,
641                    answer: None,
642                },
643                SocraticQuestion {
644                    id: 1,
645                    question: "Q2".into(),
646                    category: QuestionCategory::Evidence,
647                    follows_up: Some(0),
648                    depth: 1,
649                    answer_type: AnswerType::Evidence,
650                    answered: false,
651                    answer: None,
652                },
653                SocraticQuestion {
654                    id: 2,
655                    question: "Q3".into(),
656                    category: QuestionCategory::Implications,
657                    follows_up: Some(1),
658                    depth: 2,
659                    answer_type: AnswerType::Explanation,
660                    answered: true,
661                    answer: None,
662                },
663            ],
664            category_coverage: vec![],
665            aporias: vec![],
666            insights: vec![],
667            weaknesses: vec![],
668            hidden_assumptions: vec![],
669            revised_claim: None,
670            post_examination_confidence: 0.5,
671        };
672
673        assert!((result.avg_depth() - 1.0).abs() < 0.01);
674        assert!((result.answer_rate() - 2.0 / 3.0).abs() < 0.01);
675    }
676}