import streamlit as st import os import tempfile import gc import base64 import time import requests from datetime import datetime from groq import Groq from src.agentic_rag.tools.custom_tool import DocumentSearchTool st.set_page_config(page_title="NeuralDocs β€” Agentic RAG", page_icon="🧠", layout="wide", initial_sidebar_state="expanded") st.markdown(""" """, unsafe_allow_html=True) # ── Session State ───────────────────────────────────────────────────────────── defaults = { "messages": [], "pdf_tool": None, "pdf_name": None, "total_queries": 0, "pdf_hits": 0, "web_hits": 0, "query_log": [], "pending_followups": [], "pending_prompt": None, } for k, v in defaults.items(): if k not in st.session_state: st.session_state[k] = v def reset_chat(): st.session_state.messages = [] st.session_state.pending_followups = [] gc.collect() def full_reset(): for k in list(defaults.keys()): st.session_state[k] = defaults[k] gc.collect() def display_pdf(file_bytes, file_name): b64 = base64.b64encode(file_bytes).decode("utf-8") st.markdown(f'', unsafe_allow_html=True) def web_search(query: str) -> str: api_key = os.environ.get("SERPER_API_KEY", "") if not api_key: return "" try: response = requests.post( "https://google.serper.dev/search", headers={"X-API-KEY": api_key, "Content-Type": "application/json"}, json={"q": query, "num": 3}, timeout=10, ) data = response.json() results = [] for r in data.get("organic", [])[:3]: results.append(f"{r.get('title','')}: {r.get('snippet','')}") return "\n".join(results) except: return "" def detect_source(result_text): web_signals = ["according to", "website", "http", "search result", "online"] return "web" if any(s in result_text.lower() for s in web_signals) else "pdf" def estimate_confidence(result_text): if "i'm sorry" in result_text.lower() or "couldn't find" in result_text.lower(): return 20 if len(result_text) > 400: return 88 if len(result_text) > 150: return 72 return 55 def generate_followups(query, answer): keywords = [w for w in query.lower().split() if len(w) > 4] return [ f"Can you elaborate on {keywords[0] if keywords else 'this'} in more detail?", "What are the practical applications of this?", "What are the limitations or challenges here?", ] def run_query(prompt): """Direct Groq call β€” no CrewAI overhead, much faster.""" client = Groq(api_key=os.environ.get("GROQ_API_KEY")) # Step 1: Try PDF search context = "" source = "web" if st.session_state.pdf_tool: try: pdf_result = st.session_state.pdf_tool._run(prompt) if pdf_result and "No relevant" not in pdf_result: context = f"Document context:\n{pdf_result}\n\n" source = "pdf" except: pass # Step 2: If no PDF context, search web if not context: web_result = web_search(prompt) if web_result: context = f"Web search results:\n{web_result}\n\n" source = "web" # Step 3: Ask Groq directly system_prompt = "You are a helpful research assistant. Answer questions concisely based on the provided context. If no context is provided, answer from your knowledge. Keep answers under 200 words." user_message = f"{context}Question: {prompt}" response = client.chat.completions.create( model="llama-3.1-8b-instant", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_message} ], max_tokens=300, temperature=0.1, ) return response.choices[0].message.content, source # ── Sidebar ─────────────────────────────────────────────────────────────────── with st.sidebar: st.markdown("""
🧠
NeuralDocs
Agentic RAG Β· Groq + LLaMA 3
""", unsafe_allow_html=True) st.markdown('', unsafe_allow_html=True) uploaded_file = st.file_uploader("Upload PDF", type=["pdf"], label_visibility="collapsed") if uploaded_file is not None: if st.session_state.pdf_tool is None or st.session_state.pdf_name != uploaded_file.name: with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, uploaded_file.name) with open(path, "wb") as f: f.write(uploaded_file.getvalue()) with st.spinner("Indexing document…"): st.session_state.pdf_tool = DocumentSearchTool(file_path=path) st.session_state.pdf_name = uploaded_file.name st.session_state.messages = [] st.session_state.pending_followups = [] st.markdown(f'
{uploaded_file.name[:28]}{"…" if len(uploaded_file.name)>28 else ""}
', unsafe_allow_html=True) with st.expander("Preview PDF", expanded=False): display_pdf(uploaded_file.getvalue(), uploaded_file.name) else: st.markdown('
No document loaded
', unsafe_allow_html=True) st.markdown('', unsafe_allow_html=True) total = st.session_state.total_queries or 0 pdf_h = st.session_state.pdf_hits or 0 web_h = st.session_state.web_hits or 0 pdf_pct = int((pdf_h/total)*100) if total else 0 web_pct = int((web_h/total)*100) if total else 0 st.markdown(f"""
{total}
Queries
{pdf_pct}%
PDF hits
{web_pct}%
Web hits
{len(st.session_state.messages)//2}
Exchanges
""", unsafe_allow_html=True) if st.session_state.query_log: st.markdown('', unsafe_allow_html=True) for item in reversed(st.session_state.query_log[-6:]): src_icon = "🌐" if item["source"] == "web" else "πŸ“„" st.markdown(f'
{src_icon} {item["q"][:48]}{"…" if len(item["q"])>48 else ""}
{item["ts"]}
', unsafe_allow_html=True) st.markdown('', unsafe_allow_html=True) col1, col2 = st.columns(2) with col1: st.button("Clear Chat", on_click=reset_chat, use_container_width=True) with col2: st.button("Full Reset", on_click=full_reset, use_container_width=True) # ── Main ────────────────────────────────────────────────────────────────────── st.markdown("""
NeuralDocs
Agentic RAG Β· PDF + Web Intelligence
GROQ Β· LLAMA 3
""", unsafe_allow_html=True) if not st.session_state.messages: st.markdown("""
🧠
Upload a PDF in the sidebar, then ask anything.
The agent searches your document first β€” then the web.
""", unsafe_allow_html=True) for i, message in enumerate(st.session_state.messages): with st.chat_message(message["role"]): st.markdown(message["content"]) if message["role"] == "assistant" and i == len(st.session_state.messages) - 1 and st.session_state.pending_followups: conf = estimate_confidence(message["content"]) src = message.get("source", "pdf") st.markdown(f"""
{'🌐 Web' if src=='web' else 'πŸ“„ PDF'}
Confidence
{conf}%
""", unsafe_allow_html=True) st.markdown('
πŸ’‘ FOLLOW-UP SUGGESTIONS
', unsafe_allow_html=True) cols = st.columns(len(st.session_state.pending_followups)) for j, (col, q) in enumerate(zip(cols, st.session_state.pending_followups)): with col: if st.button(q, key=f"followup_{i}_{j}", use_container_width=True): st.session_state.pending_prompt = q def handle_query(prompt): st.session_state.pending_followups = [] st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) with st.chat_message("assistant"): with st.spinner("Researching…"): result, src = run_query(prompt) st.markdown(result) ts = datetime.now().strftime("%H:%M") st.session_state.total_queries += 1 if src == "pdf": st.session_state.pdf_hits += 1 else: st.session_state.web_hits += 1 st.session_state.query_log.append({"q": prompt, "ts": ts, "source": src}) st.session_state.messages.append({"role": "assistant", "content": result, "source": src}) st.session_state.pending_followups = generate_followups(prompt, result) st.rerun() if st.session_state.pending_prompt: prompt = st.session_state.pending_prompt st.session_state.pending_prompt = None handle_query(prompt) prompt = st.chat_input("Ask anything about your document or search the web…") if prompt: handle_query(prompt)