Spaces:
Sleeping
Sleeping
Commit ·
e535e98
1
Parent(s): 7053098
feat: show rewritten query in chat UI
Browse filesWhen query rewriter changes the original question, display a badge
"Query được viết lại: ..." below the user bubble to make the
rewriting step visible in the pipeline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api.py +5 -3
- src/rag.py +4 -3
- static/index.html +7 -0
api.py
CHANGED
|
@@ -38,6 +38,7 @@ class Source(BaseModel):
|
|
| 38 |
class QueryResponse(BaseModel):
|
| 39 |
answer: str
|
| 40 |
sources: list[Source]
|
|
|
|
| 41 |
|
| 42 |
|
| 43 |
@app.get("/", response_class=HTMLResponse)
|
|
@@ -52,6 +53,7 @@ def query(req: QueryRequest):
|
|
| 52 |
return QueryResponse(
|
| 53 |
answer=result["answer"],
|
| 54 |
sources=[Source(**s) for s in result["sources"]],
|
|
|
|
| 55 |
)
|
| 56 |
|
| 57 |
|
|
@@ -83,9 +85,9 @@ async def stream(req: QueryRequest):
|
|
| 83 |
await asyncio.sleep(0)
|
| 84 |
|
| 85 |
loop = asyncio.get_event_loop()
|
| 86 |
-
contexts, sources = await loop.run_in_executor(None, retrieve, req.question, req.top_k)
|
| 87 |
|
| 88 |
-
yield f"data: {json.dumps({'type': 'sources', 'sources': sources})}\n\n"
|
| 89 |
await asyncio.sleep(0)
|
| 90 |
|
| 91 |
prompt = build_prompt(req.question, contexts)
|
|
@@ -113,7 +115,7 @@ async def stream(req: QueryRequest):
|
|
| 113 |
def compare(req: QueryRequest):
|
| 114 |
"""So sánh Text RAG vs ColPali retrieval trên cùng câu hỏi."""
|
| 115 |
from src.colpali_retriever import query as colpali_query
|
| 116 |
-
_, text_sources = retrieve(req.question, top_k=req.top_k)
|
| 117 |
try:
|
| 118 |
colpali_hits = colpali_query(req.question, top_k=req.top_k)
|
| 119 |
except Exception as e:
|
|
|
|
| 38 |
class QueryResponse(BaseModel):
|
| 39 |
answer: str
|
| 40 |
sources: list[Source]
|
| 41 |
+
rewritten_query: str = ""
|
| 42 |
|
| 43 |
|
| 44 |
@app.get("/", response_class=HTMLResponse)
|
|
|
|
| 53 |
return QueryResponse(
|
| 54 |
answer=result["answer"],
|
| 55 |
sources=[Source(**s) for s in result["sources"]],
|
| 56 |
+
rewritten_query=result.get("rewritten_query", ""),
|
| 57 |
)
|
| 58 |
|
| 59 |
|
|
|
|
| 85 |
await asyncio.sleep(0)
|
| 86 |
|
| 87 |
loop = asyncio.get_event_loop()
|
| 88 |
+
contexts, sources, rewritten = await loop.run_in_executor(None, retrieve, req.question, req.top_k)
|
| 89 |
|
| 90 |
+
yield f"data: {json.dumps({'type': 'sources', 'sources': sources, 'rewritten_query': rewritten})}\n\n"
|
| 91 |
await asyncio.sleep(0)
|
| 92 |
|
| 93 |
prompt = build_prompt(req.question, contexts)
|
|
|
|
| 115 |
def compare(req: QueryRequest):
|
| 116 |
"""So sánh Text RAG vs ColPali retrieval trên cùng câu hỏi."""
|
| 117 |
from src.colpali_retriever import query as colpali_query
|
| 118 |
+
_, text_sources, _ = retrieve(req.question, top_k=req.top_k)
|
| 119 |
try:
|
| 120 |
colpali_hits = colpali_query(req.question, top_k=req.top_k)
|
| 121 |
except Exception as e:
|
src/rag.py
CHANGED
|
@@ -32,7 +32,7 @@ TRẢ LỜI:"""
|
|
| 32 |
|
| 33 |
|
| 34 |
def retrieve(question: str, top_k: int = 5):
|
| 35 |
-
"""Rewrite -> embed -> hybrid retrieve -> rerank. Returns (contexts, sources)."""
|
| 36 |
rewritten = rewrite_query(question)
|
| 37 |
if rewritten != question:
|
| 38 |
print(f"Query rewritten: {rewritten}")
|
|
@@ -53,12 +53,12 @@ def retrieve(question: str, top_k: int = 5):
|
|
| 53 |
}
|
| 54 |
for c in contexts
|
| 55 |
]
|
| 56 |
-
return contexts, sources
|
| 57 |
|
| 58 |
|
| 59 |
def answer(question: str, top_k: int = 5) -> dict:
|
| 60 |
"""RAG pipeline: rewrite -> embed -> hybrid retrieve -> rerank -> generate."""
|
| 61 |
-
contexts, sources = retrieve(question, top_k)
|
| 62 |
prompt = build_prompt(question, contexts)
|
| 63 |
response = client.chat.completions.create(
|
| 64 |
model=LLM_MODEL,
|
|
@@ -68,4 +68,5 @@ def answer(question: str, top_k: int = 5) -> dict:
|
|
| 68 |
"answer": response.choices[0].message.content,
|
| 69 |
"contexts": [c["text"] for c in contexts],
|
| 70 |
"sources": sources,
|
|
|
|
| 71 |
}
|
|
|
|
| 32 |
|
| 33 |
|
| 34 |
def retrieve(question: str, top_k: int = 5):
|
| 35 |
+
"""Rewrite -> embed -> hybrid retrieve -> rerank. Returns (contexts, sources, rewritten_query)."""
|
| 36 |
rewritten = rewrite_query(question)
|
| 37 |
if rewritten != question:
|
| 38 |
print(f"Query rewritten: {rewritten}")
|
|
|
|
| 53 |
}
|
| 54 |
for c in contexts
|
| 55 |
]
|
| 56 |
+
return contexts, sources, rewritten
|
| 57 |
|
| 58 |
|
| 59 |
def answer(question: str, top_k: int = 5) -> dict:
|
| 60 |
"""RAG pipeline: rewrite -> embed -> hybrid retrieve -> rerank -> generate."""
|
| 61 |
+
contexts, sources, rewritten = retrieve(question, top_k)
|
| 62 |
prompt = build_prompt(question, contexts)
|
| 63 |
response = client.chat.completions.create(
|
| 64 |
model=LLM_MODEL,
|
|
|
|
| 68 |
"answer": response.choices[0].message.content,
|
| 69 |
"contexts": [c["text"] for c in contexts],
|
| 70 |
"sources": sources,
|
| 71 |
+
"rewritten_query": rewritten,
|
| 72 |
}
|
static/index.html
CHANGED
|
@@ -122,6 +122,8 @@
|
|
| 122 |
.uploaded-badge { display: inline-block; background: var(--green-bg); color: var(--green-text); border-radius: 4px; padding: 1px 5px; font-size: 0.68rem; font-weight: 600; margin-left: 4px; }
|
| 123 |
.ctx-badge { display: inline-block; background: #fef3c7; color: #92400e; border-radius: 4px; padding: 1px 5px; font-size: 0.65rem; font-weight: 600; margin-left: 4px; }
|
| 124 |
[data-theme="dark"] .ctx-badge { background: #3a2a0a; color: #fbbf24; }
|
|
|
|
|
|
|
| 125 |
.expand-btn { background: none; border: 1px solid var(--border); border-radius: 5px; padding: 2px 7px; font-size: 0.68rem; color: var(--text3); cursor: pointer; margin-top: 4px; transition: all 0.15s; }
|
| 126 |
.expand-btn:hover { border-color: var(--primary); color: var(--primary); }
|
| 127 |
.source-text { display: none; margin-top: 8px; padding: 8px 10px; background: var(--surface2); border-left: 3px solid var(--primary); border-radius: 0 6px 6px 0; font-size: 0.76rem; line-height: 1.7; color: var(--text2); white-space: pre-wrap; word-break: break-word; max-height: 200px; overflow-y: auto; }
|
|
@@ -450,6 +452,7 @@
|
|
| 450 |
});
|
| 451 |
|
| 452 |
const bubble = item.querySelector('.bubble-a');
|
|
|
|
| 453 |
let fullText = '', sources = [];
|
| 454 |
let copyBtn = null;
|
| 455 |
|
|
@@ -471,6 +474,10 @@
|
|
| 471 |
sources = msg.sources;
|
| 472 |
bubble.innerHTML = '<div class="answer-text"></div>';
|
| 473 |
copyBtn = addCopyBtn(bubble, () => fullText);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
} else if (msg.type === 'token') {
|
| 475 |
fullText += msg.text;
|
| 476 |
bubble.querySelector('.answer-text').innerHTML = highlightCitations(fullText) + '<span class="cursor">▋</span>';
|
|
|
|
| 122 |
.uploaded-badge { display: inline-block; background: var(--green-bg); color: var(--green-text); border-radius: 4px; padding: 1px 5px; font-size: 0.68rem; font-weight: 600; margin-left: 4px; }
|
| 123 |
.ctx-badge { display: inline-block; background: #fef3c7; color: #92400e; border-radius: 4px; padding: 1px 5px; font-size: 0.65rem; font-weight: 600; margin-left: 4px; }
|
| 124 |
[data-theme="dark"] .ctx-badge { background: #3a2a0a; color: #fbbf24; }
|
| 125 |
+
.rewrite-badge { font-size: 0.74rem; color: var(--text3); font-style: italic; padding: 3px 0 0 4px; }
|
| 126 |
+
.rewrite-badge span { color: var(--primary); font-style: normal; font-weight: 600; }
|
| 127 |
.expand-btn { background: none; border: 1px solid var(--border); border-radius: 5px; padding: 2px 7px; font-size: 0.68rem; color: var(--text3); cursor: pointer; margin-top: 4px; transition: all 0.15s; }
|
| 128 |
.expand-btn:hover { border-color: var(--primary); color: var(--primary); }
|
| 129 |
.source-text { display: none; margin-top: 8px; padding: 8px 10px; background: var(--surface2); border-left: 3px solid var(--primary); border-radius: 0 6px 6px 0; font-size: 0.76rem; line-height: 1.7; color: var(--text2); white-space: pre-wrap; word-break: break-word; max-height: 200px; overflow-y: auto; }
|
|
|
|
| 452 |
});
|
| 453 |
|
| 454 |
const bubble = item.querySelector('.bubble-a');
|
| 455 |
+
const bubbleQ = item.querySelector('.bubble-q');
|
| 456 |
let fullText = '', sources = [];
|
| 457 |
let copyBtn = null;
|
| 458 |
|
|
|
|
| 474 |
sources = msg.sources;
|
| 475 |
bubble.innerHTML = '<div class="answer-text"></div>';
|
| 476 |
copyBtn = addCopyBtn(bubble, () => fullText);
|
| 477 |
+
if (msg.rewritten_query && msg.rewritten_query !== q) {
|
| 478 |
+
bubbleQ.insertAdjacentHTML('afterend',
|
| 479 |
+
`<div class="rewrite-badge">🔄 Query được viết lại: <span>"${msg.rewritten_query}"</span></div>`);
|
| 480 |
+
}
|
| 481 |
} else if (msg.type === 'token') {
|
| 482 |
fullText += msg.text;
|
| 483 |
bubble.querySelector('.answer-text').innerHTML = highlightCitations(fullText) + '<span class="cursor">▋</span>';
|