larrysim commited on
Commit
b404c7b
Β·
verified Β·
1 Parent(s): 5c075f8

Update app.py

Browse files

remove the google

Files changed (1) hide show
  1. app.py +232 -132
app.py CHANGED
@@ -2,35 +2,30 @@ import streamlit as st
2
  import pandas as pd
3
  import os
4
  import warnings
 
5
  import sqlite3
6
  import shutil
7
- import asyncio
8
 
9
  # ==========================================
10
- # 0. ASYNC FIX
11
  # ==========================================
12
- try:
13
- asyncio.get_running_loop()
14
- except RuntimeError:
15
- asyncio.set_event_loop(asyncio.new_event_loop())
16
 
17
- # ==========================================
18
- # 1. CONFIG & IMPORTS
19
- # ==========================================
20
- st.set_page_config(page_title="Bank Loan Agent", layout="wide")
21
  warnings.filterwarnings("ignore")
22
 
 
 
 
23
  DB_FILE = "bank.db"
24
  INDEX_PATH = "faiss_index"
25
  REQUIRED_PDFS = ["Bank Loan Overall Risk Policy.pdf", "Bank Loan Interest Rate Policy.pdf"]
26
 
27
  try:
28
  from langchain_groq import ChatGroq
29
- import google.generativeai as genai
30
- from langchain_google_genai import ChatGoogleGenerativeAI
31
-
32
  from langchain_huggingface import HuggingFaceEmbeddings
33
  from langchain_community.vectorstores import FAISS
 
34
  from langchain_community.document_loaders import PyPDFLoader
35
  from langchain_text_splitters import CharacterTextSplitter
36
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
@@ -38,218 +33,323 @@ try:
38
  from langchain_core.output_parsers import StrOutputParser
39
  from langchain_core.tools import tool
40
  from langchain.agents import AgentExecutor, create_tool_calling_agent
 
41
  except ImportError as e:
42
- st.error(f"❌ Import Error: {e}")
43
  st.stop()
44
 
45
  # ==========================================
46
- # 2. DATABASE & TOOLS
47
  # ==========================================
48
  def init_db():
49
- if os.path.exists(DB_FILE): return
 
 
 
50
  conn = sqlite3.connect(DB_FILE)
51
- csv_files = {"credit_score": "credit_score.csv", "account_status": "account_status.csv", "pr_status": "pr_status.csv"}
 
 
 
 
 
52
  try:
53
  for table, file in csv_files.items():
54
  if os.path.exists(file):
55
  df = pd.read_csv(file)
56
  df.columns = [c.strip() for c in df.columns]
57
- if 'ID' in df.columns: df['ID'] = df['ID'].astype(str)
58
- try: df.to_sql(table, conn, if_exists='replace', index=False)
59
- except: pass
60
- finally: conn.close()
 
 
 
 
 
 
 
 
 
61
  init_db()
62
 
 
63
  def run_query(query, params=()):
64
  try:
65
  with sqlite3.connect(DB_FILE) as conn:
66
  cursor = conn.cursor()
67
  cursor.execute(query, params)
68
  return cursor.fetchone()
69
- except Exception as e: return f"DB Error: {e}"
 
 
 
 
 
70
 
71
- # --- TOOLS ---
72
  @tool
73
  def get_credit_score(user_id: str) -> str:
74
  """Queries SQL DB for Credit Score."""
75
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
76
  row = run_query("SELECT Credit_Score FROM credit_score WHERE ID = ?", (clean_id,))
77
- return f"Credit Score: {row[0]}" if (row and not isinstance(row, str)) else "User ID not found."
 
 
78
 
79
  @tool
80
  def get_account_status(user_id: str) -> str:
81
  """Queries SQL DB for Name, Nationality, Status, and Email."""
82
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
83
- row = run_query("SELECT Name, Nationality, Account_Status, Email FROM account_status WHERE ID = ?", (clean_id,))
 
 
 
84
  if row and not isinstance(row, str):
85
  return f"Customer Name: {row[0]}, Nationality: {row[1]}, Status: {row[2]}, Email: {row[3]}"
86
- return "User ID not found."
87
 
88
  @tool
89
  def check_pr_status(user_id: str) -> str:
90
  """Queries SQL DB for PR Status."""
