1use serde::{Deserialize, Serialize};
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SocraticConfig {
43 pub categories: Vec<QuestionCategory>,
45 pub min_questions_per_category: usize,
47 pub max_questions: usize,
49 pub follow_up_depth: usize,
51 pub detect_aporia: bool,
53 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
118pub enum QuestionCategory {
119 Clarification,
121 Assumptions,
123 Evidence,
125 Viewpoints,
127 Implications,
129 MetaQuestions,
131 DevilsAdvocate,
133 SteelMan,
135}
136
137impl QuestionCategory {
138 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct SocraticQuestion {
210 pub id: usize,
212 pub question: String,
214 pub category: QuestionCategory,
216 pub follows_up: Option<usize>,
218 pub depth: usize,
220 pub answer_type: AnswerType,
222 pub answered: bool,
224 pub answer: Option<String>,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230pub enum AnswerType {
231 Definition,
233 Evidence,
235 Explanation,
237 Uncertainty,
239 CounterExample,
241 Reformulation,
243 Concession,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct Aporia {
250 pub description: String,
252 pub tension: Vec<String>,
254 pub triggering_questions: Vec<usize>,
256 pub potential_resolutions: Vec<String>,
258 pub genuine_puzzle: bool,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct SocraticResult {
265 pub claim: String,
267 pub questions: Vec<SocraticQuestion>,
269 pub category_coverage: Vec<(QuestionCategory, usize)>,
271 pub aporias: Vec<Aporia>,
273 pub insights: Vec<String>,
275 pub weaknesses: Vec<String>,
277 pub hidden_assumptions: Vec<String>,
279 pub revised_claim: Option<String>,
281 pub post_examination_confidence: f32,
283}
284
285impl SocraticResult {
286 pub fn reached_aporia(&self) -> bool {
288 self.aporias.iter().any(|a| a.genuine_puzzle)
289 }
290
291 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 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 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
322pub struct SocraticPrompts;
324
325impl SocraticPrompts {
326 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 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 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 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 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}