larrysim commited on
Commit
f7fdf8f
Β·
verified Β·
1 Parent(s): ab9c410

Update app.py

Browse files

fix event loop

Files changed (1) hide show
  1. app.py +87 -137
app.py CHANGED
@@ -5,9 +5,19 @@ import warnings
5
  import time
6
  import sqlite3
7
  import shutil
 
8
 
9
  # ==========================================
10
- # 1. PAGE CONFIG (MUST BE FIRST)
 
 
 
 
 
 
 
 
 
11
  # ==========================================
12
  st.set_page_config(page_title="Bank Loan Agent (Multi-Model)", layout="wide")
13
 
@@ -22,10 +32,9 @@ INDEX_PATH = "faiss_index"
22
  REQUIRED_PDFS = ["Bank Loan Overall Risk Policy.pdf", "Bank Loan Interest Rate Policy.pdf"]
23
 
24
  try:
25
- # GROQ IMPORTS
26
  from langchain_groq import ChatGroq
27
- # GOOGLE IMPORTS
28
- from langchain_google_genai import ChatGoogleGenerativeAI
29
 
30
  # SHARED IMPORTS
31
  from langchain_huggingface import HuggingFaceEmbeddings
@@ -41,7 +50,7 @@ try:
41
 
42
  except ImportError as e:
43
  st.error(f"❌ Critical Import Error: {e}")
44
- st.info("πŸ’‘ Suggestion: Add 'langchain-google-genai' to requirements.txt")
45
  st.stop()
46
 
47
  # ==========================================
@@ -49,8 +58,7 @@ except ImportError as e:
49
  # ==========================================
50
  def init_db():
51
  """Converts CSV files to SQLite DB."""
52
- if os.path.exists(DB_FILE):
53
- return
54
 
55
  conn = sqlite3.connect(DB_FILE)
