Nyha15 commited on
Commit
f8fe5ac
·
1 Parent(s): d55aceb

Refactored

Browse files
Files changed (1) hide show
  1. app.py +172 -917
app.py CHANGED
@@ -11,33 +11,26 @@ import time
11
  import json
12
  from typing import List, Dict, Any, Optional
13
 
14
- try:
15
- # Import required libraries
16
- import gradio as gr
17
- from langchain.agents import AgentExecutor, create_openai_tools_agent
18
- from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
19
- from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
20
- from langchain_core.tools import BaseTool, StructuredTool, tool
21
- from langchain_openai import ChatOpenAI
22
- from langchain_community.vectorstores import Chroma
23
- from langchain_openai import OpenAIEmbeddings
24
- from langchain.text_splitter import RecursiveCharacterTextSplitter
25
- except ImportError as e:
26
- print(f"Error importing required libraries: {e}")
27
- print("Please install required packages: pip install -r requirements.txt")
28
- sys.exit(1)
29
-
30
- # Set up API Key
31
  api_key = os.environ.get("OPENAI_API_KEY")
32
  if not api_key:
33
  api_key = input("Please enter your OpenAI API key: ")
34
  os.environ["OPENAI_API_KEY"] = api_key
35
 
36
- # Global workflow log to track the execution flow
37
- WORKFLOW_LOG = []
38
 
39
- def log_workflow(step, details=None):
40
- """Add a step to the workflow log"""
41
  timestamp = time.strftime("%H:%M:%S")
42
  entry = {"time": timestamp, "step": step}
43
  if details:
@@ -45,963 +38,225 @@ def log_workflow(step, details=None):
45
  WORKFLOW_LOG.append(entry)
46
  print(f"[{timestamp}] {step}{': ' + str(details) if details else ''}")
47
 
48
- def get_workflow_log():
49
- """Get the workflow log as formatted text"""
50
  if not WORKFLOW_LOG:
51
  return "No workflow steps recorded yet."
52
-
53
- log_text = "## Workflow Execution Log:\n\n"
54
- for entry in WORKFLOW_LOG:
55
- log_text += f"**[{entry['time']}]** {entry['step']}\n"
56
- if 'details' in entry and entry['details']:
57
- details = entry['details']
58
- if isinstance(details, dict):
59
- for k, v in details.items():
60
- if isinstance(v, str) and len(v) > 100:
61
- details[k] = v[:100] + "..."
62
- log_text += f"``````\n"
63
- else:
64
- log_text += f"{details}\n"
65
-
66
- return log_text
67
 
68
  def clear_workflow_log():
69
- """Clear the workflow log"""
70
- global WORKFLOW_LOG
71
- WORKFLOW_LOG = []
72
 
73
- # Data collector for international students
74
- class InternationalStudentDataCollector:
75
- """Collects financial data for international students from different countries"""
76
 
77
- def __init__(self):
78
- """Initialize the data collector with a model for generating data"""
79
- self.llm = ChatOpenAI(temperature=0.1, model="gpt-3.5-turbo")
80
- self.cache = {}
81
-
82
- def _get_data_with_caching(self, prompt_key, prompt):
83
- """Get data with caching to avoid repeated API calls"""
84
- log_workflow(f"Collecting data for {prompt_key}")
85
-
86
- if prompt_key in self.cache:
87
- log_workflow("Using cached data")
88
- return self.cache[prompt_key]
89
-
90
- try:
91
- response = self.llm.invoke(prompt)
92
- facts = [line.strip() for line in response.content.split('\n') if line.strip()]
93
- self.cache[prompt_key] = facts
94
- log_workflow(f"Collected {len(facts)} facts")
95
- return facts
96
- except Exception as e:
97
- log_workflow("Error collecting data", str(e))
98
- return [f"Error retrieving information: {str(e)}"]
99
-
100
- def get_banking_data(self, country):
101
- """Get banking information for international students from specific country"""
102
- prompt_key = f"banking_{country.lower()}"
103
- banking_prompt = f"""
104
- Provide 5 specific, actionable facts about banking options for international students from {country} in the United States.
105
- Focus on:
106
- 1. The best US banks that offer accounts for {country} students with minimal fees
107
- 2. Exact documentation requirements for {country} students to open an account
108
- 3. Special features available to international students from {country}
109
- 4. Precise fee structures and minimum balances for recommended accounts
110
- 5. Best options for international money transfers between {country} and US
111
-
112
- Format as a list of factual, specific statements, one per line.
113
- Be extremely specific and include bank names, exact documentation needed, and fee amounts where possible.
114
- """
115
-
116
- return self._get_data_with_caching(prompt_key, banking_prompt)
117
-
118
- def get_credit_data(self, country):
119
- """Get credit building information for international students from specific country"""
120
- prompt_key = f"credit_{country.lower()}"
121
- credit_prompt = f"""
122
- Provide 5 specific, actionable facts about credit building options for international students from {country} in the United States.
123
- Focus on:
124
- 1. Exact credit card options available to {country} students without US credit history (with specific bank names)
125
- 2. Precisely how {country} credit history can or cannot be used in the US (e.g., Nova Credit)
126
- 3. Detailed secured credit card requirements and deposit amounts for specific cards
127
- 4. Step-by-step strategies for building credit scores for {country} nationals
128
- 5. Specific credit-building pitfalls that {country} students should avoid
129
-
130
- Format as a list of factual, specific statements, one per line.
131
- Include exact credit card names, specific dollar amounts for deposits, and precise steps where possible.
132
- """
133
-
134
- return self._get_data_with_caching(prompt_key, credit_prompt)
135
-
136
- def get_budget_data(self, country):
137
- """Get budget management information for international students from specific country"""
138
- prompt_key = f"budget_{country.lower()}"
139
- budget_prompt = f"""
140
- Provide 5 specific, actionable facts about budget management for international students from {country} in the United States.
141
- Focus on:
142
- 1. Exact breakdown of typical monthly expenses for {country} students in the US (with dollar amounts)
143
- 2. Specific money transfer services popular with {country} students (with fee structures)
144
- 3. Detailed tax implications for {country} students with TA/RA stipends (including tax treaty benefits)
145
- 4. Names of specific budget apps or tools popular with {country} students
146
- 5. Step-by-step plan for managing a $2,500 monthly TA stipend, including saving for emergencies
147
-
148
- Format as a list of factual, specific statements, one per line.
149
- Include exact dollar amounts, percentages, and specific service names where possible.
150
- """
151
-
152
- return self._get_data_with_caching(prompt_key, budget_prompt)
153
-
154
- def get_currency_data(self, country):
155
- """Get currency exchange information for international students from specific country"""
156
- prompt_key = f"currency_{country.lower()}"
157
- currency_prompt = f"""
158
- Provide 5 specific, actionable facts about currency exchange and international money transfers for {country} students in the US.
159
- Focus on:
160
- 1. Current exchange rate trends between {country} currency and USD (with specific ranges)
161
- 2. Exact fee structures of money transfer services for {country}-US transfers (Wise, Remitly, etc.)
162
- 3. Specific regulatory considerations for moving money from {country} to US (limits, documentation)
163
- 4. Precise breakdown of hidden fees and exchange rate markups typical in {country}-US transfers
164
- 5. Step-by-step strategies for optimizing currency exchange for {country} students
165
-
166
- Format as a list of factual, specific statements, one per line.
167
- Include exact service names, fee percentages, and dollar amounts where possible.
168
- """
169
-
170
- return self._get_data_with_caching(prompt_key, currency_prompt)
171
-
172
- def get_loan_data(self, country):
173
- """Get student loan information for international students from specific country"""
174
- prompt_key = f"loan_{country.lower()}"
175
- loan_prompt = f"""
176
- Provide 5 specific, actionable facts about student loan options for international students from {country} studying in the US.
177
- Focus on:
178
- 1. Names of specific education loan providers in {country} for international study (with interest rates)
179
- 2. Exact US-based lenders that serve {country} students without US cosigners (Prodigy, MPOWER, etc.)
180
- 3. Precise interest rates and terms for various {country} student loan options
181
- 4. Specific collateral requirements for loans to {country} students (with dollar amounts)
182
- 5. Names of loan forgiveness or assistance programs available to {country} students
183
-
184
- Format as a list of factual, specific statements, one per line.
185
- Include exact lender names, interest rate percentages, and dollar amounts where possible.
186
- """
187
-
188
- return self._get_data_with_caching(prompt_key, loan_prompt)
189
-
190
- def get_career_data(self, country):
191
- """Get career financial planning information for international students from specific country"""
192
- prompt_key = f"career_{country.lower()}"
193
- career_prompt = f"""
194
- Provide 5 specific, actionable facts about career financial planning for international students from {country} in the US.
195
- Focus on:
196
- 1. Exact F-1 visa work restrictions and opportunities (with hour limits and eligible positions)
197
- 2. Detailed CPT/OPT regulations affecting {country} students (application timeline, costs)
198
- 3. Step-by-step financial planning for summer internships specifically for {country} students
199
- 4. Specific post-graduation work authorization financial considerations (with costs and timeline)
200
- 5. Precise salary negotiation strategies and benefits evaluation for {country} nationals
201
-
202
- Format as a list of factual, specific statements, one per line.
203
- Include exact hour limits, application fees, timeline durations, and dollar amounts where possible.
204
- """
205
-
206
- return self._get_data_with_caching(prompt_key, career_prompt)
207
-
208
- def get_legal_data(self, country):
209
- """Get legal financial information for international students from specific country"""
210
- prompt_key = f"legal_{country.lower()}"
211
- legal_prompt = f"""
212
- Provide 5 specific, actionable facts about legal financial considerations for international students from {country} in the US.
213
- Focus on:
214
- 1. Exact visa maintenance financial requirements for {country} students (with dollar amounts)
215
- 2. Specific tax treaty benefits between US and {country} (with article numbers and percentage rates)
216
- 3. Detailed FBAR and foreign account reporting requirements for {country} nationals ($10,000 threshold, etc.)
217
- 4. Precise financial documentation needed for visa renewals/applications (with dollar amounts)
218
- 5. Specific legal implications of different types of income for {country} students on F-1 visas
219
-
220
- Format as a list of factual, specific statements, one per line.
221
- Include exact dollar thresholds, tax treaty article numbers, and specific form names where possible.
222
- """
223
-
224
- return self._get_data_with_caching(prompt_key, legal_prompt)
225
 
