annator-command-center / scripts /retrieve_lancedb_conversations.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 6)
383cb38 verified
Raw
History Blame Contribute Delete
12.1 kB
#!/usr/bin/env python3
"""
LanceDB Conversation Retrieval Script
This script demonstrates how to retrieve conversations from LanceDB
using the existing memory system integration in Atom.
Features:
- Retrieve conversation history for specific users
- Search conversations using semantic similarity
- Export conversation data in various formats
- Test LanceDB connectivity and health
"""
import argparse
import asyncio
from datetime import datetime
import json
import os
import sys
from typing import Dict, List, Optional
# Add backend to path to import the necessary modules
sys.path.append(os.path.join(os.path.dirname(__file__), "backend"))
try:
from backend.python_api_service.lancedb_handler import (
get_conversation_history,
get_lancedb_connection,
search_conversation_context,
store_conversation_context,
)
LANCEDB_AVAILABLE = True
except ImportError as e:
print(f"Warning: LanceDB modules not available: {e}")
LANCEDB_AVAILABLE = False
class LanceDBConversationRetriever:
"""Class to handle conversation retrieval from LanceDB"""
def __init__(self, db_path: str = "data/lancedb"):
self.db_path = db_path
self.db_connection = None
async def initialize(self):
"""Initialize LanceDB connection"""
if not LANCEDB_AVAILABLE:
print("LanceDB is not available. Please check the installation.")
return False
try:
self.db_connection = await get_lancedb_connection(self.db_path)
print(f"✅ Successfully connected to LanceDB at {self.db_path}")
return True
except Exception as e:
print(f"❌ Failed to connect to LanceDB: {e}")
return False
async def get_user_conversations(
self,
user_id: str,
session_id: Optional[str] = None,
limit: int = 50,
offset: int = 0,
) -> Dict:
"""Get conversation history for a specific user"""
if not self.db_connection:
return {"status": "error", "message": "LanceDB not connected"}
try:
result = await get_conversation_history(
self.db_connection, user_id, session_id, limit, offset
)
return result
except Exception as e:
return {"status": "error", "message": f"Failed to get conversations: {e}"}
async def search_conversations(
self,
query_text: str,
user_id: str,
session_id: Optional[str] = None,
limit: int = 10,
) -> Dict:
"""Search conversations using semantic similarity"""
if not self.db_connection:
return {"status": "error", "message": "LanceDB not connected"}
try:
# Generate a simple embedding for the query (placeholder)
# In production, this would use a proper embedding model
query_embedding = [0.1] * 384 # Standard embedding dimension
result = await search_conversation_context(
self.db_connection, query_embedding, user_id, session_id, limit
)
return result
except Exception as e:
return {
"status": "error",
"message": f"Failed to search conversations: {e}",
}
async def get_conversation_stats(self, user_id: str) -> Dict:
"""Get conversation statistics for a user"""
if not self.db_connection:
return {"status": "error", "message": "LanceDB not connected"}
try:
# Get all conversations for the user
result = await get_conversation_history(
self.db_connection, user_id, limit=1000
)
if result.get("status") != "success":
return result
conversations = result.get("conversations", [])
# Calculate statistics
stats = {
"total_conversations": len(conversations),
"user_id": user_id,
"first_conversation": None,
"last_conversation": None,
"message_counts": {"user": 0, "assistant": 0, "system": 0},
"timeline": [],
}
if conversations:
# Sort by timestamp
sorted_conv = sorted(
conversations, key=lambda x: x.get("timestamp", "")
)
stats["first_conversation"] = sorted_conv[0].get("timestamp")
stats["last_conversation"] = sorted_conv[-1].get("timestamp")
# Count messages by role
for conv in conversations:
role = conv.get("role", "user")
stats["message_counts"][role] = (
stats["message_counts"].get(role, 0) + 1
)
# Add to timeline
stats["timeline"].append(
{
"timestamp": conv.get("timestamp"),
"role": role,
"content_preview": conv.get("content", "")[:100] + "..."
if len(conv.get("content", "")) > 100
else conv.get("content", ""),
}
)
return {"status": "success", "stats": stats}
except Exception as e:
return {"status": "error", "message": f"Failed to get stats: {e}"}
async def test_lancedb_connection():
"""Test LanceDB connection and basic functionality"""
print("🧪 Testing LanceDB Connection...")
retriever = LanceDBConversationRetriever()
connected = await retriever.initialize()
if not connected:
print("❌ LanceDB connection test failed")
return False
print("✅ LanceDB connection test passed")
return True
async def retrieve_user_conversations(user_id: str, limit: int = 20):
"""Retrieve and display conversations for a specific user"""
print(f"📝 Retrieving conversations for user: {user_id}")
retriever = LanceDBConversationRetriever()
await retriever.initialize()
# Get conversation history
result = await retriever.get_user_conversations(user_id, limit=limit)
if result.get("status") == "success":
conversations = result.get("conversations", [])
total_count = result.get("total_count", 0)
print(f"📊 Found {len(conversations)} conversations (total: {total_count})")
print("-" * 80)
for i, conv in enumerate(conversations, 1):
timestamp = conv.get("timestamp", "Unknown")
role = conv.get("role", "unknown").upper()
content = conv.get("content", "")
session_id = conv.get("session_id", "N/A")
print(f"{i}. [{timestamp}] {role} (Session: {session_id})")
print(f" {content[:200]}{'...' if len(content) > 200 else ''}")
print()
else:
print(f"❌ Failed to retrieve conversations: {result.get('message')}")
async def search_user_conversations(user_id: str, query: str, limit: int = 10):
"""Search conversations for a specific user"""
print(f"🔍 Searching conversations for user '{user_id}': '{query}'")
retriever = LanceDBConversationRetriever()
await retriever.initialize()
# Search conversations
result = await retriever.search_conversations(query, user_id, limit=limit)
if result.get("status") == "success":
results = result.get("results", [])
print(f"📊 Found {len(results)} relevant conversations")
print("-" * 80)
for i, res in enumerate(results, 1):
timestamp = res.get("timestamp", "Unknown")
role = res.get("role", "unknown").upper()
content = res.get("content", "")
similarity = res.get("similarity_score", 0)
session_id = res.get("session_id", "N/A")
print(f"{i}. [{timestamp}] {role} (Session: {session_id})")
print(f" Similarity: {similarity:.3f}")
print(f" {content[:200]}{'...' if len(content) > 200 else ''}")
print()
else:
print(f"❌ Failed to search conversations: {result.get('message')}")
async def export_conversations(user_id: str, output_file: str):
"""Export conversations to JSON file"""
print(f"💾 Exporting conversations for user '{user_id}' to {output_file}")
retriever = LanceDBConversationRetriever()
await retriever.initialize()
# Get all conversations (with large limit)
result = await retriever.get_user_conversations(user_id, limit=1000)
if result.get("status") == "success":
conversations = result.get("conversations", [])
# Prepare export data
export_data = {
"export_timestamp": datetime.now().isoformat(),
"user_id": user_id,
"total_conversations": len(conversations),
"conversations": conversations,
}
# Write to file
with open(output_file, "w", encoding="utf-8") as f:
json.dump(export_data, f, indent=2, ensure_ascii=False)
print(
f"✅ Successfully exported {len(conversations)} conversations to {output_file}"
)
else:
print(f"❌ Failed to export conversations: {result.get('message')}")
async def get_user_stats(user_id: str):
"""Get conversation statistics for a user"""
print(f"📊 Getting conversation statistics for user: {user_id}")
retriever = LanceDBConversationRetriever()
await retriever.initialize()
result = await retriever.get_conversation_stats(user_id)
if result.get("status") == "success":
stats = result.get("stats", {})
print(f"📈 Conversation Statistics for {user_id}:")
print(f" Total Conversations: {stats.get('total_conversations', 0)}")
print(f" First Conversation: {stats.get('first_conversation', 'N/A')}")
print(f" Last Conversation: {stats.get('last_conversation', 'N/A')}")
print(f" Message Counts:")
for role, count in stats.get("message_counts", {}).items():
print(f" - {role.capitalize()}: {count}")
else:
print(f"❌ Failed to get statistics: {result.get('message')}")
def main():
"""Main function with command line interface"""
parser = argparse.ArgumentParser(description="Retrieve conversations from LanceDB")
parser.add_argument(
"--user-id", required=True, help="User ID to retrieve conversations for"
)
parser.add_argument(
"--action",
choices=["retrieve", "search", "export", "stats", "test"],
default="retrieve",
help="Action to perform",
)
parser.add_argument("--query", help="Search query (for search action)")
parser.add_argument(
"--limit", type=int, default=20, help="Number of conversations to retrieve"
)
parser.add_argument("--output", help="Output file for export")
args = parser.parse_args()
if not LANCEDB_AVAILABLE:
print("❌ LanceDB is not available. Please ensure:")
print(" - LanceDB is installed: pip install lancedb")
print(" - The backend modules are accessible")
sys.exit(1)
# Perform the requested action
if args.action == "test":
asyncio.run(test_lancedb_connection())
elif args.action == "retrieve":
asyncio.run(retrieve_user_conversations(args.user_id, args.limit))
elif args.action == "search":
if not args.query:
print("❌ Please provide a search query with --query")
sys.exit(1)
asyncio.run(search_user_conversations(args.user_id, args.query, args.limit))
elif args.action == "export":
output_file = (
args.output
or f"conversations_{args.user_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
)
asyncio.run(export_conversations(args.user_id, output_file))
elif args.action == "stats":
asyncio.run(get_user_stats(args.user_id))
if __name__ == "__main__":
main()