56
  csv_files = {
@@ -64,14 +72,9 @@ def init_db():
64
  if os.path.exists(file):
65
  df = pd.read_csv(file)
66
  df.columns = [c.strip() for c in df.columns]
67
- if 'ID' in df.columns:
68
- df['ID'] = df['ID'].astype(str)
69
- try:
70
- df.to_sql(table, conn, if_exists='replace', index=False)
71
- except Exception:
72
- pass
73
- except Exception as e:
74
- st.error(f"DB Init Error: {e}")
75
  finally:
76
  conn.close()
77
 
@@ -83,30 +86,23 @@ def run_query(query, params=()):
83
  cursor = conn.cursor()
84
  cursor.execute(query, params)
85
  return cursor.fetchone()
86
- except Exception as e:
87
- return f"DB Error: {e}"
88
 
89
  # ==========================================
90
  # 4. DEFINE TOOLS
91
  # ==========================================
92
-
93
  @tool
94
  def get_credit_score(user_id: str) -> str:
95
  """Queries SQL DB for Credit Score."""
96
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
97
  row = run_query("SELECT Credit_Score FROM credit_score WHERE ID = ?", (clean_id,))
98
- if row and not isinstance(row, str):
99
- return f"Credit Score: {row[0]}"
100
- return "User ID not found."
101
 
102
  @tool
103
  def get_account_status(user_id: str) -> str:
104
  """Queries SQL DB for Name, Nationality, Status, and Email."""
105
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
106
- row = run_query(
107
- "SELECT Name, Nationality, Account_Status, Email FROM account_status WHERE ID = ?",
108
- (clean_id,)
109
- )
110
  if row and not isinstance(row, str):
111
  return f"Customer Name: {row[0]}, Nationality: {row[1]}, Status: {row[2]}, Email: {row[3]}"
112
  return "User ID not found."
@@ -118,20 +114,15 @@ def check_pr_status(user_id: str) -> str:
118
  row = run_query("SELECT PR_Status FROM pr_status WHERE ID = ?", (clean_id,))
119
  if not row or (isinstance(row, str) and "no such column" in row.lower()):
120
  row = run_query("SELECT Is_PR FROM pr_status WHERE ID = ?", (clean_id,))
121
- if row and not isinstance(row, str):
122
- return f"PR Status: {row[0]}"
123
- return "PR Status: False."
124
 
125
  # ==========================================
126
  # 5. STREAMLIT APP UI
127
  # ==========================================
128
  st.title("πŸ€– Multi-Model Loan Assessor")
129
  st.markdown("Agent connects to **SQLite Database** and **Persistent Vector Store**")
130
-
131
- # Calculate missing PDFs
132
  pdfs_missing = [f for f in REQUIRED_PDFS if not os.path.exists(f)]
133
 
134
- # --- METRICS FUNCTION ---
135
  def update_metrics(placeholder):
136
  manual_time = 15 * 60
137
  if 'execution_time' in st.session_state:
@@ -143,29 +134,27 @@ def update_metrics(placeholder):
143
  col_kpi1.metric("AI Processing", f"{ai_time:.1f}s")
144
  col_kpi2.metric("Time Saved", f"{time_saved/60:.1f} min", delta=f"{saved_pct:.1f}% faster")
145
 
146
- # --- SIDEBAR (MULTI-PROVIDER) ---
147
  with st.sidebar:
148
  st.header("πŸ” Authentication")
149
 
150
- # 1. Provider Selector
151
  provider_option = st.radio("Select AI Model:", ["Groq (Llama-3)", "Google (Gemini)"])
152
 
153
- # Initialize State
154
  if 'auth_status' not in st.session_state:
155
  st.session_state['auth_status'] = False
156
  st.session_state['api_key'] = None
157
  st.session_state['provider'] = None
158
 
159
- # Reset if user switches provider
160
  if st.session_state.get('provider') != provider_option:
161
  st.session_state['auth_status'] = False
162
  st.session_state['api_key'] = None
163
  st.session_state['provider'] = provider_option
164
 
165
- # 2. Authentication Logic
166
  if not st.session_state['auth_status']:
167
  api_key_input = st.text_input(f"Enter {provider_option} API Key", type="password")
168
-
169
  if st.button("Validate Key"):
170
  if not api_key_input:
171
  st.error("⚠️ Please enter a key.")
@@ -178,9 +167,7 @@ with st.sidebar:
178
  else:
179
  test_llm = ChatGoogleGenerativeAI(google_api_key=api_key_input, model="gemini-1.5-flash")
180
 
181
- test_llm.invoke("Test Connection")
182
-
183
- # Store Success
184
  st.session_state['auth_status'] = True
185
  st.session_state['api_key'] = api_key_input
186
  st.success("βœ… Valid Key!")
@@ -190,32 +177,22 @@ with st.sidebar:
190
  st.error(f"❌ Connection Failed: {e}")
191
  else:
192
  st.success(f"βœ… {st.session_state['provider']} Active")
193
- if st.button("πŸ”΄ Logout / Change Model"):
194
  st.session_state['auth_status'] = False
195
  st.session_state['api_key'] = None
196
  st.rerun()
197
 
198
  st.divider()
199
- st.subheader("πŸ› οΈ Maintenance")
200
-
201
- if st.button("♻️ Rebuild Knowledge Base"):
202
  if os.path.exists(INDEX_PATH): shutil.rmtree(INDEX_PATH)
203
  st.cache_resource.clear()
204
- st.success("Cache cleared.")
205
- time.sleep(1)
206
  st.rerun()
207
 
208
- if st.button("πŸ’Ύ Reload CSVs"):
209
- if os.path.exists(DB_FILE): os.remove(DB_FILE)
210
- init_db()
211
- st.success("Database refreshed.")
212
-
213
- st.divider()
214
  if os.path.exists(DB_FILE) and not pdfs_missing:
215
  st.success("βœ… System Ready")
216
  else:
217
  st.warning(f"⚠️ Missing: {pdfs_missing}")
218
-
219
  st.header("πŸ“Š Metrics")
220
  metrics_placeholder = st.empty()
221
  update_metrics(metrics_placeholder)
@@ -223,133 +200,106 @@ with st.sidebar:
223
  # --- MAIN LOGIC ---
224
  if st.session_state.get('auth_status', False):
225
 
226
- # --- RAG SETUP (Shared Embeddings) ---
 
 
 
227
  @st.cache_resource
228
- def setup_rag():
229
- if pdfs_missing:
230
- st.error(f"Missing PDFs: {pdfs_missing}")
231
- st.stop()
232
-
233
- # We use HuggingFace embeddings for BOTH providers to keep the Vector Store compatible
234
- # This prevents having to rebuild the index every time you switch models.
235
- embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
236
 
237
- if os.path.exists(INDEX_PATH):
238
- return FAISS.load_local(INDEX_PATH, embeddings, allow_dangerous_deserialization=True).as_retriever()
 
239
  else:
240
- documents = []
241
- for pdf_file in REQUIRED_PDFS:
242
- loader = PyPDFLoader(pdf_file)
243
- documents.extend(loader.load())
244
-
245
- text_splitter = CharacterTextSplitter(chunk_size=600, chunk_overlap=50)
246
- final_docs = text_splitter.split_documents(documents)
247
-
248
- vectorstore = FAISS.from_documents(final_docs, embeddings)
249
- vectorstore.save_local(INDEX_PATH)
250
- return vectorstore.as_retriever()
251
 
252
- with st.spinner("Initializing Knowledge Base..."):
253
- retriever = setup_rag()
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- # --- DYNAMIC LLM INSTANTIATION ---
256
- current_key = st.session_state['api_key']
257
- current_provider = st.session_state['provider']
258
 
 
259
  if "Groq" in current_provider:
260
  llm = ChatGroq(api_key=current_key, temperature=0, model_name="llama-3.3-70b-versatile")
261
  else:
262
  llm = ChatGoogleGenerativeAI(google_api_key=current_key, temperature=0, model="gemini-1.5-flash")
263
 
264
- # --- AGENT CHAIN ---
265
- rag_prompt = ChatPromptTemplate.from_template("Answer based on context:\n{context}\nQuestion: {question}")
266
  rag_chain = (
267
  {"context": retriever | (lambda d: "\n".join([x.page_content for x in d])), "question": RunnablePassthrough()}
268
- | rag_prompt | llm | StrOutputParser()
269
  )
270
 
271
  @tool
272
  def consult_policy_doc(query: str) -> str:
273
- """Consults Policy Documents for Risk Rules."""
274
  return rag_chain.invoke(query)
275
 
276
  tools = [get_credit_score, get_account_status, check_pr_status, consult_policy_doc]
277
-
278
  prompt = ChatPromptTemplate.from_messages([
279
- ("system", "You are a Loan Risk Officer. Query SQL DB for customer info. Consult Policy Docs for rules."),
280
  ("human", "{input}"),
281
  MessagesPlaceholder(variable_name="agent_scratchpad"),
282
  ])
283
 
284
- agent = create_tool_calling_agent(llm, tools, prompt)
285
- agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, return_intermediate_steps=True)
286
 
