merchantkevin
Final changes, added conversational query rewriting
f329d5d
Raw
History Blame Contribute Delete
5.26 kB
"""RAG Document Chat β€” Gradio UI (entry point for Hugging Face Spaces)."""
import os
import gradio as gr
try: # optional: load a local .env during development (no-op on Spaces)
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
from rag import pipeline, llm
ALLOWED_EXT = [".pdf", ".docx", ".txt"]
DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
# --- Built-in corpus (Constitution of India), indexed once at startup ---
DEFAULT_STORE = None
DEFAULT_STATUS = ""
def _default_doc_paths():
if not os.path.isdir(DATA_DIR):
return []
return sorted(
os.path.join(DATA_DIR, f)
for f in os.listdir(DATA_DIR)
if os.path.splitext(f)[1].lower() in ALLOWED_EXT
)
def init_default_store():
"""Build the built-in index once. Failures are non-fatal (upload still works)."""
global DEFAULT_STORE, DEFAULT_STATUS
paths = _default_doc_paths()
if not paths:
return
try:
DEFAULT_STORE, summaries = pipeline.build_index(paths)
names = ", ".join(n for n, _ in summaries)
DEFAULT_STATUS = (
f"πŸ“š **Built-in corpus loaded:** Constitution of India β€” {names}. "
"Ask a question below, or upload your own files to use them instead."
)
except Exception as e:
DEFAULT_STATUS = f"⚠️ Could not load the built-in corpus: {e}"
def process_files(files):
"""Build a session-scoped index from uploaded files."""
if not files:
return None, "Please upload at least one PDF, DOCX, or TXT file."
paths = [getattr(f, "name", f) for f in files] # works for str or file obj
bad = [os.path.basename(p) for p in paths
if os.path.splitext(p)[1].lower() not in ALLOWED_EXT]
if bad:
return None, f"❌ Unsupported file(s): {', '.join(bad)}. Allowed: PDF, DOCX, TXT."
try:
store, summaries = pipeline.build_index(paths)
except Exception as e: # surface a clean message to the user
return None, f"❌ {e}"
total = sum(c for _, c in summaries)
lines = "\n".join(f"- **{n}** β€” {c} chunks" for n, c in summaries)
status = (
f"βœ… Indexed {len(summaries)} document(s) into {total} chunks.\n{lines}\n\n"
"You can start asking questions."
)
return store, status
def chat_fn(message, history, store):
"""Handle a chat turn; history uses the OpenAI-style messages format."""
message = (message or "").strip()
if not message:
return history, ""
active = store if (store is not None and len(store)) else DEFAULT_STORE
if active is None or len(active) == 0:
reply = "Please upload and process documents before asking questions."
else:
reply, hits = pipeline.answer(active, message, history=history)
if hits and reply != pipeline.OUT_OF_SCOPE_MSG and reply != pipeline.INJECTION_MSG:
sources = sorted({h["source"] for h in hits})
reply += f"\n\n*Sources: {', '.join(sources)}*"
history = history + [
{"role": "user", "content": message},
{"role": "assistant", "content": reply},
]
return history, ""
# Build the built-in index at startup so DEFAULT_STATUS is ready for the UI.
init_default_store()
with gr.Blocks(title="RAG Document Chat") as demo:
gr.Markdown(
"# πŸ“„ RAG Document Chat\n"
"Ask questions answered strictly from document content, with guardrails "
"for prompt injection and out-of-scope questions. This demo comes preloaded "
"with the **Constitution of India** (Fundamental Rights, Directive Principles, "
"Fundamental Duties) β€” try a question right away, or upload your own "
"PDF / DOCX / TXT files."
)
store_state = gr.State(None) # per-session vector store (overrides built-in)
with gr.Row():
with gr.Column(scale=1):
files = gr.File(
label="Upload your own documents (optional)",
file_count="multiple",
file_types=ALLOWED_EXT,
type="filepath",
)
process_btn = gr.Button("Process documents", variant="primary")
status = gr.Markdown(DEFAULT_STATUS)
if not llm.is_configured():
gr.Markdown(
"⚠️ **No LLM API key detected.** Set `GROQ_API_KEY` "
"(or configure another provider) to enable answering."
)
with gr.Column(scale=2):
chatbot = gr.Chatbot(label="Chat", height=460)
msg = gr.Textbox(
placeholder="e.g. What does Article 21 protect?",
show_label=False,
autofocus=True,
)
with gr.Row():
send = gr.Button("Send", variant="primary")
clear = gr.Button("Clear chat")
process_btn.click(process_files, inputs=[files], outputs=[store_state, status])
send.click(chat_fn, inputs=[msg, chatbot, store_state], outputs=[chatbot, msg])
msg.submit(chat_fn, inputs=[msg, chatbot, store_state], outputs=[chatbot, msg])
clear.click(lambda: [], outputs=[chatbot])
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft())