//! RAG Agent using Rig framework for Claude integration use serde::{Deserialize, Serialize}; use crate::context::WorkspaceContext; use crate::error::Result; use crate::llm::LlmClient; use crate::retrieval::{RetrieverEngine, SearchResult}; /// RAG Agent response #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentResponse { /// The answer to the user's question pub answer: String, /// Source documents used for the answer pub sources: Vec, /// Confidence score (0.0-1.0) pub confidence: f32, /// Retrieved context used pub context: String, } /// RAG Agent that answers questions using retrieved documents pub struct RagAgent { retriever: RetrieverEngine, workspace_context: WorkspaceContext, llm_client: LlmClient, } impl RagAgent { /// Create a new RAG agent with Claude LLM pub fn new( retriever: RetrieverEngine, workspace_context: WorkspaceContext, llm_model: String, ) -> Result { let llm_client = LlmClient::new(llm_model)?; Ok(Self { retriever, workspace_context, llm_client, }) } /// Ask a question and get an answer based on retrieved documents pub async fn ask(&self, question: &str) -> Result { // 1. Enrich query with workspace context let enriched_query = self.workspace_context.enrich_query(question); tracing::info!("Processing question: {}", question); tracing::debug!("Enriched query: {}", enriched_query); // 2. Retrieve relevant documents let sources = self.retriever.search(&enriched_query, None).await?; tracing::info!("Retrieved {} documents", sources.len()); if sources.is_empty() { return Ok(AgentResponse { answer: "I could not find relevant information to answer your question. Please \ provide more context or check if the documentation has been indexed." .to_string(), sources: vec![], confidence: 0.0, context: "No documents found".to_string(), }); } // 3. Build context from retrieved documents let context = build_context(&sources); // 4. Generate answer using Claude API let answer = self .llm_client .generate_answer(&enriched_query, &context) .await?; // 5. Calculate confidence based on number and quality of sources let confidence = (sources.len() as f32 / 5.0).min(1.0); Ok(AgentResponse { answer, sources, confidence, context, }) } /// Get agent metadata pub fn metadata(&self) -> AgentMetadata { AgentMetadata { model: self.llm_client.model.clone(), workspace: self.workspace_context.workspace_name.clone(), version: "0.1.0".to_string(), } } } /// Agent metadata #[derive(Debug, Clone, Serialize)] pub struct AgentMetadata { pub model: String, pub workspace: String, pub version: String, } /// Build context string from retrieved documents fn build_context(sources: &[SearchResult]) -> String { let mut context = String::from("# Retrieved Context\n\n"); for (idx, source) in sources.iter().enumerate() { context.push_str(&format!("## Document {}: {}\n", idx + 1, source.doc_id)); context.push_str(&format!("**Type**: {}\n", source.doc_type)); context.push_str(&format!("**Source**: {}\n", source.source_path)); if !source.metadata.is_empty() { context.push_str("**Metadata**:\n"); for (key, value) in &source.metadata { context.push_str(&format!(" - {}: {}\n", key, value)); } } context.push_str(&format!("\n{}\n\n", source.content)); } context } #[cfg(test)] mod tests { use super::*; #[test] fn test_context_building() { let sources = vec![ SearchResult { doc_id: "doc-1".to_string(), source_path: "docs/arch.md".to_string(), doc_type: "markdown".to_string(), content: "System architecture details".to_string(), similarity: 0.9, metadata: std::collections::HashMap::new(), }, SearchResult { doc_id: "doc-2".to_string(), source_path: "docs/deploy.md".to_string(), doc_type: "markdown".to_string(), content: "Deployment procedures".to_string(), similarity: 0.85, metadata: std::collections::HashMap::new(), }, ]; let context = build_context(&sources); assert!(context.contains("Retrieved Context")); assert!(context.contains("doc-1")); assert!(context.contains("doc-2")); assert!(context.contains("markdown")); } }