""" app.py -------------- Agentic RAG — HuggingFace Space entry point (ZeroGPU version). """ import spaces # noqa: F401 - MUST stay first; see docstring above import time import gradio as gr from config import CONFIDENCE_THRESHOLD, MAX_PROMPT_TOKENS from retrieval import run_pipeline_phase1 from generation import generate_answer from utils import extract_sources, clean_final_answer from ui_helpers import format_metrics # Chat handler def chat(message: str, history: list): """ Sync generator for gr.ChatInterface(fn=chat, additional_outputs=[...]). This function has no GPU-specific code. It calls run_pipeline_phase1() (FAISS + Tavily, both CPU-only) and generate_answer() (which internally calls the @spaces.GPU-decorated _generate_on_gpu() exactly once). Yields 2-tuples: (bot_message_string, metrics_markdown_string). """ t_total_start = time.perf_counter() # Step 1: Immediate placeholder yield "🔍 Searching...", format_metrics() # Step 2: Retrieval (FAISS + optional Tavily, CPU-only) state = run_pipeline_phase1(message, history=history) t_retrieve = state.get("t_retrieve", 0.0) t_web = state.get("t_web", 0.0) confidence = state.get("confidence", 0.0) source_type = state.get("source_type", "none") sources_count = state.get("sources_count", 0) if state.get("needs_web") and sources_count > 0: status = ( f"🌐 {sources_count} sources fetched ({t_web:.1f}s). " f"Generating on GPU…" ) elif state.get("needs_web"): status = "🌐 Web search done. Generating on GPU…" else: status = f"📚 FAISS (sim={confidence:.3f}). Generating on GPU…" yield status, format_metrics( source_type = source_type, confidence = confidence, sources_count = sources_count, generating = True, elapsed = time.perf_counter() - t_total_start, t_retrieve = t_retrieve, t_web = t_web, ) # Step 3: Word-by-word generation # generate_answer() internally calls the @spaces.GPU-decorated # _generate_on_gpu() once (blocking - but on ZeroGPU this should # take a few seconds), then yields the response word by word. answer = "" output_tokens = 0 prompt_tokens = 0 t_gen_start = time.perf_counter() last_metrics = format_metrics( source_type = source_type, confidence = confidence, sources_count = sources_count, generating = True, elapsed = 0.0, t_retrieve = t_retrieve, t_web = t_web, ) try: for word_text, prompt_tok_count in generate_answer(state): answer += word_text output_tokens += 1 prompt_tokens = prompt_tok_count if output_tokens % 5 == 0: last_metrics = format_metrics( source_type = source_type, confidence = confidence, sources_count = sources_count, generating = True, elapsed = time.perf_counter() - t_gen_start, t_retrieve = t_retrieve, t_web = t_web, token_so_far = output_tokens, ) yield answer, last_metrics except Exception as e: error_msg = f"{type(e).__name__}: {e}" yield f"❌ Generation failed: {error_msg}", format_metrics(error=error_msg) return # Step 4: Final answer + sources + metrics t_generate = time.perf_counter() - t_gen_start t_total = time.perf_counter() - t_total_start tokens_per_sec = output_tokens / t_generate if t_generate > 0 else 0.0 sources_section = extract_sources(state) final_answer = clean_final_answer(answer) + sources_section yield final_answer, format_metrics( source_type = source_type, confidence = confidence, sources_count = sources_count, prompt_tokens = prompt_tokens, t_retrieve = t_retrieve, t_web = t_web, t_generate = t_generate, t_total = t_total, tokens_per_sec = tokens_per_sec, output_tokens = output_tokens, ) # UI Layout with gr.Blocks(theme=gr.themes.Soft(), title="Agentic RAG (ZeroGPU)") as demo: gr.Markdown( "## 🤖 Agentic RAG  " ) gr.Markdown( "💡**Tip:** Include full context in every question for best results. \n" "Follow-up questions are supported but specific questions always " "perform better.\n\n" "⚡**Note:** Running on a shared free GPU (ZeroGPU). Most responses " "complete in a few seconds, though you may occasionally wait in a " "short queue if the shared GPU is busy with other Spaces." ) with gr.Row(): with gr.Column(scale=3): metrics_panel = gr.Markdown(value=format_metrics(), render=False) gr.ChatInterface( fn=chat, additional_outputs=[metrics_panel], ) with gr.Column(scale=1, min_width=220): gr.Markdown("**📊 Metrics**") metrics_panel.render() # Launch # No share=True — HF Spaces provides its own public URL automatically. demo.queue(default_concurrency_limit=1) demo.launch()