File size: 8,635 Bytes
1a6d259 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | """
Research Paper Answer Bot — Streamlit UI
Retrieves from an existing Pinecone index (built by Research_Paper_Answer_Bot.ipynb)
and answers questions with Groq's Llama model, grounded with citations.
"""
import os
import streamlit as st
from sentence_transformers import SentenceTransformer, CrossEncoder
from pinecone import Pinecone
from groq import Groq
# ------------------------------------------------------------------
# Page setup
# ------------------------------------------------------------------
st.set_page_config(page_title="Research Paper Answer Bot", page_icon="📚", layout="centered")
# ------------------------------------------------------------------
# Light brown theme (extra styling on top of .streamlit/config.toml)
# ------------------------------------------------------------------
st.markdown("""
<style>
.stApp { background-color: #F5EBDD; }
section[data-testid="stSidebar"] {
background-color: #EFDFC5;
border-right: 1px solid #D8BE93;
}
h1, h2, h3 { color: #4A2E12 !important; }
p, span, li, label { color: #3E2A18; }
/* chat bubbles */
div[data-testid="stChatMessage"] {
background-color: #FFF9F0;
border: 1px solid #E1C79A;
border-radius: 14px;
padding: 6px 10px;
margin-bottom: 6px;
}
/* force readable text inside chat bubbles — this is what was invisible */
div[data-testid="stChatMessage"] p,
div[data-testid="stChatMessage"] li,
div[data-testid="stChatMessage"] span,
div[data-testid="stChatMessage"] div[data-testid="stMarkdownContainer"] {
color: #3E2A18 !important;
}
.stCaption, [data-testid="stCaptionContainer"],
div[data-testid="stChatMessage"] [data-testid="stCaptionContainer"] p {
color: #7A5C3E !important;
}
.stButton>button {
background-color: #8B5E3C;
color: #FFF9F0;
border-radius: 8px;
border: none;
}
.stButton>button:hover { background-color: #6E4A2E; color: #FFF9F0; }
div[data-testid="stChatInput"] {
background-color: #FFF9F0;
border: 1px solid #D8BE93;
border-radius: 12px;
}
</style>
""", unsafe_allow_html=True)
# ------------------------------------------------------------------
# Sidebar — config
# ------------------------------------------------------------------
with st.sidebar:
st.markdown("### ⚙️ Settings")
index_name = st.text_input("Pinecone index name", value="research-bot")
top_k = st.slider("Chunks to retrieve (k)", 2, 10, 5)
use_reranker = st.checkbox("Use cross-encoder re-ranker", value=True)
st.divider()
if st.button("🗑️ Clear chat"):
st.session_state.messages = []
st.session_state.history = []
st.rerun()
st.caption("This app only queries the Pinecone index — run the notebook first to embed and upsert the PDFs.")
groq_api_key= "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"
pinecone_api_key= "pcsk_3jtVHH_5GGvLrt3pEgoexxL4TF17Su3TQ5a1Qjm1CEnG4rTcHreADx1R4F72Ui1KJJEgwo"
if not groq_api_key or not pinecone_api_key:
st.title("📚 Research Paper Answer Bot")
st.info("Enter your Groq and Pinecone API keys in the sidebar to start chatting.")
st.stop()
# ------------------------------------------------------------------
# Cached resources
# ------------------------------------------------------------------
@st.cache_resource(show_spinner="Loading embedding model…")
def load_embedder():
return SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
@st.cache_resource(show_spinner="Loading re-ranker…")
def load_reranker():
return CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
@st.cache_resource(show_spinner=False)
def get_clients(groq_key, pine_key):
return Groq(api_key=groq_key), Pinecone(api_key=pine_key)
embedder = load_embedder()
groq_client, pc = get_clients(groq_api_key, pinecone_api_key)
existing_indexes = [ix["name"] for ix in pc.list_indexes()]
if index_name not in existing_indexes:
st.title("📚 Research Paper Answer Bot")
st.error(
f"Pinecone index '{index_name}' doesn't exist yet. "
"Run the notebook's indexing steps first (Sections 3–6) to create and populate it."
)
st.stop()
index = pc.Index(index_name)
reranker = load_reranker() if use_reranker else None
GROQ_MODEL = "llama-3.1-8b-instant"
SYSTEM_PROMPT = (
"You are a precise research assistant. Answer the user's question using ONLY the "
"provided context. If the answer is not in the context, say you don't know. "
"Cite sources inline as [source p.PAGE]."
)
# ------------------------------------------------------------------
# RAG pipeline (mirrors the notebook)
# ------------------------------------------------------------------
def embed(texts):
return embedder.encode(texts, show_progress_bar=False, normalize_embeddings=True).tolist()
def retrieve_cosine(query, k=5):
qv = embed([query])[0]
res = index.query(vector=qv, top_k=k, include_metadata=True)
return [
{
"text": m["metadata"]["text"],
"source": m["metadata"]["source"],
"page": m["metadata"]["page"],
"score": m["score"],
}
for m in res["matches"]
]
def retrieve_rerank(query, k=5, pool=20):
candidates = retrieve_cosine(query, k=pool)
pairs = [(query, c["text"]) for c in candidates]
scores = reranker.predict(pairs)
for c, s in zip(candidates, scores):
c["rerank_score"] = float(s)
return sorted(candidates, key=lambda c: c["rerank_score"], reverse=True)[:k]
def build_context(hits):
blocks = [f"[{h['source']} p.{h['page']}]\n{h['text']}" for h in hits]
return "\n\n---\n\n".join(blocks)
def rag_answer(query, k=5, use_rerank=True):
hits = retrieve_rerank(query, k=k) if use_rerank else retrieve_cosine(query, k=k)
context = build_context(hits)
resp = groq_client.chat.completions.create(
model=GROQ_MODEL,
temperature=0.1,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Context:\n{context}\n\nQuestion: {query}"},
],
)
answer = resp.choices[0].message.content
sources = sorted({(h["source"], h["page"]) for h in hits})
return answer, sources
def condense(question, history):
if not history:
return question
convo = "\n".join(f"{r}: {c}" for r, c in history[-6:])
resp = groq_client.chat.completions.create(
model=GROQ_MODEL,
temperature=0.0,
messages=[
{
"role": "system",
"content": "Rewrite the follow-up question as a standalone question "
"using the chat history. Return only the rewritten question.",
},
{"role": "user", "content": f"Chat history:\n{convo}\n\nFollow-up: {question}"},
],
)
return resp.choices[0].message.content.strip()
# ------------------------------------------------------------------
# Session state
# ------------------------------------------------------------------
if "messages" not in st.session_state:
st.session_state.messages = []
if "history" not in st.session_state:
st.session_state.history = []
# ------------------------------------------------------------------
# UI
# ------------------------------------------------------------------
st.title("📚 Research Paper Answer Bot")
st.caption("Ask about the indexed papers — grounded, cited answers powered by Pinecone + Groq.")
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
if msg.get("sources"):
st.caption("Sources: " + ", ".join(f"{s}:p{p}" for s, p in msg["sources"]))
if prompt := st.chat_input("Ask something about the papers…"):
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("Thinking…"):
standalone = condense(prompt, st.session_state.history)
answer, sources = rag_answer(standalone, k=top_k, use_rerank=use_reranker)
st.markdown(answer)
if sources:
st.caption("Sources: " + ", ".join(f"{s}:p{p}" for s, p in sources))
st.session_state.history.append(("user", prompt))
st.session_state.history.append(("assistant", answer))
st.session_state.messages.append({"role": "assistant", "content": answer, "sources": sources})
|