Refine & Feedback¶
Gaia provides two post-query operations that improve answer quality over time: Refine narrows an answer to specific documents, and Feedback signals whether an answer was helpful. Both use the queryUid returned by the initial /ask call.
Refine: Focused Answers from Selected Documents¶
The Problem¶
When Gaia answers a question, it retrieves multiple documents and synthesizes an answer from all of them. Sometimes the retrieved set includes marginally relevant documents that dilute the answer. Refine lets the user (or your application logic) select the most relevant documents and regenerate the answer using only those.
How It Works¶
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 1. Initial /ask │────▶│ 2. User reviews │────▶│ 3. POST /refine │
│ Returns docs │ │ documents, │ │ with selected│
│ + answer │ │ selects best │ │ docIds │
│ + queryUid │ │ ones │ │ → new answer │
└─────────────────┘ └─────────────────┘ └─────────────────┘
API: POST /ask/refine¶
Request body:
{
"queryUid": "abc-123",
"datasetNames": ["quarterly-reports"],
"queryString": "What was our Q4 revenue?",
"docIds": ["doc-7", "doc-12"]
}
| Field | Type | Description |
|---|---|---|
queryUid | string | The queryUid from the original /ask response |
datasetNames | string[] | Same dataset(s) used in the original query |
queryString | string | The original (or modified) question |
docIds | string[] | Document IDs to use for the refined answer |
Response:
{
"responseString": "Based on the selected financial statements, Q4 revenue was $4.2B...",
"queryUid": "def-456",
"documents": [
{
"docId": "doc-7",
"filename": "q4-financials.pdf",
"snippet": "Total revenue for Q4 was $4.2 billion..."
}
]
}
Backend Implementation¶
Using the SDK:
from fastapi import APIRouter, Depends
from gaia_sdk import GaiaClient
from backend.api.dependencies import get_gaia_client
from pydantic import BaseModel, Field
router = APIRouter()
class RefineRequest(BaseModel):
query_uid: str = Field(alias="queryUid")
dataset_names: list[str] = Field(alias="datasetNames")
query: str = Field(alias="queryString")
doc_ids: list[str] = Field(alias="docIds")
model_config = {"populate_by_name": True}
class RefineResponse(BaseModel):
response: str | None
query_uid: str | None
documents: list | None = None
@router.post("/ask/refine", response_model=RefineResponse, tags=["Ask"])
async def refine(
request: RefineRequest,
client: GaiaClient = Depends(get_gaia_client),
):
result = await client.refine(
query_uid=request.query_uid,
dataset_names=request.dataset_names,
query=request.query,
doc_ids=request.doc_ids,
)
return RefineResponse(
response=result.response_string,
query_uid=result.query_uid,
documents=result.documents,
)
Using raw httpx:
async def refine_answer(
api_key: str,
query_uid: str,
dataset_names: list[str],
query: str,
doc_ids: list[str],
) -> dict:
async with httpx.AsyncClient() as client:
response = await client.post(
"https://helios.cohesity.com/v2/mcm/gaia/ask/refine",
headers={"apiKey": api_key, "Content-Type": "application/json"},
json={
"queryUid": query_uid,
"datasetNames": dataset_names,
"queryString": query,
"docIds": doc_ids,
},
)
response.raise_for_status()
return response.json()
Frontend: Document Selection UI¶
Build a component that shows source documents from the initial answer and lets the user select which ones to use for refinement.
// src/components/DocumentSelector.tsx
import { useState } from "react";
interface Document {
docId: string;
filename: string;
snippet: string;
score: number;
}
interface Props {
documents: Document[];
onRefine: (selectedDocIds: string[]) => void;
isLoading?: boolean;
}
export function DocumentSelector({ documents, onRefine, isLoading }: Props) {
const [selected, setSelected] = useState<Set<string>>(new Set());
const toggle = (docId: string) => {
setSelected((prev) => {
const next = new Set(prev);
if (next.has(docId)) {
next.delete(docId);
} else {
next.add(docId);
}
return next;
});
};
return (
<div className="border rounded-lg p-4 space-y-3">
<h3 className="font-semibold">Source Documents</h3>
<p className="text-sm text-gray-500">
Select relevant documents to refine the answer.
</p>
<ul className="space-y-2">
{documents.map((doc) => (
<li
key={doc.docId}
className={`border rounded p-3 cursor-pointer transition ${
selected.has(doc.docId)
? "border-purple-500 bg-purple-50"
: "border-gray-200 hover:border-gray-300"
}`}
onClick={() => toggle(doc.docId)}
>
<div className="flex items-center justify-between">
<span className="font-medium text-sm">{doc.filename}</span>
<span className="text-xs text-gray-400">
Score: {doc.score?.toFixed(2)}
</span>
</div>
<p className="text-sm text-gray-600 mt-1 line-clamp-2">
{doc.snippet}
</p>
</li>
))}
</ul>
<button
onClick={() => onRefine(Array.from(selected))}
disabled={selected.size === 0 || isLoading}
className="w-full rounded bg-purple-600 px-4 py-2 text-white text-sm font-medium hover:bg-purple-700 disabled:opacity-50"
>
{isLoading ? "Refining..." : `Refine with ${selected.size} document(s)`}
</button>
</div>
);
}
The API call:
// src/api/gaia.ts
export async function refine(
queryUid: string,
datasetNames: string[],
query: string,
docIds: string[],
): Promise<AskResponse> {
return api.post<AskResponse>("/ask/refine", {
queryUid,
datasetNames,
queryString: query,
docIds,
});
}
Feedback: Thumbs Up / Thumbs Down¶
Why Feedback Matters¶
Feedback helps Gaia's underlying models improve over time. When users rate answers, Gaia can adjust ranking and retrieval for future queries on the same datasets.
API: POST /query-feedback¶
Request body:
| Field | Type | Description |
|---|---|---|
queryUid | string | The queryUid from the /ask response |
isGood | boolean | true for positive, false for negative feedback |
feedbackText | string (optional) | Free-text explanation from the user |
Response: 200 OK with an empty body or acknowledgment.
Backend Implementation¶
class FeedbackRequest(BaseModel):
query_uid: str = Field(alias="queryUid")
is_good: bool = Field(alias="isGood")
feedback_text: str | None = Field(None, alias="feedbackText")
model_config = {"populate_by_name": True}
@router.post("/feedback", tags=["Feedback"])
async def send_feedback(
request: FeedbackRequest,
client: GaiaClient = Depends(get_gaia_client),
):
await client.send_feedback(
query_uid=request.query_uid,
is_good=request.is_good,
feedback_text=request.feedback_text,
)
return {"status": "ok"}
Frontend: Feedback Buttons¶
// src/components/FeedbackButtons.tsx
import { useState } from "react";
import { api } from "../api/client";
interface Props {
queryUid: string;
}
export function FeedbackButtons({ queryUid }: Props) {
const [submitted, setSubmitted] = useState<boolean | null>(null);
const handleFeedback = async (isGood: boolean) => {
try {
await api.post("/feedback", { queryUid, isGood });
setSubmitted(isGood);
} catch {
// best effort — don't block UI on feedback failure
}
};
if (submitted !== null) {
return (
<span className="text-sm text-gray-400">
{submitted ? "👍 Thanks!" : "👎 We'll improve."}
</span>
);
}
return (
<div className="flex gap-2">
<button
onClick={() => handleFeedback(true)}
className="text-sm text-gray-500 hover:text-green-600"
title="Good answer"
>
👍
</button>
<button
onClick={() => handleFeedback(false)}
className="text-sm text-gray-500 hover:text-red-600"
title="Bad answer"
>
👎
</button>
</div>
);
}
Integrate into ChatMessage:
{!message.isStreaming && message.queryUid && (
<div className="mt-2 flex items-center gap-3">
<FeedbackButtons queryUid={message.queryUid} />
</div>
)}
Putting It Together: The Refine + Feedback Flow¶
1. User asks: "What was our Q4 revenue?"
→ POST /ask → response + documents + queryUid
2. User sees answer + source documents
→ Clicks 👍 or 👎
→ POST /query-feedback { queryUid, isGood }
3. User selects 2 of 5 documents as most relevant
→ Clicks "Refine"
→ POST /ask/refine { queryUid, docIds: [...] }
→ New, more focused answer
4. User rates the refined answer
→ POST /query-feedback { queryUid: newQueryUid, isGood }
Refine returns a new queryUid
The refined answer has its own queryUid. Use this new UID when sending feedback on the refined answer — not the original query's UID.
Next Steps¶
- Dataset Discovery — Explore dataset contents for smarter UIs.
- Streaming Responses — Stream refined answers too.
- Error Handling — Handle refine/feedback errors.