import streamlit as st
import os
from langchain_community.vectorstores import Chroma
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_groq import ChatGroq
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import HumanMessage, AIMessage
# --- API Keys ---
if not os.getenv("GOOGLE_API_KEY") or not os.getenv("GROQ_API_KEY"):
st.error("❌ Set GOOGLE_API_KEY and GROQ_API_KEY in your environment.")
st.stop()
# --- Page Config ---
st.set_page_config(page_title="Jurisprudence Chatbot", layout="wide")
# CSS styling and JS
css = """
"""
st.markdown(css, unsafe_allow_html=True)
# Title
st.markdown("""
📚 Jurisprudence Conversational Chatbot
""", unsafe_allow_html=True)
# --- In-Memory Chat History (Thread-safe)
if "memory_dict" not in st.session_state:
st.session_state.memory_dict = {"history": []}
memory_dict = st.session_state.memory_dict
# --- New Chat Button ---
if st.button("🆕 New Chat"):
st.session_state.memory_dict = {"history": []}
st.rerun()
# --- Display Chat History like ChatGPT ---
for msg in memory_dict["history"]:
role = "user" if isinstance(msg, HumanMessage) else "assistant"
with st.chat_message(role):
st.markdown(msg.content)
# --- Chat Input ---
question = st.chat_input("💬 Ask a question from the Jurisprudence textbook...")
if question:
# --- Show User Message ---
memory_dict["history"].append(HumanMessage(content=question))
with st.chat_message("user"):
st.markdown(question)
# --- Embeddings + Vector DB ---
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
vectordb = Chroma(persist_directory="chroma_db", embedding_function=embeddings)
retriever = vectordb.as_retriever(search_type="mmr", search_kwargs={"k": 3})
# --- Format Context from Docs ---
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
def get_context(question):
docs = retriever.get_relevant_documents(question)
return {
"question": question,
"context": format_docs(docs),
"docs": docs
}
# --- Prompt & LLM ---
prompt = ChatPromptTemplate.from_messages([
("system", """You are a legal assistant specialized in Jurisprudence, helping students understand complex legal topics from the textbook "Jurisprudence–I (Legal Method).
Instructions:
- Use ONLY the provided context to answer questions.
- If the user asks for a summary, generate a concise summary of the most relevant sections.
- If the user says "Explain like I’m five" or "ELI5", simplify the legal explanation as much as possible using analogies and plain language.
- Always provide citations in the format: [Section Title or Page Number].
- If you don’t know the answer, say you don’t know instead of making it up.
Context:
{context}"""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{question}")
])
llm = ChatGroq(model_name="llama3-70b-8192", api_key=os.getenv("GROQ_API_KEY"))
parser = StrOutputParser()
# --- Chain with memory injected manually (no st.session_state in threads) ---
def chain_with_memory(input_dict):
context_info = get_context(input_dict["input"])
chain_input = {
"question": context_info["question"],
"context": context_info["context"],
"chat_history": memory_dict["history"],
}
answer = prompt | llm | parser
result = answer.invoke(chain_input)
return {"result": result, "docs": context_info["docs"]}
chain = RunnablePassthrough.assign() | RunnableLambda(chain_with_memory)
# --- Get AI Response ---
output = chain.invoke({"input": question})
response = output["result"]
memory_dict["history"].append(AIMessage(content=response))
# --- Show AI Response ---
with st.chat_message("assistant"):
st.markdown(response)
# # --- Show Sources (optional) ---
# if output.get("docs"):
# with st.expander("📄 Source Documents"):
# for i, doc in enumerate(output["docs"], 1):
# st.markdown(f"**{i}.** {doc.page_content}")
st.markdown(
"""
""",
unsafe_allow_html=True
)