Platform restructured into crates/, added AI service and detector,
migrated control-center-ui to Leptos 0.8
324 lines
12 KiB
Rust
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(())
|
|
}
|