91
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
92
  row = run_query("SELECT PR_Status FROM pr_status WHERE ID = ?", (clean_id,))
 
93
  if not row or (isinstance(row, str) and "no such column" in row.lower()):
94
  row = run_query("SELECT Is_PR FROM pr_status WHERE ID = ?", (clean_id,))
95
- return f"PR Status: {row[0]}" if (row and not isinstance(row, str)) else "PR Status: False."
 
 
 
96
 
97
  # ==========================================
98
- # 3. UI & LOGIC
99
  # ==========================================
100
- st.title("πŸ€– Multi-Model Loan Assessor")
 
 
 
101
  pdfs_missing = [f for f in REQUIRED_PDFS if not os.path.exists(f)]
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  with st.sidebar:
104
  st.header("πŸ” Authentication")
105
- provider_opt = st.radio("Model:", ["Groq (Llama-3)", "Google (Gemini)"])
106
 
107
- if 'auth' not in st.session_state: st.session_state.auth = False
108
- if st.session_state.get('last_provider') != provider_opt:
109
- st.session_state.auth = False
110
- st.session_state.last_provider = provider_opt
111
-
112
- if not st.session_state.auth:
113
- key_in = st.text_input("API Key", type="password")
114
- if st.button("Validate"):
115
- try:
116
- if "Groq" in provider_opt:
117
- ChatGroq(api_key=key_in).invoke("Hi")
118
- else:
119
- genai.configure(api_key=key_in)
120
- [m.name for m in genai.list_models()]
121
-
122
- st.session_state.auth = True
123
- st.session_state.key = key_in
124
- st.success("Valid!")
125
- st.rerun()
126
- except Exception as e:
127
- st.error(f"Invalid: {e}")
128
  else:
129
- st.success("Active")
130
- if st.button("Logout"):
131
- st.session_state.auth = False
 
132
  st.rerun()
133
 
134
- if st.button("♻️ Reset DB"):
135
- if os.path.exists(INDEX_PATH): shutil.rmtree(INDEX_PATH)
 
 
 
 
136
  st.cache_resource.clear()
 
 
137
  st.rerun()
138
 
139
- if st.session_state.auth:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  @st.cache_resource
141
  def setup_rag():
142
- if pdfs_missing: return None
 
 
 
143
  embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
 
144
  if os.path.exists(INDEX_PATH):
145
  return FAISS.load_local(INDEX_PATH, embeddings, allow_dangerous_deserialization=True).as_retriever()
146
-
147
- docs = []
148
- for f in REQUIRED_PDFS: docs.extend(PyPDFLoader(f).load())
149
- splits = CharacterTextSplitter(chunk_size=600, chunk_overlap=50).split_documents(docs)
150
- vectorstore = FAISS.from_documents(splits, embeddings)
151
- vectorstore.save_local(INDEX_PATH)
152
- return vectorstore.as_retriever()
153
-
154
- retriever = setup_rag()
155
-
156
- # --- LLM INIT ---
157
- if "Groq" in provider_opt:
158
- llm = ChatGroq(api_key=st.session_state.key, model_name="llama-3.3-70b-versatile", temperature=0)
159
- else:
160
- llm = ChatGoogleGenerativeAI(
161
- google_api_key=st.session_state.key,
162
- model="gemini-1.5-flash",
163
- transport="rest"
164
- )
165
 
166
- # --- RAG TOOL ---
 
 
167
  rag_chain = (
168
  {"context": retriever | (lambda d: "\n".join([x.page_content for x in d])), "question": RunnablePassthrough()}
169
- | ChatPromptTemplate.from_template("Info: {context}\nQ: {question}\nA:")
170
- | llm
171
- | StrOutputParser()
172
  )
173
 
174
  @tool
175
  def consult_policy_doc(query: str) -> str:
176
- """Consults Policy Documents. Input: Question string."""
177
  return rag_chain.invoke(query)
178
 
179
  tools = [get_credit_score, get_account_status, check_pr_status, consult_policy_doc]
180
 
