thaidinhz1 Claude Sonnet 4.6 commited on
Commit
e535e98
·
1 Parent(s): 7053098

feat: show rewritten query in chat UI

Browse files

When 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>

Files changed (3) hide show
  1. api.py +5 -3
  2. src/rag.py +4 -3
  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>';