226
 
227
  # =======================================
228
  # Knowledge Base (RAG Implementation)
 
229
  # =======================================
230
 
231
  class KnowledgeBase:
232
- """RAG implementation for domain-specific knowledge retrieval"""
233
-
234
  def __init__(self, domain: str):
235
- """Initialize the knowledge base for a specific domain"""
236
  self.domain = domain
237
- self.vector_store = None
238
- self.retriever = None
239
- self.data_collector = InternationalStudentDataCollector()
240
- self.embeddings = OpenAIEmbeddings()
241
-
242
- def _initialize_for_country(self, country: str):
243
- """Initialize the vector store for a specific country"""
244
- domain_key = f"{self.domain}_{country.lower()}"
245
- log_workflow(f"Initializing knowledge base", {"domain": self.domain, "country": country})
246
-
247
- if self.vector_store is not None:
248
- log_workflow("Using existing vector store")
249
- return
250
-
251
- # Get country-specific data from the data collector
252
- if self.domain == "banking":
253
- domain_texts = self.data_collector.get_banking_data(country)
254
- elif self.domain == "credit":
255
- domain_texts = self.data_collector.get_credit_data(country)
256
- elif self.domain == "budget":
257
- domain_texts = self.data_collector.get_budget_data(country)
258
- elif self.domain == "currency":
259
- domain_texts = self.data_collector.get_currency_data(country)
260
- elif self.domain == "loans":
261
- domain_texts = self.data_collector.get_loan_data(country)
262
- elif self.domain == "career":
263
- domain_texts = self.data_collector.get_career_data(country)
264
- elif self.domain == "legal":
265
- domain_texts = self.data_collector.get_legal_data(country)
266
- else:
267
- domain_texts = [f"General information for {self.domain} domain for {country} international students."]
268
-
269
- log_workflow(f"Creating vector store with {len(domain_texts)} documents")
270
-
271
- # Create text splitter for chunking
272
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
273
- splits = text_splitter.split_text("\n\n".join(domain_texts))
274
-
275
- # Create vector store with embeddings
276
- try:
277
- self.vector_store = Chroma.from_texts(
278
- splits,
279
- self.embeddings,
280
- collection_name=domain_key
281
- )
282
-
283
- # Create retriever for similarity search
284
- self.retriever = self.vector_store.as_retriever(
285
- search_type="similarity",
286
- search_kwargs={"k": 3}
287
- )
288
- log_workflow("Vector store created successfully")
289
- except Exception as e:
290
- log_workflow("Error creating vector store", str(e))
291
- # We'll fall back to direct retrieval if vector storage fails
292
 
293
  def retrieve(self, query: str, country: str) -> List[str]:
294
- """Retrieve relevant information using vector similarity search"""
295
- log_workflow(f"RAG Pattern: Retrieving {self.domain} knowledge", {"query": query[:50], "country": country})
296
-
297
- try:
298
- # Initialize the vector store if needed
299
- self._initialize_for_country(country)
300
-
301
- if self.retriever:
302
- # Use the retriever to find similar content
303
- documents = self.retriever.get_relevant_documents(query)
304
- results = [doc.page_content for doc in documents]
305
- log_workflow(f"Retrieved {len(results)} relevant documents")
306
- return results
307
- else:
308
- raise ValueError("Retriever not initialized properly")
309
- except Exception as e:
310
- log_workflow("Error in RAG retrieval, falling back to direct retrieval", str(e))
311
- # Fallback to direct retrieval if vector storage fails
312
- if self.domain == "banking":
313
- return self.data_collector.get_banking_data(country)
314
- elif self.domain == "credit":
315
- return self.data_collector.get_credit_data(country)
316
- elif self.domain == "budget":
317
- return self.data_collector.get_budget_data(country)
318
- elif self.domain == "currency":
319
- return self.data_collector.get_currency_data(country)
320
- elif self.domain == "loans":
321
- return self.data_collector.get_loan_data(country)
322
- elif self.domain == "career":
323
- return self.data_collector.get_career_data(country)
324
- elif self.domain == "legal":
325
- return self.data_collector.get_legal_data(country)
326
- else:
327
- return [f"Information about {self.domain} for {country} international students."]
328
 
329
 
330
  # =======================================
331
- # Domain Specialist Agents
332
  # =======================================
333
 
334
  class SpecialistAgent:
335
- """Base class for specialist agents with domain expertise"""
336
-
337
  def __init__(self, name: str, domain: str, llm=None):
338
- """Initialize a specialist agent with domain expertise"""
339
  self.name = name
340
  self.domain = domain
341
- self.knowledge_base = KnowledgeBase(domain)
342
- self.llm = llm if llm else ChatOpenAI(temperature=0.2)
343
 
344
  def run(self, query: str, country: str) -> str:
345
- """Run the specialist agent to get domain-specific advice"""
346
- log_workflow(f"Role-based Cooperation: {self.name} analyzing query", {"query": query[:50]})
347
-
348
- # Get country-specific knowledge using RAG
349
- knowledge = self.knowledge_base.retrieve(query, country)
350
-
351
- # Join the knowledge items with newlines
352
- knowledge_text = "\n".join('- ' + item for item in knowledge)
353
-
354
- # Prepare a detailed prompt with the knowledge and query
355
  prompt = f"""
356
- As a specialist {self.name} for international students, provide detailed, specific financial advice for a student from {country}.
357
-
358
- STUDENT QUERY:
359
- {query}
360
-
361
- RELEVANT KNOWLEDGE FROM RAG:
362
- {knowledge_text}
363
-
364
- Provide extremely detailed, actionable advice addressing the query with these requirements:
365
- 1. Include specific bank/service/product names with exact fees or rates where applicable
366
- 2. Provide step-by-step instructions for any processes (account opening, credit building, etc.)
367
- 3. Include specific dollar amounts, percentages, and time frames
368
- 4. List exact documentation requirements where relevant
369
- 5. Address all aspects of the query related to your domain of {self.domain}
370
-
371
- Format your response with clear sections, bullet points, and numbered steps.
372
- """
373
-
374
- try:
375
- log_workflow(f"{self.name} generating advice")
376
- response = self.llm.invoke(prompt)
377
- advice = response.content
378
- log_workflow(f"{self.name} generated advice", {"length": len(advice)})
379
- return advice
380
- except Exception as e:
381
- log_workflow(f"Error in {self.name}", str(e))
382
- return f"The {self.name} encountered an issue: {str(e)}"
383
-
384
-
385
- # Specialized agent implementations
386
- class BankingAdvisor(SpecialistAgent):
387
- """Specialist agent for banking advice"""
388
- def __init__(self, llm=None):
389
- super().__init__(name="Banking Advisor", domain="banking", llm=llm)
390
-
391
-
392
- class CreditBuilder(SpecialistAgent):
393
- """Specialist agent for credit building advice"""
394
- def __init__(self, llm=None):
395
- super().__init__(name="Credit Builder", domain="credit", llm=llm)
396
-
397
-
398
- class BudgetManager(SpecialistAgent):
399
- """Specialist agent for budget management advice"""
400
- def __init__(self, llm=None):
401
- super().__init__(name="Budget Manager", domain="budget", llm=llm)
402
-
403
-
404
- class CurrencyExchangeSpecialist(SpecialistAgent):
405
- """Specialist agent for currency exchange advice"""
406
- def __init__(self, llm=None):
407
- super().__init__(name="Currency Exchange Specialist", domain="currency", llm=llm)
408
-
409
-
410
- class StudentLoanAdvisor(SpecialistAgent):
411
- """Specialist agent for student loan advice"""
412
- def __init__(self, llm=None):
413
- super().__init__(name="Student Loan Advisor", domain="loans", llm=llm)
414
 
 
 
415
 
416
- class CareerFinancePlanner(SpecialistAgent):
417
- """Specialist agent for career financial planning advice"""
418
- def __init__(self, llm=None):
419
- super().__init__(name="Career Finance Planner", domain="career", llm=llm)
420
-
 
 
 
 
 
421
 
422
- class LegalFinanceAdvisor(SpecialistAgent):
423
- """Specialist agent for legal financial advice"""
424
- def __init__(self, llm=None):
425
- super().__init__(name="Legal Finance Advisor", domain="legal", llm=llm)
 
 
 
 
426
 
427
 
428
  # =======================================
429
- # Coordinator Agent (Central Agent)
 
430
  # =======================================
431
 
432
  class CoordinatorAgent:
433
- """Central coordinator agent that orchestrates specialist agents"""
434
-
435
  def __init__(self, llm=None):
436
- """Initialize the coordinator agent"""
437
- self.llm = llm if llm else ChatOpenAI(temperature=0.3)
438
-
439
- # Initialize specialist agents
440
- self.banking_advisor = BankingAdvisor(self.llm)
441
- self.credit_builder = CreditBuilder(self.llm)
442
- self.budget_manager = BudgetManager(self.llm)
443
- self.currency_specialist = CurrencyExchangeSpecialist(self.llm)
444
- self.loan_advisor = StudentLoanAdvisor(self.llm)
445
- self.career_planner = CareerFinancePlanner(self.llm)
446
- self.legal_advisor = LegalFinanceAdvisor(self.llm)
447
-
448
- # Map domains to specialists
449
  self.specialists = {
450
- "banking": self.banking_advisor,
451
- "credit": self.credit_builder,
452
- "budget": self.budget_manager,
453
- "currency": self.currency_specialist,
454
- "loans": self.loan_advisor,
455
- "career": self.career_planner,
456
- "legal": self.legal_advisor
457
  }
458
 