181
- # --- SYSTEM PROMPT (THE FIX) ---
182
- # NOTE: We removed {tools} because create_tool_calling_agent binds them automatically.
183
- system_instruction = """You are a Loan Risk Officer. You must execute the assessment in 4 steps.
 
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  INSTRUCTIONS:
186
- Step 1. Retrieve information
187
- - Call SQL tools (get_credit_score, get_account_status).
188
- - If NOT Singaporean, call check_pr_status.
189
-
190
- Step 2. Check Overall Risk
191
- - Call 'consult_policy_doc' to find "Risk Classification Matrix".
192
- - Compare Score/Status vs Matrix.
193
-
194
- Step 3. Check Interest Rate
195
- - Call 'consult_policy_doc' to find "Interest Rate Table".
196
- - Find rate for the Risk Level.
197
-
198
- Step 4. Final Report
199
- - Output the final recommendation in the EXACT format below.
200
-
201
- EXAMPLE OUTPUT:
202
- **Customer Information:** [Name], [ID]
203
- **Step 1. Retrieve information**
204
- Credit Score: [Score], Account Status: [Status], Nationality: [Nationality]
205
- **Step 2. Check Overall Risk**
206
- Credit Score: [Score] + Account Status: [Status] -> Overall Risk: [Level]
207
- **Step 3. Check Interest Rate**
208
- Overall Risk: [Level] -> [Rate]%
209
- **Step 4. Report**
210
- Recommend the loan at [Rate]% interest.
211
  """
212
 
213
- # Use ChatPromptTemplate (Best for Agent Executors)
214
  prompt = ChatPromptTemplate.from_messages([
215
  ("system", system_instruction),
216
  ("human", "{input}"),
217
  MessagesPlaceholder(variable_name="agent_scratchpad"),
218
  ])
219
-
220
- # --- AGENT EXECUTOR ---
221
  agent = create_tool_calling_agent(llm, tools, prompt)
222
- agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
223
 
224
  col1, col2 = st.columns([1, 2])
225
  with col1:
 
226
  uid = st.text_input("Customer ID", "1111")
227
- use_sim = st.checkbox("Simulation Mode")
228
- s_score = st.slider("Score", 300, 900, 450) if use_sim else 0
229
- s_status = st.selectbox("Status", ["good-standing", "closed", "delinquent"]) if use_sim else ""
230
- btn = st.button("Assess")
 
 
 
231
 
 
 
 
232
  with col2:
233
  if btn:
234
- q = f"Process Loan ID {uid}. "
235
- if use_sim: q += f"SIMULATION: Score {s_score}, Status '{s_status}'. Skip DB for these."
236
- else: q += "Query DB for all data."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
- with st.status("Agent Working...", expanded=True):
 
239
  try:
240
- res = agent_executor.invoke({"input": q})
241
- st.write("Done!")
 
 
 
 
242
  except Exception as e:
243
  st.error(f"Error: {e}")
244
- res = {"output": "Failed"}
245
 
246
- st.success("### Final Report")
247
- st.markdown(res['output'])
248
 
249
- if not use_sim:
 
 
 
 
 
250
  st.divider()
251
- with st.expander("Draft Email"):
252
- st.text_area("Content", value=llm.invoke(f"Draft email for: {res['output']}").content)
 
 
 
253
 
254
- else:
255
- st.info("πŸ‘ˆ Login Required")
 
2
  import pandas as pd
3
  import os
4
  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 (SQL)", layout="wide")
 
 
 
13
 
14
+ # Suppress warnings
 
 
 
15
  warnings.filterwarnings("ignore")
16
 
17
+ # ==========================================
18
+ # 2. GLOBAL CONSTANTS & IMPORTS
19
+ # ==========================================
20
  DB_FILE = "bank.db"
21
  INDEX_PATH = "faiss_index"
22
  REQUIRED_PDFS = ["Bank Loan Overall Risk Policy.pdf", "Bank Loan Interest Rate Policy.pdf"]
23
 
24
  try:
25
  from langchain_groq import ChatGroq
 
 
 
26
  from langchain_huggingface import HuggingFaceEmbeddings
27
  from langchain_community.vectorstores import FAISS
28
+ from langchain_community.callbacks import StreamlitCallbackHandler
29
  from langchain_community.document_loaders import PyPDFLoader
30
  from langchain_text_splitters import CharacterTextSplitter
31
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
 
33
  from langchain_core.output_parsers import StrOutputParser
34
  from langchain_core.tools import tool
35
  from langchain.agents import AgentExecutor, create_tool_calling_agent
36
+
37
  except ImportError as e:
