Files changed (1) hide show
  1. app.py +175 -121
app.py CHANGED
@@ -1,125 +1,179 @@
1
- # app.py Omantel Insurance Q&A (RAG) with system prompt + simple config
2
- import os
3
- import gradio as gr
4
- from pinecone import Pinecone, ServerlessSpec
5
- from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext, Settings
6
- from llama_index.vector_stores.pinecone import PineconeVectorStore
7
- from llama_index.embeddings.openai import OpenAIEmbedding
8
- from llama_index.llms.openai import OpenAI
9
-
10
- # --- System Prompt (polite + answer-from-document constraint) ---
11
- SYSTEM_PROMPT = """You are Aisha, a polite and professional Insurance assistant.
12
- Answer ONLY using the information found in the indexed insurance document(s).
13
- If the answer is not in the document(s), say: "I couldn’t find that in the document."
14
- Keep responses concise, helpful, and courteous.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  """
16
 
17
- # ===== Minimal CONFIG (only necessary keys) =====
18
- PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
19
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
20
- if not PINECONE_API_KEY or not OPENAI_API_KEY:
21
- raise RuntimeError("Missing PINECONE_API_KEY or OPENAI_API_KEY (set them in Space → Settings → Variables).")
22
-
23
- DATA_DIR = "data" # Put insurance docs here (e.g., data/insurance.pdf)
24
- LOGO_PATH = os.path.join(DATA_DIR, "Omantel_Logo_new.png") # Mandatory logo
25
- if not os.path.exists(LOGO_PATH):
26
- raise RuntimeError("Logo not found: data/Omantel_Logo_new.png (commit it to your Space repo).")
27
-
28
- EMBED_MODEL = "text-embedding-3-small" # 1536-dim
29
- LLM_MODEL = "gpt-4o-mini"
30
- TOP_K = 4 # internal similarity_top_k
31
-
32
- # ===== LlamaIndex / Pinecone (simple, fixed serverless: aws/us-east-1) =====
33
- Settings.embed_model = OpenAIEmbedding(model=EMBED_MODEL, api_key=OPENAI_API_KEY)
34
- Settings.llm = OpenAI(model=LLM_MODEL, api_key=OPENAI_API_KEY, system_prompt=SYSTEM_PROMPT)
35
-
36
- pc = Pinecone(api_key=PINECONE_API_KEY)
37
- def ensure_index(name: str, dim: int = 1536):
38
- names = [i["name"] for i in pc.list_indexes()]
39
- if name not in names:
40
- pc.create_index(
41
- name=name, dimension=dim, metric="cosine",
42
- spec=ServerlessSpec(cloud="aws", region="us-east-1"),
43
- )
44
- return pc.Index(name)
45
-
46
- # Fixed index name for simplicity
47
- pinecone_index = ensure_index("dds-insurance-index", dim=1536)
48
- vector_store = PineconeVectorStore(pinecone_index=pinecone_index)
49
-
50
- def bootstrap_index():
51
- if not os.path.isdir(DATA_DIR):
52
- raise RuntimeError("No 'data/' directory found. Commit your documents to data/ in the Space repo.")
53
- docs = SimpleDirectoryReader(DATA_DIR).load_data()
54
- if not docs:
55
- raise RuntimeError("No documents found in data/. Add e.g., data/insurance.pdf")
56
- storage_ctx = StorageContext.from_defaults(vector_store=vector_store)
57
- VectorStoreIndex.from_documents(docs, storage_context=storage_ctx, show_progress=True)
58
-
59
- bootstrap_index()
60
-
61
- def answer(query: str) -> str:
62
- if not query.strip():
63
- return "Please enter a question (or select one from the FAQ list)."
64
- index = VectorStoreIndex.from_vector_store(vector_store)
65
- resp = index.as_query_engine(similarity_top_k=TOP_K).query(query)
66
- return str(resp)
67
-
68
- FAQS = [
69
- "",
70
- "What benefits are covered under the policy?",
71
- "How do I file a claim and what documents are required?",
72
- "What are the exclusions and limitations?",
73
- "Is pre-authorization needed for hospitalization?",
74
- "What is the reimbursement timeline?",
75
- "How are outpatient vs inpatient services handled?",
76
- "How can I check my network hospitals/clinics?",
77
- "What is the co-pay or deductible policy?",
78
- ]
79
-
80
- def use_faq(selected_faq: str, free_text: str):
81
- prompt = (selected_faq or "").strip() or (free_text or "").strip()
82
- if not prompt:
83
- return "", "Please select a FAQ or type your question."
84
- return prompt, answer(prompt)
85
-
86
- # ===== UI =====
87
- CSS = """
88
- .header { display:flex; flex-direction:column; align-items:center; gap:6px; }
89
- .logo img { width:300px; height:300px; object-fit:contain; } /* fixed 300x300 */
90
- .title { text-align:center; font-weight:700; font-size:1.4rem; margin:6px 0 0 0; }
91
- .subnote { text-align:center; margin-top:-2px; opacity:0.8; }
92
- """
93
-
94
- with gr.Blocks(css=CSS, theme=gr.themes.Soft()) as demo:
95
- with gr.Row():
96
- with gr.Column():
97
- gr.Markdown("<div class='header'>")
98
- gr.Image(value=LOGO_PATH, show_label=False, elem_classes=["logo"])
99
- gr.Markdown(
100
- "<h1 class='title'>Omantel Insurance Q&A — RAG Assistant</h1>"
101
- "<p class='subnote'>Answers strictly from your insurance document(s)</p>"
102
- )
103
- gr.Markdown("</div>")
104
 