459
- def _identify_relevant_specialists(self, query: str) -> List[str]:
460
- """Identify which specialists are relevant to the query"""
461
- log_workflow("Analyzing query to identify relevant specialists")
462
-
463
- relevance_prompt = f"""
464
- Based on this financial query from an international student:
465
- "{query}"
466
-
467
- Which of the following specialist advisors should be consulted? Choose only the relevant ones.
468
- - banking (Banking Advisor: bank accounts, account types, transfers, documentation)
469
- - credit (Credit Builder: credit cards, credit scores, credit history)
470
- - budget (Budget Manager: expense tracking, savings, stipend management)
471
- - currency (Currency Exchange Specialist: exchange rates, money transfers)
472
- - loans (Student Loan Advisor: educational loans, repayment strategies)
473
- - career (Career Finance Planner: internships, CPT/OPT, job preparation)
474
- - legal (Legal Finance Advisor: visa regulations, tax implications)
475
-
476
- Return a comma-separated list of ONLY the relevant domain codes (e.g., "banking,credit").
477
- """
478
-
479
- try:
480
- response = self.llm.invoke(relevance_prompt)
481
- domains = [domain.strip().lower() for domain in response.content.split(',')]
482
- valid_domains = [domain for domain in domains if domain in self.specialists]
483
-
484
- # Add budget domain if query mentions stipend or expenses
485
- if "budget" not in valid_domains and ("stipend" in query.lower() or "expense" in query.lower()):
486
- valid_domains.append("budget")
487
-
488
- # Add legal domain if query mentions tax or visa
489
- if "legal" not in valid_domains and ("tax" in query.lower() or "visa" in query.lower()):
490
- valid_domains.append("legal")
491
-
492
- # Add career domain if query mentions internship, CPT, or OPT
493
- if "career" not in valid_domains and any(term in query.lower() for term in ["internship", "cpt", "opt"]):
494
- valid_domains.append("career")
495
-
496
- log_workflow("Identified relevant specialists", {"domains": valid_domains})
497
- return valid_domains
498
- except Exception as e:
499
- log_workflow("Error identifying specialists", str(e))
500
- # Default to essential domains if there's an error
501
- default_domains = ["banking", "budget"]
502
- if "credit" in query.lower():
503
- default_domains.append("credit")
504
- return default_domains
505
-
506
- def _conduct_vote(self, question: str, options: List[str], country: str) -> Dict[str, Any]:
507
- """Implement voting-based cooperation between specialists"""
508
- log_workflow("Voting-based Cooperation: Specialists voting on options",
509
- {"question": question[:50], "options": options})
510
-
511
- voting_results = {option: 0 for option in options}
512
- specialist_votes = {}
513
-
514
- # Create options text separately
515
- options_text = "\n".join([f"{i+1}. {option}" for i, option in enumerate(options)])
516
-
517
- voting_prompt = f"""
518
- As a financial advisor for international students from {country}, which of the following options would you recommend?
519
-
520
- QUESTION: {question}
521
-
522
- OPTIONS:
523
- {options_text}
524
-
525
- Analyze the options carefully, then respond with ONLY the number of your recommendation (e.g., "1" or "2").
526
- """
527
-
528
- # Select appropriate specialists for voting
529
- relevant_domains = self._identify_relevant_specialists(question)
530
- for domain in relevant_domains:
531
- specialist = self.specialists[domain]
532
- try:
533
- response = self.llm.invoke(voting_prompt)
534
- vote_text = response.content.strip()
535
-
536
- # Try to extract a number from the response
537
- vote = None
538
- for i, option in enumerate(options):
539
- if str(i+1) in vote_text:
540
- vote = options[i]
541
- break
542
-
543
- if vote is None and len(options) > 0:
544
- vote = options[0] # Default to first option if parsing fails
545
-
546
- if vote in voting_results:
547
- voting_results[vote] += 1
548
- specialist_votes[domain] = vote
549
- log_workflow(f"{domain.capitalize()} voted for: {vote}")
550
- except Exception as e:
551
- log_workflow(f"Error during voting from {domain}", str(e))
552
-
553
- # Find the winner
554
- winner = max(voting_results.items(), key=lambda x: x[1]) if voting_results else (options[0], 0)
555
-
556
- log_workflow(f"Voting complete, winner determined",
557
- {"winner": winner[0], "vote_count": winner[1]})
558
-
559
- return {
560
- "winner": winner[0],
561
- "votes": voting_results,
562
- "specialist_votes": specialist_votes
563
  }
564
-
565
- def _generate_plans(self, financial_goal: str, constraints: str, country: str) -> Dict[str, str]:
566
- """Implement Multi-path Plan Generator pattern"""
567
- log_workflow("Multi-path Plan Generator: Creating financial plans",
568
- {"goal": financial_goal[:50], "country": country})
569
-
570
- # Create prompts for different risk approaches
571
- planning_prompt_template = f"""
572
- As a financial advisor for international students from {country}, create a {{approach}} financial strategy for:
573
-
574
- GOAL: {financial_goal}
575
-
576
- CONSTRAINTS: {constraints}
577
-
578
- Your {{approach}} strategy should include:
579
- 1. Detailed step-by-step actions with timeline
580
- 2. Specific financial products/services with exact names and costs
581
- 3. Precise breakdown of benefits and risks
582
- 4. Expected outcomes with realistic numbers
583
- 5. Mitigation strategies for potential challenges
584
-
585
- Format with clear headings, bullet points, and numbered steps.
586
- Include specific bank names, service providers, dollar amounts, and time frames.
587
- """
588
-
589
  plans = {}
 
 
 
 
590
 
