Spaces:
Sleeping
Sleeping
| from typing import Any | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from starlette.requests import Request | |
| from app.api.dependencies import verify_api_key | |
| from app.api.limiter import limiter | |
| from app.core.logger import get_logger | |
| from app.core.metrics import metrics | |
| from app.models.report import EngineeringReport | |
| from app.models.repository import RepositoryRequest, RepositoryResponse | |
| from app.models.job import JobStatus, JobStatusResponse, JobSubmitResponse | |
| from app.worker.tasks import analyze_repository_task | |
| from app.services.analysis_service import run_full_analysis | |
| from app.agents import repo_analysis_agent | |
| from app.models.review import ReviewFeedback | |
| from app.tools.github_tool import clone_repository, delete_repository | |
| from app.api.limiter import LIMIT_SYNC_ANALYZE, LIMIT_ASYNC_SUBMIT, LIMIT_JOB_POLL | |
| from celery.result import AsyncResult | |
| router = APIRouter() | |
| logger = get_logger(__name__) | |
| def health_check() -> dict[str, str]: | |
| return {"status": "ok", "message": "AI Code Review Agent is running"} | |
| # REPLACE the body of analyze_repository with: | |
| async def analyze_repository( | |
| request: Request, body: RepositoryRequest | |
| ) -> EngineeringReport: | |
| """ | |
| Full pipeline with Redis cache: | |
| 1. Check cache by HEAD SHA | |
| 2. If miss: clone, run agents, store result | |
| 3. Return engineering report | |
| """ | |
| try: | |
| return await run_full_analysis(body.github_url, body.base_sha) | |
| except HTTPException: | |
| raise | |
| except Exception as exc: | |
| logger.exception( | |
| "Pipeline failed", extra={"url": body.github_url, "error": str(exc)} | |
| ) | |
| raise HTTPException( | |
| status_code=500, detail="Internal server error. Check server logs." | |
| ) | |
| async def analyze_repository_async( | |
| request: Request, body: RepositoryRequest | |
| ) -> JobSubmitResponse: | |
| """ | |
| Enqueue a full analysis pipeline run on a Celery worker and return | |
| immediately with a job ID. Poll GET /jobs/{job_id} for status/result. | |
| """ | |
| task = analyze_repository_task.delay(body.github_url, body.base_sha) | |
| return JobSubmitResponse(job_id=task.id, status=JobStatus.PENDING) | |
| def get_job_status(request: Request, job_id: str) -> JobStatusResponse: | |
| """Poll the status/result of an async analysis job.""" | |
| result = AsyncResult(job_id) | |
| status_map = { | |
| "PENDING": JobStatus.PENDING, | |
| "STARTED": JobStatus.STARTED, | |
| "SUCCESS": JobStatus.SUCCESS, | |
| "FAILURE": JobStatus.FAILURE, | |
| "RETRY": JobStatus.STARTED, | |
| } | |
| status = status_map.get(result.status, JobStatus.PENDING) | |
| if status == JobStatus.SUCCESS: | |
| return JobStatusResponse(job_id=job_id, status=status, result=result.result) | |
| if status == JobStatus.FAILURE: | |
| return JobStatusResponse(job_id=job_id, status=status, error=str(result.result)) | |
| return JobStatusResponse(job_id=job_id, status=status) | |
| async def quick_analyze( | |
| request: Request, body: RepositoryRequest | |
| ) -> RepositoryResponse: | |
| """ | |
| Only run repository analysis agent. | |
| Faster, for testing purposes. | |
| """ | |
| local_path = None | |
| try: | |
| local_path = clone_repository(body.github_url) | |
| metadata = await repo_analysis_agent.run(body.github_url, local_path) | |
| return RepositoryResponse( | |
| success=True, message="Repository analyzed successfully", data=metadata | |
| ) | |
| except HTTPException: | |
| raise | |
| except Exception as exc: | |
| logger.exception( | |
| "Quick analyze failed", extra={"url": body.github_url, "error": str(exc)} | |
| ) | |
| raise HTTPException( | |
| status_code=500, detail="Internal server error. Check server logs." | |
| ) | |
| finally: | |
| if local_path: | |
| delete_repository(local_path) | |
| def get_metrics() -> dict[str, Any]: | |
| """Observability endpoint: agent run counts, error rates, latencies.""" | |
| return metrics.snapshot() | |
| _feedback_store: list[dict] = [] | |
| def _clear_feedback_store() -> None: # test helper only | |
| _feedback_store.clear() | |
| async def submit_feedback(body: ReviewFeedback) -> dict[str, str]: | |
| _feedback_store.append(body.model_dump()) | |
| logger.info( | |
| "Feedback received", extra={"useful": body.useful, "finding": body.finding[:50]} | |
| ) | |
| return {"status": "recorded"} | |
| def feedback_summary() -> dict[str, int]: | |
| useful = sum(1 for f in _feedback_store if f["useful"]) | |
| not_useful = len(_feedback_store) - useful | |
| return {"useful": useful, "not_useful": not_useful, "total": len(_feedback_store)} | |