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.platform import router as platform_router
|
||||||
from ..v1.llm_internal import router as llm_internal_router
|
from ..v1.llm_internal import router as llm_internal_router
|
||||||
from ..v1.chatbot import router as chatbot_router
|
from ..v1.chatbot import router as chatbot_router
|
||||||
|
from .debugging import router as debugging_router
|
||||||
|
|
||||||
# Create internal API router
|
# Create internal API router
|
||||||
internal_api_router = APIRouter()
|
internal_api_router = APIRouter()
|
||||||
@@ -65,4 +66,7 @@ internal_api_router.include_router(plugin_registry_router, prefix="/plugins", ta
|
|||||||
internal_api_router.include_router(llm_internal_router, prefix="/llm", tags=["internal-llm"])
|
internal_api_router.include_router(llm_internal_router, prefix="/llm", tags=["internal-llm"])
|
||||||
|
|
||||||
# Include chatbot routes (frontend chatbot management)
|
# Include chatbot routes (frontend chatbot management)
|
||||||
internal_api_router.include_router(chatbot_router, prefix="/chatbot", tags=["internal-chatbot"])
|
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
|
# Add analytics middleware
|
||||||
setup_analytics_middleware(app)
|
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
|
# Add security middleware
|
||||||
from app.middleware.security import setup_security_middleware
|
from app.middleware.security import setup_security_middleware
|
||||||
setup_security_middleware(app, enabled=settings.API_SECURITY_ENABLED)
|
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,25 +119,52 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
|
|||||||
setInput("")
|
setInput("")
|
||||||
setIsLoading(true)
|
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 {
|
try {
|
||||||
// Build conversation history in OpenAI format
|
// Build conversation history in OpenAI format
|
||||||
const conversationHistory = messages.map(msg => ({
|
const conversationHistory = messages.map(msg => ({
|
||||||
role: msg.role,
|
role: msg.role,
|
||||||
content: msg.content
|
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(
|
const data = await chatbotApi.chat(
|
||||||
chatbotId,
|
chatbotId,
|
||||||
messageToSend,
|
messageToSend,
|
||||||
{
|
requestData
|
||||||
messages: conversationHistory,
|
|
||||||
conversation_id: conversationId || undefined
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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
|
// Update conversation ID if it's a new conversation
|
||||||
if (!conversationId && data.conversation_id) {
|
if (!conversationId && data.conversation_id) {
|
||||||
setConversationId(data.conversation_id)
|
setConversationId(data.conversationId)
|
||||||
|
console.log('Updated conversation ID:', data.conversation_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const assistantMessage: ChatMessage = {
|
const assistantMessage: ChatMessage = {
|
||||||
@@ -150,8 +177,18 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
|
|||||||
|
|
||||||
setMessages(prev => [...prev, assistantMessage])
|
setMessages(prev => [...prev, assistantMessage])
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Chat error:', error)
|
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
|
// Handle different error types
|
||||||
if (error && typeof error === 'object') {
|
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