38
+ st.error(f"❌ Critical Import Error: {e}")
39
  st.stop()
40
 
41
  # ==========================================
42
+ # 3. DATABASE SETUP
43
  # ==========================================
44
  def init_db():
45
+ """Converts CSV files to SQLite DB. Handles errors gracefully."""
46
+ if os.path.exists(DB_FILE):
47
+ return
48
+
49
  conn = sqlite3.connect(DB_FILE)
50
+ csv_files = {
51
+ "credit_score": "credit_score.csv",
52
+ "account_status": "account_status.csv",
53
+ "pr_status": "pr_status.csv"
54
+ }
55
+
56
  try:
57
  for table, file in csv_files.items():
58
  if os.path.exists(file):
59
  df = pd.read_csv(file)
60
  df.columns = [c.strip() for c in df.columns]
61
+ if 'ID' in df.columns:
62
+ df['ID'] = df['ID'].astype(str)
63
+
64
+ try:
65
+ df.to_sql(table, conn, if_exists='replace', index=False)
66
+ except Exception:
67
+ pass
68
+ except Exception as e:
69
+ st.error(f"DB Init Error: {e}")
70
+ finally:
71
+ conn.close()
72
+
73
+ # Initialize DB on startup
74
  init_db()
75
 
76
+ # Helper for SQL tools
77
  def run_query(query, params=()):
78
  try:
79
  with sqlite3.connect(DB_FILE) as conn:
80
  cursor = conn.cursor()
81
  cursor.execute(query, params)
82
  return cursor.fetchone()
83
+ except Exception as e:
84
+ return f"DB Error: {e}"
85
+
86
+ # ==========================================
87
+ # 4. DEFINE TOOLS
88
+ # ==========================================
89
 
 
90
  @tool
91
  def get_credit_score(user_id: str) -> str:
92
  """Queries SQL DB for Credit Score."""
93
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
94
  row = run_query("SELECT Credit_Score FROM credit_score WHERE ID = ?", (clean_id,))
95
+ if row and not isinstance(row, str):
96
+ return f"Credit Score: {row[0]}"
97
+ return "User ID not found in Credit DB."
98
 
99
  @tool
100
  def get_account_status(user_id: str) -> str:
101
  """Queries SQL DB for Name, Nationality, Status, and Email."""
102
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
103
+ row = run_query(
104
+ "SELECT Name, Nationality, Account_Status, Email FROM account_status WHERE ID = ?",
105
+ (clean_id,)
106
+ )
107
  if row and not isinstance(row, str):
108
  return f"Customer Name: {row[0]}, Nationality: {row[1]}, Status: {row[2]}, Email: {row[3]}"
109
+ return "User ID not found in Account DB."
110
 
111
  @tool
112
  def check_pr_status(user_id: str) -> str:
113
  """Queries SQL DB for PR Status."""
114
  clean_id = ''.join(filter(str.isdigit, str(user_id)))
115
  row = run_query("SELECT PR_Status FROM pr_status WHERE ID = ?", (clean_id,))
116
+
117
  if not row or (isinstance(row, str) and "no such column" in row.lower()):
118
  row = run_query("SELECT Is_PR FROM pr_status WHERE ID = ?", (clean_id,))
119
+
120
+ if row and not isinstance(row, str):
121
+ return f"PR Status: {row[0]}"
122
+ return "PR Status: False (Record not found)"
123
 
124
  # ==========================================
125
+ # 5. STREAMLIT APP UI
126
  # ==========================================
127
+ st.title("πŸ€– Multi-Policy Loan Assessor (SQL + RAG)")
128
+ st.markdown("Agent connects to **SQLite Database** and **Persistent Vector Store**")
129
+
130
+ # Calculate missing PDFs globally so everyone can see it
131
  pdfs_missing = [f for f in REQUIRED_PDFS if not os.path.exists(f)]
132
 
133
+ # --- METRICS FUNCTION ---
134
+ def update_metrics(placeholder):
135
+ manual_time = 15 * 60
136
+ if 'execution_time' in st.session_state:
137
+ ai_time = st.session_state.execution_time
138
+ time_saved = manual_time - ai_time
139
+ saved_pct = (time_saved / manual_time) * 100
140
+ with placeholder.container():
141
+ col_kpi1, col_kpi2 = st.columns(2)
142
+ col_kpi1.metric("AI Processing", f"{ai_time:.1f}s")
143
+ col_kpi2.metric("Time Saved", f"{time_saved/60:.1f} min", delta=f"{saved_pct:.1f}% faster")
144
+
145
+ # --- SIDEBAR ---
146
  with st.sidebar:
147
  st.header("πŸ” Authentication")
 
148
 
149
+ if 'is_key_valid' not in st.session_state:
150
+ st.session_state['is_key_valid'] = False
151
+
152
+ if not st.session_state['is_key_valid']:
153
+ api_key_input = st.text_input("Enter Groq API Key", type="password", key="input_key")
154
+ if st.button("Validate API Key"):
155
+ if not api_key_input:
156
+ st.error("⚠️ Please enter a key.")
157
+ else:
158
+ try:
159
+ with st.spinner("Validating..."):
160
+ test_llm = ChatGroq(api_key=api_key_input, model_name="llama-3.3-70b-versatile")
161
+ test_llm.invoke("Test")
162
+ st.session_state['groq_api_key'] = api_key_input
163
+ st.session_state['is_key_valid'] = True
164
+ st.success("βœ… Valid Key!")
165
+ time.sleep(0.5)
166
+ st.rerun()
167
+ except Exception as e:
168
+ st.error(f"❌ Invalid Key: {e}")
 
169
  else:
170
+ st.success("βœ… API Key Active")
171
+ if st.button("πŸ”΄ Reset Key"):
172
+ st.session_state['is_key_valid'] = False
173
+ st.session_state['groq_api_key'] = None
174
  st.rerun()
175
 
176
+ st.divider()
177
+ st.subheader("πŸ› οΈ System Maintenance")
178
+
179
+ if st.button("♻️ Rebuild Knowledge Base"):
180
+ if os.path.exists(INDEX_PATH):
181
+ shutil.rmtree(INDEX_PATH)
182
  st.cache_resource.clear()
183
+ st.success("Cache cleared.")
184
+ time.sleep(1)
185
  st.rerun()
186
 
187
+ if st.button("πŸ’Ύ Reload CSVs to DB"):
188
+ if os.path.exists(DB_FILE):
189
+ os.remove(DB_FILE)
190
+ init_db()
191
+ st.success("Database refreshed.")
192
+
193
+ st.divider()
194
+
195
+ if os.path.exists(DB_FILE) and not pdfs_missing:
196
+ st.success("βœ… System Ready")
197
+ else:
198
+ st.warning(f"⚠️ Missing: {pdfs_missing}")
199
+
200
+ st.header("πŸ“Š Metrics")
201
+ metrics_placeholder = st.empty()
202
+ update_metrics(metrics_placeholder)
203
+
204
+ # --- MAIN LOGIC ---
205
+ if st.session_state.get('is_key_valid', False):
206
+
207
+ os.environ["GROQ_API_KEY"] = st.session_state['groq_api_key']
208
+
209
+ # --- RAG SETUP ---
210
  @st.cache_resource
211
  def setup_rag():
212
+ if pdfs_missing:
213
+ st.error(f"Missing PDFs: {pdfs_missing}")
214
+ st.stop()
215
+
216
  embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
217
+
218
  if os.path.exists(INDEX_PATH):
219
  return FAISS.load_local(INDEX_PATH, embeddings, allow_dangerous_deserialization=True).as_retriever()
220
+ else:
221
+ documents = []
222
+ for pdf_file in REQUIRED_PDFS:
223
+ loader = PyPDFLoader(pdf_file)
224
+ documents.extend(loader.load())
225
+
226
+ text_splitter = CharacterTextSplitter(chunk_size=600, chunk_overlap=50)
227
+ final_docs = text_splitter.split_documents(documents)
228
+
229
+ vectorstore = FAISS.from_documents(final_docs, embeddings)
230
+ vectorstore.save_local(INDEX_PATH)
231
+ return vectorstore.as_retriever()
232
+
233
+ with st.spinner("Initializing AI..."):
234
+ retriever = setup_rag()
 
 
 
 
235
 