287
- # --- UI INPUT ---
288
  col1, col2 = st.columns([1, 2])
289
  with col1:
290
- st.subheader("1. Customer Details")
291
  uid = st.text_input("Customer ID", "1111")
292
- use_simulation = st.checkbox("Simulation Mode")
293
-
294
- sim_score = 650
295
- sim_status = "good-standing"
296
- if use_simulation:
297
- sim_score = st.slider("Sim Credit Score", 300, 900, 450)
298
- sim_status = st.selectbox("Sim Status", ["good-standing", "closed", "delinquent"])
299
-
300
- st.divider()
301
- btn = st.button("Assess Loan Risk", type="primary")
302
-
303
  with col2:
304
  if btn:
305
- # Build Prompt
306
- if use_simulation:
307
- query = f"""
308
- Process Loan for Customer ID: {uid}.
309
- *** SIMULATION MODE ACTIVE ***
310
- 1. DO NOT query 'get_credit_score' or 'account_status' for Score/Status.
311
- 2. USE: Score: {sim_score}, Status: {sim_status}
312
- 3. Query 'get_account_status' ONLY for Name/Nationality.
313
- 4. Consult Policy Docs for risk/rates.
314
- 5. Output Final Report table + Justification.
315
- """
316
- else:
317
- query = f"""
318
- Process Loan for Customer ID: {uid}.
319
- 1. Query SQL tools for Name, Email, Nationality, Status, Score.
320
- 2. IF Nationality is 'Singaporean', SKIP 'check_pr_status'.
321
- 3. Consult Policy Docs for risk/rates.
322
- 4. Output Final Report table + Justification.
323
- """
324
 
325
- with st.status(f"πŸ€– Agent ({current_provider}) is processing...", expanded=True) as status:
326
  st_callback = StreamlitCallbackHandler(st.container())
327
  try:
328
  start_time = time.time()
329
  res = agent_executor.invoke({"input": query}, {"callbacks": [st_callback]})
330
- end_time = time.time()
331
- st.session_state.execution_time = end_time - start_time
332
  update_metrics(metrics_placeholder)
333
- status.update(label="βœ… Complete!", state="complete", expanded=False)
334
  except Exception as e:
335
  st.error(f"Error: {e}")
336
  st.stop()
337
 
338
- st.success("### πŸ“‹ Final Recommendation")
339
  st.markdown(res['output'])
340
-
341
- with st.expander("πŸ” Detailed Trace"):
342
- steps = res.get("intermediate_steps", [])
343
- for i, (action, observation) in enumerate(steps):
344
- st.markdown(f"**Step {i+1}:** Tool `{action.tool}` | Output: `{observation}`")
345
 
346
- if not use_simulation:
347
  st.divider()
348
- with st.expander("βœ‰οΈ Draft Email"):
349
- email_prompt = f"Write a formal email based on this decision: {res['output']}"
350
- with st.spinner("Drafting..."):
351
- email_draft = llm.invoke(email_prompt).content
352
- st.text_area("Email Draft", value=email_draft, height=200)
353
 
354
  elif not st.session_state.get('auth_status', False):
355
- st.info("πŸ‘ˆ Please select a provider (Groq or Gemini) and validate your key.")
 
5
  import time
6
  import sqlite3
7
  import shutil
8
+ import asyncio # <--- NEW IMPORT
9
 
10
  # ==========================================
11
+ # 0. ASYNC FIX (CRITICAL FOR STREAMLIT)
12
+ # ==========================================
13
+ # This fixes "There is no current event loop" errors
14
+ try:
15
+ asyncio.get_running_loop()
16
+ except RuntimeError:
17
+ asyncio.set_event_loop(asyncio.new_event_loop())
18
+
19
+ # ==========================================
20
+ # 1. PAGE CONFIG (MUST BE FIRST STREAMLIT CMD)
21
  # ==========================================
22
  st.set_page_config(page_title="Bank Loan Agent (Multi-Model)", layout="wide")
23
 
 
32
  REQUIRED_PDFS = ["Bank Loan Overall Risk Policy.pdf", "Bank Loan Interest Rate Policy.pdf"]
33
 
34
  try:
35
+ # PROVIDER IMPORTS
36
  from langchain_groq import ChatGroq
37
+ from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
 
38
 
39
  # SHARED IMPORTS
40
  from langchain_huggingface import HuggingFaceEmbeddings
 
50
 
51
  except ImportError as e:
52
  st.error(f"❌ Critical Import Error: {e}")
53
+ st.info("πŸ’‘ Suggestion: Check requirements.txt contains 'langchain-google-genai'")
54
  st.stop()
55
 
56
  # ==========================================
 
58
  # ==========================================
59
  def init_db():
60
  """Converts CSV files to SQLite DB."""
61
+ if os.path.exists(DB_FILE): return
 
62
 
63
  conn = sqlite3.connect(DB_FILE)
