larrysim commited on
Commit
0c4d024
Β·
verified Β·
1 Parent(s): 7f80ff6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -80
app.py CHANGED
@@ -5,38 +5,33 @@ import warnings
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
-
24
- # Suppress warnings
25
  warnings.filterwarnings("ignore")
26
 
27
  # ==========================================
28
- # 2. GLOBAL CONSTANTS & IMPORTS
29
  # ==========================================
30
  DB_FILE = "bank.db"
31
  INDEX_PATH = "faiss_index"
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
41
  from langchain_community.vectorstores import FAISS
42
  from langchain_community.callbacks import StreamlitCallbackHandler
@@ -47,26 +42,17 @@ try:
47
  from langchain_core.output_parsers import StrOutputParser
48
  from langchain_core.tools import tool
49
  from langchain.agents import AgentExecutor, create_tool_calling_agent
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
  # ==========================================
57
  # 3. DATABASE SETUP
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 = {
65
- "credit_score": "credit_score.csv",
66
- "account_status": "account_status.csv",
67
- "pr_status": "pr_status.csv"
68
- }
69
-
70
  try:
71
  for table, file in csv_files.items():
72
  if os.path.exists(file):
@@ -75,9 +61,7 @@ def init_db():
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
-
81
  init_db()
82
 
83
  def run_query(query, params=()):
@@ -89,7 +73,7 @@ def run_query(query, params=()):
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:
@@ -117,128 +101,133 @@ def check_pr_status(user_id: str) -> str:
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:
129
  ai_time = st.session_state.execution_time
130
- time_saved = manual_time - ai_time
131
- saved_pct = (time_saved / manual_time) * 100
132
- with placeholder.container():
133
- col_kpi1, col_kpi2 = st.columns(2)
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.")
161
  else:
162
  try:
163
- with st.spinner(f"Contacting {provider_option}..."):
164
- # DYNAMIC VALIDATION
165
  if "Groq" in provider_option:
166
  test_llm = ChatGroq(api_key=api_key_input, model_name="llama-3.3-70b-versatile")
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!")
174
  time.sleep(0.5)
175
  st.rerun()
176
  except Exception as e:
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)
199
 
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 = (
@@ -252,6 +241,7 @@ if st.session_state.get('auth_status', False):
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}"),
@@ -260,10 +250,9 @@ if st.session_state.get('auth_status', False):
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
@@ -291,15 +280,16 @@ if st.session_state.get('auth_status', False):
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")
 
5
  import time
6
  import sqlite3
7
  import shutil
8
+ import asyncio
9
 
10
  # ==========================================
11
  # 0. ASYNC FIX (CRITICAL FOR STREAMLIT)
12
  # ==========================================
13
+ # Fixes "No 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
21
  # ==========================================
22
+ st.set_page_config(page_title="Bank Loan Agent", layout="wide")
 
 
23
  warnings.filterwarnings("ignore")
24
 
25
  # ==========================================
26
+ # 2. CONSTANTS & IMPORTS
27
  # ==========================================
28
  DB_FILE = "bank.db"
29
  INDEX_PATH = "faiss_index"
30
  REQUIRED_PDFS = ["Bank Loan Overall Risk Policy.pdf", "Bank Loan Interest Rate Policy.pdf"]
31
 
32
  try:
 
33
  from langchain_groq import ChatGroq
34
  from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
 
 
35
  from langchain_huggingface import HuggingFaceEmbeddings
36
  from langchain_community.vectorstores import FAISS
37
  from langchain_community.callbacks import StreamlitCallbackHandler
 
42
  from langchain_core.output_parsers import StrOutputParser
43
  from langchain_core.tools import tool
44
  from langchain.agents import AgentExecutor, create_tool_calling_agent
 
45
  except ImportError as e:
46
  st.error(f"❌ Critical Import Error: {e}")
 
47
  st.stop()
48
 
49
  # ==========================================
50
  # 3. DATABASE SETUP
51
  # ==========================================
52
  def init_db():
 
53
  if os.path.exists(DB_FILE): return
 
