| |
| """ |
| 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}") |
|
|
| |
| result = await client.get_conversation_history(user_id, limit=1000) |
|
|
| if result.get("status") == "success": |
| conversations = result.get("conversations", []) |
|
|
| |
| export_data = { |
| "export_timestamp": datetime.now().isoformat(), |
| "user_id": user_id, |
| "total_conversations": len(conversations), |
| "conversations": conversations, |
| "source_api": client.base_url, |
| } |
|
|
| |
| 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() |
|
|
| |
| client = LanceDBAPIClient(base_url=args.base_url) |
|
|
| async def run_actions(): |
| async with client: |
| |
| if args.action != "test": |
| connected = await test_api_connection(client) |
| if not connected: |
| print("❌ Cannot proceed without API connection") |
| return |
|
|
| |
| 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) |
|
|
| |
| asyncio.run(run_actions()) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|