64
  csv_files = {
 
72
  if os.path.exists(file):
73
  df = pd.read_csv(file)
74
  df.columns = [c.strip() for c in df.columns]
75
+ if 'ID' in df.columns: df['ID'] = df['ID'].astype(str)
76
+ try: df.to_sql(table, conn, if_exists='replace', index=False)
77
+ except: pass
 
 
 
 
 
78
  finally:
79
  conn.close()
80
 
 
86
  cursor = conn.cursor()
87
  cursor.execute(query, params)
88
  return cursor.fetchone()
89
+ except Exception as e: return f"DB Error: {e}"
 
90
 
91
  # ==========================================
92
  # 4. DEFINE TOOLS
93
  # ==========================================
 
94
  @tool
95
  def get_credit_score(user_id: str) -> str:
96
  """Queries SQL DB for Credit Score."""
97
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
98
  row = run_query("SELECT Credit_Score FROM credit_score WHERE ID = ?", (clean_id,))
99
+ return f"Credit Score: {row[0]}" if (row and not isinstance(row, str)) else "User ID not found."
 
 
100
 
101
  @tool
102
  def get_account_status(user_id: str) -> str:
103
  """Queries SQL DB for Name, Nationality, Status, and Email."""
104
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
105
+ row = run_query("SELECT Name, Nationality, Account_Status, Email FROM account_status WHERE ID = ?", (clean_id,))
 
 
 
106
  if row and not isinstance(row, str):
107
  return f"Customer Name: {row[0]}, Nationality: {row[1]}, Status: {row[2]}, Email: {row[3]}"
108
  return "User ID not found."
 
114
  row = run_query("SELECT PR_Status FROM pr_status WHERE ID = ?", (clean_id,))
115
  if not row or (isinstance(row, str) and "no such column" in row.lower()):
116
  row = run_query("SELECT Is_PR FROM pr_status WHERE ID = ?", (clean_id,))
117
+ return f"PR Status: {row[0]}" if (row and not isinstance(row, str)) else "PR Status: False."
 
 
118
 
119
  # ==========================================
120
  # 5. STREAMLIT APP UI
121
  # ==========================================
122
  st.title("πŸ€– Multi-Model Loan Assessor")
123
  st.markdown("Agent connects to **SQLite Database** and **Persistent Vector Store**")
 
 
124
  pdfs_missing = [f for f in REQUIRED_PDFS if not os.path.exists(f)]
125
 
 
126
  def update_metrics(placeholder):
127
  manual_time = 15 * 60
128
  if 'execution_time' in st.session_state:
 
134
  col_kpi1.metric("AI Processing", f"{ai_time:.1f}s")
135
  col_kpi2.metric("Time Saved", f"{time_saved/60:.1f} min", delta=f"{saved_pct:.1f}% faster")
136
 
137
+ # --- SIDEBAR ---
138
  with st.sidebar:
139
  st.header("πŸ” Authentication")
140
 
141
+ # 1. Select Provider
142
  provider_option = st.radio("Select AI Model:", ["Groq (Llama-3)", "Google (Gemini)"])
143
 
144
+ # State Management
145
  if 'auth_status' not in st.session_state:
146
  st.session_state['auth_status'] = False
147
  st.session_state['api_key'] = None
148
  st.session_state['provider'] = None
149
 
 
150
  if st.session_state.get('provider') != provider_option:
151
  st.session_state['auth_status'] = False
152
  st.session_state['api_key'] = None
153
  st.session_state['provider'] = provider_option
154
 
155
+ # 2. Auth Logic
156
  if not st.session_state['auth_status']:
157
  api_key_input = st.text_input(f"Enter {provider_option} API Key", type="password")
 
158
  if st.button("Validate Key"):
159
  if not api_key_input:
160
  st.error("⚠️ Please enter a key.")
 
167
  else:
168
  test_llm = ChatGoogleGenerativeAI(google_api_key=api_key_input, model="gemini-1.5-flash")
169
 
170
+ test_llm.invoke("Test")
 
 
171
  st.session_state['auth_status'] = True
172
  st.session_state['api_key'] = api_key_input
173
  st.success("βœ… Valid Key!")
 
177
  st.error(f"❌ Connection Failed: {e}")
178
  else:
179
  st.success(f"βœ… {st.session_state['provider']} Active")
180
+ if st.button("πŸ”΄ Logout"):
181
  st.session_state['auth_status'] = False
182
  st.session_state['api_key'] = None
183
  st.rerun()
184
 
185
  st.divider()
186
+ if st.button("♻️ Rebuild DB/RAG"):
 
 
187
  if os.path.exists(INDEX_PATH): shutil.rmtree(INDEX_PATH)
188
  st.cache_resource.clear()
 
 
189
  st.rerun()
190
 
 
 
 
 
 
 
191
  if os.path.exists(DB_FILE) and not pdfs_missing:
192
  st.success("βœ… System Ready")
193
  else:
194
  st.warning(f"⚠️ Missing: {pdfs_missing}")
195
+
196
  st.header("πŸ“Š Metrics")
197
  metrics_placeholder = st.empty()
198
  update_metrics(metrics_placeholder)
 
200
  # --- MAIN LOGIC ---
201
  if st.session_state.get('auth_status', False):
202
 
203
+ current_key = st.session_state['api_key']
204
+ current_provider = st.session_state['provider']
205
+
206
+ # --- DYNAMIC RAG SETUP ---
207
  @st.cache_resource
208
+ def setup_rag(_provider, _key): # Arguments included to force refresh on switch
209
+ if pdfs_missing: st.stop()
 
 
 
 
 
 
210
 
211
+ # Use Google Embeddings if Gemini, else HuggingFace
212
+ if "Google" in _provider:
213
+ embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=_key)
214
  else:
215
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
 
 
 
 
 
 
 
 
 
 
216
 