591
- try:
592
- # Create conservative plan using Budget Manager
593
- log_workflow("Generating conservative plan")
594
- conservative_prompt = planning_prompt_template.format(approach="CONSERVATIVE (lowest risk)")
595
- plans["conservative"] = self.budget_manager.run(conservative_prompt, country)
596
-
597
- # Create balanced plan using Banking Advisor
598
- log_workflow("Generating balanced plan")
599
- balanced_prompt = planning_prompt_template.format(approach="BALANCED (moderate risk/reward)")
600
- plans["balanced"] = self.banking_advisor.run(balanced_prompt, country)
601
-
602
- # Create growth plan using Credit Builder
603
- log_workflow("Generating growth plan")
604
- growth_prompt = planning_prompt_template.format(approach="GROWTH-ORIENTED (higher potential returns)")
605
- plans["growth"] = self.credit_builder.run(growth_prompt, country)
606
-
607
- log_workflow("All plans generated successfully")
608
- return plans
609
- except Exception as e:
610
- log_workflow("Error generating financial plans", str(e))
611
- return {
612
- "conservative": f"Error generating conservative plan: {str(e)}",
613
- "balanced": f"Error generating balanced plan: {str(e)}",
614
- "growth": f"Error generating growth plan: {str(e)}"
615
- }
616
-
617
- def _reflect_on_recommendation(self, recommendation: str, student_profile: Dict[str, Any]) -> str:
618
- """Implement Self-reflection pattern"""
619
- country = student_profile.get("home_country", "unknown")
620
- visa_type = student_profile.get("visa_type", "unknown")
621
-
622
- log_workflow("Self-reflection: Reviewing recommendations",
623
- {"country": country, "visa_type": visa_type})
624
-
625
- reflection_prompt = f"""
626
- As a Legal Financial Advisor for international students, evaluate this financial recommendation:
627
-
628
- STUDENT PROFILE:
629
- Home Country: {country}
630
- Visa Type: {visa_type}
631
- University: {student_profile.get('university', 'unknown')}
632
- Funding: {student_profile.get('funding', 'unknown')}
633
- Additional Info: {student_profile.get('additional_info', 'none')}
634
-
635
- RECOMMENDATION:
636
- {recommendation}
637
-
638
- Please reflect on:
639
- 1. Does this recommendation fully comply with {visa_type} visa restrictions?
640
- 2. Is the advice properly tailored to {country} students' unique circumstances?
641
- 3. Are there any assumptions that might not apply to international students?
642
- 4. Could any part of this advice create legal/immigration issues?
643
- 5. Is the recommendation practical given typical international student constraints?
644
- 6. Does it address all aspects of the original query completely?
645
-
646
- Provide a detailed assessment with specific recommendations for improvement.
647
- """
648
-
649
- try:
650
- log_workflow("Generating legal reflection")
651
- reflection = self.legal_advisor.run(reflection_prompt, country)
652
- log_workflow("Reflection complete")
653
- return reflection
654
- except Exception as e:
655
- log_workflow("Error during self-reflection", str(e))
656
- return f"Unable to complete self-reflection due to an error: {str(e)}"
657
-
658
- def run(self, query: str, student_profile: Dict[str, Any]) -> str:
659
- """Orchestrate the specialist agents to create a comprehensive response"""
660
- log_workflow("COORDINATOR: Processing new query", {"query": query[:100]})
661
-
662
- country = student_profile.get("home_country", "unknown")
663
-
664
- # 1. Analyze the query to identify which specialists to consult
665
- relevant_domains = self._identify_relevant_specialists(query)
666
-
667
- # 2. Collect advice from relevant specialists
668
- specialist_advice = {}
669
- for domain in relevant_domains:
670
- if domain in self.specialists:
671
- specialist = self.specialists[domain]
672
- advice = specialist.run(query, country)
673
- specialist_advice[domain] = advice
674
-
675
- # 3. Generate multi-path financial plans for the query
676
- constraints = f"""
677
- Home Country: {country}
678
- Visa Type: {student_profile.get('visa_type', 'F-1')}
679
- University: {student_profile.get('university', 'unknown')}
680
- Funding: {student_profile.get('funding', 'unknown')}
681
- Additional Info: {student_profile.get('additional_info', 'none')}
682
- """
683
 
 
 
 
 
 
 
 
 
 
 
684
  plans = self._generate_plans(query, constraints, country)
685
-
686
- # 4. Synthesize the collected advice and plans into a coherent response
687
- log_workflow("Synthesizing comprehensive response")
688
-
689
- # Create the specialist advice text separately
690
- specialist_advice_text = "\n".join([f"--- {domain.upper()} SPECIALIST ---\n{advice[:1000]}\n" for domain, advice in specialist_advice.items()])
691
-
692
- synthesis_prompt = f"""
693
- As the coordinator for an International Student Finance Portal, synthesize specialist advice and financial plans into a comprehensive response.
694
-
695
- STUDENT:
696
- - Home Country: {country}
697
- - Visa Type: {student_profile.get('visa_type', 'F-1')}
698
- - University: {student_profile.get('university', 'unknown')}
699
- - Funding: {student_profile.get('funding', 'unknown')}
700
- - Additional Info: {student_profile.get('additional_info', 'none')}
701
-
702
- QUERY:
703
- {query}
704
-
705
- SPECIALIST ADVICE:
706
- {specialist_advice_text}
707
-
708
- FINANCIAL APPROACHES:
709
- --- CONSERVATIVE APPROACH ---
710
- {plans.get('conservative', 'No conservative plan available.')[:1000]}
711
-
712
- --- BALANCED APPROACH ---
713
- {plans.get('balanced', 'No balanced plan available.')[:1000]}
714
-
715
- --- GROWTH-ORIENTED APPROACH ---
716
- {plans.get('growth', 'No growth-oriented plan available.')[:1000]}
717
-
718
- Create a detailed response with:
719
- 1. PART 1: Direct answers to each specific aspect of the query - banking, credit, stipend management, etc.
720
- 2. PART 2: Multiple financial approaches (conservative, balanced, growth-oriented)
721
-
722
- Each section must be extremely detailed with:
723
- - Specific bank/service names
724
- - Exact documentation requirements
725
- - Step-by-step processes
726
- - Precise dollar amounts
727
- - Concrete timelines
728
-
729
- Format with clear headings, bullet points, and numbered steps.
730
- """
731
-
732
- try:
733
- # Generate the synthesized response
734
- log_workflow("Generating final synthesized response")
735
- synthesis_response = self.llm.invoke(synthesis_prompt)
736
-
737
- # 5. Self-reflection (check for international student appropriateness)
738
- log_workflow("Performing self-reflection")
739
- reflection = self._reflect_on_recommendation(synthesis_response.content, student_profile)
740
-
741
- # 6. Final response with reflection incorporated
742
- log_workflow("Creating final response with reflection incorporated")
743
- final_prompt = f"""
744
- Revise this financial advice based on legal reflection:
745
-
746
- ORIGINAL ADVICE:
747
- {synthesis_response.content}
748
-
749
- LEGAL REFLECTION:
750
- {reflection}
751
-
752
- Create a final version that:
753
- 1. Incorporates all legal considerations
754
- 2. Maintains the comprehensive nature of the original advice
755
- 3. Addresses EVERY aspect of the original query specifically and in detail:
756
- - Bank account setup (specific banks, fees, documents)
757
- - Credit building (specific cards, exact steps)
758
- - Money transfers (exact services, fees, processes)
759
- - Stipend management (precise budget breakdown)
760
- - Tax implications (specific treaty benefits, forms)
761
- - CPT/internship planning (exact timeline, requirements)
762
- 4. Includes all three financial approaches (conservative, balanced, growth)
763
-
764
- Format with clear headings, bullet points, and numbered steps.
765
- """
766
-
767
- log_workflow("Generating final response")
768
- final_response = self.llm.invoke(final_prompt)
769
- log_workflow("Response generation complete")
770
-
771
- # Return both the response and the workflow log
772
- return final_response.content
773
- except Exception as e:
774
- log_workflow("Error in coordinator synthesis", str(e))
775
-
776
- # Fallback response if synthesis fails
777
- fallback = "## Financial Advice Summary\n\n"
778
- for domain, advice in specialist_advice.items():
779
- domain_name = domain.replace("_", " ").title()
780
- fallback += f"### {domain_name} Advice\n{advice[:500]}...\n\n"
781
-
782
- fallback += "\n## Multiple Financial Approaches\n\n"
783
- for approach, plan in plans.items():
784
- approach_name = approach.replace("_", " ").title()
785
- fallback += f"### {approach_name} Approach\n{plan[:500]}...\n\n"
786
-
787
- return fallback
788
-
789
 
