mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 23:44:24 +01:00
debugging
This commit is contained in:
@@ -18,6 +18,7 @@ from ..v1.plugin_registry import router as plugin_registry_router
|
||||
from ..v1.platform import router as platform_router
|
||||
from ..v1.llm_internal import router as llm_internal_router
|
||||
from ..v1.chatbot import router as chatbot_router
|
||||
from .debugging import router as debugging_router
|
||||
|
||||
# Create internal API router
|
||||
internal_api_router = APIRouter()
|
||||
@@ -66,3 +67,6 @@ internal_api_router.include_router(llm_internal_router, prefix="/llm", tags=["in
|
||||
|
||||
# Include chatbot routes (frontend chatbot management)
|
||||
internal_api_router.include_router(chatbot_router, prefix="/chatbot", tags=["internal-chatbot"])
|
||||
|
||||
# Include debugging routes (troubleshooting and diagnostics)
|
||||
internal_api_router.include_router(debugging_router, prefix="/debugging", tags=["internal-debugging"])
|
||||
214
backend/app/api/internal_v1/debugging.py
Normal file
214
backend/app/api/internal_v1/debugging.py
Normal file
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
Debugging API endpoints for troubleshooting chatbot issues
|
||||
"""
|
||||
from typing import Dict, Any, List
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.core.security import get_current_user
|
||||
from app.db.database import get_db
|
||||
from app.models.user import User
|
||||
from app.models.chatbot import ChatbotInstance, PromptTemplate
|
||||
from app.models.rag_collection import RagCollection
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/chatbot/{chatbot_id}/config")
|
||||
async def get_chatbot_config_debug(
|
||||
chatbot_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get detailed configuration for debugging a specific chatbot"""
|
||||
|
||||
# Get chatbot instance
|
||||
chatbot = db.query(ChatbotInstance).filter(
|
||||
ChatbotInstance.id == chatbot_id,
|
||||
ChatbotInstance.user_id == current_user.id
|
||||
).first()
|
||||
|
||||
if not chatbot:
|
||||
raise HTTPException(status_code=404, detail="Chatbot not found")
|
||||
|
||||
# Get prompt template
|
||||
prompt_template = db.query(PromptTemplate).filter(
|
||||
PromptTemplate.type == chatbot.chatbot_type
|
||||
).first()
|
||||
|
||||
# Get RAG collections if configured
|
||||
rag_collections = []
|
||||
if chatbot.rag_collection_ids:
|
||||
collection_ids = chatbot.rag_collection_ids
|
||||
if isinstance(collection_ids, str):
|
||||
import json
|
||||
try:
|
||||
collection_ids = json.loads(collection_ids)
|
||||
except:
|
||||
collection_ids = []
|
||||
|
||||
if collection_ids:
|
||||
collections = db.query(RagCollection).filter(
|
||||
RagCollection.id.in_(collection_ids)
|
||||
).all()
|
||||
rag_collections = [
|
||||
{
|
||||
"id": col.id,
|
||||
"name": col.name,
|
||||
"document_count": col.document_count,
|
||||
"qdrant_collection_name": col.qdrant_collection_name,
|
||||
"is_active": col.is_active
|
||||
}
|
||||
for col in collections
|
||||
]
|
||||
|
||||
# Get recent conversations count
|
||||
from app.models.chatbot import ChatbotConversation
|
||||
conversation_count = db.query(ChatbotConversation).filter(
|
||||
ChatbotConversation.chatbot_instance_id == chatbot_id
|
||||
).count()
|
||||
|
||||
return {
|
||||
"chatbot": {
|
||||
"id": chatbot.id,
|
||||
"name": chatbot.name,
|
||||
"type": chatbot.chatbot_type,
|
||||
"description": chatbot.description,
|
||||
"created_at": chatbot.created_at,
|
||||
"is_active": chatbot.is_active,
|
||||
"conversation_count": conversation_count
|
||||
},
|
||||
"prompt_template": {
|
||||
"type": prompt_template.type if prompt_template else None,
|
||||
"system_prompt": prompt_template.system_prompt if prompt_template else None,
|
||||
"variables": prompt_template.variables if prompt_template else []
|
||||
},
|
||||
"rag_collections": rag_collections,
|
||||
"configuration": {
|
||||
"max_tokens": chatbot.max_tokens,
|
||||
"temperature": chatbot.temperature,
|
||||
"streaming": chatbot.streaming,
|
||||
"memory_config": chatbot.memory_config
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/chatbot/{chatbot_id}/test-rag")
|
||||
async def test_rag_search(
|
||||
chatbot_id: str,
|
||||
query: str = "test query",
|
||||
top_k: int = 5,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Test RAG search for a specific chatbot"""
|
||||
|
||||
# Get chatbot instance
|
||||
chatbot = db.query(ChatbotInstance).filter(
|
||||
ChatbotInstance.id == chatbot_id,
|
||||
ChatbotInstance.user_id == current_user.id
|
||||
).first()
|
||||
|
||||
if not chatbot:
|
||||
raise HTTPException(status_code=404, detail="Chatbot not found")
|
||||
|
||||
# Test RAG search
|
||||
try:
|
||||
from app.modules.rag.main import rag_module
|
||||
|
||||
# Get collection IDs
|
||||
collection_ids = []
|
||||
if chatbot.rag_collection_ids:
|
||||
if isinstance(chatbot.rag_collection_ids, str):
|
||||
import json
|
||||
try:
|
||||
collection_ids = json.loads(chatbot.rag_collection_ids)
|
||||
except:
|
||||
pass
|
||||
elif isinstance(chatbot.rag_collection_ids, list):
|
||||
collection_ids = chatbot.rag_collection_ids
|
||||
|
||||
if not collection_ids:
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"message": "No RAG collections configured for this chatbot"
|
||||
}
|
||||
|
||||
# Perform search
|
||||
search_results = await rag_module.search(
|
||||
query=query,
|
||||
collection_ids=collection_ids,
|
||||
top_k=top_k,
|
||||
score_threshold=0.5
|
||||
)
|
||||
|
||||
return {
|
||||
"query": query,
|
||||
"results": search_results,
|
||||
"collections_searched": collection_ids,
|
||||
"result_count": len(search_results)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"error": str(e),
|
||||
"message": "RAG search failed"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/system/status")
|
||||
async def get_system_status(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get system status for debugging"""
|
||||
|
||||
# Check database connectivity
|
||||
try:
|
||||
db.execute(text("SELECT 1"))
|
||||
db_status = "healthy"
|
||||
except Exception as e:
|
||||
db_status = f"error: {str(e)}"
|
||||
|
||||
# Check module status
|
||||
module_status = {}
|
||||
try:
|
||||
from app.services.module_manager import module_manager
|
||||
modules = module_manager.list_modules()
|
||||
for module_name, module_info in modules.items():
|
||||
module_status[module_name] = {
|
||||
"status": module_info.get("status", "unknown"),
|
||||
"enabled": module_info.get("enabled", False)
|
||||
}
|
||||
except Exception as e:
|
||||
module_status = {"error": str(e)}
|
||||
|
||||
# Check Redis (if configured)
|
||||
redis_status = "not configured"
|
||||
try:
|
||||
from app.core.cache import core_cache
|
||||
await core_cache.ping()
|
||||
redis_status = "healthy"
|
||||
except Exception as e:
|
||||
redis_status = f"error: {str(e)}"
|
||||
|
||||
# Check Qdrant (if configured)
|
||||
qdrant_status = "not configured"
|
||||
try:
|
||||
from app.services.qdrant_service import qdrant_service
|
||||
collections = await qdrant_service.list_collections()
|
||||
qdrant_status = f"healthy ({len(collections)} collections)"
|
||||
except Exception as e:
|
||||
qdrant_status = f"error: {str(e)}"
|
||||
|
||||
return {
|
||||
"database": db_status,
|
||||
"modules": module_status,
|
||||
"redis": redis_status,
|
||||
"qdrant": qdrant_status,
|
||||
"timestamp": "UTC"
|
||||
}
|
||||
@@ -135,6 +135,10 @@ app.add_middleware(
|
||||
# Add analytics middleware
|
||||
setup_analytics_middleware(app)
|
||||
|
||||
# Add debugging middleware for detailed request/response logging
|
||||
from app.middleware.debugging import setup_debugging_middleware
|
||||
setup_debugging_middleware(app)
|
||||
|
||||
# Add security middleware
|
||||
from app.middleware.security import setup_security_middleware
|
||||
setup_security_middleware(app, enabled=settings.API_SECURITY_ENABLED)
|
||||
|
||||
115
backend/app/middleware/debugging.py
Normal file
115
backend/app/middleware/debugging.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Debugging middleware for detailed request/response logging
|
||||
"""
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
from fastapi import Request, Response
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from uuid import uuid4
|
||||
|
||||
from app.core.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class DebuggingMiddleware(BaseHTTPMiddleware):
|
||||
"""Middleware to log detailed request/response information for debugging"""
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
# Generate unique request ID for tracing
|
||||
request_id = str(uuid4())
|
||||
|
||||
# Skip debugging for health checks and static files
|
||||
if request.url.path in ["/health", "/docs", "/redoc", "/openapi.json"] or \
|
||||
request.url.path.startswith("/static"):
|
||||
return await call_next(request)
|
||||
|
||||
# Log request details
|
||||
request_body = None
|
||||
if request.method in ["POST", "PUT", "PATCH"]:
|
||||
try:
|
||||
# Clone request body to avoid consuming it
|
||||
body_bytes = await request.body()
|
||||
if body_bytes:
|
||||
try:
|
||||
request_body = json.loads(body_bytes)
|
||||
except json.JSONDecodeError:
|
||||
request_body = body_bytes.decode('utf-8', errors='replace')
|
||||
# Restore body for downstream processing
|
||||
request._body = body_bytes
|
||||
except Exception:
|
||||
request_body = "[Failed to read request body]"
|
||||
|
||||
# Extract headers we care about
|
||||
headers_to_log = {
|
||||
"authorization": request.headers.get("Authorization", "")[:50] + "..." if
|
||||
request.headers.get("Authorization") else None,
|
||||
"content-type": request.headers.get("Content-Type"),
|
||||
"user-agent": request.headers.get("User-Agent"),
|
||||
"x-forwarded-for": request.headers.get("X-Forwarded-For"),
|
||||
"x-real-ip": request.headers.get("X-Real-IP"),
|
||||
}
|
||||
|
||||
# Log request
|
||||
logger.info("=== API REQUEST DEBUG ===", extra={
|
||||
"request_id": request_id,
|
||||
"method": request.method,
|
||||
"url": str(request.url),
|
||||
"path": request.url.path,
|
||||
"query_params": dict(request.query_params),
|
||||
"headers": {k: v for k, v in headers_to_log.items() if v is not None},
|
||||
"body": request_body,
|
||||
"client_ip": request.client.host if request.client else None,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
# Process the request
|
||||
start_time = time.time()
|
||||
response = None
|
||||
response_body = None
|
||||
|
||||
try:
|
||||
response = await call_next(request)
|
||||
|
||||
# Capture response body for successful JSON responses
|
||||
if response.status_code < 400 and isinstance(response, JSONResponse):
|
||||
try:
|
||||
response_body = json.loads(response.body.decode('utf-8'))
|
||||
except:
|
||||
response_body = "[Failed to decode response body]"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Request processing failed: {str(e)}", extra={
|
||||
"request_id": request_id,
|
||||
"error": str(e),
|
||||
"error_type": type(e).__name__
|
||||
})
|
||||
response = JSONResponse(
|
||||
status_code=500,
|
||||
content={"error": "INTERNAL_ERROR", "message": "Internal server error"}
|
||||
)
|
||||
|
||||
# Calculate timing
|
||||
end_time = time.time()
|
||||
duration = (end_time - start_time) * 1000 # milliseconds
|
||||
|
||||
# Log response
|
||||
logger.info("=== API RESPONSE DEBUG ===", extra={
|
||||
"request_id": request_id,
|
||||
"status_code": response.status_code,
|
||||
"duration_ms": round(duration, 2),
|
||||
"response_body": response_body,
|
||||
"response_headers": dict(response.headers),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def setup_debugging_middleware(app):
|
||||
"""Add debugging middleware to the FastAPI app"""
|
||||
app.add_middleware(DebuggingMiddleware)
|
||||
logger.info("Debugging middleware configured")
|
||||
@@ -119,6 +119,16 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
|
||||
setInput("")
|
||||
setIsLoading(true)
|
||||
|
||||
// Enhanced logging for debugging
|
||||
const debugInfo = {
|
||||
chatbotId,
|
||||
messageLength: messageToSend.length,
|
||||
conversationId,
|
||||
timestamp: new Date().toISOString(),
|
||||
messagesCount: messages.length
|
||||
}
|
||||
console.log('=== CHAT REQUEST DEBUG ===', debugInfo)
|
||||
|
||||
try {
|
||||
// Build conversation history in OpenAI format
|
||||
const conversationHistory = messages.map(msg => ({
|
||||
@@ -126,18 +136,35 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
|
||||
content: msg.content
|
||||
}))
|
||||
|
||||
const requestData = {
|
||||
messages: conversationHistory,
|
||||
conversation_id: conversationId || undefined
|
||||
}
|
||||
|
||||
console.log('=== CHAT API REQUEST ===', {
|
||||
url: `/api-internal/v1/chatbot/${chatbotId}/chat/completions`,
|
||||
data: requestData
|
||||
})
|
||||
|
||||
const data = await chatbotApi.chat(
|
||||
chatbotId,
|
||||
messageToSend,
|
||||
{
|
||||
messages: conversationHistory,
|
||||
conversation_id: conversationId || undefined
|
||||
}
|
||||
requestData
|
||||
)
|
||||
|
||||
console.log('=== CHAT API RESPONSE ===', {
|
||||
status: 'success',
|
||||
data,
|
||||
responseKeys: Object.keys(data),
|
||||
hasChoices: !!data.choices,
|
||||
hasResponse: !!data.response,
|
||||
content: data.choices?.[0]?.message?.content || data.response || 'No response'
|
||||
})
|
||||
|
||||
// Update conversation ID if it's a new conversation
|
||||
if (!conversationId && data.conversation_id) {
|
||||
setConversationId(data.conversation_id)
|
||||
setConversationId(data.conversationId)
|
||||
console.log('Updated conversation ID:', data.conversation_id)
|
||||
}
|
||||
|
||||
const assistantMessage: ChatMessage = {
|
||||
@@ -150,8 +177,18 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
|
||||
|
||||
setMessages(prev => [...prev, assistantMessage])
|
||||
|
||||
} catch (error) {
|
||||
console.error('Chat error:', error)
|
||||
} catch (error: any) {
|
||||
console.error('=== CHAT ERROR DEBUG ===', {
|
||||
errorType: typeof error,
|
||||
error,
|
||||
errorMessage: error?.message,
|
||||
errorCode: error?.code,
|
||||
errorResponse: error?.response?.data,
|
||||
errorStatus: error?.response?.status,
|
||||
errorConfig: error?.config,
|
||||
errorStack: error?.stack,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
|
||||
// Handle different error types
|
||||
if (error && typeof error === 'object') {
|
||||
|
||||
1
frontend/tsconfig.tsbuildinfo
Normal file
1
frontend/tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user