diff --git a/libs/ktem/ktem/pages/chat/__init__.py b/libs/ktem/ktem/pages/chat/__init__.py index 9edebcce..63a0c781 100644 --- a/libs/ktem/ktem/pages/chat/__init__.py +++ b/libs/ktem/ktem/pages/chat/__init__.py @@ -27,6 +27,7 @@ from .control import ConversationControl from .memory import MemoryPage from .rag_setting import RAGSetting from .report import ReportIssue +from .verification import VerificationPage DEFAULT_SETTING = "(default)" INFO_PANEL_SCALES = {True: 8, False: 4} @@ -102,6 +103,7 @@ class ChatPage(BasePage): # long-term memory page self.long_term_memory_btn = gr.Button("Long-term Memory") + self.verify_answer_btn = gr.Button("Verify Answer") if len(self._app.index_manager.indices) > 0: with gr.Accordion(label="Quick Upload", open=False) as _: @@ -156,6 +158,8 @@ class ChatPage(BasePage): ) as self.memory_accordion: self.memory_page = MemoryPage(self._app) + self.verification_page = VerificationPage(self._app) + with gr.Accordion(label="Information panel", open=True): self.modal = gr.HTML("
") self.plot_panel = gr.Plot(visible=False) @@ -462,6 +466,18 @@ class ChatPage(BasePage): outputs=[self.memory_accordion, self.memory_page.memory_detail_panel], ) + self.verify_answer_btn.click( + self.verification_page.verify_answer, + inputs=[ + self.chat_panel.chatbot, + self.original_retrieval_history, + ], + outputs=[ + self.verification_page.verification_ui, + self.verification_page.verification_result, + ], + ) + # evidence display on message selection self.chat_panel.chatbot.select( self.message_selected, diff --git a/libs/ktem/ktem/pages/chat/verification.py b/libs/ktem/ktem/pages/chat/verification.py new file mode 100644 index 00000000..aea3c470 --- /dev/null +++ b/libs/ktem/ktem/pages/chat/verification.py @@ -0,0 +1,94 @@ +import logging + +import gradio as gr +from ktem.app import BasePage +from ktem.reasoning.prompt_optimization.verify_answer import ( + verify_answer_groundedness_azure, +) +from ktem.reasoning.simple import AnswerWithContextPipeline +from ktem.utils.render import Render + +from kotaemon.base import Document + +logger = logging.getLogger(__name__) + + +class VerificationPage(BasePage): + """Verify the groundedness of the answer""" + + def __init__(self, app): + self._app = app + + self.on_building_ui() + + def on_building_ui(self): + with gr.Accordion( + "Verification Result", + visible=False, + ) as self.verification_ui: + self.verification_result = gr.HTML() + self.close_button = gr.Button( + "Close", + variant="secondary", + ) + + def on_register_events(self): + self.close_button.click( + fn=lambda: gr.update(visible=False), + outputs=[self.verification_ui], + ) + + def highlight_spans(self, text, spans): + spans = sorted(spans, key=lambda x: x["start"]) + highlighted_text = text[: spans[0]["start"]] + for idx, span in enumerate(spans): + to_highlight = text[span["start"] : span["end"]] + highlighted_text += Render.highlight(to_highlight) + if idx < len(spans) - 1: + highlighted_text += text[span["end"] : spans[idx + 1]["start"]] + highlighted_text += text[spans[-1]["end"] :] + + return highlighted_text + + def verify_answer(self, chat_history, retrieval_history): + if len(chat_history) < 1: + raise gr.Error("Empty chat.") + + query = chat_history[-1][0] + answer = chat_history[-1][1] + + last_evidence = retrieval_history[-1] + text_only_evidence, _ = AnswerWithContextPipeline.extract_evidence_images( + last_evidence + ) + + gr.Info("Verifying the groundedness of the answer. Please wait...") + result = verify_answer_groundedness_azure(query, answer, [text_only_evidence]) + + verification_output = "