rag_agent / app.py
agnixcode's picture
Update app.py
d3ed3b3 verified
Raw
History Blame Contribute Delete
12.3 kB
import os
import faiss
import numpy as np
import requests
import gradio as gr
from openai import OpenAI
from pypdf import PdfReader
from sentence_transformers import SentenceTransformer
css = """
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
*, *::before, *::after { box-sizing: border-box; }
:root {
--M: #E20074;
--MD: #B5005C;
--MBG: rgba(226,0,116,0.07);
--bg: #F6F3F8;
--white: #FFFFFF;
--surf2: #F2EEF5;
--border: #E6DEED;
--border2: #D0C4DC;
--text: #1A1525;
--sub: #5A5272;
--muted: #9990AA;
--ok: #00A878;
--okbg: #E8F8F3;
--okborder: #AADFC8;
--f: 'Plus Jakarta Sans', sans-serif;
--mono: 'JetBrains Mono', monospace;
--sh-sm: 0 1px 4px rgba(0,0,0,0.06);
--sh-md: 0 3px 14px rgba(0,0,0,0.08);
--sh-mg: 0 4px 20px rgba(226,0,116,0.18);
}
/* ── BASE ── */
body {
font-family: var(--f) !important;
background: var(--bg) !important;
color: var(--text) !important;
}
.gradio-container {
max-width: 1060px !important;
margin: 0 auto !important;
padding: 0 20px 48px !important;
background: transparent !important;
box-shadow: none !important;
}
/* ── HEADING (gr.Markdown title) ── */
h1 {
font-family: var(--f) !important;
font-size: 1.6rem !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
color: var(--text) !important;
padding: 28px 0 4px !important;
border-bottom: 3px solid var(--M) !important;
margin-bottom: 24px !important;
position: relative;
}
/* animated stripe under title */
h1::after {
content: '';
position: absolute;
bottom: -3px; left: 0;
width: 80px; height: 3px;
background: #FF6BB5;
animation: titleSlide 2s ease-in-out infinite alternate;
}
@keyframes titleSlide {
0% { width: 60px; opacity: 0.6; }
100% { width: 160px; opacity: 1; }
}
/* emoji in title stays natural */
h1 .emoji { color: inherit !important; }
/* ── ALL CARDS / PANELS ── */
.gr-box, .gr-panel, .gr-group, .gr-form,
[class*="panel"], [class*="block"] {
background: var(--white) !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
box-shadow: var(--sh-sm) !important;
transition: box-shadow 0.2s, border-color 0.2s !important;
}
.gr-box:hover, .gr-panel:hover {
box-shadow: 0 4px 20px rgba(226,0,116,0.10) !important;
border-color: rgba(226,0,116,0.22) !important;
}
/* ── LABELS ── */
label, .gr-label, .label-wrap span {
font-family: var(--f) !important;
font-size: 0.63rem !important;
font-weight: 700 !important;
letter-spacing: 0.10em !important;
text-transform: uppercase !important;
color: var(--muted) !important;
}
/* ── INPUTS (link box + question box) ── */
textarea, input[type="text"] {
font-family: var(--f) !important;
background: var(--surf2) !important;
color: var(--text) !important;
border: 1px solid var(--border2) !important;
border-radius: 8px !important;
padding: 12px 15px !important;
font-size: 0.88rem !important;
line-height: 1.55 !important;
transition: border-color 0.2s, box-shadow 0.2s !important;
}
textarea:focus, input[type="text"]:focus {
border-color: var(--M) !important;
box-shadow: 0 0 0 3px rgba(226,0,116,0.12) !important;
outline: none !important;
background: var(--white) !important;
}
textarea::placeholder, input[type="text"]::placeholder {
color: var(--muted) !important;
}
/* Status textbox (readonly) gets green monospace style */
textarea[readonly] {
font-family: var(--mono) !important;
font-size: 0.76rem !important;
color: var(--ok) !important;
background: var(--okbg) !important;
border-color: var(--okborder) !important;
}
/* ── BUTTONS ── */
button, .gr-button {
font-family: var(--f) !important;
font-weight: 700 !important;
font-size: 0.75rem !important;
letter-spacing: 0.08em !important;
text-transform: uppercase !important;
border-radius: 8px !important;
padding: 12px 24px !important;
border: none !important;
cursor: pointer !important;
transition: all 0.2s ease !important;
}
/* Primary = magenta gradient */
button.primary, .gr-button-primary, button[variant="primary"] {
background: linear-gradient(135deg, #E20074 0%, #B5005C 100%) !important;
color: #fff !important;
box-shadow: var(--sh-mg) !important;
}
button.primary:hover, .gr-button-primary:hover {
box-shadow: 0 6px 28px rgba(226,0,116,0.40) !important;
transform: translateY(-1px) !important;
}
button.primary:active, .gr-button-primary:active {
transform: translateY(0) !important;
}
/* ── CHATBOT CONTAINER ── */
.gr-chatbot, [data-testid="chatbot"] {
background: #FAFAFA !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
box-shadow: var(--sh-sm) !important;
}
/* User bubble — right, magenta */
.message.user, [data-testid="user"] .message {
background: linear-gradient(135deg, #E20074, #B5005C) !important;
color: #fff !important;
border-radius: 12px 12px 3px 12px !important;
padding: 11px 15px !important;
font-size: 0.86rem !important;
line-height: 1.65 !important;
max-width: 72% !important;
margin-left: auto !important;
box-shadow: 0 3px 14px rgba(226,0,116,0.22) !important;
animation: slideR 0.22s ease !important;
}
/* Bot bubble — left, white with magenta stripe */
.message.bot, [data-testid="bot"] .message {
background: var(--white) !important;
border: 1px solid var(--border) !important;
border-left: 3px solid var(--M) !important;
color: var(--text) !important;
border-radius: 2px 12px 12px 12px !important;
padding: 11px 15px !important;
font-size: 0.86rem !important;
line-height: 1.65 !important;
max-width: 80% !important;
box-shadow: var(--sh-sm) !important;
animation: slideL 0.22s ease !important;
}
@keyframes slideR {
from { opacity: 0; transform: translateX(10px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideL {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
/* ── SCROLLBAR ── */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: var(--surf2); border-radius: 3px; }
::-webkit-scrollbar-thumb { background: rgba(226,0,116,0.28); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--M); }
/* ── HIDE GRADIO FOOTER ── */
footer { display: none !important; }
.gap { gap: 14px !important; }
"""
# ── GLOBALS ────────────────────────────────────────────────────────────────────
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
index = None
chunks = []
chat_history = []
client = OpenAI(
api_key=os.getenv("GROQ_API_KEY"),
base_url="https://api.groq.com/openai/v1",
)
def convert_drive_link(link):
try:
return f"https://drive.google.com/uc?id={link.split('/d/')[1].split('/')[0]}"
except Exception:
return link
def load_pdf_from_link(link):
global index, chunks
resp = requests.get(convert_drive_link(link), timeout=30)
with open("temp.pdf", "wb") as f:
f.write(resp.content)
reader = PdfReader("temp.pdf")
texts = [p.extract_text() for p in reader.pages if p.extract_text()]
chunks = []
for t in texts:
words = t.split()
for i in range(0, len(words), 500):
chunks.append(" ".join(words[i:i+500]))
emb = embed_model.encode(chunks)
index = faiss.IndexFlatL2(emb.shape[1])
index.add(np.array(emb, dtype="float32"))
return f"✅ {len(chunks)} chunks indexed · {len(texts)} pages parsed · ready"
def retrieve(query, k=3):
if index is None: return []
q = embed_model.encode([query])
_, idx = index.search(np.array(q, dtype="float32"), k)
return [chunks[i] for i in idx[0]]
def generate_answer(query, ctx=""):
if index is None:
return "⚠️ Please load a PDF first."
rag = "\n\n".join(retrieve(query))
prompt = f"""You are a senior financial analyst at Deutsche Telekom.
You have memory of past conversation and access to official report data.
Conversation history:
{ctx}
Report excerpts (cite ONLY from these for facts):
{rag}
Question: {query}
Respond precisely, use numbers, and keep it concise."""
resp = client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[{"role": "user", "content": prompt}],
temperature=0.4, max_tokens=650,
)
return resp.choices[0].message.content
def chat(user_input, history):
global chat_history
ctx = "\n".join(f"User: {h['user']}\nAssistant: {h['bot']}" for h in chat_history[-5:])
answer = generate_answer(user_input, ctx)
chat_history.append({"user": user_input, "bot": answer})
new_history = history + [
{"role": "user", "content": user_input},
{"role": "assistant", "content": answer},
]
return new_history, new_history
LOGO = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Deutsche_Telekom_logo_simple.svg/120px-Deutsche_Telekom_logo_simple.svg.png"
with gr.Blocks(title="Telekom Finance RAG", css=css) as app:
gr.HTML(f"""
<div class="app-header">
<div class="header-brand">
<div class="t-logo">T</div>
<div>
<div class="header-title">Finance <em>RAG</em> Intelligence</div>
<div class="header-sub">Deutsche Telekom · AI-Powered Document Analysis</div>
</div>
</div>
<div class="status-pill">
<div class="pulse-dot"></div>
<span class="status-label">System Live</span>
</div>
</div>
<div class="info-chips">
<div class="chip"><div class="chip-dot"></div>Groq · LLaMA 3 8B</div>
<div class="chip"><div class="chip-dot" style="background:#6600AA"></div>FAISS Vector Search</div>
<div class="chip"><div class="chip-dot" style="background:#00C896"></div>MiniLM-L6 Embeddings</div>
<div class="chip"><div class="chip-dot" style="background:#378ADD"></div>RAG · Top-K = 3</div>
<div class="chip"><div class="chip-dot" style="background:#FFB700"></div>Conversation Memory</div>
</div>
""")
gr.HTML('<div class="section-label">📎 Document Loader</div>')
with gr.Row(equal_height=True):
with gr.Column(scale=6):
link_input = gr.Textbox(
label="Google Drive PDF Link",
placeholder="https://drive.google.com/file/d/xxxxxxx/view",
)
with gr.Column(scale=1, min_width=150):
load_btn = gr.Button("📥 Load & Index", variant="primary")
status = gr.Textbox(label="System Status", interactive=False, placeholder="Waiting for PDF…")
gr.HTML("<div style='height:5px;background:linear-gradient(90deg,#E20074,transparent);border-radius:3px;opacity:.4;margin:16px 0 20px'></div>")
gr.HTML('<div class="section-label">🤖 AI Financial Analyst</div>')
chatbot = gr.Chatbot(height=460, show_label=False, avatar_images=(None, LOGO))
with gr.Row(equal_height=True):
with gr.Column(scale=8):
msg = gr.Textbox(
show_label=False,
placeholder="Ask about revenue, EBITDA, KPIs, capex, strategic outlook…",
container=False,
)
with gr.Column(scale=1, min_width=120):
send_btn = gr.Button("Send ↑", variant="primary")
gr.HTML("""
<div class="app-footer">
<div class="f-text">Powered by <span>Groq</span> · <span>LLaMA 3</span> · <span>FAISS</span> · <span>MiniLM</span></div>
<div class="f-text">Deutsche Telekom AG · Internal AI Tool · Confidential</div>
</div>
""")
load_btn.click(load_pdf_from_link, inputs=link_input, outputs=status)
msg.submit(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
msg.submit(lambda: "", outputs=msg)
send_btn.click(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
send_btn.click(lambda: "", outputs=msg)
if __name__ == "__main__":
app.launch()