54
  conn = sqlite3.connect(DB_FILE)
55
+ csv_files = {"credit_score": "credit_score.csv", "account_status": "account_status.csv", "pr_status": "pr_status.csv"}
 
 
 
 
 
56
  try:
57
  for table, file in csv_files.items():
58
  if os.path.exists(file):
 
61
  if 'ID' in df.columns: df['ID'] = df['ID'].astype(str)
62
  try: df.to_sql(table, conn, if_exists='replace', index=False)
63
  except: pass
64
+ finally: conn.close()
 
 
65
  init_db()
66
 
67
  def run_query(query, params=()):
 
73
  except Exception as e: return f"DB Error: {e}"
74
 
75
  # ==========================================
76
+ # 4. TOOLS
77
  # ==========================================
78
  @tool
79
  def get_credit_score(user_id: str) -> str:
 
101
  return f"PR Status: {row[0]}" if (row and not isinstance(row, str)) else "PR Status: False."
102
 
103
  # ==========================================
104
+ # 5. UI & AUTH
105
  # ==========================================
106
  st.title("πŸ€– Multi-Model Loan Assessor")
 
107
  pdfs_missing = [f for f in REQUIRED_PDFS if not os.path.exists(f)]
108
 
109
  def update_metrics(placeholder):
 
110
  if 'execution_time' in st.session_state:
111
  ai_time = st.session_state.execution_time
112
+ col1, col2 = placeholder.columns(2)
113
+ col1.metric("Processing Time", f"{ai_time:.2f}s")
114
+ col2.metric("Efficiency", "High")
 
 
 
115
 
116
  # --- SIDEBAR ---
117
  with st.sidebar:
118
  st.header("πŸ” Authentication")
119
 
120
+ # Provider Selection
121
+ provider_option = st.radio("Select Model:", ["Groq (Llama-3)", "Google (Gemini)"])
122
 
123
+ # Init State
124
  if 'auth_status' not in st.session_state:
125
  st.session_state['auth_status'] = False
126
  st.session_state['api_key'] = None
127
  st.session_state['provider'] = None
128
 
129
+ # Reset on Switch
130
  if st.session_state.get('provider') != provider_option:
131
  st.session_state['auth_status'] = False
132
  st.session_state['api_key'] = None
133
  st.session_state['provider'] = provider_option
134
 
135
+ # Auth Logic
136
  if not st.session_state['auth_status']:
137
  api_key_input = st.text_input(f"Enter {provider_option} API Key", type="password")
138
+
139
  if st.button("Validate Key"):
140
  if not api_key_input:
141
+ st.error("⚠️ Enter a key.")
142
  else:
143
  try:
144
+ with st.spinner(f"Connecting to {provider_option}..."):
145
+ # --- VALIDATION LOGIC ---
146
  if "Groq" in provider_option:
147
  test_llm = ChatGroq(api_key=api_key_input, model_name="llama-3.3-70b-versatile")
148
  else:
149
+ # FORCE REST TRANSPORT TO FIX SPINNING
150
+ test_llm = ChatGoogleGenerativeAI(
151
+ google_api_key=api_key_input,
152
+ model="gemini-1.5-flash",
153
+ transport="rest" # <--- THIS IS THE FIX
154
+ )
155
+
156
+ # Simple Invoke to test connection
157
+ test_llm.invoke("Hello")
158
 
 
159
  st.session_state['auth_status'] = True
160
  st.session_state['api_key'] = api_key_input
161
+ st.success("βœ… Connected!")
162
  time.sleep(0.5)
163
  st.rerun()
164
  except Exception as e:
165
+ st.error(f"❌ Error: {e}")
166
  else:
167
+ st.success(f"βœ… {st.session_state['provider']} Ready")
168
  if st.button("πŸ”΄ Logout"):
169
  st.session_state['auth_status'] = False
 
170
  st.rerun()
171
 
172
  st.divider()
173
+ if st.button("♻️ Rebuild Database"):
174
  if os.path.exists(INDEX_PATH): shutil.rmtree(INDEX_PATH)
175
  st.cache_resource.clear()
