umairali64488's picture
Upload 6 files
c8cd4b6 verified
Raw History Blame Contribute Delete
5.73 kB
"""
main.py — FastAPI app for the CodeDebug Multi-LLM Debugger.
Endpoints:
GET / → serves frontend/index.html
GET /health → health check
GET /api/v1/models → list configured models
POST /api/v1/debug → main pipeline (3 panel LLMs → Qwen3 judge)
"""
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from pydantic import BaseModel, Field, validator
from app.config import settings
from app.llm_chain import debug_query
# ── Lifespan ──────────────────────────────────────────────────────────────────
@asynccontextmanager
async def lifespan(app: FastAPI):
print("⚡ Starting CodeDebug Multi-LLM Debugger...")
print(f" Panel models : {settings.PANEL_MODELS}")
print(f" Judge model : {settings.JUDGE_MODEL}")
if not settings.OPENROUTER_API_KEY:
print("⚠️ WARNING: OPENROUTER_API_KEY is not set — requests will fail!")
else:
print("✅ API key loaded. Ready!")
yield
print("🛑 Shutting down")
# ── App ───────────────────────────────────────────────────────────────────────
app = FastAPI(
title="CodeDebug Multi-LLM Debugger",
version="2.0.0",
description="3 panel LLMs in parallel → Qwen3 judge synthesizes the final answer.",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── Validation error handler ──────────────────────────────────────────────────
@app.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, exc: RequestValidationError):
print(f"❌ Validation error: {exc.errors()}")
return JSONResponse(
status_code=422,
content={"error": str(exc.errors()), "detail": exc.errors()},
)
# ── Request schema ────────────────────────────────────────────────────────────
class DebugRequest(BaseModel):
question: str = Field(..., min_length=1, description="The debugging question or code snippet")
temperature: float = Field(default=0.3, ge=0.0, le=1.0, description="Sampling temperature for panel models")
@validator("temperature", pre=True)
def coerce_float(cls, v):
try:
return float(v)
except Exception:
return 0.3
@validator("question")
def strip_question(cls, v):
v = v.strip()
if not v:
raise ValueError("question must not be empty")
return v
# ── Frontend ──────────────────────────────────────────────────────────────────
FRONTEND = Path(__file__).parent.parent / "frontend"
@app.get("/", include_in_schema=False)
def serve_ui():
"""Serve the single-page frontend."""
index = FRONTEND / "index.html"
if index.exists():
return FileResponse(str(index))
return JSONResponse(
status_code=200,
content={"message": "CodeDebug API running. See /docs for endpoints."},
)
# ── System endpoints ──────────────────────────────────────────────────────────
@app.get("/health", tags=["system"])
def health():
"""Health check."""
return {
"status": "ok",
"version": "2.0.0",
"api_key_set": bool(settings.OPENROUTER_API_KEY),
}
@app.get("/api/v1/models", tags=["system"])
def list_models():
"""List all configured models."""
return {
"panel_models": [
{"model": m, "label": settings.PANEL_MODEL_LABELS.get(m, m)}
for m in settings.PANEL_MODELS
],
"judge_model": {
"model": settings.JUDGE_MODEL,
"label": settings.JUDGE_LABEL,
},
}
# ── Debug endpoint ────────────────────────────────────────────────────────────
@app.post("/api/v1/debug", tags=["debug"])
async def debug(body: DebugRequest):
"""
Main endpoint.
Sends `question` to 3 panel LLMs in parallel (LangChain LCEL chains),
then forwards all responses to the Qwen3 judge which returns:
- reasoning : judge's analysis of the three answers
- final_answer: synthesised definitive answer
"""
if not settings.OPENROUTER_API_KEY:
raise HTTPException(
status_code=503,
detail="OPENROUTER_API_KEY is not configured on the server.",
)
print(f"📨 '{body.question[:70]}...' | temp={body.temperature}")
result = await debug_query(body.question, body.temperature)
successful = sum(1 for p in result["panel"] if not p.get("error"))
print(f"✅ {successful}/3 panel responses · judge {'ok' if not result['judge'].get('error') else 'failed'} · {result['total_ms']}ms")
return result