Spaces:
Configuration error
Configuration error
| 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 | |
| # ------------------------- | |
| 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"]) | |