GIT_DOC_RAG / app.py
Vanshcc's picture
Upload 8 files
3c2f7d6 verified
Raw History Blame Contribute Delete
15.6 kB
import streamlit as st
import json
import torch
import numpy as np
import pandas as pd
from sentence_transformers import SentenceTransformer
import lancedb
import os
import requests
from groq import Groq
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# ===============================
# PAGE CONFIG & PREMIUM TECH UI
# ===============================
st.set_page_config(page_title="Git Intelligence System", page_icon="πŸ’»", layout="wide")
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
/* ADVANCED TECH THEME */
html, body, [class*="css"] {
font-family: 'Space Grotesk', sans-serif;
}
.stApp {
background: #020617;
color: #f8fafc;
overflow-x: hidden;
}
/* 🟦 ANIMATED GRID BACKGROUND */
.stApp::before {
content: "";
position: fixed;
top: 0; left: 0;
width: 200%; height: 200%;
background-image:
linear-gradient(rgba(56, 189, 248, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(56, 189, 248, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
transform: perspective(500px) rotateX(60deg) translateY(-50%);
animation: gridMove 20s linear infinite;
z-index: -1;
opacity: 0.5;
}
@keyframes gridMove {
0% { transform: perspective(500px) rotateX(60deg) translateY(-50%) translateZ(0); }
100% { transform: perspective(500px) rotateX(60deg) translateY(-50%) translateZ(50px); }
}
/* πŸ“Ÿ SCANLINE EFFECT */
.stApp::after {
content: "";
position: fixed;
top: 0; left: 0;
width: 100%; height: 2px;
background: rgba(56, 189, 248, 0.1);
opacity: 0.2;
z-index: 1000;
pointer-events: none;
animation: scanline 8s linear infinite;
}
@keyframes scanline {
0% { top: -5%; }
100% { top: 105%; }
}
/* πŸ’Ž ULTRA GLASS CARDS */
.answer-box, .glass-card {
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(56, 189, 248, 0.15);
border-radius: 20px;
padding: 2.5rem;
box-shadow:
0 20px 50px rgba(0, 0, 0, 0.4),
inset 0 0 20px rgba(56, 189, 248, 0.05);
margin-bottom: 2.5rem;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.glass-card:hover {
border-color: rgba(56, 189, 248, 0.5);
transform: translateY(-5px) scale(1.01);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.6), 0 0 20px rgba(56, 189, 248, 0.2);
}
/* ⚑ HEADERS & TYPOGRAPHY */
.main-title {
font-weight: 800;
font-size: 4.5rem;
background: linear-gradient(to right, #7dd3fc, #38bdf8, #10b981);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-align: center;
margin-bottom: 0.5rem;
filter: drop-shadow(0 0 15px rgba(56, 189, 248, 0.3));
letter-spacing: -2px;
}
.sub-title {
text-align: center;
color: #94a3b8;
font-size: 1.1rem;
margin-bottom: 4rem;
font-family: 'JetBrains Mono', monospace;
letter-spacing: 3px;
text-transform: uppercase;
opacity: 0.8;
}
/* πŸ•ΉοΈ STATUS INDICATOR */
.status-container {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-bottom: 2rem;
}
.status-dot {
width: 8px; height: 8px;
background: #10b981;
border-radius: 50%;
box-shadow: 0 0 10px #10b981;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.5); opacity: 0.5; }
100% { transform: scale(1); opacity: 1; }
}
/* ⌨️ TERMINAL INPUT FORCE */
div[data-testid="stChatInput"] {
padding-bottom: 20px !important;
}
div[data-testid="stChatInput"] textarea {
background: rgba(7, 10, 25, 0.95) !important;
border: 2px solid rgba(56, 189, 248, 0.3) !important;
border-radius: 16px !important;
color: #f0f9ff !important;
font-family: 'JetBrains Mono', monospace !important;
font-size: 1rem !important;
box-shadow: 0 0 30px rgba(56, 189, 248, 0.1) !important;
}
/* 🏷️ SOURCE LABELS */
.source-pill {
display: inline-block;
background: rgba(56, 189, 248, 0.08);
border: 1px solid rgba(56, 189, 248, 0.3);
color: #7dd3fc !important;
padding: 0.4rem 1rem;
border-radius: 10px;
font-size: 0.75rem;
margin-right: 0.6rem;
margin-bottom: 0.6rem;
font-family: 'JetBrains Mono', monospace;
transition: all 0.2s ease;
}
.source-pill:hover {
background: rgba(56, 189, 248, 0.2);
border-color: #38bdf8;
transform: scale(1.05);
}
.token-pill {
display: inline-block;
background: rgba(30, 41, 59, 0.8);
border: 1px solid rgba(148, 163, 184, 0.2);
color: #94a3b8 !important;
padding: 0.25rem 0.75rem;
border-radius: 8px;
font-size: 0.7rem;
margin-left: 0.4rem;
font-family: 'JetBrains Mono', monospace;
}
/* SCROLLBAR CUSTOMIZATION */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: #020617; }
::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, #334155, #1e293b);
border-radius: 10px;
border: 2px solid #020617;
}
::-webkit-scrollbar-thumb:hover { background: #475569; }
</style>
""", unsafe_allow_html=True)
# ===============================
# RESOURCE LOADING
# ===============================
@st.cache_resource
def load_resources():
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = SentenceTransformer("BAAI/bge-m3", device=device)
# Add FlashRank reranker as in Ramayana app
from flashrank import Ranker, RerankRequest
ranker = Ranker(model_name="ms-marco-MiniLM-L-12-v2")
db = lancedb.connect("./.lancedb")
tbl = db.open_table("git_knowledge")
with open('alias_map.json', 'r', encoding='utf-8') as f:
alias_map = json.load(f)
return model, ranker, tbl, alias_map
try:
model, ranker, tbl, alias_map = load_resources()
except Exception as e:
st.error(f"Failed to load resources: {e}")
st.stop()
# ===============================
# LLM LOGIC
# ===============================
def get_answer_from_llm(query, context):
system_prompt = """You are a STERN and HIGHLY PRECISE Technical Document Analyst.
Your objective is to answer questions using ONLY the provided Context Chunks.
STRICT ANTI-HALLUCINATION PROTOCOLS:
1. ABSOLUTE GROUNDING: Every claim you make MUST be directly supported by the provided text.
2. NO EXTERNAL KNOWLEDGE: Do NOT use your internal training data to explain commands, syntax, or concepts. If the information is not in the context, you MUST say: "The provided documentation does not contain information regarding [Topic]."
3. LIST vs. DESCRIPTION: If the context merely lists a command (e.g., in a 'git --help' style table) but does not provide details or syntax, you MUST state: "The documentation mentions [Command] but provides no instructions or explanations."
4. CITE EVERYTHING: When providing an answer, explicitly mention which 'Source' provided the data.
5. NEGATIVE CONSTRAINT: Do NOT be 'helpful' by providing external tutorials. Your helpfulness is measured by how accurately you report ONLY what is in the document."""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Context Chunks:\n{context}\n\nQuestion: {query}"}
]
input_tokens = len(str(messages)) // 4
g_key = os.getenv("GROQ_API_KEY")
if g_key:
try:
client = Groq(api_key=g_key)
# Try different models
for m in ["llama-3.3-70b-versatile", "mixtral-8x7b-32768"]:
try:
res = client.chat.completions.create(model=m, messages=messages, temperature=0.1)
ans = res.choices[0].message.content
return ans, m, input_tokens, len(ans) // 4
except Exception:
continue
except Exception:
pass
s_key = os.getenv("SAMBANOVA_API_KEY")
if s_key and s_key != "your_key_here":
try:
url = "https://api.sambanova.ai/v1/chat/completions"
headers = {"Authorization": f"Bearer {s_key}", "Content-Type": "application/json"}
data = {
"model": "Meta-Llama-3.3-70B-Instruct",
"messages": messages,
"temperature": 0.1,
"stream": False
}
res = requests.post(url, headers=headers, json=data, timeout=12)
if res.status_code == 200:
ans = res.json()['choices'][0]['message']['content']
return ans, "SambaNova (Llama-3.3)", input_tokens, len(ans) // 4
except Exception:
pass
return "LLM integration failed. Please check API keys.", "None", 0, 0
# ===============================
# MAIN UI
# ===============================
st.markdown('<h1 class="main-title">Git Documentation AI</h1>', unsafe_allow_html=True)
st.markdown('<div class="status-container"><div class="status-dot"></div><div style="color: #10b981; font-family: JetBrains Mono; font-size: 0.8rem;">SYSTEM CORE: ONLINE</div></div>', unsafe_allow_html=True)
st.markdown('<p class="sub-title">> git query --intelligent-search --ultra-premium</p>', unsafe_allow_html=True)
if "messages" not in st.session_state:
st.session_state.messages = []
# ===============================
# SIDEBAR
# ===============================
with st.sidebar:
st.markdown('<div class="metadata-section" style="background: rgba(15,23,42,0.6); padding: 1.5rem; border-radius: 12px; border: 1px solid rgba(56, 189, 248, 0.2);">', unsafe_allow_html=True)
st.markdown("### πŸ› οΈ Git Archive Specs")
st.write(f"**Storage:** LanceDB Vector")
st.write(f"**Embedding:** BGE-M3")
st.write(f"**Reranker:** FlashRank")
st.markdown("---")
st.markdown("### πŸ“¦ Knowledge Base")
try:
count = len(tbl.to_pandas())
st.write(f"**Total Chunks:** {count}")
st.write(f"**Source:** Git Documentation")
except:
st.write("**Chunks:** 16")
with st.expander("πŸ” System Internals"):
st.markdown("""
- **Pipeline**: Pypdf Extraction
- **Search**: Hybrid (Vector + Rerank)
- **LLM**: Groq Professional
""")
st.markdown('</div>', unsafe_allow_html=True)
# Display Messages
for msg in st.session_state.messages:
if msg["role"] == "user":
st.markdown(f'<div style="text-align: right; color: #38bdf8; font-family: JetBrains Mono; margin: 1rem;">$ {msg["content"]}</div>', unsafe_allow_html=True)
else:
st.markdown(msg["content"], unsafe_allow_html=True)
# Landing Page Content
if not st.session_state.messages:
st.markdown("""
<div class="glass-card">
<h3 style="color: #38bdf8; margin-top: 0; font-family: Space Grotesk;">System Initialized</h3>
<p style="color: #e2e8f0; font-size: 1.1rem;">Expert RAG system for official Git Documentation with FlashRank Optimization.</p>
<ul style="color: #94a3b8; line-height: 2;">
<li><b>Command Execution</b>: "How do I rebase my feature branch?"</li>
<li><b>Conceptual Clarity</b>: "Explain the Git object model."</li>
<li><b>Workflow Optimization</b>: "Best practices for merge conflicts."</li>
</ul>
<div style="margin-top: 2rem; display: flex; gap: 10px;">
<code style="background: rgba(56, 189, 248, 0.1); padding: 5px 15px; border-radius: 6px; color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.2);">v2.0.4-stable</code>
<code style="background: rgba(16, 185, 129, 0.1); padding: 5px 15px; border-radius: 6px; color: #10b981; border: 1px solid rgba(16, 185, 129, 0.2);">RAG-ACTIVE</code>
</div>
</div>
""", unsafe_allow_html=True)
# Chat Input
if user_query := st.chat_input("Enter your Git question..."):
# Store user message
st.session_state.messages.append({"role": "user", "content": user_query})
st.rerun()
# Processing the latest user message
if st.session_state.messages and st.session_state.messages[-1]["role"] == "user":
query = st.session_state.messages[-1]["content"]
with st.spinner("Searching documentation..."):
# 1. Expand aliases
processed_query = query.lower()
mapping = alias_map.get("alias_map", {})
for k, v in mapping.items():
processed_query = processed_query.replace(k, v)
# 2. Vector Search (Initial Fetch)
q_vec = model.encode(processed_query)
initial_results = tbl.search(q_vec).limit(15).to_list()
# 3. Rerank with FlashRank
from flashrank import RerankRequest
passages = [{"id": i, "text": r['text'], "meta": r} for i, r in enumerate(initial_results)]
rerank_request = RerankRequest(query=processed_query, passages=passages)
reranked_results = ranker.rerank(rerank_request)[:5]
# 4. Build Context
context_str = "\n---\n".join([f"Source: {r['meta']['title']}\nContent: {r['meta']['text']}" for r in reranked_results])
# 5. Get Answer
answer, model_name, in_t, out_t = get_answer_from_llm(query, context_str)
# 6. Format Output
sources_html = "".join([f'<span class="source-pill">{r["meta"]["title"]}</span>' for r in reranked_results])
formatted_answer = f"""
<div class="answer-box">
<h3 style="color: #10b981; margin-top: 0;">Response</h3>
<div style="color: #f8fafc; line-height: 1.6;">{answer}</div>
<div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.1); display: flex; justify-content: space-between; align-items: flex-end;">
<div>
<div style="font-size: 0.7rem; color: #64748b; margin-bottom: 0.5rem;">CITED SOURCES:</div>
{sources_html}
</div>
<div style="text-align: right;">
<span class="token-pill">in:{in_t}</span>
<span class="token-pill">out:{out_t}</span>
<span class="token-pill">{model_name}</span>
</div>
</div>
</div>
"""
st.session_state.messages.append({"role": "assistant", "content": formatted_answer})
st.rerun()