105
  with gr.Row():
106
- with gr.Column(scale=1):
107
- gr.Markdown("### Ask from Frequently Asked Questions")
108
- faq = gr.Dropdown(choices=FAQS, value=FAQS[0], label="Select a common question")
109
-
110
- gr.Markdown("### Or type your question")
111
- user_q = gr.Textbox(
112
- label="Your question",
113
- placeholder="e.g., What is covered under outpatient benefits?",
114
- lines=2
115
- )
116
- ask_btn = gr.Button("Ask", variant="primary")
117
-
118
- with gr.Column(scale=1):
119
- chosen_prompt = gr.Textbox(label="Query sent", interactive=False)
120
- answer_box = gr.Markdown()
121
-
122
- ask_btn.click(use_faq, inputs=[faq, user_q], outputs=[chosen_prompt, answer_box])
123
-
124
- if __name__ == "__main__":
125
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- Gradio UI (Aurora Glass theme) ---
2
+ LOGO_URL = "https://raw.githubusercontent.com/Decoding-Data-Science/Omantel/main/Omantel_Logo%20(1).png"
3
+
4
+ CUSTOM_CSS = """
5
+ :root{
6
+ --bg:#0a0f1c;
7
+ --panel:#0f1629f2;
8
+ --stroke:#1d2740;
9
+ --muted:#96a3be;
10
+ --brand:#2e90fa;
11
+ --accent:#f59e0b;
12
+ --ok:#10b981;
13
+ --danger:#ef4444;
14
+ }
15
+ .gradio-container{
16
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial !important;
17
+ }
18
+ body{
19
+ background:
20
+ radial-gradient(1100px 500px at 10% -10%, #2e90fa22, transparent 70%),
21
+ radial-gradient(1000px 500px at 110% 10%, #f59e0b18, transparent 60%),
22
+ linear-gradient(180deg, var(--bg) 0%, #0a0f1c 100%);
23
+ }
24
+ .header{
25
+ position:relative;
26
+ padding:18px 12px 8px;
27
+ border-bottom:1px solid var(--stroke);
28
+ display:grid;
29
+ grid-template-columns: 140px 1fr 140px;
30
+ align-items:center;
31
+ gap:12px;
32
+ }
33
+ .header .logo img{height:50px;object-fit:contain; filter: drop-shadow(0 4px 14px rgba(0,0,0,.25));}
34
+ .title{
35
+ text-align:center; color:#e5e7eb;
36
+ }
37
+ .title h1{
38
+ margin:0; font-weight:800; font-size:1.7rem; letter-spacing:.2px;
39
+ }
40
+ .title p{
41
+ margin:6px 0 0; color:var(--muted); font-size:.95rem;
42
+ }
43
+ .pill{
44
+ display:inline-flex; align-items:center; gap:8px;
45
+ padding:6px 10px; border-radius:999px; font-size:12px;
46
+ background:linear-gradient(180deg,#0d1425,#0a1222);
47
+ border:1px solid var(--stroke); color:#cbd5e1;
48
+ }
49
+ .pill .dot{width:8px;height:8px;border-radius:999px;background:var(--ok); box-shadow:0 0 0 4px #10b98126;}
50
+ .panel{
51
+ background:var(--panel); border:1px solid var(--stroke);
52
+ border-radius:16px; padding:16px; backdrop-filter: blur(10px);
53
+ box-shadow:0 12px 36px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.03);
54
+ }
55
+ .hero{
56
+ position:relative; border-radius:18px; padding:18px; margin-top:8px;
57
+ border:1px solid #25314e;
58
+ background:
59
+ radial-gradient(800px 200px at 12% -12%, rgba(46, 144, 250, .24), transparent 70%),
60
+ radial-gradient(700px 140px at 90% 0%, rgba(245, 158, 11, .14), transparent 60%),
61
+ linear-gradient(180deg, #0d1428, #0c1224);
62
+ }
63
+ .hero h2{margin:0 0 6px;color:#e5e7eb;font-weight:700; font-size:1.1rem;}
64
+ .hero p{margin:0;color:var(--muted);font-size:.95rem;}
65
+ .label, label{color:#dbe3f0 !important; font-weight:600 !important;}
66
+ .helper{font-size:12px;color:#9aa5b1;margin-top:6px;}
67
+ .chips{display:flex; gap:8px; flex-wrap:wrap; margin-top:8px;}
68
+ .chip{
69
+ cursor:pointer; user-select:none;
70
+ padding:8px 12px; border-radius:12px; font-size:.9rem;
71
+ background:#0b1222; border:1px solid #273043; color:#cbd5e1;
72
+ }
73
+ .chip:hover{border-color:#35507a}
74
+ .actions{display:flex; gap:8px; flex-wrap:wrap;}
75
+ button.primary{
76
+ background:linear-gradient(180deg,#3b82f6,#1e40af); color:white; border:1px solid #1e3a8a;
77
+ }
78
+ button.secondary{
79
+ background:#0b1222; color:#cbd5e1; border:1px solid #273043;
80
+ }
81
+ .footer{ text-align:center; font-size:12px; color:#94a3b8; padding:12px 0 0;}
82
+ #answer_box{min-height:200px;}
83
+ .small-note{font-size:12px;color:#9aa5b1;}
84
+ kbd{
85
+ background:#0f172a; color:#e5e7eb; border:1px solid #334155;
86
+ border-bottom-width:2px; padding:2px 6px; border-radius:6px; font-size:.8em;
87
+ }
88
  """