790
  # =======================================
791
  # Main Portal Interface
792
  # =======================================
793
 
794
  class FinancePortal:
795
- """Main interface for the International Student Finance Portal"""
796
-
797
  def __init__(self):
798
- """Initialize the finance portal with a coordinator agent"""
799
  self.coordinator = CoordinatorAgent()
800
- self.student_profiles = {}
801
-
802
- def register_student(self, student_id: str, profile: Dict[str, Any]):
803
- """Register a new student profile"""
804
- self.student_profiles[student_id] = profile
805
 
806
- def get_student_profile(self, student_id: str) -> Optional[Dict[str, Any]]:
807
- """Get a student's profile"""
808
- return self.student_profiles.get(student_id)
809
 
810
- def handle_query(self, student_id: str, query: str) -> str:
811
- """Process a student query"""
812
- profile = self.get_student_profile(student_id)
813
-
814
- if not profile:
815
- return "Please provide your profile information first."
816
-
817
- if not query or query.strip() == "":
818
- return "Please enter a specific financial question."
819
-
820
- log_workflow(f"Processing query for student {student_id}", {"query": query[:50]})
821
-
822
- # Clear workflow log for new query
823
- clear_workflow_log()
824
-
825
- try:
826
- # Process the query with the coordinator
827
- response = self.coordinator.run(query, profile)
828
-
829
- # Get the workflow log
830
- workflow_log = get_workflow_log()
831
-
832
- # Combine the response and workflow log
833
- full_response = f"{response}\n\n---\n\n{workflow_log}"
834
-
835
- return full_response
836
- except Exception as e:
837
- log_workflow(f"Error handling query", str(e))
838
-
839
- # Return the error with the workflow log
840
- workflow_log = get_workflow_log()
841
- return f"I encountered an error while processing your request: {str(e)}\n\n---\n\n{workflow_log}"
842
 
843
 
844
  def create_interface():
845
- """Create the Gradio interface for the finance portal"""
846
  portal = FinancePortal()
847
-
848
- def handle_query(query, country, visa_type, university, funding, additional_info):
849
- """Handler for query submission"""
850
- if not query or query.strip() == "":
851
- return "Please enter a financial question."
852
-
853
- if not country:
854
- return "Please select your home country."
855
-
856
- if not visa_type:
857
- return "Please select your visa type."
858
-
859
- # Create a composite student profile
860
- student_id = "current_user"
861
- profile = {
862
- "home_country": country,
863
- "visa_type": visa_type,
864
- "university": university,
865
- "funding": funding,
866
- "additional_info": additional_info
867
- }
868
-
869
- portal.register_student(student_id, profile)
870
- return portal.handle_query(student_id, query)
871
-
872
- # Create Gradio interface
873
- with gr.Blocks(title="International Student Finance Portal") as demo:
874
- gr.Markdown("# International Student Finance Portal")
875
- gr.Markdown("Get personalized financial advice tailored for international graduate students with visible workflow.")
876
-
877
- with gr.Row():
878
- with gr.Column(scale=2):
879
- country = gr.Dropdown(
880
- label="Home Country",
881
- choices=["", "India", "China", "Brazil", "Other"],
882
- value=""
883
- )
884
- visa_type = gr.Dropdown(
885
- label="Visa Type",
886
- choices=["", "F-1", "J-1", "M-1", "Other"],
887
- value=""
888
- )
889
- university = gr.Textbox(
890
- label="University",
891
- placeholder="e.g., Stanford University"
892
- )
893
- funding = gr.Dropdown(
894
- label="Primary Funding Source",
895
- choices=["", "Self/Family", "Scholarship", "TA/RA Position", "Education Loan", "Other"],
896
- value=""
897
- )
898
- additional_info = gr.Textbox(
899
- label="Additional Information (Optional)",
900
- placeholder="Program, expected duration, family situation, etc."
901
- )
902
-
903
- # Predefined query templates
904
- query_templates = gr.Dropdown(
905
- label="Common Questions (Select or type your own below)",
906
- choices=[
907
- "",
908
- "How do I open a bank account as an international student?",
909
- "What's the best way to build credit in the US?",
910
- "How should I manage my TA/RA stipend?",
911
- "What are my options for sending/receiving money from home?",
912
- "How do CPT/OPT affect my financial situation?",
913
- "What student loan options are available to me?",
914
- "How should I budget for living expenses in the US?",
915
- "I just arrived in the US from India on an F-1 visa to start my PhD program at MIT with a teaching assistantship. I need advice on opening a bank account with minimal fees, building credit from scratch since I have no US history, sending money between India and the US at the best rates, managing my $2,500 monthly TA stipend while saving for emergencies, and understanding tax implications under the US-India tax treaty. Also, how should I financially prepare for a potential CPT internship next summer?"
916
- ],
917
- value=""
918
- )
919
-
920
- query = gr.Textbox(
921
- label="Your Financial Question",
922
- placeholder="Type your financial question here...",
923
- lines=4
924
- )
925
-
926
- # Update query box when template is selected
927
- query_templates.change(
928
- fn=lambda x: x if x else "",
929
- inputs=query_templates,
930
- outputs=query
931
- )
932
-
933
- submit_btn = gr.Button("Get Financial Advice", variant="primary")
934
- clear_btn = gr.Button("Reset")
935
-
936
- with gr.Column(scale=3):
937
- # Use a textbox with markdown enabled
938
- with gr.Group():
939
- gr.Markdown("### Your Personalized Financial Advice")
940
- response = gr.Markdown()
941
-
942
- # Add a loading message while waiting for response
943
- submit_btn.click(
944
- fn=lambda: "## Processing Your Query\n\nConsulting specialist advisors and generating multiple financial approaches...\n\nPlease wait a moment as this may take up to a minute.",
945
- inputs=None,
946
- outputs=response,
947
- queue=False
948
- )
949
-
950
- # Handle main query submission
951
- submit_btn.click(
952
- fn=handle_query,
953
- inputs=[query, country, visa_type, university, funding, additional_info],
954
- outputs=response,
955
- queue=True
956
- )
957
-
958
- # Handle reset button
959
- clear_btn.click(
960
- fn=lambda: (
961
- "",
962
- "",
963
- "",
964
- "",
965
- "",
966
- "",
967
- ""
968
- ),
969
- inputs=None,
970
- outputs=[query, country, visa_type, university, funding, additional_info, response]
971
- )
972
-
973
- # Feature explanation section
974
- with gr.Accordion("How This System Works", open=False):
975
- gr.Markdown("""
976
- ### Financial Advisory Features
977
-
978
- This portal uses advanced AI with multiple agent design patterns to provide personalized financial guidance:
979
-
980
- 1. **Retrieval Augmented Generation (RAG)**: Uses vector embeddings to retrieve country-specific financial knowledge
981
-
982
- 2. **Role-based Cooperation**: Specialized agents collaborate based on their domain expertise
983
- - Banking Advisor: Account setup, transfers, banking documentation
984
- - Credit Builder: Credit cards, credit history building, credit scores
985
- - Budget Manager: Expense tracking, savings goals, stipend management
986
- - Currency Exchange Specialist: International transfers, exchange rates
987
- - Student Loan Advisor: Loan options, repayment strategies
988
- - Career Finance Planner: CPT/OPT financial planning, internships
989
- - Legal Finance Advisor: Visa compliance, tax treaties, reporting requirements
990
-
991
- 3. **Voting-based Cooperation**: Specialists vote on recommendations when multiple options exist
992
-
993
- 4. **Self-reflection**: Legal/visa compliance check on all financial advice
994
-
995
- 5. **Multi-path Plan Generator**: Different financial strategies based on risk tolerance
996
-
997
- The workflow log at the bottom of each response shows you exactly which components ran and in what order.
998
- """)
999
-
1000
  return demo
