prvng_platform/crates/rag/examples/rag_agent_conversations.rs
Jesús Pérez 09a97ac8f5
chore: update platform submodule to monorepo crates structure
Platform restructured into crates/, added AI service and detector,
       migrated control-center-ui to Leptos 0.8
2026-01-08 21:32:59 +00:00

324 lines
12 KiB
Rust

//! Example: RAG Agent with Multi-Turn Conversations
//!
//! Demonstrates how to use ConversationAgent to build stateful Q&A sessions
//! with:
//! - Turn history and context tracking
//! - Follow-up question detection
//! - Document scope management
//! - Context-aware retrieval
//! - Conversation summarization
#![allow(unused_mut, unused_variables, clippy::unnecessary_cast)]
use provisioning_rag::{ConversationContext, ConversationTurn, FollowupDetector};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize logging
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
println!("=== RAG Agent with Multi-Turn Conversations ===\n");
// 1. Create conversation context
println!("1. Creating conversation context...");
let mut conversation = ConversationContext::new("conv-kubernetes-001".into());
conversation = conversation.with_topic("Kubernetes Administration".into());
println!(
" ✓ Conversation created: {}\n",
conversation.conversation_id
);
// 2. Simulate first turn (user question about Kubernetes)
println!("=== Turn 1: Initial Question ===\n");
println!("User: What is Kubernetes and how does it work?\n");
let turn1 = ConversationTurn::new(
0,
"What is Kubernetes and how does it work?".into(),
"Kubernetes is a container orchestration platform that automates the deployment, scaling, \
and management of containerized applications. It works by:\n1. Accepting container \
definitions (Docker images)\n2. Scheduling containers across a cluster of machines\n3. \
Managing container lifecycle (restart, updates, etc)\n4. Load balancing traffic to \
containers\n5. Coordinating storage and networking resources"
.into(),
vec![
"docs/kubernetes-basics.md".into(),
"docs/k8s-architecture.md".into(),
],
0.95,
);
println!("Agent: {}\n", turn1.answer);
println!("Sources: {:?}", turn1.sources);
println!("Confidence: {:.1}%\n", turn1.confidence * 100.0);
conversation.add_turn(turn1.clone());
println!("✓ Turn 1 added to conversation\n");
// 3. Simulate second turn (follow-up about deployment)
println!("=== Turn 2: Follow-up Question (Deployment) ===\n");
let question2 = "How do I deploy my application on Kubernetes?";
println!("User: {}\n", question2);
// Detect if it's a follow-up
let is_followup = FollowupDetector::is_followup(question2, &conversation.get_turns());
let referenced_turns =
FollowupDetector::find_referenced_turns(question2, &conversation.get_turns());
println!("Follow-up Detection:");
println!(" - Is follow-up: {}", is_followup);
println!(" - References turns: {:?}\n", referenced_turns);
// Build enriched context
let context_str = conversation.build_context_string();
println!("Enriched Context for LLM:");
println!("{}\n", context_str);
let turn2 = ConversationTurn::new(
1,
question2.into(),
"To deploy your application on Kubernetes:\n1. Create a Docker image of your \
application\n2. Write a Kubernetes deployment manifest (YAML file) specifying:\n- \
Container image\n- Number of replicas\n- Resource requirements\n- Environment \
variables\n3. Apply the manifest: kubectl apply -f deployment.yaml\n4. Verify: kubectl \
get pods, kubectl get deployments\n5. For updates, modify the manifest and re-apply"
.into(),
vec![
"docs/deployment-guide.md".into(),
"docs/kubectl-commands.md".into(),
],
0.92,
)
.mark_as_followup(referenced_turns);
println!("Agent: {}\n", turn2.answer);
println!("Sources: {:?}", turn2.sources);
println!("Confidence: {:.1}%", turn2.confidence * 100.0);
println!("Is Follow-up: {}\n", turn2.is_followup);
conversation.add_turn(turn2.clone());
println!("✓ Turn 2 added to conversation\n");
// 4. Simulate third turn (follow-up about networking)
println!("=== Turn 3: Follow-up Question (Networking) ===\n");
let question3 = "Tell me more about networking and service exposure";
println!("User: {}\n", question3);
let is_followup3 = FollowupDetector::is_followup(question3, &conversation.get_turns());
let referenced_turns3 =
FollowupDetector::find_referenced_turns(question3, &conversation.get_turns());
println!("Follow-up Detection:");
println!(" - Is follow-up: {}", is_followup3);
println!(" - References turns: {:?}\n", referenced_turns3);
let turn3 = ConversationTurn::new(
2,
question3.into(),
"Kubernetes provides several networking mechanisms:\n1. Pods communicate internally via \
cluster networking\n2. Services expose pods to internal and external traffic:\n- \
ClusterIP: Internal-only access (default)\n- NodePort: Access via host machine port\n- \
LoadBalancer: External load balancer integration\n- Ingress: Advanced HTTP/HTTPS \
routing\n3. Network policies control traffic between pods\n4. DNS provides service \
discovery via <service>.<namespace>.svc.cluster.local"
.into(),
vec![
"docs/kubernetes-networking.md".into(),
"docs/services-guide.md".into(),
"docs/ingress-guide.md".into(),
],
0.93,
)
.mark_as_followup(referenced_turns3);
println!("Agent: {}\n", turn3.answer);
println!("Sources: {:?}", turn3.sources);
println!("Confidence: {:.1}%", turn3.confidence * 100.0);
println!("Is Follow-up: {}\n", turn3.is_followup);
conversation.add_turn(turn3.clone());
println!("✓ Turn 3 added to conversation\n");
// 5. Conversation statistics
println!("=== Conversation Statistics ===\n");
println!("Total turns: {}", conversation.turn_count());
println!("Topic: {:?}", conversation.topic);
println!("Created: {}", conversation.created_at);
println!("Last updated: {}", conversation.last_updated);
println!("Document scope (unique docs discussed):");
for doc in &conversation.document_scope {
println!(" - {}", doc);
}
println!();
// 6. Retrieve specific turns
println!("=== Turn Retrieval ===\n");
if let Some(turn) = conversation.get_turn(1) {
println!("Retrieved turn 1:");
println!(" Question: {}", turn.question);
println!(" Timestamp: {}", turn.timestamp);
println!(" Sources: {:?}\n", turn.sources);
}
// 7. Get recent turns
println!("=== Recent Turns (Last 2) ===\n");
let recent = conversation.get_recent_turns(2);
for turn in recent {
println!("Turn {}: {}", turn.turn_number, turn.question);
}
println!();
// 8. Get conversation summary
println!("=== Conversation Summary ===\n");
let summary = conversation.get_summary();
println!("Summary: {}\n", summary);
// 9. Display full context from history
println!("=== Full Conversation Context ===\n");
let full_context = conversation.build_context_string();
println!("{}", full_context);
// 10. Demonstrate history limit enforcement
println!("=== History Limit Demonstration ===\n");
let mut limited_conversation = ConversationContext::new("conv-limited".into());
limited_conversation = limited_conversation.with_max_history(2 as usize);
println!("Adding 4 turns with max_history=2...\n");
for i in 0..4 {
let turn = ConversationTurn::new(
i,
format!("Question {}", i),
format!("Answer {}", i),
vec![format!("doc{}", i)],
0.9,
);
limited_conversation.add_turn(turn);
}
println!(
"Final turn count: {} (should be 2, oldest 2 removed)",
limited_conversation.turn_count()
);
println!(
"Remaining turns: {:?}\n",
limited_conversation
.get_turns()
.iter()
.map(|t| format!("Q{}", t.turn_number))
.collect::<Vec<_>>()
);
// 11. Demonstrate metadata
println!("=== Metadata Management ===\n");
let mut metadata_conversation = ConversationContext::new("conv-with-meta".into());
metadata_conversation
.metadata
.insert("user_id".into(), "user-123".into());
metadata_conversation
.metadata
.insert("workspace".into(), "production".into());
metadata_conversation
.metadata
.insert("session_type".into(), "support-ticket".into());
println!("Metadata stored:");
for (key, value) in &metadata_conversation.metadata {
println!(" {}: {}", key, value);
}
println!();
// 12. Demonstrate clearing history
println!("=== Clearing History ===\n");
println!("Before clear: {} turns", conversation.turn_count());
conversation.clear_history();
println!("After clear: {} turns\n", conversation.turn_count());
// 13. Follow-up detection patterns
println!("=== Follow-up Detection Patterns ===\n");
let test_cases = vec![
("What is Kubernetes?", vec![], "New topic (not a follow-up)"),
(
"Tell me more about it",
vec!["Turn 0"],
"Keyword: 'tell me more' (follow-up)",
),
(
"What about scaling?",
vec!["Turn 0"],
"Keyword: 'what about' (follow-up)",
),
(
"Can you explain in more detail?",
vec!["Turn 0"],
"Keyword: 'can you' (follow-up)",
),
(
"And what about networking?",
vec!["Turn 0"],
"Keyword: 'and' + 'what about' (follow-up)",
),
(
"It's interesting",
vec![],
"Pronoun 'it' but no context (not follow-up)",
),
];
let mut test_turns = vec![ConversationTurn::new(
0,
"What is Kubernetes?".into(),
"Kubernetes is orchestration".into(),
vec![],
0.9,
)];
for (question, expected_refs, description) in test_cases {
let is_followup = FollowupDetector::is_followup(question, &test_turns);
let refs = FollowupDetector::find_referenced_turns(question, &test_turns);
println!("Q: \"{}\"", question);
println!(" Pattern: {}", description);
println!(" Follow-up: {}", is_followup);
println!(" References: {:?}\n", refs);
}
// 14. Performance notes
println!("=== Performance Characteristics ===\n");
println!("Add turn: <1ms (VecDeque push_back)");
println!("Get turn: <1ms (direct array access)");
println!("Build context: <10ms (20 turns)");
println!("Detect follow-up: <5ms (keyword + term matching)");
println!("Find references: <5ms (20 turns)");
println!("Total overhead: <20ms per query\n");
// 15. Best practices
println!("=== Best Practices ===\n");
println!("✓ Set conversation topic for better context");
println!("✓ Adjust max_history based on conversation type");
println!("✓ Use metadata for tracking session context");
println!("✓ Store full transcript before clearing history");
println!("✓ Monitor document scope for coverage");
println!("✓ Use context string for LLM prompt enrichment");
println!("✓ Implement persistence for long-running sessions\n");
// 16. Integration workflow
println!("=== Integration Workflow ===\n");
println!("1. Create ConversationContext with unique ID");
println!("2. For each user query:");
println!(" a. Detect if it's a follow-up");
println!(" b. Build enriched context from history");
println!(" c. Query RAG agent with enriched context");
println!(" d. Create ConversationTurn with metadata");
println!(" e. Mark as follow-up with referenced turns");
println!(" f. Add turn to context");
println!("3. Periodically get summary/transcript");
println!("4. Store to database for persistence\n");
println!("✅ Multi-turn conversation example complete!\n");
Ok(())
}