89
 
90
+ with gr.Blocks(css=CUSTOM_CSS, title="Mulhim Omantel Knowledge Assistant (Aurora)") as demo:
91
+ # Header
92
+ with gr.Row(elem_classes="header"):
93
+ with gr.Column(scale=0, elem_classes="logo"):
94
+ gr.HTML(f'''
95
+ <div class="pill"><span class="dot"></span> Online • Mulhim</div>
96
+ <br><img src="{LOGO_URL}" alt="Omantel Logo">
97
+ ''')
98
+ with gr.Column(scale=1, elem_classes="title"):
99
+ gr.HTML("""
100
+ <h1>Mulhim — Omantel Knowledge Assistant</h1>
101
+ <p>Answers are <b>strictly</b> grounded in your indexed PDF(s). If it’s not in the file, you’ll be told.</p>
102
+ """)
103
+ with gr.Column(scale=0):
104
+ gr.HTML("") # spacer
105
+
106
+ # Explainer / Hero
107
+ gr.HTML("""
108
+ <div class="hero panel">
109
+ <h2>How it works</h2>
110
+ <p>Ask about internal applications, steps, or focal points. Mulhim cites only what's inside your indexed document.</p>
111
+ </div>
112
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  with gr.Row():
115
+ # Left: Ask + Answer
116
+ with gr.Column(scale=7):
117
+ with gr.Group(elem_classes="panel"):
118
+ inp = gr.Textbox(
119
+ label="Your question",
120
+ placeholder="e.g., Procurement System — steps and owner contact?",
121
+ lines=3,
122
+ autofocus=True
123
+ )
124
+ with gr.Row(elem_classes="actions"):
125
+ ask_btn = gr.Button("Ask Mulhim", variant="primary", elem_classes="primary")
126
+ clear_btn = gr.Button("Clear", variant="secondary", elem_classes="secondary")
127
+ gr.Markdown('<div class="helper">Tip: Mention the process or department for best results. Press <kbd>Enter</kbd> to submit.</div>')
128
+
129
+ # quick chips
130
+ with gr.Row(elem_classes="chips"):
131
+ chip1 = gr.Button("Focal point • Technology Strategy", elem_id=None, elem_classes="chip", variant="secondary")
132
+ chip2 = gr.Button("Steps • New vendor contract", elem_classes="chip", variant="secondary")
133
+ chip3 = gr.Button("Process • Customer complaints (CEX)", elem_classes="chip", variant="secondary")
134
+ chip4 = gr.Button("Finance • Budget reallocation", elem_classes="chip", variant="secondary")
135
+
136
+ with gr.Group(elem_classes="panel"):
137
+ out = gr.Textbox(
138
+ label="Answer",
139
+ lines=14,
140
+ elem_id="answer_box",
141
+ show_copy_button=True
142
+ )
143
+ gr.Markdown('<div class="small-note">If the answer isn’t found in the PDF, you’ll see: <i>“I couldn’t find that in the document.”</i></div>')
144
+
145
+ # Right: Examples + Notes
146
+ with gr.Column(scale=5):
147
+ with gr.Group(elem_classes="panel"):
148
+ gr.Markdown("**Quick examples**")
149
+ examples = gr.Examples(
150
+ examples=[
151
+ ["Who is the focal point for Technology Strategy and how can I reach them?"],
152
+ ["I need to submit a new vendor contract. Which application and steps?"],
153
+ ["For customer complaints (CEX), what's the process and owner contact?"],
154
+ ["Finance: how to request a budget reallocation and who to email?"],
155
+ ],
156
+ inputs=inp,
157
+ )
158
+ with gr.Group(elem_classes="panel"):
159
+ gr.Markdown("**Notes**")
160
+ gr.Markdown(
161
+ "- Answers are sourced only from the indexed PDF/document.\n"
162
+ "- If information isn’t found, Mulhim will say so.\n"
163
+ "- For department-specific queries, the related contact is shown if present in the doc."
164
+ )
165
+
166
+ gr.Markdown(f'<div class="footer">Pinecone index: <b>{index_name}</b> • Namespace: <b>{PINECONE_NAMESPACE}</b> • LlamaIndex + Pinecone</div>')
167
+
168
+ # Interactions
169
+ ask_btn.click(fn=query_doc, inputs=inp, outputs=out)
170
+ inp.submit(fn=query_doc, inputs=inp, outputs=out)
171
+ clear_btn.click(lambda: ("", ""), inputs=None, outputs=[inp, out])
172
+
173
+ # Chip wiring (pre-fill the input, then auto-ask)
174
+ chip1.click(lambda: "Who is the focal point for Technology Strategy and how can I reach them?", outputs=inp).then(query_doc, inp, out)
175
+ chip2.click(lambda: "I need to submit a new vendor contract. Which application and steps?", outputs=inp).then(query_doc, inp, out)
176
+ chip3.click(lambda: "For customer complaints (CEX), what's the process and owner contact?", outputs=inp).then(query_doc, inp, out)
177
+ chip4.click(lambda: "Finance: how to request a budget reallocation and who to email?", outputs=inp).then(query_doc, inp, out)
178
+
179
+ demo.launch()