mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 07:24:34 +01:00
355 lines
14 KiB
Python
355 lines
14 KiB
Python
"""
|
|
Chatbot API test client for comprehensive workflow testing.
|
|
"""
|
|
|
|
import aiohttp
|
|
import asyncio
|
|
from typing import Dict, List, Optional, Any, AsyncGenerator
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
|
|
|
|
class ChatbotAPITestClient:
|
|
"""Test client for chatbot API workflows"""
|
|
|
|
def __init__(self, base_url: str = "http://localhost:3001"):
|
|
self.base_url = base_url.rstrip('/')
|
|
self.session_timeout = aiohttp.ClientTimeout(total=60)
|
|
self.auth_token = None
|
|
self.api_key = None
|
|
|
|
async def authenticate(self, email: str = "test@example.com", password: str = "testpass123") -> Dict[str, Any]:
|
|
"""Authenticate user and get JWT token"""
|
|
login_data = {"email": email, "password": password}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.base_url}/api-internal/v1/auth/login",
|
|
json=login_data
|
|
) as response:
|
|
if response.status == 200:
|
|
result = await response.json()
|
|
self.auth_token = result.get("access_token")
|
|
return {"success": True, "token": self.auth_token}
|
|
else:
|
|
error = await response.text()
|
|
return {"success": False, "error": error, "status": response.status}
|
|
|
|
async def register_user(self, email: str, password: str, username: str) -> Dict[str, Any]:
|
|
"""Register a new user"""
|
|
user_data = {
|
|
"email": email,
|
|
"password": password,
|
|
"username": username
|
|
}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.base_url}/api-internal/v1/auth/register",
|
|
json=user_data
|
|
) as response:
|
|
result = await response.json() if response.content_type == 'application/json' else await response.text()
|
|
return {
|
|
"status_code": response.status,
|
|
"success": response.status == 201,
|
|
"data": result
|
|
}
|
|
|
|
async def create_rag_collection(self, name: str, description: str = "") -> Dict[str, Any]:
|
|
"""Create a RAG collection"""
|
|
if not self.auth_token:
|
|
return {"error": "Not authenticated"}
|
|
|
|
collection_data = {
|
|
"name": name,
|
|
"description": description,
|
|
"processing_config": {
|
|
"chunk_size": 1000,
|
|
"chunk_overlap": 200
|
|
}
|
|
}
|
|
|
|
headers = {"Authorization": f"Bearer {self.auth_token}"}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.base_url}/api-internal/v1/rag/collections",
|
|
json=collection_data,
|
|
headers=headers
|
|
) as response:
|
|
result = await response.json() if response.content_type == 'application/json' else await response.text()
|
|
return {
|
|
"status_code": response.status,
|
|
"success": response.status == 201,
|
|
"data": result
|
|
}
|
|
|
|
async def upload_document(self, collection_id: str, file_content: str, filename: str) -> Dict[str, Any]:
|
|
"""Upload document to RAG collection"""
|
|
if not self.auth_token:
|
|
return {"error": "Not authenticated"}
|
|
|
|
headers = {"Authorization": f"Bearer {self.auth_token}"}
|
|
|
|
# Create form data
|
|
data = aiohttp.FormData()
|
|
data.add_field('file', file_content, filename=filename, content_type='text/plain')
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.base_url}/api-internal/v1/rag/collections/{collection_id}/upload",
|
|
data=data,
|
|
headers=headers
|
|
) as response:
|
|
result = await response.json() if response.content_type == 'application/json' else await response.text()
|
|
return {
|
|
"status_code": response.status,
|
|
"success": response.status == 201,
|
|
"data": result
|
|
}
|
|
|
|
async def wait_for_document_processing(self, document_id: str, timeout: int = 60) -> Dict[str, Any]:
|
|
"""Wait for document processing to complete"""
|
|
if not self.auth_token:
|
|
return {"error": "Not authenticated"}
|
|
|
|
headers = {"Authorization": f"Bearer {self.auth_token}"}
|
|
start_time = time.time()
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
while time.time() - start_time < timeout:
|
|
async with session.get(
|
|
f"{self.base_url}/api-internal/v1/rag/documents/{document_id}",
|
|
headers=headers
|
|
) as response:
|
|
if response.status == 200:
|
|
doc = await response.json()
|
|
status = doc.get("processing_status")
|
|
|
|
if status == "completed":
|
|
return {"success": True, "status": "completed", "document": doc}
|
|
elif status == "failed":
|
|
return {"success": False, "status": "failed", "document": doc}
|
|
|
|
await asyncio.sleep(1)
|
|
else:
|
|
return {"success": False, "error": f"Failed to check status: {response.status}"}
|
|
|
|
return {"success": False, "error": "Timeout waiting for processing"}
|
|
|
|
async def create_chatbot(self,
|
|
name: str,
|
|
chatbot_type: str = "assistant",
|
|
use_rag: bool = False,
|
|
rag_collection: str = None,
|
|
**config) -> Dict[str, Any]:
|
|
"""Create a chatbot"""
|
|
if not self.auth_token:
|
|
return {"error": "Not authenticated"}
|
|
|
|
chatbot_data = {
|
|
"name": name,
|
|
"chatbot_type": chatbot_type,
|
|
"model": "test-model",
|
|
"system_prompt": "You are a helpful assistant.",
|
|
"use_rag": use_rag,
|
|
"rag_collection": rag_collection,
|
|
"rag_top_k": 3,
|
|
"temperature": 0.7,
|
|
"max_tokens": 1000,
|
|
**config
|
|
}
|
|
|
|
headers = {"Authorization": f"Bearer {self.auth_token}"}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.base_url}/api-internal/v1/chatbot/create",
|
|
json=chatbot_data,
|
|
headers=headers
|
|
) as response:
|
|
result = await response.json() if response.content_type == 'application/json' else await response.text()
|
|
return {
|
|
"status_code": response.status,
|
|
"success": response.status == 201,
|
|
"data": result
|
|
}
|
|
|
|
async def create_api_key_for_chatbot(self, chatbot_id: str, name: str = "Test API Key") -> Dict[str, Any]:
|
|
"""Create API key for chatbot"""
|
|
if not self.auth_token:
|
|
return {"error": "Not authenticated"}
|
|
|
|
api_key_data = {
|
|
"name": name,
|
|
"scopes": ["chatbot.chat"],
|
|
"budget_limit": 100.0,
|
|
"chatbot_id": chatbot_id
|
|
}
|
|
|
|
headers = {"Authorization": f"Bearer {self.auth_token}"}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.base_url}/api-internal/v1/chatbot/{chatbot_id}/api-key",
|
|
json=api_key_data,
|
|
headers=headers
|
|
) as response:
|
|
result = await response.json() if response.content_type == 'application/json' else await response.text()
|
|
if response.status == 201 and isinstance(result, dict):
|
|
self.api_key = result.get("key")
|
|
return {
|
|
"status_code": response.status,
|
|
"success": response.status == 201,
|
|
"data": result
|
|
}
|
|
|
|
async def chat_with_bot(self,
|
|
chatbot_id: str,
|
|
message: str,
|
|
conversation_id: Optional[str] = None,
|
|
api_key: Optional[str] = None) -> Dict[str, Any]:
|
|
"""Send message to chatbot"""
|
|
if not api_key and not self.api_key:
|
|
return {"error": "No API key available"}
|
|
|
|
chat_data = {
|
|
"message": message,
|
|
"conversation_id": conversation_id
|
|
}
|
|
|
|
headers = {"Authorization": f"Bearer {api_key or self.api_key}"}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.base_url}/api/v1/chatbot/{chatbot_id}/chat",
|
|
json=chat_data,
|
|
headers=headers
|
|
) as response:
|
|
result = await response.json() if response.content_type == 'application/json' else await response.text()
|
|
return {
|
|
"status_code": response.status,
|
|
"success": response.status == 200,
|
|
"data": result
|
|
}
|
|
|
|
async def test_rag_workflow(self,
|
|
collection_name: str,
|
|
document_content: str,
|
|
chatbot_name: str,
|
|
test_question: str) -> Dict[str, Any]:
|
|
"""Test complete RAG workflow from document upload to chat"""
|
|
workflow_results = {}
|
|
|
|
# Step 1: Create RAG collection
|
|
collection_result = await self.create_rag_collection(collection_name, "Test collection for workflow")
|
|
workflow_results["collection_creation"] = collection_result
|
|
|
|
if not collection_result["success"]:
|
|
return {"success": False, "error": "Failed to create collection", "results": workflow_results}
|
|
|
|
collection_id = collection_result["data"]["id"]
|
|
|
|
# Step 2: Upload document
|
|
document_result = await self.upload_document(collection_id, document_content, "test_doc.txt")
|
|
workflow_results["document_upload"] = document_result
|
|
|
|
if not document_result["success"]:
|
|
return {"success": False, "error": "Failed to upload document", "results": workflow_results}
|
|
|
|
document_id = document_result["data"]["id"]
|
|
|
|
# Step 3: Wait for processing
|
|
processing_result = await self.wait_for_document_processing(document_id)
|
|
workflow_results["document_processing"] = processing_result
|
|
|
|
if not processing_result["success"]:
|
|
return {"success": False, "error": "Document processing failed", "results": workflow_results}
|
|
|
|
# Step 4: Create chatbot with RAG
|
|
chatbot_result = await self.create_chatbot(
|
|
name=chatbot_name,
|
|
use_rag=True,
|
|
rag_collection=collection_name
|
|
)
|
|
workflow_results["chatbot_creation"] = chatbot_result
|
|
|
|
if not chatbot_result["success"]:
|
|
return {"success": False, "error": "Failed to create chatbot", "results": workflow_results}
|
|
|
|
chatbot_id = chatbot_result["data"]["id"]
|
|
|
|
# Step 5: Create API key
|
|
api_key_result = await self.create_api_key_for_chatbot(chatbot_id)
|
|
workflow_results["api_key_creation"] = api_key_result
|
|
|
|
if not api_key_result["success"]:
|
|
return {"success": False, "error": "Failed to create API key", "results": workflow_results}
|
|
|
|
# Step 6: Test chat with RAG
|
|
chat_result = await self.chat_with_bot(chatbot_id, test_question)
|
|
workflow_results["chat_test"] = chat_result
|
|
|
|
if not chat_result["success"]:
|
|
return {"success": False, "error": "Chat test failed", "results": workflow_results}
|
|
|
|
# Step 7: Verify RAG sources in response
|
|
chat_response = chat_result["data"]
|
|
has_sources = "sources" in chat_response and len(chat_response["sources"]) > 0
|
|
workflow_results["rag_verification"] = {
|
|
"has_sources": has_sources,
|
|
"source_count": len(chat_response.get("sources", [])),
|
|
"sources": chat_response.get("sources", [])
|
|
}
|
|
|
|
return {
|
|
"success": True,
|
|
"workflow_complete": True,
|
|
"rag_working": has_sources,
|
|
"results": workflow_results
|
|
}
|
|
|
|
async def test_conversation_memory(self, chatbot_id: str, api_key: str = None) -> Dict[str, Any]:
|
|
"""Test conversation memory functionality"""
|
|
messages = [
|
|
"My name is Alice and I like cats.",
|
|
"What's my name?",
|
|
"What do I like?"
|
|
]
|
|
|
|
conversation_id = None
|
|
results = []
|
|
|
|
for i, message in enumerate(messages):
|
|
result = await self.chat_with_bot(chatbot_id, message, conversation_id, api_key)
|
|
|
|
if result["success"]:
|
|
conversation_id = result["data"].get("conversation_id")
|
|
results.append({
|
|
"message_index": i,
|
|
"message": message,
|
|
"response": result["data"].get("response"),
|
|
"conversation_id": conversation_id
|
|
})
|
|
else:
|
|
results.append({
|
|
"message_index": i,
|
|
"message": message,
|
|
"error": result.get("error"),
|
|
"status_code": result.get("status_code")
|
|
})
|
|
|
|
# Analyze memory performance
|
|
memory_working = False
|
|
if len(results) >= 3:
|
|
# Check if the bot remembers the name in the second response
|
|
response2 = results[1].get("response", "").lower()
|
|
response3 = results[2].get("response", "").lower()
|
|
memory_working = "alice" in response2 and ("cat" in response3 or "like" in response3)
|
|
|
|
return {
|
|
"conversation_results": results,
|
|
"memory_working": memory_working,
|
|
"conversation_maintained": all(r.get("conversation_id") == results[0].get("conversation_id") for r in results if r.get("conversation_id"))
|
|
} |