217
+ if os.path.exists(INDEX_PATH):
218
+ # Attempt load. If schema mismatch (different embeddings), rebuild.
219
+ try:
220
+ return FAISS.load_local(INDEX_PATH, embeddings, allow_dangerous_deserialization=True).as_retriever()
221
+ except:
222
+ pass # Fall through to rebuild
223
+
224
+ # Build Index
225
+ documents = []
226
+ for pdf_file in REQUIRED_PDFS:
227
+ documents.extend(PyPDFLoader(pdf_file).load())
228
+ splits = CharacterTextSplitter(chunk_size=600, chunk_overlap=50).split_documents(documents)
229
+ vectorstore = FAISS.from_documents(splits, embeddings)
230
+ vectorstore.save_local(INDEX_PATH)
231
+ return vectorstore.as_retriever()
232
 
233
+ with st.spinner("Initializing Knowledge Base..."):
234
+ # We pass args so Streamlit sees them as dependencies
235
+ retriever = setup_rag(current_provider, current_key)
236
 
237
+ # --- LLM INSTANTIATION ---
238
  if "Groq" in current_provider:
239
  llm = ChatGroq(api_key=current_key, temperature=0, model_name="llama-3.3-70b-versatile")
240
  else:
241
  llm = ChatGoogleGenerativeAI(google_api_key=current_key, temperature=0, model="gemini-1.5-flash")
242
 
243
+ # --- AGENT SETUP ---
 
244
  rag_chain = (
245
  {"context": retriever | (lambda d: "\n".join([x.page_content for x in d])), "question": RunnablePassthrough()}
246
+ | ChatPromptTemplate.from_template("{context}\nQ:{question}") | llm | StrOutputParser()
247
  )
248
 
249
  @tool
250
  def consult_policy_doc(query: str) -> str:
251
+ """Consults Policy Documents."""
252
  return rag_chain.invoke(query)
253
 
254
  tools = [get_credit_score, get_account_status, check_pr_status, consult_policy_doc]
 
255
  prompt = ChatPromptTemplate.from_messages([
256
+ ("system", "Act as a Loan Officer. Query SQL DB for info. Check Policies via tool. Output Markdown report."),
257
  ("human", "{input}"),
258
  MessagesPlaceholder(variable_name="agent_scratchpad"),
259
  ])
260
 
261
+ agent_executor = AgentExecutor(agent=create_tool_calling_agent(llm, tools, prompt), tools=tools, verbose=True, return_intermediate_steps=True)
 
262
 
263
+ # --- UI ---
264
  col1, col2 = st.columns([1, 2])
265
  with col1:
266
+ st.subheader("1. Details")
267
  uid = st.text_input("Customer ID", "1111")
268
+ use_sim = st.checkbox("Simulation Mode")
269
+ sim_score = st.slider("Score", 300, 900, 450) if use_sim else 0
270
+ sim_status = st.selectbox("Status", ["good-standing", "closed", "delinquent"]) if use_sim else ""
271
+ btn = st.button("Assess Risk", type="primary")
272
+
 
 
 
 
 
 
273
  with col2:
274
  if btn:
275
+ query = f"Process Loan {uid}. "
276
+ if use_sim: query += f"SIMULATION: Use Score {sim_score}, Status {sim_status}. Only query Name/Nationality from DB."
277
+ else: query += "Query SQL for all data."
278
+ query += " Check Policies. Output Final Report."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
+ with st.status(f"πŸ€– Agent ({current_provider}) Working...", expanded=True) as status:
281
  st_callback = StreamlitCallbackHandler(st.container())
282
  try:
283
  start_time = time.time()
284
  res = agent_executor.invoke({"input": query}, {"callbacks": [st_callback]})
285
+ st.session_state.execution_time = time.time() - start_time
 
286
  update_metrics(metrics_placeholder)
287
+ status.update(label="βœ… Done", state="complete", expanded=False)
288
  except Exception as e:
289
  st.error(f"Error: {e}")
290
  st.stop()
291
 
292
+ st.success("### πŸ“‹ Final Report")
293
  st.markdown(res['output'])
294
+ with st.expander("Trace"):
295
+ for action, obs in res.get("intermediate_steps", []):
296
+ st.markdown(f"**Tool:** `{action.tool}` -> `{obs}`")
 
 
297
 
298
+ if not use_sim:
299
  st.divider()
300
+ with st.expander("βœ‰οΈ Email Draft"):
301
+ email = llm.invoke(f"Draft formal email for: {res['output']}").content
302
+ st.text_area("Draft", value=email, height=200)
 
 
303
 
304
  elif not st.session_state.get('auth_status', False):
305
+ st.info("πŸ‘ˆ Select AI Provider & Validate Key in Sidebar")