""" Gradio app v2 que expone las 4 tools como MCP Server (streamable HTTP). Diferencia con v1: la base ChromaDB se descarga del dataset HF Hub al primer uso. La carga del modelo de embeddings + descarga del snapshot se hace lazy en la primera tool call, no al arrancar el Space. Local: uv run python -m rag_books_mcp.app HF Spaces: Ver `deploy_to_hf_space.py`. """ from __future__ import annotations import gradio as gr from rag_books_mcp.tools import ( cite_foundation, get_section, list_available_topics, search_theory, ) def _build_search_tab() -> gr.Interface: return gr.Interface( fn=search_theory, inputs=[ gr.Textbox( label="query", value="bias-variance tradeoff", placeholder="Consulta en lenguaje natural", ), gr.Radio(choices=["both", "esl", "islp"], value="both", label="book"), gr.Slider(minimum=1, maximum=10, step=1, value=5, label="top_k"), ], outputs=gr.Markdown(label="Resultados"), title="🔎 search_theory", description=( "Búsqueda semántica en ESL e ISLP. Devuelve los fragmentos más " "relevantes ordenados por similitud." ), api_name="search_theory", ) def _build_get_section_tab() -> gr.Interface: return gr.Interface( fn=get_section, inputs=[ gr.Radio(choices=["esl", "islp"], value="islp", label="book"), gr.Textbox( label="chapter", value="8 Tree-Based Methods", placeholder="Nombre del capítulo (búsqueda parcial soportada)", ), gr.Textbox( label="section", value="", placeholder="(Opcional) Nombre de la sección", ), gr.Slider(minimum=1, maximum=15, step=1, value=5, label="max_chunks"), ], outputs=gr.Markdown(label="Sección"), title="📑 get_section", description=( "Recupera una sección específica de ESL o ISLP. Si no se encuentra " "por metadata, hace fallback a búsqueda semántica." ), api_name="get_section", ) def _build_cite_tab() -> gr.Interface: return gr.Interface( fn=cite_foundation, inputs=[ gr.Textbox( label="topic", value="ridge regression", placeholder="Tema a fundamentar", ), gr.Radio( choices=["brief", "medium", "deep"], value="medium", label="detail_level", ), ], outputs=gr.Markdown(label="Fundamentación"), title="📚 cite_foundation", description=( "Fundamentación teórica que cita ambos libros: ISLP (intuitivo) y " "ESL (riguroso)." ), api_name="cite_foundation", ) def _build_list_topics_tab() -> gr.Interface: return gr.Interface( fn=list_available_topics, inputs=[], outputs=gr.Markdown(label="Contenido indexado"), title="🗂️ list_available_topics", description="Lista los capítulos y secciones indexados en ChromaDB.", api_name="list_available_topics", ) def build_demo() -> gr.Blocks: """Construye la UI tabulada del MCP Server v2.""" with gr.Blocks(title="rag-books-mcp v2 · ESL + ISLP") as demo: gr.Markdown( """ # 📖 RAG Books MCP v2 — ESL + ISLP Servidor MCP que expone búsqueda semántica sobre dos libros de referencia de Statistical Learning: - **ESL** — *The Elements of Statistical Learning* (Hastie, Tibshirani, Friedman) - **ISLP** — *An Introduction to Statistical Learning with Python* (James, Witten, Hastie, Tibshirani) **v2 vs v1:** la base ChromaDB se carga desde el dataset HF `gusdelact/rag-esl-islp-chromadb` en lugar de empaquetarla con el código. Permite versionar el índice independientemente y reusarlo desde otros clientes. **Endpoint MCP:** `/gradio_api/mcp/` (streamable HTTP). **Embeddings:** `sentence-transformers/all-MiniLM-L6-v2` (local, sin API key). **Vector store:** ChromaDB con 1977 chunks (1093 ESL + 884 ISLP). La primera tool call descarga el dataset (~40 MB). Las siguientes son cache hit. """ ) gr.TabbedInterface( interface_list=[ _build_search_tab(), _build_cite_tab(), _build_get_section_tab(), _build_list_topics_tab(), ], tab_names=["search_theory", "cite_foundation", "get_section", "list_available_topics"], ) return demo def main() -> None: demo = build_demo() demo.launch(mcp_server=True, server_name="0.0.0.0") if __name__ == "__main__": main()