398 lines
12 KiB
Rust
398 lines
12 KiB
Rust
use rtx_llm_tools::{
|
||
error::{LlmToolsError, ReasoningError},
|
||
reasoning::{
|
||
ChainOfThought, Evidence, ReasoningContext, ReasoningEngine, ReasoningStep, StepValidator,
|
||
},
|
||
};
|
||
|
||
#[tokio::test]
|
||
async fn test_basic_reasoning_chain() {
|
||
let mut chain = ChainOfThought::new("basic_logic");
|
||
|
||
let step1 = ReasoningStep::new(
|
||
"step1",
|
||
"All humans are mortal",
|
||
vec![Evidence::new(
|
||
"premise",
|
||
"Universal truth about human mortality",
|
||
)],
|
||
"Humans have finite lifespans",
|
||
);
|
||
|
||
let step2 = ReasoningStep::new(
|
||
"step2",
|
||
"Socrates is human",
|
||
vec![Evidence::new(
|
||
"fact",
|
||
"Historical record confirms Socrates was human",
|
||
)],
|
||
"Socrates belongs to the category of humans",
|
||
);
|
||
|
||
let step3 = ReasoningStep::new(
|
||
"step3",
|
||
"Therefore, Socrates is mortal",
|
||
vec![
|
||
Evidence::new("from_step1", "step1"),
|
||
Evidence::new("from_step2", "step2"),
|
||
],
|
||
"By logical deduction, Socrates must be mortal",
|
||
);
|
||
|
||
chain.add_step(step1).await.unwrap();
|
||
chain.add_step(step2).await.unwrap();
|
||
chain.add_step(step3).await.unwrap();
|
||
|
||
let result = chain.execute().await.unwrap();
|
||
assert!(result.is_valid);
|
||
assert_eq!(
|
||
result.conclusion,
|
||
"By logical deduction, Socrates must be mortal"
|
||
);
|
||
assert_eq!(result.steps_executed, 3);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_step_validation_failure() {
|
||
let mut chain = ChainOfThought::new("invalid_logic");
|
||
|
||
let step1 = ReasoningStep::new(
|
||
"step1",
|
||
"All birds can fly",
|
||
vec![Evidence::new(
|
||
"assumption",
|
||
"Common but incorrect assumption",
|
||
)],
|
||
"Every bird has flight capability",
|
||
);
|
||
|
||
let step2 = ReasoningStep::new(
|
||
"step2",
|
||
"Penguins are birds",
|
||
vec![Evidence::new("fact", "Penguins are classified as birds")],
|
||
"Penguins belong to the bird category",
|
||
);
|
||
|
||
let step3 = ReasoningStep::new(
|
||
"step3",
|
||
"Therefore, penguins can fly",
|
||
vec![
|
||
Evidence::new("from_step1", "step1"),
|
||
Evidence::new("from_step2", "step2"),
|
||
],
|
||
"By deduction, penguins should be able to fly",
|
||
);
|
||
|
||
// Add a validator that knows penguins can't fly
|
||
let mut validator = StepValidator::new();
|
||
validator.add_contradiction_rule("penguin_flight", |step: &ReasoningStep| {
|
||
step.conclusion.contains("penguins") && step.conclusion.contains("fly")
|
||
});
|
||
|
||
chain.set_validator(validator);
|
||
chain.add_step(step1).await.unwrap();
|
||
chain.add_step(step2).await.unwrap();
|
||
chain.add_step(step3).await.unwrap();
|
||
|
||
let result = chain.execute().await;
|
||
assert!(result.is_err());
|
||
|
||
match result.unwrap_err() {
|
||
LlmToolsError::Reasoning(ReasoningError::Contradiction { step1, step2 }) => {
|
||
assert!(step1.contains("step3") || step2.contains("step3"));
|
||
}
|
||
_ => panic!("Expected contradiction error"),
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_evidence_insufficiency() {
|
||
let mut chain = ChainOfThought::new("insufficient_evidence");
|
||
|
||
let step = ReasoningStep::new(
|
||
"step1",
|
||
"Quantum consciousness exists",
|
||
vec![], // No evidence provided
|
||
"Human consciousness operates on quantum principles",
|
||
);
|
||
|
||
chain.add_step(step).await.unwrap();
|
||
|
||
let result = chain.execute().await;
|
||
assert!(result.is_err());
|
||
|
||
match result.unwrap_err() {
|
||
LlmToolsError::Reasoning(ReasoningError::InsufficientEvidence { step_id }) => {
|
||
assert_eq!(step_id, "step1");
|
||
}
|
||
_ => panic!("Expected insufficient evidence error"),
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_circular_reasoning_detection() {
|
||
let mut chain = ChainOfThought::new("circular");
|
||
|
||
let step1 = ReasoningStep::new(
|
||
"step1",
|
||
"A is true because B is true",
|
||
vec![Evidence::new("from_step2", "step2")],
|
||
"A is established",
|
||
);
|
||
|
||
let step2 = ReasoningStep::new(
|
||
"step2",
|
||
"B is true because A is true",
|
||
vec![Evidence::new("from_step1", "step1")],
|
||
"B is established",
|
||
);
|
||
|
||
chain.add_step(step1).await.unwrap();
|
||
chain.add_step(step2).await.unwrap();
|
||
|
||
let result = chain.execute().await;
|
||
assert!(result.is_err());
|
||
|
||
match result.unwrap_err() {
|
||
LlmToolsError::Reasoning(ReasoningError::CircularReasoning) => {
|
||
// Expected
|
||
}
|
||
_ => panic!("Expected circular reasoning error"),
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_reasoning_context_variables() {
|
||
let mut context = ReasoningContext::new();
|
||
context.set_variable("domain", "mathematics");
|
||
context.set_variable("rigor_level", "formal");
|
||
|
||
let mut chain = ChainOfThought::with_context("math_proof", context);
|
||
|
||
let step = ReasoningStep::new(
|
||
"step1",
|
||
"For all real numbers x, x² ≥ 0",
|
||
vec![Evidence::new(
|
||
"axiom",
|
||
"Property of real numbers and multiplication",
|
||
)],
|
||
"Squares of real numbers are non-negative",
|
||
);
|
||
|
||
chain.add_step(step).await.unwrap();
|
||
|
||
let result = chain.execute().await.unwrap();
|
||
assert!(result.is_valid);
|
||
|
||
let context = chain.get_context();
|
||
assert_eq!(context.get_variable("domain"), Some("mathematics"));
|
||
assert_eq!(context.get_variable("rigor_level"), Some("formal"));
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_step_branching() {
|
||
let mut chain = ChainOfThought::new("branching");
|
||
|
||
// Main premise
|
||
let main_step = ReasoningStep::new(
|
||
"main",
|
||
"We need to solve the climate crisis",
|
||
vec![Evidence::new(
|
||
"consensus",
|
||
"Scientific consensus on climate change",
|
||
)],
|
||
"Action is required to address climate change",
|
||
);
|
||
|
||
// Branch 1: Technology solution
|
||
let tech_step = ReasoningStep::new(
|
||
"tech_branch",
|
||
"Technology can provide solutions",
|
||
vec![Evidence::new("from_main", "main")],
|
||
"Renewable energy and innovation can help",
|
||
);
|
||
|
||
// Branch 2: Policy solution
|
||
let policy_step = ReasoningStep::new(
|
||
"policy_branch",
|
||
"Policy changes are necessary",
|
||
vec![Evidence::new("from_main", "main")],
|
||
"Regulation and incentives can drive change",
|
||
);
|
||
|
||
// Convergence step
|
||
let convergence_step = ReasoningStep::new(
|
||
"convergence",
|
||
"Multiple approaches needed",
|
||
vec![
|
||
Evidence::new("from_tech", "tech_branch"),
|
||
Evidence::new("from_policy", "policy_branch"),
|
||
],
|
||
"Both technological and policy solutions are required",
|
||
);
|
||
|
||
chain.add_step(main_step).await.unwrap();
|
||
chain.add_step_as_branch(tech_step, "main").await.unwrap();
|
||
chain.add_step_as_branch(policy_step, "main").await.unwrap();
|
||
chain.add_step(convergence_step).await.unwrap();
|
||
|
||
let result = chain.execute().await.unwrap();
|
||
assert!(result.is_valid);
|
||
assert_eq!(result.steps_executed, 4);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_reasoning_engine_with_templates() {
|
||
let mut engine = ReasoningEngine::new();
|
||
|
||
// Register a reasoning template for scientific method
|
||
engine
|
||
.register_template(
|
||
"scientific_method",
|
||
vec![
|
||
"observation".to_string(),
|
||
"hypothesis".to_string(),
|
||
"prediction".to_string(),
|
||
"experiment".to_string(),
|
||
"analysis".to_string(),
|
||
"conclusion".to_string(),
|
||
],
|
||
)
|
||
.await
|
||
.unwrap();
|
||
|
||
let mut context = ReasoningContext::new();
|
||
context.set_variable("subject", "plant growth");
|
||
context.set_variable("hypothesis", "Plants grow faster with more sunlight");
|
||
|
||
let chain = engine
|
||
.create_chain_from_template("scientific_method", context)
|
||
.await
|
||
.unwrap();
|
||
assert_eq!(chain.expected_steps(), 6);
|
||
assert_eq!(chain.get_template_name(), Some("scientific_method"));
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_reasoning_with_uncertainty() {
|
||
let mut chain = ChainOfThought::new("uncertainty");
|
||
|
||
let mut step1 = ReasoningStep::new(
|
||
"step1",
|
||
"There's a 70% chance of rain tomorrow",
|
||
vec![Evidence::new("weather_model", "Meteorological prediction")],
|
||
"Rain is likely but not certain",
|
||
);
|
||
step1.set_confidence(0.7);
|
||
|
||
let mut step2 = ReasoningStep::new(
|
||
"step2",
|
||
"If it rains, the picnic will be cancelled",
|
||
vec![Evidence::new("policy", "Event cancellation policy")],
|
||
"Rain would force picnic cancellation",
|
||
);
|
||
step2.set_confidence(0.9);
|
||
|
||
let step3 = ReasoningStep::new(
|
||
"step3",
|
||
"Therefore, there's about a 63% chance the picnic will be cancelled",
|
||
vec![
|
||
Evidence::new("from_step1", "step1"),
|
||
Evidence::new("from_step2", "step2"),
|
||
],
|
||
"Probability of cancellation is 0.7 × 0.9 = 0.63",
|
||
);
|
||
|
||
chain.add_step(step1).await.unwrap();
|
||
chain.add_step(step2).await.unwrap();
|
||
chain.add_step(step3).await.unwrap();
|
||
|
||
let result = chain.execute().await.unwrap();
|
||
assert!(result.is_valid);
|
||
assert!((result.overall_confidence - 0.63).abs() < 0.01);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_reasoning_timeout() {
|
||
let mut chain = ChainOfThought::new("timeout_test");
|
||
chain.set_timeout(std::time::Duration::from_millis(100));
|
||
|
||
// Add a step that simulates long computation
|
||
let slow_step = ReasoningStep::new(
|
||
"slow",
|
||
"Complex calculation requiring time",
|
||
vec![Evidence::new("computation", "Mathematical proof")],
|
||
"Result of lengthy computation",
|
||
);
|
||
|
||
chain.add_step(slow_step).await.unwrap();
|
||
|
||
// Mock long execution time
|
||
let result = chain.execute().await;
|
||
// This should timeout (depending on implementation details)
|
||
// For now, just verify it completes or times out appropriately
|
||
assert!(
|
||
result.is_ok()
|
||
|| matches!(
|
||
result.unwrap_err(),
|
||
LlmToolsError::Reasoning(ReasoningError::Timeout { .. })
|
||
)
|
||
);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_step_dependency_validation() {
|
||
let mut chain = ChainOfThought::new("dependency_validation");
|
||
|
||
// Try to add a step that depends on a non-existent step
|
||
let invalid_step = ReasoningStep::new(
|
||
"invalid",
|
||
"This depends on missing step",
|
||
vec![Evidence::new("from_missing", "nonexistent_step")],
|
||
"Should fail validation",
|
||
);
|
||
|
||
chain.add_step(invalid_step).await.unwrap();
|
||
|
||
let result = chain.execute().await;
|
||
assert!(result.is_err());
|
||
|
||
match result.unwrap_err() {
|
||
LlmToolsError::Reasoning(ReasoningError::InsufficientEvidence { .. }) => {
|
||
// Expected - evidence references non-existent step
|
||
}
|
||
_ => panic!("Expected insufficient evidence error due to missing dependency"),
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_reasoning_step_recovery() {
|
||
let mut chain = ChainOfThought::new("recovery");
|
||
chain.enable_error_recovery(true);
|
||
|
||
let faulty_step = ReasoningStep::new(
|
||
"faulty",
|
||
"This step has issues",
|
||
vec![Evidence::new("weak", "Questionable source")],
|
||
"Dubious conclusion",
|
||
);
|
||
|
||
let recovery_step = ReasoningStep::new(
|
||
"recovery",
|
||
"Alternative approach",
|
||
vec![Evidence::new("strong", "Reliable source")],
|
||
"Better conclusion",
|
||
);
|
||
|
||
chain.add_step(faulty_step).await.unwrap();
|
||
chain
|
||
.add_recovery_step("faulty", recovery_step)
|
||
.await
|
||
.unwrap();
|
||
|
||
let result = chain.execute().await.unwrap();
|
||
assert!(result.is_valid);
|
||
assert!(result.recovered_from_errors);
|
||
assert_eq!(result.recovery_count, 1);
|
||
}
|