interviewtask / src /streamlit_app.py
krishna1705's picture
Update src/streamlit_app.py
8e266b3 verified
Raw
History Blame Contribute Delete
5.2 kB
from typing import TypedDict, List
import streamlit as st
from langgraph.graph import StateGraph
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from transformers import pipeline
# -------------------------
# STATE TYPE FOR LANGGRAPH
# -------------------------
class AgentState(TypedDict):
question: str
documents: List[str]
answer: str
reflection: str
# -------------------------
# LLM: HUGGINGFACE PIPELINE
# -------------------------
# Using a smaller model for faster loading on CPU and HF Spaces
llm_pipeline = pipeline(
"text2text-generation",
model="google/flan-t5-small", # you can switch to flan-t5-base if you want
)
def generate_text(prompt: str) -> str:
"""Call the HF pipeline and return plain text."""
out = llm_pipeline(prompt, max_new_tokens=256)
if isinstance(out, list) and len(out) > 0 and "generated_text" in out[0]:
return out[0]["generated_text"]
return str(out)
# -------------------------
# EMBEDDINGS AND DOCUMENTS
# -------------------------
@st.cache_resource
def load_vectorstore():
"""Load documents, split, and build Chroma vector store once."""
# Load plain text file; make sure knowledge.txt is in the same folder
loader = TextLoader("knowledge.txt")
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50,
)
split_docs = text_splitter.split_documents(docs)
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
vectordb = Chroma.from_documents(
split_docs,
embedding=embeddings,
persist_directory="./db"
)
return vectordb
vectordb = load_vectorstore()
# -------------------------
# LANGGRAPH NODES
# -------------------------
def plan_node(state: AgentState) -> AgentState:
print("[PLAN] Understanding question...")
# For now, always decide to retrieve. You could add logic here later.
return state
def retrieve_node(state: AgentState) -> AgentState:
print("[RETRIEVE] Searching knowledge base...")
query = state["question"]
# Newer LangChain versions: use similarity_search directly for stability
docs = vectordb.similarity_search(query, k=4)
state["documents"] = [doc.page_content for doc in docs]
print(f"[RETRIEVE] Retrieved {len(state['documents'])} documents.")
return state
def answer_node(state: AgentState) -> AgentState:
print("[ANSWER] Generating answer from context...")
context = "\n".join(state["documents"])
prompt = f"""
You are a helpful assistant. Use only the context below to answer the question.
Context:
{context}
Question:
{state['question']}
Answer in 2-4 sentences, concise and clear.
"""
answer = generate_text(prompt)
state["answer"] = answer.strip()
return state
def reflect_node(state: AgentState) -> AgentState:
print("[REFLECT] Evaluating answer relevance...")
reflection_prompt = f"""
Question: {state['question']}
Answer: {state['answer']}
Evaluate if the answer is relevant and complete based only on the question.
Reply in this format:
- Verdict: YES or NO
- Reason: one short sentence
"""
reflection = generate_text(reflection_prompt)
state["reflection"] = reflection.strip()
return state
# -------------------------
# BUILD LANGGRAPH WORKFLOW
# -------------------------
builder = StateGraph(AgentState)
builder.add_node("plan", plan_node)
builder.add_node("retrieve", retrieve_node)
builder.add_node("answer", answer_node)
builder.add_node("reflect", reflect_node)
builder.set_entry_point("plan")
builder.add_edge("plan", "retrieve")
builder.add_edge("retrieve", "answer")
builder.add_edge("answer", "reflect")
agent = builder.compile()
# -------------------------
# STREAMLIT UI
# -------------------------
st.title("RAG Q&A Agent with LangGraph (Hugging Face Models)")
st.write(
"Ask a question based on the knowledge stored in `knowledge.txt`. "
"The agent will retrieve relevant context, answer, and then reflect on its own answer."
)
user_question = st.text_input("Enter your question:", value="What is renewable energy?")
if st.button("Ask"):
if not user_question.strip():
st.warning("Please enter a question.")
else:
# Initial state for LangGraph
init_state: AgentState = {
"question": user_question,
"documents": [],
"answer": "",
"reflection": "",
}
with st.spinner("Running agent (plan → retrieve → answer → reflect)..."):
result = agent.invoke(init_state)
st.subheader("Final Answer")
st.write(result["answer"])
if result.get("documents"):
st.subheader("Retrieved Context")
for i, doc in enumerate(result["documents"], start=1):
st.markdown(f"**Chunk {i}:**")
st.write(doc)
st.subheader("Reflection")
st.write(result["reflection"])