176
+ st.success("Reset Complete.")
177
+ time.sleep(1)
178
  st.rerun()
179
 
180
+ st.divider()
181
  if os.path.exists(DB_FILE) and not pdfs_missing:
182
  st.success("βœ… System Ready")
183
  else:
184
  st.warning(f"⚠️ Missing: {pdfs_missing}")
185
 
186
+ st.header("Metrics")
187
  metrics_placeholder = st.empty()
188
  update_metrics(metrics_placeholder)
189
 
190
+ # ==========================================
191
+ # 6. MAIN APP LOGIC
192
+ # ==========================================
193
  if st.session_state.get('auth_status', False):
194
 
195
  current_key = st.session_state['api_key']
196
  current_provider = st.session_state['provider']
197
 
198
+ # --- RAG SETUP ---
199
  @st.cache_resource
200
+ def setup_rag(_provider, _key):
201
  if pdfs_missing: st.stop()
202
 
203
+ # Use HuggingFace embeddings for stability across both models
204
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
 
 
 
205
 
206
  if os.path.exists(INDEX_PATH):
207
+ return FAISS.load_local(INDEX_PATH, embeddings, allow_dangerous_deserialization=True).as_retriever()
208
+ else:
209
+ documents = []
210
+ for pdf_file in REQUIRED_PDFS:
211
+ documents.extend(PyPDFLoader(pdf_file).load())
212
+ splits = CharacterTextSplitter(chunk_size=600, chunk_overlap=50).split_documents(documents)
213
+ vectorstore = FAISS.from_documents(splits, embeddings)
214
+ vectorstore.save_local(INDEX_PATH)
215
+ return vectorstore.as_retriever()
216
+
217
+ with st.spinner("Loading Knowledge Base..."):
 
 
 
 
 
 
218
  retriever = setup_rag(current_provider, current_key)
219
 
220
+ # --- LLM SETUP ---
221
  if "Groq" in current_provider:
222
  llm = ChatGroq(api_key=current_key, temperature=0, model_name="llama-3.3-70b-versatile")
223
  else:
224
+ # FORCE REST TRANSPORT HERE TOO
225
+ llm = ChatGoogleGenerativeAI(
226
+ google_api_key=current_key,
227
+ temperature=0,
228
+ model="gemini-1.5-flash",
229
+ transport="rest" # <--- CRITICAL FIX
230
+ )
231
 
232
  # --- AGENT SETUP ---
233
  rag_chain = (
 
241
  return rag_chain.invoke(query)
242
 
243
  tools = [get_credit_score, get_account_status, check_pr_status, consult_policy_doc]
244
+
245
  prompt = ChatPromptTemplate.from_messages([
246
  ("system", "Act as a Loan Officer. Query SQL DB for info. Check Policies via tool. Output Markdown report."),
247
  ("human", "{input}"),
 
250
 
251
  agent_executor = AgentExecutor(agent=create_tool_calling_agent(llm, tools, prompt), tools=tools, verbose=True, return_intermediate_steps=True)
252
 
253
+ # --- INPUT UI ---
254
  col1, col2 = st.columns([1, 2])
255
  with col1:
 
256
  uid = st.text_input("Customer ID", "1111")
257
  use_sim = st.checkbox("Simulation Mode")
258
  sim_score = st.slider("Score", 300, 900, 450) if use_sim else 0
 
280
 
281
  st.success("### πŸ“‹ Final Report")
282
  st.markdown(res['output'])
283
+
284
  with st.expander("Trace"):
285
  for action, obs in res.get("intermediate_steps", []):
286
+ st.markdown(f"**Tool:** `{action.tool}`\n**Result:** `{obs}`")
287
 
288
  if not use_sim:
289
  st.divider()
290
  with st.expander("βœ‰οΈ Email Draft"):
291
+ email = llm.invoke(f"Draft email for: {res['output']}").content
292
  st.text_area("Draft", value=email, height=200)
293
 
294
  elif not st.session_state.get('auth_status', False):
295
+ st.info("πŸ‘ˆ Please Select Provider & Validate Key in Sidebar")