236
+ llm = ChatGroq(temperature=0, model_name="llama-3.3-70b-versatile")
237
+
238
+ rag_prompt = ChatPromptTemplate.from_template("Answer based on context:\n{context}\nQuestion: {question}")
239
  rag_chain = (
240
  {"context": retriever | (lambda d: "\n".join([x.page_content for x in d])), "question": RunnablePassthrough()}
241
+ | rag_prompt | llm | StrOutputParser()
 
 
242
  )
243
 
244
  @tool
245
  def consult_policy_doc(query: str) -> str:
246
+ """Consults Policy Documents for Risk Rules."""
247
  return rag_chain.invoke(query)
248
 
249
  tools = [get_credit_score, get_account_status, check_pr_status, consult_policy_doc]
250
 
251
+ # ============================================================
252
+ # MODIFIED PROMPT: Enforcing the PDF Steps Structure
253
+ # ============================================================
254
+ system_instruction = """You are a Bank Loan Officer.
255
+ You MUST execute the loan assessment following strictly these 4 steps and this exact output format.
256
 
257
+ REQUIRED OUTPUT FORMAT:
258
+ Customer Information: [Name], [ID], [Email]
259
+
260
+ Step 1.Retrieve information for customer information
261
+ Credit Score: [Score] , Account Status: [Status] , Nationality: [Nationality]
262
+
263
+ Step 2.Check Overall Risk
264
+ Credit Score: [Score] , Account Status: [Status] -> overall risk: [Low/Medium/High]
265
+ (Consult policy doc for the risk matrix to decide this)
266
+
267
+ Step 3.Check interest rate
268
+ overall risk: [Level] -> [Rate]%
269
+ (Consult policy doc for interest rates)
270
+
271
+ Step 4. Report
272
+ Recommend the loan interest rate [Rate]%
273
+
274
  INSTRUCTIONS:
275
+ 1. Use SQL tools to get Name, ID, Email, Score, Status, Nationality.
276
+ 2. If Nationality is NOT Singaporean, you MUST check PR status.
277
+ 3. Use 'consult_policy_doc' to find the Risk Matrix and Interest Rates.
278
+ 4. Do not output markdown code blocks (```), just the text format above.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  """
280
 
 
281
  prompt = ChatPromptTemplate.from_messages([
282
  ("system", system_instruction),
283
  ("human", "{input}"),
284
  MessagesPlaceholder(variable_name="agent_scratchpad"),
285
  ])
286
+
 
287
  agent = create_tool_calling_agent(llm, tools, prompt)
288
+ agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, return_intermediate_steps=True)
289
 
290
  col1, col2 = st.columns([1, 2])
291
  with col1:
292
+ st.subheader("1. Customer Details")
293
  uid = st.text_input("Customer ID", "1111")
294
+ use_simulation = st.checkbox("Simulation Mode")
295
+
296
+ sim_score = 650
297
+ sim_status = "good-standing"
298
+ if use_simulation:
299
+ sim_score = st.slider("Sim Credit Score", 300, 900, 450)
300
+ sim_status = st.selectbox("Sim Status", ["good-standing", "closed", "delinquent"])
301
 
302
+ st.divider()
303
+ btn = st.button("Assess Loan Risk", type="primary")
304
+
305
  with col2:
306
  if btn:
307
+ # We simplified the query here because the strict instructions are now in the System Prompt
308
+ if use_simulation:
309
+ query = f"""
310
+ Process Loan for Customer ID: {uid}.
311
+ *** SIMULATION MODE ***
312
+ 1. DO NOT query 'get_credit_score' or 'account_status' for Score/Status.
313
+ 2. USE: Score: {sim_score}, Status: {sim_status}
314
+ 3. Query 'get_account_status' ONLY for Name/Nationality/Email.
315
+ 4. Follow the strict 4-step format defined in your system instructions.
316
+ """
317
+ else:
318
+ query = f"""
319
+ Process Loan for Customer ID: {uid}.
320
+ 1. Query SQL tools for Name, Email, Nationality, Status, Score.
321
+ 2. IF Nationality is 'Singaporean', SKIP 'check_pr_status'.
322
+ 3. Follow the strict 4-step format defined in your system instructions.
323
+ """
324
 
325
+ with st.status("πŸ€– Agent 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.text(res['output']) # Changed to text to preserve the formatting better
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('is_key_valid', False):
355
+ st.info("πŸ‘ˆ Please validate your Groq API Key.")