rag-module 0.6.7

Enterprise RAG module with chat context storage, vector search, session management, and model downloading. Rust implementation with Node.js compatibility.
use anyhow::Result;
use rag_module::RagModule;
use rag_module::services::search_service::ChatSearchOptions;

#[tokio::main]
async fn main() -> Result<()> {
    println!("šŸ”„ Continue Chat Example: Adding to Existing Conversation");
    println!("This example continues an existing context with new messages\n");

    // Detect which mode we're running in
    let qdrant_url = std::env::var("QDRANT_URL").ok();
    let mode = match &qdrant_url {
        Some(url) if url.contains("amazonaws.com") => "ā˜ļø AWS Cloud Mode",
        Some(_) => "🌐 Local Server Mode",
        None => "šŸ’¾ Embedded Mode",
    };

    println!("Running in: {}", mode);
    if let Some(url) = &qdrant_url {
        println!("Qdrant URL: {}", url);
    }
    println!("─────────────────────────────────────────────────────────\n");

    // Initialize RAG module with SAME path as complete_chat_example
    let mut rag_module = RagModule::new("./example-enhanced-chat-data").await?;
    rag_module.initialize().await?;

    println!("āœ… RAG Module initialized\n");

    // Use SAME user_id and context_id as complete_chat_example
    let user_id = "cudewvsbxakj1n";
    let context_id = "test5"; // Same context as before - messages should continue from existing index
    let chat_title = Some("Complete Chat Demo - REST vs GraphQL"); // Same title

    rag_module.set_user_context(user_id).await?;

    println!("šŸ”„ Continuing existing conversation...");
    println!("āœ… Context ID: {} (reusing existing context)", context_id);
    println!("šŸ“ User ID: {}\n", user_id);

    // First, retrieve existing conversation to see current state
    println!("šŸ“š Retrieving existing conversation history...");
    let existing_history = rag_module.get_query_response_pairs(context_id, None).await?;
    println!("āœ… Found {} existing conversation pairs\n", existing_history.total_pairs);

    // Show existing conversation
    if existing_history.total_pairs > 0 {
        println!("šŸ“– EXISTING CONVERSATION:");
        println!("─────────────────────────────────────────────────────");
        for (i, pair) in existing_history.pairs.iter().enumerate() {
            println!("Turn {}: User: {}", i + 1, pair.query.content);
            println!("Turn {}: Assistant: {}...\n", i + 1, &pair.response.content[..100]);
        }
        println!("─────────────────────────────────────────────────────\n");
    }

    // === NEW TURN 4: Continue with different topic but related ===
    println!("šŸ’¬ NEW TURN 4: Asking about performance");
    let prompt4 = "Which one is faster in terms of performance?";
    println!("User: {}", prompt4);

    let prompt_id4 = rag_module.add_prompt(context_id, prompt4, user_id, chat_title).await?;
    println!("āœ… Prompt stored (ID: {})", prompt_id4);

    let response4 = "Performance comparison depends on the use case:\n\n**REST is typically faster when**:\n- Simple queries with full resource access\n- Using HTTP caching effectively\n- Accessing single resources\n- Example: GET /users/123 with browser cache = instant!\n\n**GraphQL can be faster when**:\n- Need multiple resources (saves round trips)\n- Mobile with slow networks (smaller payloads)\n- Complex nested data requirements\n\n**Real example**:\n```\nREST: 3 requests for user + posts + comments\n- /users/123 (200ms)\n- /posts?userId=123 (300ms) \n- /comments?userId=123 (250ms)\nTotal: 750ms + overhead\n\nGraphQL: 1 request\n- POST /graphql (400ms)\nTotal: 400ms!\n```\n\n**Bottom line**: GraphQL wins for complex data fetching, REST wins for simple cached resources. It's about optimizing for your specific access patterns, which relates to everything we discussed about over-fetching and caching.";

    let response_id4 = rag_module.add_response(context_id, response4, user_id, chat_title).await?;
    println!("Assistant: {}...", &response4[..120]);
    println!("āœ… Response stored (ID: {})\n", response_id4);

    // === NEW TURN 5: Ask about implementation ===
    println!("šŸ’¬ NEW TURN 5: Implementation question");
    let prompt5 = "How do I decide which one to use for a new mobile app project?";
    println!("User: {}", prompt5);

    let prompt_id5 = rag_module.add_prompt(context_id, prompt5, user_id, chat_title).await?;
    println!("āœ… Prompt stored (ID: {})", prompt_id5);

    let response5 = "Great question for a new mobile app! Here's a decision framework based on everything we've covered:\n\n**Choose REST when**:\nāœ… Simple CRUD operations\nāœ… Team already knows REST well\nāœ… Standard resource-based API\nāœ… Heavy caching requirements\nāœ… Rapid prototyping needed\n\n**Choose GraphQL when**:\nāœ… Complex, nested data relationships\nāœ… Multiple client types (iOS, Android, Web) with different needs\nāœ… Frequent API changes/iterations\nāœ… Need to minimize mobile data usage (remember the over-fetching problem!)\nāœ… Want real-time features (subscriptions)\nāœ… Backend has microservices you want to unify\n\n**For your mobile app specifically**:\n1. Start with data requirements - how complex are relationships?\n2. Consider team expertise - learning curve matters\n3. Think about data usage - mobile users care about payload size\n4. Plan for growth - will requirements change often?\n\n**My recommendation**: If your app has complex data needs or you're targeting users with limited data plans, GraphQL's flexibility and efficiency (avoiding over-fetching as we discussed) makes it worth the initial complexity. Otherwise, REST's simplicity might get you to market faster.\n\nRemember: You can also start with REST and migrate later, or even use both (REST for simple endpoints, GraphQL for complex ones)!";

    let response_id5 = rag_module.add_response(context_id, response5, user_id, chat_title).await?;
    println!("Assistant: {}...", &response5[..120]);
    println!("āœ… Response stored (ID: {})\n", response_id5);

    // === SEARCH AND VERIFY CONTINUATION ===
    println!("šŸ” Testing Chat Search Service with Continued Context:");

    let chat_search_options = ChatSearchOptions {
        context_id: Some(context_id.to_string()),
        role: None,
        from_timestamp: None,
        to_timestamp: None,
        from_message_index: None,
        to_message_index: None,
        limit: Some(20),
        include_metadata: true,
        user_id: Some(user_id.to_string()),
    };

    let chat_search_results = rag_module.search_service
        .search_chat_history(chat_search_options)
        .await?;

    println!("āœ… Chat search returned complete conversation history:");
    println!("\n{}", serde_json::to_string_pretty(&chat_search_results).unwrap_or_else(|_| "Error formatting".to_string()));

    // Verify message count
    if let Some(result) = chat_search_results.first() {
        if let Some(messages) = result.get("m").and_then(|v| v.as_array()) {
            println!("\nāœ… Total messages in conversation: {}", messages.len());
            println!("   Expected: 10 messages (3 original pairs + 2 new pairs = 5 Q&A pairs Ɨ 2)");

            // Show message indices to verify continuation
            println!("\nšŸ“Š Message indices verification:");
            for (idx, msg) in messages.iter().enumerate() {
                if let (Some(role), Some(content)) = (
                    msg.get("r").and_then(|v| v.as_str()),
                    msg.get("c").and_then(|v| v.as_str())
                ) {
                    let content_preview = if content.len() > 60 {
                        format!("{}...", &content[..60])
                    } else {
                        content.to_string()
                    };
                    println!("   Message {}: role={}, content={}", idx, role, content_preview);
                }
            }
        }
    }

    // === VERIFY CONTEXT RETRIEVAL ===
    println!("\nšŸ”„ Verifying Updated Context Retrieval:");
    let final_history = rag_module.get_query_response_pairs(context_id, None).await?;
    println!("   Total conversation pairs: {}", final_history.total_pairs);
    println!("   Context ID: {}", final_history.context_id);
    println!("   Chat title: {}", final_history.chat_title);

    println!("\nšŸ“– COMPLETE CONVERSATION HISTORY:");
    println!("─────────────────────────────────────────────────────");
    for (i, pair) in final_history.pairs.iter().enumerate() {
        println!("Turn {}: User: {}", i + 1, pair.query.content);
        println!("Turn {}: Assistant: {}...\n", i + 1, &pair.response.content[..80]);
    }
    println!("─────────────────────────────────────────────────────");

    // === FINAL SUMMARY ===
    println!("\n");
    println!("═══════════════════════════════════════════════════════════");
    println!("              CONVERSATION CONTINUATION SUMMARY            ");
    println!("═══════════════════════════════════════════════════════════");
    println!("āœ… Successfully continued existing conversation!");
    println!("   • Context ID: {}", context_id);
    println!("   • User ID: {}", user_id);
    println!("   • Previous messages: {} pairs", existing_history.total_pairs);
    println!("   • New messages: 2 pairs");
    println!("   • Total messages: {} pairs", final_history.total_pairs);
    println!();
    println!("šŸ”‘ Key Features Demonstrated:");
    println!("   āœ… Message index continuation (didn't restart from 0)");
    println!("   āœ… Context preservation across sessions");
    println!("   āœ… Proper deduplication of messages");
    println!("   āœ… Conversation flow maintained");
    println!("   āœ… Same user_id and context_id across examples");
    println!();
    println!("šŸ“ Storage Path: ./example-enhanced-chat-data (shared with complete_chat_example)");
    println!("═══════════════════════════════════════════════════════════");

    Ok(())
}