File size: 3,756 Bytes
d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 bde8ba0 d074d09 bde8ba0 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 c7de1c8 d074d09 |
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 |
import os
import streamlit as st
from qdrant_client import QdrantClient
from langchain_qdrant import (
QdrantVectorStore,
RetrievalMode,
FastEmbedSparse
)
from langchain_huggingface import HuggingFaceEmbeddings
from sentence_transformers import CrossEncoder
from langchain_groq import ChatGroq
# ------------------------------
# Streamlit Config (MUST RUN FAST)
# ------------------------------
st.set_page_config(
page_title="Nepal Constitution AI",
page_icon="π§ββοΈ",
layout="wide"
)
st.title("π§ββοΈ Nepal Constitution β AI Legal Assistant")
st.caption("Hybrid RAG (Dense + BM25) + Cross-Encoder Reranking")
# π₯ EARLY VISIBILITY (HF health check helper)
st.write("β
App booted successfully.")
# ------------------------------
# Hard stop if DB missing (NO SILENT FAIL)
# ------------------------------
if not os.path.exists("./qdrant_db"):
st.error("β qdrant_db folder not found. You must commit it to the repo.")
st.stop()
# ------------------------------
# User Input
# ------------------------------
query = st.text_input(
"Ask a constitutional or legal question:",
placeholder="e.g. What does Article 275 say about local governance?"
)
# ------------------------------
# Cached Heavy Stuff
# ------------------------------
@st.cache_resource
def load_embeddings():
return HuggingFaceEmbeddings(
model_name="BAAI/bge-m3",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True}
)
@st.cache_resource
def load_sparse_embeddings():
return FastEmbedSparse(model_name="Qdrant/bm25")
@st.cache_resource
def load_reranker():
return CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
@st.cache_resource
def load_vector_store():
embeddings = load_embeddings()
sparse_embeddings = load_sparse_embeddings()
client = QdrantClient(path="./qdrant_db")
return QdrantVectorStore(
client = client,
collection_name="nepal_law",
embedding=embeddings,
sparse_embedding=sparse_embeddings,
retrieval_mode=RetrievalMode.HYBRID
)
@st.cache_resource
def load_llm():
return ChatGroq(
model="llama-3.1-8b-instant",
temperature=0.2,
max_tokens=600
)
# ------------------------------
# Reranking
# ------------------------------
def rerank(query, docs, top_k=8):
reranker = load_reranker()
pairs = [(query, d.page_content) for d in docs]
scores = reranker.predict(pairs)
ranked = sorted(
zip(docs, scores),
key=lambda x: x[1],
reverse=True
)
return [doc for doc, _ in ranked[:top_k]]
if query:
with st.spinner("π Searching constitution..."):
vector_store = load_vector_store()
retrieved = vector_store.similarity_search(query, k=20)
reranked = rerank(query, retrieved)
context = "\n\n".join(
f"[Source {i+1}]\n{doc.page_content}"
for i, doc in enumerate(reranked)
)
prompt = f"""
You are a constitutional law assistant for Nepal.
RULES:
- Use ONLY the provided context.
- Do NOT invent articles, clauses, or interpretations.
- If the answer is not found, say so explicitly.
- Use formal, neutral legal language.
- Reference article/section numbers when mentioned.
CONTEXT:
{context}
QUESTION:
{query}
ANSWER:
"""
with st.spinner("π§ Generating answer..."):
llm = load_llm()
response = llm.invoke(prompt)
st.markdown("### β
Answer")
st.write(response.content)
with st.expander("π Retrieved Constitutional Sources"):
for i, doc in enumerate(reranked):
st.markdown(f"**Source {i+1}**")
st.write(doc.page_content)
st.markdown("---")
|