debugging

This commit is contained in:
2025-09-17 18:11:43 +02:00
parent 7e17f0e0b2
commit 26070da235
6 changed files with 385 additions and 10 deletions

View File

@@ -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"])

View 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"
}

View File

@@ -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)

View 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")

View File

@@ -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 data = await chatbotApi.chat(
chatbotId,
messageToSend,
{
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,
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') {

File diff suppressed because one or more lines are too long