Spaces:
Sleeping
Sleeping
# app.py | |
import os | |
import streamlit as st | |
from dotenv import load_dotenv | |
from langchain.docstore.document import Document | |
from langchain_community.retrievers import BM25Retriever | |
from langchain.tools import Tool | |
from langgraph.graph.message import add_messages | |
from langgraph.graph import START, StateGraph | |
from langgraph.prebuilt import ToolNode, tools_condition | |
from langchain_core.messages import AnyMessage, HumanMessage | |
from langchain_groq import ChatGroq | |
from typing import TypedDict, Annotated | |
import fitz # PyMuPDF | |
# Load .env vars | |
load_dotenv() | |
os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY") | |
groq_api_key = os.getenv("GROQ_API_KEY") | |
# --- PDF uploader and parser --- | |
def parse_pdfs(uploaded_files): | |
pdf_docs = [] | |
for uploaded_file in uploaded_files: | |
with fitz.open(stream=uploaded_file.read(), filetype="pdf") as doc: | |
text = "" | |
for page in doc: | |
text += page.get_text() | |
pdf_docs.append(Document(page_content=text, metadata={"source": uploaded_file.name})) | |
return pdf_docs | |
# --- Guest info retrieval --- | |
def build_retriever(all_docs): | |
return BM25Retriever.from_documents(all_docs) | |
def extract_text(query: str, retriever): | |
results = retriever.invoke(query) | |
if results: | |
return "\n\n".join([doc.page_content for doc in results[:3]]) | |
else: | |
return "لم يتم العثور على معلومات مطابقة في الملفات." | |
# --- Streamlit UI --- | |
st.set_page_config(page_title="NINU Agent", page_icon="🏛️") | |
st.title("🏛️ NINU - Guest & PDF Assistant") | |
st.markdown("** Hint:** NINU can help summarize lectures and quiz you step-by-step in simple English.") | |
# Initialize session state to hold conversation history | |
if "conversation_history" not in st.session_state: | |
st.session_state.conversation_history = [] | |
# User input area | |
query = st.text_area("📝 اكتب سؤالك أو كمل مذاكرتك هنا:") | |
uploaded_files = st.file_uploader("📄 ارفع ملفات PDF للمحاضرات", type=["pdf"], accept_multiple_files=True) | |
if st.button("Ask NINU") and query: | |
# 1. Parse PDF | |
user_docs = parse_pdfs(uploaded_files) if uploaded_files else [] | |
bm25_retriever = build_retriever(user_docs) | |
# 2. Create Tool | |
NINU_tool = Tool( | |
name="NINU_Lec_retriever", | |
func=lambda q: extract_text(q, bm25_retriever), | |
description="Retrieves content from uploaded PDFs based on a query." | |
) | |
# 3. Create LLM with tools | |
llm = ChatGroq(model="deepseek-r1-distill-llama-70b", groq_api_key=groq_api_key) | |
tools = [NINU_tool] | |
llm_with_tools = llm.bind_tools(tools) | |
class AgentState(TypedDict): | |
messages: Annotated[list[AnyMessage], add_messages] | |
def assistant(state: AgentState): | |
return { | |
"messages": [llm_with_tools.invoke(state["messages"])] | |
} | |
# 4. Build Agent Graph | |
builder = StateGraph(AgentState) | |
builder.add_node("assistant", assistant) | |
builder.add_node("tools", ToolNode(tools)) | |
builder.add_edge(START, "assistant") | |
builder.add_conditional_edges("assistant", tools_condition) | |
builder.add_edge("tools", "assistant") | |
NINU = builder.compile() | |
# 5. Prepare full conversation messages | |
if len(st.session_state.conversation_history) == 0: | |
# Add the custom prompt first | |
intro_prompt = """ | |
I uploaded a lecture PDF. I want you to study it with me step by step. | |
- Summarize the lecture part by part. | |
- Explain each part in very simple English like you're teaching a friend. | |
- After each part, ask me 2-3 MCQ questions in English. | |
- Wait for my answer before moving to the next part. | |
- If I answer incorrectly, explain why. | |
Let's begin! 💪 | |
""" | |
st.session_state.conversation_history.append(HumanMessage(content=intro_prompt)) | |
# Add the new user message | |
st.session_state.conversation_history.append(HumanMessage(content=query)) | |
# 6. Invoke agent with full conversation | |
response = NINU.invoke({"messages": st.session_state.conversation_history}) | |
# 7. Add assistant response to history | |
assistant_reply = response["messages"][-1] | |
st.session_state.conversation_history.append(assistant_reply) | |
# 8. Show output | |
st.markdown("### NINU's Response:") | |
st.write(assistant_reply.content) | |
# 9. Show full conversation history (optional) | |
with st.expander("🧾 Show full conversation history"): | |
for msg in st.session_state.conversation_history: | |
role = " You" if msg.type == "human" else " NINU" | |
st.markdown(f"**{role}:** {msg.content}") | |