File size: 3,568 Bytes
ac787df
30b1aef
ac787df
e8cd7a9
b95fe6f
ac787df
 
7ac3a44
 
ac787df
30b1aef
b95fe6f
 
ac787df
 
 
 
e8cd7a9
b95fe6f
7ac3a44
e8cd7a9
ac787df
 
 
 
b95fe6f
ac787df
b95fe6f
 
e8cd7a9
30b1aef
 
 
 
 
 
e8cd7a9
ac787df
b95fe6f
 
30b1aef
 
 
ac787df
b95fe6f
ac787df
 
30b1aef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b95fe6f
 
 
 
 
 
 
 
ac787df
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import uuid
import os
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from app.database import get_db
from app.auth import get_current_user
from app.models.user import User
from app.models.chat import ChatMessage
from app.models.evaluation import EvaluationResult
from app.models.metrics import Metrics
from app.services.analysis import RetrievalAnalyzer

router = APIRouter(prefix="/api/analysis", tags=["analysis"])

@router.get("/{message_id}")
async def analyze_message(message_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
    # 1. Fetch the message natively mapped
    stmt_msg = select(ChatMessage).where(ChatMessage.id == message_id, ChatMessage.user_id == current_user.id)
    msg = (await db.execute(stmt_msg)).scalars().first()
    if not msg:
        raise HTTPException(status_code=404, detail="Message not found")
        
    if msg.role != "assistant":
        raise HTTPException(status_code=400, detail="Analysis is logically only available for assistant messages")
        
    # 2. Fetch associated metrics (if any timer payloads caught mapped traces securely)
    stmt = select(Metrics).where(Metrics.message_id == message_id)
    metrics_record = (await db.execute(stmt)).scalars().first()

    eval_stmt = (
        select(EvaluationResult)
        .where(EvaluationResult.message_id == message_id)
        .order_by(EvaluationResult.created_at.desc())
    )
    eval_record = (await db.execute(eval_stmt)).scalars().first()
    
    # 3. Parse retrieved chunks and instantly leverage our abstract Analyzer class seamlessly
    chunks_data = msg.retrieved_chunks or []
    confidence_threshold = float(os.getenv("ANALYSIS_CONFIDENCE_THRESHOLD", "0.5"))
    analyzer = RetrievalAnalyzer(chunks_data, confidence_threshold=confidence_threshold)
    summary = analyzer.summary_stats()
    
    # Construct structured insight payload natively mapped
    return {
        "message_id": message_id,
        "total_chunks_retrieved": summary["total_chunks_retrieved"],
        "avg_similarity": summary["avg_similarity"],
        "warning_flag": summary["warning_flag"],
        "confidence_threshold": summary["confidence_threshold"],
        "score_distribution": summary["score_distribution"],
        "chunk_diversity": summary["chunk_diversity"],
        "top_contributors": summary["top_contributors"],
        "ranked_chunks": summary["ranked_chunks"],
        "summary_stats": summary,
        "evaluation": {
            "faithfulness": eval_record.faithfulness if eval_record else None,
            "answer_relevancy": eval_record.answer_relevancy if eval_record else None,
            "context_precision": eval_record.context_precision if eval_record else None,
            "context_recall": eval_record.context_recall if eval_record else None,
        } if eval_record else None,
        "timing_breakdown": {
            "chunking_time_ms": metrics_record.chunking_time_ms if metrics_record else None,
            "embedding_time_ms": metrics_record.embedding_time_ms if metrics_record else None,
            "retrieval_time_ms": metrics_record.retrieval_time_ms if metrics_record else None,
            "reranking_time_ms": metrics_record.reranking_time_ms if metrics_record else None,
            "llm_time_ms": metrics_record.llm_time_ms if metrics_record else None,
            "total_time_ms": metrics_record.total_time_ms if metrics_record else None,
        } if metrics_record else None
    }