Spaces:
Runtime error
Runtime error
| from fastapi import APIRouter, Depends, Request | |
| from configs.config import settings | |
| from configs.logging import get_logger | |
| from core.pipeline import run_pipeline | |
| from interfaces.api.dependencies import get_model_loader | |
| from interfaces.schemas.explain import ExplainClassificationRequest, ExplainClassificationResponse | |
| from services.explain_quota import ExplainQuotaTracker, client_ip | |
| from services.llm_service import explain_classification as llm_explain_classification | |
| router = APIRouter(tags=["Explanation"]) | |
| logger = get_logger("explain_route") | |
| explain_quota = ExplainQuotaTracker( | |
| max_per_ip=settings.EXPLAIN_MAX_PER_IP, | |
| window_seconds=settings.EXPLAIN_QUOTA_WINDOW_HOURS * 3600, | |
| ) | |
| async def explain_classification_endpoint( | |
| body: ExplainClassificationRequest, | |
| request: Request, | |
| loader=Depends(get_model_loader), | |
| ): | |
| """ | |
| Runs the full deterministic pipeline first, then optionally calls the LLM to explain | |
| the fixed labels and action (LLM does not decide routing). | |
| """ | |
| classification = run_pipeline(body.text, loader) | |
| if not settings.LLM_ENABLED or not settings.llm_configured(): | |
| return ExplainClassificationResponse( | |
| classification=classification, | |
| explanation=None, | |
| explain_meta={ | |
| "explain_source": "disabled", | |
| "error_code": "LLM_DISABLED" if not settings.LLM_ENABLED else "MISSING_API_KEY", | |
| "llm_latency_ms": None, | |
| }, | |
| ) | |
| ip = client_ip(request) | |
| if explain_quota.is_exhausted(ip): | |
| logger.info("explain_quota_exceeded", client_ip=ip) | |
| return ExplainClassificationResponse( | |
| classification=classification, | |
| explanation=None, | |
| explain_meta={ | |
| "explain_source": "quota", | |
| "error_code": "EXPLAIN_QUOTA_EXCEEDED", | |
| "llm_latency_ms": None, | |
| }, | |
| ) | |
| detail, meta, err = await llm_explain_classification( | |
| text=body.text, | |
| classification=classification, | |
| settings=settings, | |
| ) | |
| explain_meta = {**meta} | |
| if err: | |
| explain_meta["error_code"] = err | |
| elif detail is not None: | |
| explain_quota.record_success(ip) | |
| return ExplainClassificationResponse( | |
| classification=classification, | |
| explanation=detail, | |
| explain_meta=explain_meta, | |
| ) | |