Spaces:
Paused
Paused
Download app.py from cryotud-AJ/probabot: direct link, hf CLI and curl.
- Browser
- Download file 12.6 kB
-
https://huggingface.co/spaces/cryotud-AJ/probabot/resolve/f2d1c33286b5c83086ece625c68223fdcd26b567/app.py
- Command line
-
hf download hf://spaces/cryotud-AJ/probabot@f2d1c33286b5c83086ece625c68223fdcd26b567/app.py
-
curl -L -o app.py https://huggingface.co/spaces/cryotud-AJ/probabot/resolve/f2d1c33286b5c83086ece625c68223fdcd26b567/app.py
12.6 kB
| import os | |
| os.environ.setdefault("HF_HOME", "/data/hf_cache") | |
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| from src.config import ( | |
| MODEL_MAP, | |
| MODEL_CHOICES, | |
| DEFAULT_MODEL, | |
| DEFAULT_MAX_TOKENS, | |
| DEFAULT_Q, | |
| DEFAULT_M, | |
| ENTAILMENT_USE_API, | |
| ) | |
| # UI labels for the entailment backend toggle | |
| _BACKEND_LOCAL = "Local (GPU/CPU)" | |
| _BACKEND_API = "Inference API" | |
| _DEFAULT_BACKEND = _BACKEND_API if ENTAILMENT_USE_API else _BACKEND_LOCAL | |
| from src.generation import query_model_with_logprobs | |
| from src.pipeline import run_pipeline | |
| from src.rendering import LEGEND_HTML, TOOLTIP_CSS, render_token_probs_html, color_code_answer_html | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _make_client(): | |
| return InferenceClient(token=os.environ.get("HF_TOKEN")) | |
| def _result_ready_for(semantic_result, answer): | |
| """A result counts as ready only if it was computed for the CURRENT answer. | |
| Guards against a stale background pipeline (from a previous question) whose | |
| result lands after the user has already asked something new.""" | |
| return bool(semantic_result) and semantic_result.get("answer") == answer | |
| def _render_view(view, token_logprobs, semantic_result, answer): | |
| if view == "Token probability": | |
| return render_token_probs_html(token_logprobs or []) | |
| # Semantic entropy | |
| if _result_ready_for(semantic_result, answer): | |
| if semantic_result.get("error"): | |
| return ( | |
| "<p style='padding:8px;'><em>β οΈ Semantic entropy could not be computed for " | |
| "this answer. Switch the toggle to <strong>Token probability</strong> to see " | |
| "per-token confidence.</em></p>" | |
| ) | |
| return color_code_answer_html( | |
| answer, | |
| semantic_result.get("sp_to_entropy", {}), | |
| semantic_result.get("sentence_parts", []), | |
| ) | |
| return "<p><em>Semantic entropy is still being computed. Please try again in a moment, or switch to Token probability.</em></p>" | |
| # --------------------------------------------------------------------------- | |
| # Status banners | |
| # --------------------------------------------------------------------------- | |
| def _status_banner(icon, text, bg, border, fg): | |
| # Set the color directly on the text span with !important so Gradio's theme | |
| # CSS can't override it to a light color. | |
| return ( | |
| f"<div style='display:flex; align-items:center; gap:10px; padding:8px 12px; " | |
| f"border-radius:6px; font-size:0.95em; background:{bg}; border:1px solid {border};'>" | |
| f"<span style='font-size:1.3em;'>{icon}</span>" | |
| f"<span style='color:{fg} !important; font-weight:500;'>{text}</span>" | |
| "</div>" | |
| ) | |
| _STATUS_COMPUTING = _status_banner( | |
| "β³", | |
| "Computing semantic entropy in the background β this may take 30β120 s. " | |
| "Please wait, or view <strong style='color:#3a2e00 !important;'>Token probability</strong> below.", | |
| bg="#fff8e1", border="#e0a800", fg="#3a2e00", | |
| ) | |
| _STATUS_READY = _status_banner( | |
| "β ", | |
| "Semantic entropy ready β click <strong style='color:#0b3d1a !important;'>Show probabilities</strong> to view.", | |
| bg="#e6f4ea", border="#2e7d32", fg="#0b3d1a", | |
| ) | |
| _STATUS_FAILED = _status_banner( | |
| "β οΈ", | |
| "Semantic entropy computation failed. Token probability view is still available.", | |
| bg="#fdecea", border="#c62828", fg="#5c1010", | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Event handlers | |
| # --------------------------------------------------------------------------- | |
| def handle_send(question, model_key, max_tokens_val): | |
| if not question or not question.strip(): | |
| return ( | |
| "", [], model_key, None, | |
| "<p><em>Please enter a question.</em></p>", | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(visible=False), | |
| ) | |
| client = _make_client() | |
| model_name = MODEL_MAP.get(model_key, MODEL_MAP[DEFAULT_MODEL]) | |
| answer, token_logprobs = query_model_with_logprobs( | |
| question, model_name, int(max_tokens_val), client | |
| ) | |
| safe_answer = ( | |
| answer | |
| .replace("&", "&") | |
| .replace("<", "<") | |
| .replace(">", ">") | |
| .replace("\n", "<br>") | |
| ) | |
| answer_html = f"<div style='font-family:sans-serif; padding:10px; line-height:1.6;'>{safe_answer}</div>" | |
| return ( | |
| answer, # answer_state | |
| token_logprobs, # token_logprobs_state | |
| model_key, # model_name_state | |
| None, # semantic_result_state (reset) | |
| answer_html, # answer_display | |
| gr.update(value=_STATUS_COMPUTING, visible=True),# pipeline_status | |
| gr.update(visible=True), # reveal results_group | |
| "", # viz_html clear | |
| gr.update(visible=True), # cancel_btn shown | |
| ) | |
| def _failed_result(answer): | |
| """Sentinel result so semantic_result is None ONLY while still running.""" | |
| return { | |
| "factoid_to_entropy": {}, | |
| "sp_to_entropy": {}, | |
| "sentence_parts": [], | |
| "mean_semantic_entropy": 0.0, | |
| "answer": answer, | |
| "error": True, | |
| } | |
| def run_pipeline_bg(answer, model_key, max_tokens_val, Q_val, M_val, backend, progress=gr.Progress()): | |
| """Chained event: runs concurrently while user reads and rates. | |
| This is the ONLY place the semantic-entropy pipeline runs.""" | |
| if not answer: | |
| return None, gr.update(visible=False), gr.update(visible=False) | |
| client = _make_client() | |
| model_name = MODEL_MAP.get(model_key, MODEL_MAP[DEFAULT_MODEL]) | |
| use_api = (backend == _BACKEND_API) | |
| try: | |
| result = run_pipeline( | |
| answer=answer, | |
| model_name=model_name, | |
| max_tokens=int(max_tokens_val), | |
| client=client, | |
| Q=int(Q_val), | |
| M=int(M_val), | |
| progress=progress, | |
| use_api=use_api, | |
| ) | |
| return result, gr.update(value=_STATUS_READY, visible=True), gr.update(visible=False) | |
| except Exception as e: | |
| print(f"[pipeline] Background run failed: {e}") | |
| return _failed_result(answer), gr.update(value=_STATUS_FAILED, visible=True), gr.update(visible=False) | |
| _STILL_COMPUTING_MSG = ( | |
| "<p style='padding:8px;'><em>β³ Semantic entropy is still being computed. " | |
| "Please wait for the β banner above, then click <strong>Show probabilities</strong> again " | |
| "β or switch the toggle to <strong>Token probability</strong> to view that now.</em></p>" | |
| ) | |
| def on_show_probs(token_logprobs, semantic_result, answer, view): | |
| # If the semantic view is requested but the background pipeline is still | |
| # running, show a wait message and reveal the toggle so the user can view | |
| # Token probability immediately. | |
| if view == "Semantic entropy" and not _result_ready_for(semantic_result, answer): | |
| return _STILL_COMPUTING_MSG, gr.update(visible=True) | |
| html = _render_view(view, token_logprobs, semantic_result, answer) | |
| return html, gr.update(visible=True) | |
| def on_toggle_change(view, token_logprobs, semantic_result, answer): | |
| return _render_view(view, token_logprobs, semantic_result, answer) | |
| # --------------------------------------------------------------------------- | |
| # UI | |
| # --------------------------------------------------------------------------- | |
| with gr.Blocks(title="Probabot") as demo: | |
| # ---- Persistent state ---- | |
| answer_state = gr.State("") | |
| token_logprobs_state = gr.State([]) | |
| model_name_state = gr.State(DEFAULT_MODEL) | |
| semantic_result_state = gr.State(None) | |
| # ---- Header ---- | |
| gr.Markdown("# π€π Probabot v1") | |
| gr.Markdown( | |
| "Ask a question and see how confident the model is β both at the token level " | |
| "and at the factoid level via semantic entropy." | |
| ) | |
| gr.HTML(LEGEND_HTML) | |
| # ---- Input row ---- | |
| with gr.Row(): | |
| user_input = gr.Textbox( | |
| label="Your question", | |
| placeholder="What is the capital of France?", | |
| scale=4, | |
| ) | |
| send_btn = gr.Button("Send", scale=1, variant="primary") | |
| # ---- Config row ---- | |
| with gr.Row(): | |
| model_choice = gr.Dropdown( | |
| choices=MODEL_CHOICES, | |
| value=DEFAULT_MODEL, | |
| label="Model", | |
| ) | |
| max_tokens = gr.Number(value=DEFAULT_MAX_TOKENS, label="Max tokens", precision=0) | |
| q_num = gr.Number(value=DEFAULT_Q, label="Q (questions per factoid)", precision=0) | |
| m_num = gr.Number(value=DEFAULT_M, label="M (answers per question)", precision=0) | |
| with gr.Row(): | |
| backend_choice = gr.Radio( | |
| choices=[_BACKEND_LOCAL, _BACKEND_API], | |
| value=_DEFAULT_BACKEND, | |
| label="Entailment backend", | |
| info="Local = this Space's GPU/CPU Β· Inference API = HF's hosted compute", | |
| ) | |
| # ---- Answer display ---- | |
| answer_display = gr.HTML(label="Model response") | |
| with gr.Row(): | |
| pipeline_status = gr.HTML(visible=False) | |
| cancel_btn = gr.Button("Cancel computation", variant="stop", visible=False, scale=0) | |
| # ---- Hidden group: results / visualization ---- | |
| with gr.Group(visible=False) as results_group: | |
| show_probs_btn = gr.Button( | |
| "Show probabilities", variant="secondary" | |
| ) | |
| view_toggle = gr.Radio( | |
| choices=["Semantic entropy", "Token probability"], | |
| value="Semantic entropy", | |
| label="Confidence view", | |
| visible=False, | |
| ) | |
| viz_html = gr.HTML(visible=False) | |
| # ---- Events ---- | |
| # Send / submit | |
| send_outputs = [ | |
| answer_state, token_logprobs_state, model_name_state, semantic_result_state, | |
| answer_display, pipeline_status, results_group, | |
| viz_html, cancel_btn, | |
| ] | |
| pipeline_outputs = [semantic_result_state, pipeline_status, cancel_btn] | |
| send_click = send_btn.click( | |
| fn=handle_send, | |
| inputs=[user_input, model_choice, max_tokens], | |
| outputs=send_outputs, | |
| ) | |
| pipe_from_click = send_click.then( | |
| fn=run_pipeline_bg, | |
| inputs=[answer_state, model_name_state, max_tokens, q_num, m_num, backend_choice], | |
| outputs=pipeline_outputs, | |
| ) | |
| submit_event = user_input.submit( | |
| fn=handle_send, | |
| inputs=[user_input, model_choice, max_tokens], | |
| outputs=send_outputs, | |
| ) | |
| pipe_from_submit = submit_event.then( | |
| fn=run_pipeline_bg, | |
| inputs=[answer_state, model_name_state, max_tokens, q_num, m_num, backend_choice], | |
| outputs=pipeline_outputs, | |
| ) | |
| # Cancel any in-flight semantic-entropy pipeline when a NEW question is sent, | |
| # so an old (slow) run can't keep burning GPU or land a stale result. | |
| _running_pipelines = [pipe_from_click, pipe_from_submit] | |
| send_btn.click(fn=None, inputs=None, outputs=None, cancels=_running_pipelines) | |
| user_input.submit(fn=None, inputs=None, outputs=None, cancels=_running_pipelines) | |
| # Manual cancel button: abort the running pipeline and reset the UI. | |
| _STATUS_CANCELLED = _status_banner( | |
| "π", | |
| "Semantic entropy computation cancelled. Token probability is still " | |
| "available via the toggle.", | |
| bg="#f0f0f0", border="#888", fg="#222", | |
| ) | |
| def _on_cancel(): | |
| return ( | |
| gr.update(value=_STATUS_CANCELLED, visible=True), # pipeline_status | |
| gr.update(visible=False), # cancel_btn | |
| ) | |
| cancel_btn.click( | |
| fn=_on_cancel, | |
| inputs=None, | |
| outputs=[pipeline_status, cancel_btn], | |
| cancels=_running_pipelines, | |
| ) | |
| # Show probabilities | |
| show_probs_btn.click( | |
| fn=on_show_probs, | |
| inputs=[ | |
| token_logprobs_state, semantic_result_state, | |
| answer_state, view_toggle, | |
| ], | |
| outputs=[viz_html, view_toggle], | |
| ).then( | |
| fn=lambda: gr.update(visible=True), | |
| inputs=None, | |
| outputs=[viz_html], | |
| ) | |
| # Toggle view | |
| view_toggle.change( | |
| fn=on_toggle_change, | |
| inputs=[view_toggle, token_logprobs_state, semantic_result_state, answer_state], | |
| outputs=[viz_html], | |
| ) | |
| demo.launch(css=TOOLTIP_CSS) | |