Answer JSON contract for clients

A grounded answer is more than free text. It is the answer plus the sources it leaned on. Returning that as a typed JSON object means a frontend can render the citation list cleanly and an agent downstream can decide whether the evidence is strong enough to act on.

models.py
python
from pydantic import BaseModel, Field
from typing import List


class SourceRef(BaseModel):
    id: str
    score: float = Field(..., ge=0.0, le=1.0)
    snippet: str


class GroundedAnswer(BaseModel):
    answer: str
    sources: List[SourceRef]
    refused: bool = False  # True when context did not support an answer

A SourceRef carries the chunk id, similarity score, and a short snippet so the UI can show a hoverable citation. The refused flag makes the no-evidence path explicit instead of folded into the answer string.

One rule keeps this honest. The endpoint never returns answer-only with refused implied by silence. Either you have an answer with at least one source, or refused is true and answer carries the explicit no-context message. Clients and downstream agents handle both branches with one if-statement, not heuristics on the answer text.

router.py
python
from models import GroundedAnswer, QueryRequest, SourceRef


@router.post('/query', response_model=GroundedAnswer)
async def query(req: QueryRequest):
    if not req.question.strip():
        raise HTTPException(status_code=400, detail='question is required')
    result = await rag_service.query(req.question, top_k=req.top_k)
    sources = [
        SourceRef(id=s['id'], score=s['score'], snippet=s['preview'])
        for s in result['sources']
    ]
    return GroundedAnswer(
        answer=result['answer'],
        sources=sources,
        refused=not sources,
    )

GroundedAnswer replaces the earlier QueryResponse as the response_model on the query endpoint, so the API has exactly one answer shape. The service layer keeps returning its plain dict; the router maps it into the typed contract at the boundary, the same place we validate input.

Quiz: Quiz

Loading practice…