""" 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=["all", "both", "esl", "islp", "fes", "pdsh", "r4ds"], value="all", label="book", info="R4DS está en R/tidyverse; los principios se traducen a pandas/seaborn.", ), 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, ISLP, FES, PDSH y R4DS. 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", "fes", "pdsh", "r4ds"], 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, ISLP, FES, PDSH o R4DS. 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 (ej: 'bagging', 'feature selection', 'EDA')", ), 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 los 5 libros: ISLP (intuitivo), " "ESL (riguroso), FES (feature engineering), PDSH (código Python) y " "R4DS (workflow iterativo de EDA y data wrangling)." ), 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 + FES + PDSH + R4DS") as demo: gr.Markdown( """ # 📖 RAG Books MCP v2 — ESL + ISLP + FES + PDSH + R4DS Servidor MCP que expone búsqueda semántica sobre cinco libros de referencia de Statistical Learning, Data Science y Data Wrangling: - **ESL** — *The Elements of Statistical Learning* (Hastie, Tibshirani, Friedman) - **ISLP** — *An Introduction to Statistical Learning with Python* (James, Witten, Hastie, Tibshirani) - **FES** — *Feature Engineering and Selection* (Kuhn, Johnson) - **PDSH** — *Python Data Science Handbook* (VanderPlas) - **R4DS** — *R for Data Science, 2nd Ed.* (Wickham, Çetinkaya-Rundel, Grolemund) — _ejemplos en R/tidyverse, principios universales para EDA y data wrangling_ > ℹ️ R4DS está bajo licencia CC BY-NC-ND 3.0 US y está incluido en > este dataset bajo uso académico. Detalles, atribución y mecanismo > de takedown en el [DATA_CARD del dataset](https://huggingface.co/datasets/gusdelact/rag-esl-islp-chromadb). **v2 vs v1:** la base ChromaDB se carga desde el dataset HF `gusdelact/rag-esl-islp-chromadb` (tag `v2.2.0`) 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 3689 chunks (1093 ESL + 884 ISLP + 465 FES + 563 PDSH + 684 R4DS). La primera tool call descarga el dataset (~95 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()