1001
 
1002
- # If this is the main script being run
1003
  if __name__ == "__main__":
1004
- print("Starting International Student Finance Portal with Visible Workflow...")
1005
- print("This implementation tests all components and shows the workflow in real-time.")
1006
- interface = create_interface()
1007
- interface.launch()
 
11
  import json
12
  from typing import List, Dict, Any, Optional
13
 
14
+ import gradio as gr
15
+ from langchain.document_loaders import DirectoryLoader, PyPDFLoader
16
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
17
+ from langchain.embeddings import OpenAIEmbeddings
18
+ from langchain.vectorstores import Chroma
19
+ from langchain_openai import ChatOpenAI
20
+
21
+ # =======================================
22
+ # Configuration & Workflow Logging
23
+ # =======================================
24
+
25
+ # Set OpenAI API Key
 
 
 
 
 
26
  api_key = os.environ.get("OPENAI_API_KEY")
27
  if not api_key:
28
  api_key = input("Please enter your OpenAI API key: ")
29
  os.environ["OPENAI_API_KEY"] = api_key
30
 
31
+ WORKFLOW_LOG: List[Dict[str, Any]] = []
 
32
 
33
+ def log_workflow(step: str, details: Any = None):
 
34
  timestamp = time.strftime("%H:%M:%S")
35
  entry = {"time": timestamp, "step": step}
36
  if details:
 
38
  WORKFLOW_LOG.append(entry)
39
  print(f"[{timestamp}] {step}{': ' + str(details) if details else ''}")
40
 
41
+ def get_workflow_log() -> str:
 
42
  if not WORKFLOW_LOG:
43
  return "No workflow steps recorded yet."
44
+ return "\n".join(
45
+ f"**[{e['time']}]** {e['step']}" + (f" — {e['details']}" if 'details' in e else '')
46
+ for e in WORKFLOW_LOG
47
+ )
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  def clear_workflow_log():
50
+ WORKFLOW_LOG.clear()
 
 
51
 
 
 
 
52
 
53
+ # =======================================
54
+ # Retrieval Augmented Generation (RAG) Setup
55
+ # Preload and index domain-specific documents
56
+ # =======================================
57
+
58
+ EMBEDDINGS = OpenAIEmbeddings()
59
+ SPLITTER = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
60
+
61
+ # Initialize vector stores for each domain once
62
+ DOMAIN_VECTOR_STORES: Dict[str, Chroma] = {}
63
+ for domain in ["banking", "credit", "budget", "currency", "loans", "career", "legal"]:
64
+ log_workflow("Indexing domain documents", domain)
65
+ # Load text files
66
+ loader_txt = DirectoryLoader(f"data/{domain}/txt", glob="**/*.txt")
67
+ docs = loader_txt.load()
68
+ # Additionally load PDFs if present
69
+ pdf_path = f"data/{domain}/pdf"
70
+ if os.path.isdir(pdf_path):
71
+ loader_pdf = PyPDFLoader(pdf_path)
72
+ docs += loader_pdf.load()
73
+ chunks = SPLITTER.split_documents(docs)
74
+ store = Chroma.from_documents(chunks, EMBEDDINGS, collection_name=domain)
75
+ DOMAIN_VECTOR_STORES[domain] = store
76
+ log_workflow("Domain indexed", domain)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
 
79
  # =======================================
80
  # Knowledge Base (RAG Implementation)
81
+ # Uses preloaded vector stores
82
  # =======================================
83
 
84
  class KnowledgeBase:
 
 
85
  def __init__(self, domain: str):
 
86
  self.domain = domain
87
+ self.store = DOMAIN_VECTOR_STORES[domain]
88
+ self.retriever = self.store.as_retriever(search_kwargs={"k": 3})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  def retrieve(self, query: str, country: str) -> List[str]:
91
+ log_workflow(f"RAG: retrieving {self.domain}", {"query": query[:50], "country": country})
92
+ docs = self.retriever.get_relevant_documents(query)
93
+ results = [doc.page_content for doc in docs]
94
+ log_workflow(f"RAG: retrieved {len(results)} snippets", None)
95
+ return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
 
98
  # =======================================
99
+ # Domain Specialist Agents (Role-based Cooperation)
100
  # =======================================
101
 
102
  class SpecialistAgent:
 
 
103
  def __init__(self, name: str, domain: str, llm=None):
 
104
  self.name = name
105
  self.domain = domain
106
+ self.kb = KnowledgeBase(domain)
107
+ self.llm = llm or ChatOpenAI(temperature=0.2)
108
 
109
  def run(self, query: str, country: str) -> str:
110
+ log_workflow(f"{self.name}: analyzing query", {"query": query[:50]})
111
+ snippets = self.kb.retrieve(query, country)
112
+ context = "\n".join(f"- {s}" for s in snippets)
 
 
 
 
 
 
 
113
  prompt = f"""
114
+ As a specialist {self.name} for international students from {country}, use the following references:
115
+ {context}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
+ QUESTION:
118
+ {query}
119
 
120
+ Provide detailed, actionable advice:
121
+ 1. Specific product/service names and fees
122
+ 2. Step-by-step instructions
123
+ 3. Documentation requirements
124
+ 4. Exact dollar amounts and timeframes
125
+ """
126
+ log_workflow(f"{self.name}: generating advice")
127
+ resp = self.llm.invoke(prompt)
128
+ log_workflow(f"{self.name}: advice generated", len(resp.content))
129
+ return resp.content
130
 
