#!/usr/bin/env python3 """ LanceDB API Client for Atom Chat Interface This client provides programmatic access to retrieve conversations from LanceDB through the Atom Chat Interface API endpoints. Features: - Retrieve conversation history for users - Search conversations using semantic similarity - Export conversation data - Test API connectivity """ import argparse import asyncio from datetime import datetime import json import sys from typing import Any, Dict, List, Optional import aiohttp import requests class LanceDBAPIClient: """Client for interacting with LanceDB conversation endpoints""" def __init__(self, base_url: str = "http://localhost:8000"): self.base_url = base_url.rstrip("/") self.session = None async def __aenter__(self): self.session = aiohttp.ClientSession() return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self.session: await self.session.close() def _make_sync_request(self, method: str, endpoint: str, **kwargs) -> Dict: """Make synchronous HTTP request""" url = f"{self.base_url}{endpoint}" try: response = requests.request(method, url, **kwargs) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: return { "status": "error", "message": f"HTTP request failed: {str(e)}", "error": str(e), } async def _make_async_request(self, method: str, endpoint: str, **kwargs) -> Dict: """Make asynchronous HTTP request""" if not self.session: self.session = aiohttp.ClientSession() url = f"{self.base_url}{endpoint}" try: async with self.session.request(method, url, **kwargs) as response: response.raise_for_status() return await response.json() except aiohttp.ClientError as e: return { "status": "error", "message": f"HTTP request failed: {str(e)}", "error": str(e), } async def test_connection(self) -> Dict: """Test API connection and health""" return await self._make_async_request("GET", "/health") async def get_conversation_history( self, user_id: str, session_id: Optional[str] = None, limit: int = 50 ) -> Dict: """Get conversation history for a user""" endpoint = f"/api/v1/memory/history/{user_id}" params = {} if session_id: params["session_id"] = session_id if limit: params["limit"] = limit return await self._make_async_request("GET", endpoint, params=params) async def search_conversations( self, query: str, user_id: str, session_id: Optional[str] = None, limit: int = 10, similarity_threshold: float = 0.7, ) -> Dict: """Search conversations using semantic similarity""" endpoint = "/api/v1/memory/search" payload = { "query": query, "user_id": user_id, "limit": limit, "similarity_threshold": similarity_threshold, } if session_id: payload["session_id"] = session_id return await self._make_async_request("POST", endpoint, json=payload) async def get_conversation_details(self, conversation_id: str) -> Dict: """Get details for a specific conversation""" endpoint = f"/api/v1/conversations/{conversation_id}" return await self._make_async_request("GET", endpoint) async def get_analytics_overview(self) -> Dict: """Get analytics overview""" endpoint = "/api/v1/analytics/overview" return await self._make_async_request("GET", endpoint) def get_conversation_history_sync( self, user_id: str, session_id: Optional[str] = None, limit: int = 50 ) -> Dict: """Synchronous version of get_conversation_history""" endpoint = f"/api/v1/memory/history/{user_id}" params = {} if session_id: params["session_id"] = session_id if limit: params["limit"] = limit return self._make_sync_request("GET", endpoint, params=params) def search_conversations_sync( self, query: str, user_id: str, session_id: Optional[str] = None, limit: int = 10, similarity_threshold: float = 0.7, ) -> Dict: """Synchronous version of search_conversations""" endpoint = "/api/v1/memory/search" payload = { "query": query, "user_id": user_id, "limit": limit, "similarity_threshold": similarity_threshold, } if session_id: payload["session_id"] = session_id return self._make_sync_request("POST", endpoint, json=payload) async def test_api_connection(client: LanceDBAPIClient): """Test API connection""" print("๐Ÿงช Testing API Connection...") try: result = await client.test_connection() if "status" in result and result.get("status") == "healthy": print("โœ… API connection test passed") print(f" Status: {result.get('status', 'unknown')}") print(f" Memory System: {result.get('memory_system', 'unknown')}") return True else: print("โŒ API connection test failed") print(f" Response: {result}") return False except Exception as e: print(f"โŒ API connection test failed: {e}") return False async def retrieve_user_conversations( client: LanceDBAPIClient, user_id: str, limit: int = 20 ): """Retrieve and display conversations for a user""" print(f"๐Ÿ“ Retrieving conversations for user: {user_id}") result = await client.get_conversation_history(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', 'Unknown error')}" ) async def search_user_conversations( client: LanceDBAPIClient, user_id: str, query: str, limit: int = 10 ): """Search conversations for a user""" print(f"๐Ÿ” Searching conversations for user '{user_id}': '{query}'") result = await client.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', 'Unknown error')}" ) async def export_conversations( client: LanceDBAPIClient, user_id: str, output_file: str ): """Export conversations to JSON file""" print(f"๐Ÿ’พ Exporting conversations for user '{user_id}' to {output_file}") # Get all conversations with large limit result = await client.get_conversation_history(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, "source_api": client.base_url, } # 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', 'Unknown error')}" ) async def get_analytics(client: LanceDBAPIClient): """Get analytics overview""" print("๐Ÿ“ˆ Getting analytics overview...") result = await client.get_analytics_overview() if "total_conversations" in result: print(f"๐Ÿ“Š Analytics Overview:") print(f" Total Conversations: {result.get('total_conversations', 0)}") print(f" Total Messages: {result.get('total_messages', 0)}") print(f" Total AI Analyses: {result.get('total_ai_analyses', 0)}") print(f" Active Users: {result.get('active_users', 0)}") else: print(f"โŒ Failed to get analytics: {result}") def main(): """Main function with command line interface""" parser = argparse.ArgumentParser( description="LanceDB API Client for Atom Chat Interface" ) parser.add_argument( "--base-url", default="http://localhost:8000", help="Base URL of the chat interface API", ) parser.add_argument( "--user-id", required=True, help="User ID to retrieve conversations for" ) parser.add_argument( "--action", choices=["test", "retrieve", "search", "export", "analytics"], 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() # Create client client = LanceDBAPIClient(base_url=args.base_url) async def run_actions(): async with client: # Test connection first for all actions except test if args.action != "test": connected = await test_api_connection(client) if not connected: print("โŒ Cannot proceed without API connection") return # Perform the requested action if args.action == "test": await test_api_connection(client) elif args.action == "retrieve": await retrieve_user_conversations(client, args.user_id, args.limit) elif args.action == "search": if not args.query: print("โŒ Please provide a search query with --query") return await search_user_conversations( client, 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" ) await export_conversations(client, args.user_id, output_file) elif args.action == "analytics": await get_analytics(client) # Run async operations asyncio.run(run_actions()) if __name__ == "__main__": main()