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("""
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"""
{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("""
""", 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"""
""", 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)