Use full-document Q&A and refine ask row
Browse filesCo-authored-by: Codex <codex@openai.com>
- app.py +15 -12
- tests/test_app.py +40 -1
app.py
CHANGED
|
@@ -16,7 +16,6 @@ from services import ingest
|
|
| 16 |
from services.providers import get_default_api_key, instantiate_client, list_providers, validate_api_key
|
| 17 |
from services.rag_pipeline import (
|
| 18 |
AnalysisResult,
|
| 19 |
-
answer_query,
|
| 20 |
answer_query_from_full_document,
|
| 21 |
build_cached_document_artifacts,
|
| 22 |
generate_analysis_progress,
|
|
@@ -133,6 +132,12 @@ APP_CSS = """
|
|
| 133 |
#analysis-output {
|
| 134 |
min-height: 100px;
|
| 135 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
#status-output,
|
| 137 |
#chat-status {
|
| 138 |
min-height: 1.5rem;
|
|
@@ -407,7 +412,7 @@ def _format_chat_entry(answer_text: str, citations: list[dict[str, Any]], *, pro
|
|
| 407 |
if provenance == "analysis_based":
|
| 408 |
prefix = "_Based on the summary and analysis._\n\n"
|
| 409 |
elif provenance == "full_document":
|
| 410 |
-
prefix = "
|
| 411 |
|
| 412 |
if not citations:
|
| 413 |
return prefix + answer_text
|
|
@@ -701,17 +706,16 @@ def ask_question(
|
|
| 701 |
return
|
| 702 |
|
| 703 |
question_text = question.strip()
|
| 704 |
-
yield chat_history, session_state, "
|
| 705 |
|
| 706 |
try:
|
| 707 |
provider_client = instantiate_client(
|
| 708 |
record["api_config"]["provider"],
|
| 709 |
record["api_config"]["api_key"],
|
| 710 |
)
|
| 711 |
-
yield chat_history, session_state, "
|
| 712 |
-
answer =
|
| 713 |
provider_client,
|
| 714 |
-
AnalysisResult.model_validate(record["analysis"]) if record.get("analysis") else None,
|
| 715 |
record.get("vector_store"),
|
| 716 |
question_text,
|
| 717 |
doc_text=record.get("doc_text"),
|
|
@@ -731,9 +735,8 @@ def ask_question(
|
|
| 731 |
{"role": "assistant", "content": _format_chat_entry(answer.answer, citations, provenance=answer.provenance)}
|
| 732 |
]
|
| 733 |
record["chat_history"] = chat_history
|
| 734 |
-
record["pending_deeper_question"] =
|
| 735 |
-
|
| 736 |
-
yield chat_history, session_state, "", _deeper_answer_updates(answer.deeper_answer_available), deeper_hint
|
| 737 |
|
| 738 |
|
| 739 |
def run_deeper_answer(
|
|
@@ -891,15 +894,15 @@ def build_app() -> gr.Blocks:
|
|
| 891 |
with gr.Group(elem_id="chat-panel"):
|
| 892 |
gr.Markdown("## Ask Further Questions")
|
| 893 |
chatbot = gr.Chatbot(show_label=False)
|
| 894 |
-
|
|
|
|
| 895 |
with gr.Column(scale=6, min_width=0):
|
| 896 |
-
gr.Markdown("Question")
|
| 897 |
question_input = gr.Textbox(
|
| 898 |
label="Question",
|
| 899 |
placeholder="What would you like to know?",
|
| 900 |
show_label=False,
|
| 901 |
)
|
| 902 |
-
ask_button = gr.Button("Ask", scale=1, variant="primary")
|
| 903 |
chat_status = gr.Markdown(elem_id="chat-status")
|
| 904 |
deeper_answer_button = gr.Button("Run deeper full-document answer", visible=False)
|
| 905 |
deeper_answer_hint = gr.Markdown("")
|
|
|
|
| 16 |
from services.providers import get_default_api_key, instantiate_client, list_providers, validate_api_key
|
| 17 |
from services.rag_pipeline import (
|
| 18 |
AnalysisResult,
|
|
|
|
| 19 |
answer_query_from_full_document,
|
| 20 |
build_cached_document_artifacts,
|
| 21 |
generate_analysis_progress,
|
|
|
|
| 132 |
#analysis-output {
|
| 133 |
min-height: 100px;
|
| 134 |
}
|
| 135 |
+
#chat-question-row {
|
| 136 |
+
align-items: flex-start;
|
| 137 |
+
}
|
| 138 |
+
#ask-button button {
|
| 139 |
+
border-radius: 0.6rem !important;
|
| 140 |
+
}
|
| 141 |
#status-output,
|
| 142 |
#chat-status {
|
| 143 |
min-height: 1.5rem;
|
|
|
|
| 412 |
if provenance == "analysis_based":
|
| 413 |
prefix = "_Based on the summary and analysis._\n\n"
|
| 414 |
elif provenance == "full_document":
|
| 415 |
+
prefix = "_Full-document answer._\n\n"
|
| 416 |
|
| 417 |
if not citations:
|
| 418 |
return prefix + answer_text
|
|
|
|
| 706 |
return
|
| 707 |
|
| 708 |
question_text = question.strip()
|
| 709 |
+
yield chat_history, session_state, "Reading the full document for an answer...", _deeper_answer_updates(False), ""
|
| 710 |
|
| 711 |
try:
|
| 712 |
provider_client = instantiate_client(
|
| 713 |
record["api_config"]["provider"],
|
| 714 |
record["api_config"]["api_key"],
|
| 715 |
)
|
| 716 |
+
yield chat_history, session_state, "Reading the full document for an answer...", _deeper_answer_updates(False), ""
|
| 717 |
+
answer = answer_query_from_full_document(
|
| 718 |
provider_client,
|
|
|
|
| 719 |
record.get("vector_store"),
|
| 720 |
question_text,
|
| 721 |
doc_text=record.get("doc_text"),
|
|
|
|
| 735 |
{"role": "assistant", "content": _format_chat_entry(answer.answer, citations, provenance=answer.provenance)}
|
| 736 |
]
|
| 737 |
record["chat_history"] = chat_history
|
| 738 |
+
record["pending_deeper_question"] = None
|
| 739 |
+
yield chat_history, session_state, "", _deeper_answer_updates(False), ""
|
|
|
|
| 740 |
|
| 741 |
|
| 742 |
def run_deeper_answer(
|
|
|
|
| 894 |
with gr.Group(elem_id="chat-panel"):
|
| 895 |
gr.Markdown("## Ask Further Questions")
|
| 896 |
chatbot = gr.Chatbot(show_label=False)
|
| 897 |
+
gr.Markdown("Question")
|
| 898 |
+
with gr.Row(elem_id="chat-question-row"):
|
| 899 |
with gr.Column(scale=6, min_width=0):
|
|
|
|
| 900 |
question_input = gr.Textbox(
|
| 901 |
label="Question",
|
| 902 |
placeholder="What would you like to know?",
|
| 903 |
show_label=False,
|
| 904 |
)
|
| 905 |
+
ask_button = gr.Button("Ask", scale=1, variant="primary", elem_id="ask-button")
|
| 906 |
chat_status = gr.Markdown(elem_id="chat-status")
|
| 907 |
deeper_answer_button = gr.Button("Run deeper full-document answer", visible=False)
|
| 908 |
deeper_answer_hint = gr.Markdown("")
|
tests/test_app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
-
from app import _format_chat_entry, _stream_chat_entry
|
|
|
|
| 2 |
|
| 3 |
|
| 4 |
def test_format_chat_entry_wraps_supporting_snippets_in_details() -> None:
|
|
@@ -24,3 +25,41 @@ def test_stream_chat_entry_appends_collapsible_snippets_after_text_stream() -> N
|
|
| 24 |
|
| 25 |
assert frames[-2][-1]["content"] == "_Based on the summary and analysis._\n\nAnswer text"
|
| 26 |
assert "<details><summary>Supporting snippet (1)</summary>" in frames[-1][-1]["content"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app import _empty_session, _format_chat_entry, _session_record, ask_question, _stream_chat_entry
|
| 2 |
+
from services.rag_pipeline import AnswerResult, Citation
|
| 3 |
|
| 4 |
|
| 5 |
def test_format_chat_entry_wraps_supporting_snippets_in_details() -> None:
|
|
|
|
| 25 |
|
| 26 |
assert frames[-2][-1]["content"] == "_Based on the summary and analysis._\n\nAnswer text"
|
| 27 |
assert "<details><summary>Supporting snippet (1)</summary>" in frames[-1][-1]["content"]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_ask_question_uses_full_document_answers(monkeypatch) -> None:
|
| 31 |
+
session_state = _empty_session()
|
| 32 |
+
record = _session_record(session_state)
|
| 33 |
+
record.update(
|
| 34 |
+
{
|
| 35 |
+
"api_config": {"provider": "qwen", "api_key": "hf_test_token"},
|
| 36 |
+
"analysis": {},
|
| 37 |
+
"vector_store": object(),
|
| 38 |
+
"doc_text": "Full bill text",
|
| 39 |
+
"chat_history": [],
|
| 40 |
+
"pending_deeper_question": "old question",
|
| 41 |
+
}
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
monkeypatch.setattr("app.instantiate_client", lambda provider, api_key: object())
|
| 45 |
+
|
| 46 |
+
calls: list[tuple[object | None, str | None]] = []
|
| 47 |
+
|
| 48 |
+
def fake_answer_query_from_full_document(provider_client, vector_store, question, *, doc_text=None):
|
| 49 |
+
calls.append((vector_store, doc_text))
|
| 50 |
+
return AnswerResult(
|
| 51 |
+
answer="Full-document answer",
|
| 52 |
+
citations=[Citation(ref_id=1, snippet="Clause text")],
|
| 53 |
+
provenance="full_document",
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
monkeypatch.setattr("app.answer_query_from_full_document", fake_answer_query_from_full_document)
|
| 57 |
+
|
| 58 |
+
frames = list(ask_question("What does the bill require?", session_state, []))
|
| 59 |
+
|
| 60 |
+
assert calls == [(record["vector_store"], "Full bill text")]
|
| 61 |
+
assert "Reading the full document for an answer..." == frames[0][2]
|
| 62 |
+
assert "<details><summary>Supporting snippet (1)</summary>" in frames[-1][0][-1]["content"]
|
| 63 |
+
assert frames[-1][3]["visible"] is False
|
| 64 |
+
assert frames[-1][4] == ""
|
| 65 |
+
assert record["pending_deeper_question"] is None
|