131
+ # Instantiate each specialist
132
+ BankingAdvisor = lambda llm=None: SpecialistAgent("Banking Advisor", "banking", llm)
133
+ CreditBuilder = lambda llm=None: SpecialistAgent("Credit Builder", "credit", llm)
134
+ BudgetManager = lambda llm=None: SpecialistAgent("Budget Manager", "budget", llm)
135
+ CurrencyExchangeSpecialist = lambda llm=None: SpecialistAgent("Currency Exchange Specialist", "currency", llm)
136
+ StudentLoanAdvisor = lambda llm=None: SpecialistAgent("Student Loan Advisor", "loans", llm)
137
+ CareerFinancePlanner = lambda llm=None: SpecialistAgent("Career Finance Planner", "career", llm)
138
+ LegalFinanceAdvisor = lambda llm=None: SpecialistAgent("Legal Finance Advisor", "legal", llm)
139
 
140
 
141
  # =======================================
142
+ # Coordinator Agent (Central Orchestrator)
143
+ # Implements Voting-based Cooperation, Multi-path Plan Generator, Self-reflection
144
  # =======================================
145
 
146
  class CoordinatorAgent:
 
 
147
  def __init__(self, llm=None):
148
+ self.llm = llm or ChatOpenAI(temperature=0.3)
 
 
 
 
 
 
 
 
 
 
 
 
149
  self.specialists = {
150
+ "banking": BankingAdvisor(self.llm),
151
+ "credit": CreditBuilder(self.llm),
152
+ "budget": BudgetManager(self.llm),
153
+ "currency": CurrencyExchangeSpecialist(self.llm),
154
+ "loans": StudentLoanAdvisor(self.llm),
155
+ "career": CareerFinancePlanner(self.llm),
156
+ "legal": LegalFinanceAdvisor(self.llm)
157
  }
158
 
159
+ def _identify_specialists(self, query: str) -> List[str]:
160
+ # heuristic-based domain selection
161
+ domains = []
162
+ q = query.lower()
163
+ if any(k in q for k in ["account", "bank"]): domains.append("banking")
164
+ if "credit" in q: domains.append("credit")
165
+ if any(k in q for k in ["budget", "stipend"]): domains.append("budget")
166
+ if any(k in q for k in ["transfer", "exchange"]): domains.append("currency")
167
+ if "loan" in q: domains.append("loans")
168
+ if any(k in q for k in ["cpt", "opt", "internship"]): domains.append("career")
169
+ if any(k in q for k in ["tax", "visa"]): domains.append("legal")
170
+ return list(dict.fromkeys(domains)) or ["banking", "budget"]
171
+
172
+ def _conduct_vote(self, question: str, options: List[str], country: str) -> str:
173
+ log_workflow("Voting cooperation: voting on options", {"options": options})
174
+ votes = {opt:0 for opt in options}
175
+ for domain in self._identify_specialists(question):
176
+ prompt = f"Which option for question '{question}'? {json.dumps(options)}"
177
+ resp = self.llm.invoke(prompt).content.strip()
178
+ for i,opt in enumerate(options):
179
+ if str(i+1) in resp:
180
+ votes[opt]+=1
181
+ winner = max(votes, key=votes.get)
182
+ log_workflow("Voting result", {"winner": winner, "votes": votes})
183
+ return winner
184
+
185
+ def _generate_plans(self, goal: str, constraints: str, country: str) -> Dict[str,str]:
186
+ log_workflow("Generating multi-path plans", None)
187
+ approaches = {
188
+ "conservative": BudgetManager(self.llm),
189
+ "balanced": BankingAdvisor(self.llm),
190
+ "growth": CreditBuilder(self.llm)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  plans = {}
193
+ for name, agent in approaches.items():
194
+ prompt = f"Create {name} plan for goal: {goal} with {constraints}"
195
+ plans[name] = agent.run(prompt, country)
196
+ return plans
197
 
198
+ def _self_reflect(self, advice: str, profile: Dict[str,Any]) -> str:
199
+ log_workflow("Self-reflection: checking legal compliance", None)
200
+ prompt = f"Review and refine advice for profile {profile}: {advice}"
201
+ return LegalFinanceAdvisor(self.llm).run(prompt, profile.get("home_country",""))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ def run(self, query: str, profile: Dict[str, Any]) -> str:
204
+ clear_workflow_log()
205
+ log_workflow("Coordinator: processing", {"query": query})
206
+ country = profile.get("home_country","unknown")
207
+ # 1. Role-based advice
208
+ advices = {}
209
+ for d in self._identify_specialists(query):
210
+ advices[d] = self.specialists[d].run(query, country)
211
+ # 2. Multi-path plans
212
+ constraints = json.dumps(profile)
213
  plans = self._generate_plans(query, constraints, country)
214
+ # 3. Synthesize
215
+ synthesis = "".join([f"---{d.upper()}---\n{txt}\n" for d,txt in advices.items()])
216
+ synthesis += "---PLANS---\n" + json.dumps(plans)
217
+ log_workflow("Synthesis done", None)
218
+ # 4. Self-reflection
219
+ final = self._self_reflect(synthesis, profile)
220
+ # 5. Return with workflow
221
+ return final + "\n\n---\n" + get_workflow_log()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
  # =======================================
224
  # Main Portal Interface
225
  # =======================================
226
 
227
  class FinancePortal:
 
 
228
  def __init__(self):
 
229
  self.coordinator = CoordinatorAgent()
230
+ self.profiles: Dict[str,Dict[str,Any]] = {}
 
 
 
 
231
 
232
+ def register(self, uid: str, profile: Dict[str,Any]):
233
+ self.profiles[uid] = profile
 
234
 
235
+ def handle_query(self, uid: str, query: str) -> str:
236
+ profile = self.profiles.get(uid)
237
+ if not profile or not query:
238
+ return "Provide profile and question."
239
+ return self.coordinator.run(query, profile)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
 
242
  def create_interface():
 
243
  portal = FinancePortal()
244
+ def _handler(q, country, visa, uni, fund, info):
245
+ uid = "user"
246
+ portal.register(uid,{"home_country":country,"visa_type":visa,"university":uni,"funding":fund,"additional_info":info})
247
+ return portal.handle_query(uid,q)
248
+ with gr.Blocks() as demo:
249
+ # Inputs
250
+ country = gr.Dropdown(["","India","China","Brazil","Other"], label="Home Country")
251
+ visa = gr.Dropdown(["","F-1","J-1","M-1","Other"], label="Visa Type")
252
+ uni = gr.Textbox(label="University")
253
+ fund = gr.Dropdown(["","Self","Scholarship","TA/RA","Loan","Other"], label="Funding")
254
+ info = gr.Textbox(label="Additional Info")
255
+ query = gr.Textbox(label="Question", lines=3)
256
+ btn = gr.Button("Get Advice")
257
+ out = gr.Markdown()
258
+ btn.click(_handler, inputs=[query,country,visa,uni,fund,info], outputs=out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  return demo
260
 
 
261
  if __name__ == "__main__":
262
+ create_interface().launch()