annator-atom / backend /scripts /lancedb_api_client.py
techprotrade's picture
Full stack ATOM backend + AIMONEYFLOW clients (port 7860) (part 5)
f84a02d verified
Raw
History Blame Contribute Delete
12.2 kB
#!/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()