Nyha15 commited on
Commit
c704d60
·
1 Parent(s): c424724

Refactored

Browse files
Files changed (1) hide show
  1. app.py +931 -183
app.py CHANGED
@@ -6,254 +6,1002 @@ Self-reflection, and Multi-path Plan Generator
6
  """
7
 
8
  import os
 
9
  import time
10
  import json
11
- from typing import List, Dict, Any
12
-
13
- import gradio as gr
14
- from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader
15
- from langchain.text_splitter import RecursiveCharacterTextSplitter
16
- from langchain_openai import OpenAIEmbeddings, ChatOpenAI
17
- from langchain_community.vectorstores import Chroma
18
-
19
-
20
- # =======================================
21
- # Configuration & Workflow Logging
22
- # =======================================
23
-
24
- api_key = os.getenv("OPENAI_API_KEY")
 
 
 
 
 
 
25
  if not api_key:
26
  api_key = input("Please enter your OpenAI API key: ")
27
  os.environ["OPENAI_API_KEY"] = api_key
28
 
29
- WORKFLOW_LOG: List[Dict[str, Any]] = []
 
30
 
31
- def log_workflow(step: str, details: Any = None):
 
32
  timestamp = time.strftime("%H:%M:%S")
33
  entry = {"time": timestamp, "step": step}
34
- if details is not None:
35
  entry["details"] = details
36
  WORKFLOW_LOG.append(entry)
37
  print(f"[{timestamp}] {step}{': ' + str(details) if details else ''}")
38
 
39
- def get_workflow_log() -> str:
 
40
  if not WORKFLOW_LOG:
41
  return "No workflow steps recorded yet."
42
- return "\n".join(
43
- f"**[{e['time']}]** {e['step']}" + (f" — {e['details']}" if 'details' in e else '')
44
- for e in WORKFLOW_LOG
45
- )
46
-
47
- def clear_workflow_log():
48
- WORKFLOW_LOG.clear()
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # =======================================
52
- # RAG Setup: Preload and Index Documents
53
- # =======================================
54
-
55
- EMBEDDINGS = OpenAIEmbeddings()
56
- SPLITTER = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
57
- DOMAIN_VECTOR_STORES: Dict[str, Chroma] = {}
58
-
59
- for domain in ["banking", "credit", "budget", "currency", "loans", "career", "legal"]:
60
- log_workflow("Indexing domain documents", domain)
61
- docs = []
62
-
63
- # Load .txt files
64
- try:
65
- loader_txt = DirectoryLoader(f"data/{domain}", glob="**/*.txt")
66
- docs.extend(loader_txt.load())
67
- except FileNotFoundError:
68
- log_workflow("No text docs for domain", domain)
69
-
70
- # Load .pdf files
71
- data_path = f"data/{domain}"
72
- if os.path.isdir(data_path):
73
- for file in os.listdir(data_path):
74
- if file.lower().endswith(".pdf"):
75
- try:
76
- loader_pdf = PyPDFLoader(os.path.join(data_path, file))
77
- docs.extend(loader_pdf.load())
78
- except Exception as e:
79
- log_workflow("Error loading PDF", {"file": file, "error": str(e)})
80
-
81
- if not docs:
82
- log_workflow("No documents loaded for domain", domain)
83
-
84
- chunks = SPLITTER.split_documents(docs)
85
- store = Chroma.from_documents(chunks, EMBEDDINGS, collection_name=domain)
86
- DOMAIN_VECTOR_STORES[domain] = store
87
- log_workflow("Domain indexed", domain)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
 
90
  # =======================================
91
- # Knowledge Base (RAG Retrieval)
92
  # =======================================
93
 
94
  class KnowledgeBase:
 
 
95
  def __init__(self, domain: str):
 
96
  self.domain = domain
97
- self.retriever = DOMAIN_VECTOR_STORES[domain].as_retriever(search_kwargs={"k": 3})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  def retrieve(self, query: str, country: str) -> List[str]:
100
- log_workflow(f"Retrieving {self.domain} knowledge", {"query": query[:50], "country": country})
101
- docs = self.retriever.get_relevant_documents(query)
102
- snippets = [doc.page_content for doc in docs]
103
- log_workflow(f"Retrieved {len(snippets)} snippets")
104
- return snippets
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
 
107
  # =======================================
108
- # Specialist Agents (Role-based Cooperation)
109
  # =======================================
110
 
111
  class SpecialistAgent:
 
 
112
  def __init__(self, name: str, domain: str, llm=None):
 
113
  self.name = name
114
- self.kb = KnowledgeBase(domain)
115
- self.llm = llm or ChatOpenAI(temperature=0.2)
 
116
 
117
  def run(self, query: str, country: str) -> str:
118
- log_workflow(f"{self.name}: analyzing", {"query": query[:50]})
119
- snippets = self.kb.retrieve(query, country)
120
- context = "\n".join(f"- {s}" for s in snippets)
 
 
 
 
 
 
 
121
  prompt = f"""
122
- As the {self.name} for {country} students, use these references:
123
- {context}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- QUESTION:
126
- {query}
127
 
128
- Provide:
129
- 1. Specific products and fees
130
- 2. Step-by-step instructions
131
- 3. Documentation requirements
132
- 4. Exact dollar amounts and timeframes
133
- """
134
- log_workflow(f"{self.name}: generating advice")
135
- resp = self.llm.invoke(prompt)
136
- log_workflow(f"{self.name}: advice length", len(resp.content))
137
- return resp.content
138
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- # Instantiate specialists
141
- BankingAdvisor = lambda llm=None: SpecialistAgent("Banking Advisor", "banking", llm)
142
- CreditBuilder = lambda llm=None: SpecialistAgent("Credit Builder", "credit", llm)
143
- BudgetManager = lambda llm=None: SpecialistAgent("Budget Manager", "budget", llm)
144
- CurrencySpecialist = lambda llm=None: SpecialistAgent("Currency Specialist", "currency", llm)
145
- LoanAdvisor = lambda llm=None: SpecialistAgent("Student Loan Advisor", "loans", llm)
146
- CareerPlanner = lambda llm=None: SpecialistAgent("Career Planner", "career", llm)
147
- LegalAdvisor = lambda llm=None: SpecialistAgent("Legal Advisor", "legal", llm)
 
 
 
 
 
 
 
 
 
148
 
149
 
150
  # =======================================
151
- # Coordinator Agent (Voting, Multi-path, Self-reflection)
152
  # =======================================
153
 
154
  class CoordinatorAgent:
 
 
155
  def __init__(self, llm=None):
156
- self.llm = llm or ChatOpenAI(temperature=0.3)
 
 
 
 
 
 
 
 
 
 
 
 
157
  self.specialists = {
158
- "banking": BankingAdvisor(self.llm),
159
- "credit": CreditBuilder(self.llm),
160
- "budget": BudgetManager(self.llm),
161
- "currency": CurrencySpecialist(self.llm),
162
- "loans": LoanAdvisor(self.llm),
163
- "career": CareerPlanner(self.llm),
164
- "legal": LegalAdvisor(self.llm)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
 
167
- def identify_specialists(self, query: str) -> List[str]:
168
- q = query.lower()
169
- domains = []
170
- if any(k in q for k in ["bank", "account"]):
171
- domains.append("banking")
172
- if "credit" in q:
173
- domains.append("credit")
174
- if any(k in q for k in ["budget", "stipend"]):
175
- domains.append("budget")
176
- if any(k in q for k in ["transfer", "exchange"]):
177
- domains.append("currency")
178
- if "loan" in q:
179
- domains.append("loans")
180
- if any(k in q for k in ["cpt", "opt", "internship"]):
181
- domains.append("career")
182
- if any(k in q for k in ["tax", "visa"]):
183
- domains.append("legal")
184
- return list(dict.fromkeys(domains)) or ["banking", "budget"]
185
-
186
- def vote(self, question: str, options: List[str]) -> str:
187
- votes = {opt: 0 for opt in options}
188
- for domain in self.identify_specialists(question):
189
- prompt = f"Which option for '{question}'? Options: {options}"
190
- resp = self.llm.invoke(prompt).content.strip()
191
- for i, opt in enumerate(options, start=1):
192
- if str(i) in resp:
193
- votes[opt] += 1
194
- winner = max(votes, key=votes.get)
195
- log_workflow("Voting result", votes)
196
- return winner
197
-
198
- def generate_plans(self, goal: str, profile: Dict[str, Any]) -> Dict[str, str]:
199
  plans = {}
200
- for approach, agent in [
201
- ("conservative", BudgetManager(self.llm)),
202
- ("balanced", BankingAdvisor(self.llm)),
203
- ("growth", CreditBuilder(self.llm))
204
- ]:
205
- prompt = f"Create {approach} strategy for '{goal}' with constraints: {profile}"
206
- plans[approach] = agent.run(prompt, profile.get("home_country", ""))
207
- return plans
208
-
209
- def self_reflect(self, synthesis: str, profile: Dict[str, Any]) -> str:
210
- prompt = f"Review and improve advice for {profile}: {synthesis}"
211
- return LegalAdvisor(self.llm).run(prompt, profile.get("home_country", ""))
212
-
213
- def run(self, query: str, profile: Dict[str, Any]) -> str:
214
- clear_workflow_log()
215
- log_workflow("Coordinator: start processing", {"query": query[:50]})
216
- specialists = self.identify_specialists(query)
217
- advices = {d: self.specialists[d].run(query, profile.get("home_country", "")) for d in specialists}
218
- plans = self.generate_plans(query, profile)
219
- synthesis = "".join(f"---{d.upper()}---\n{txt}\n" for d, txt in advices.items())
220
- synthesis += f"---PLANS---\n{json.dumps(plans, indent=2)}"
221
- log_workflow("Synthesis complete")
222
- final_advice = self.self_reflect(synthesis, profile)
223
- return final_advice + "\n\n---\n" + get_workflow_log()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
 
226
  # =======================================
227
- # Gradio Interface
228
  # =======================================
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  def create_interface():
231
- portal = CoordinatorAgent()
 
 
 
 
 
 
232
 
233
- def handler(query, country, visa, uni, fund, info):
 
 
 
 
 
 
 
234
  profile = {
235
  "home_country": country,
236
- "visa_type": visa,
237
- "university": uni,
238
- "funding": fund,
239
- "additional_info": info
240
  }
241
- return portal.run(query, profile)
242
 
243
- with gr.Blocks() as demo:
244
- country = gr.Dropdown(["", "India", "China", "Brazil", "Other"], label="Home Country")
245
- visa = gr.Dropdown(["", "F-1", "J-1", "M-1", "Other"], label="Visa Type")
246
- uni = gr.Textbox(label="University")
247
- fund = gr.Dropdown(["", "Self", "Scholarship", "TA/RA", "Loan", "Other"], label="Funding")
248
- info = gr.Textbox(label="Additional Info")
249
- query = gr.Textbox(label="Question", lines=3)
250
- btn = gr.Button("Get Advice")
251
- out = gr.Markdown()
252
-
253
- btn.click(handler, inputs=[query, country, visa, uni, fund, info], outputs=out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  return demo
256
 
257
-
258
  if __name__ == "__main__":
259
- create_interface().launch()
 
 
 
 
6
  """
7
 
8
  import os
9
+ import sys
10
  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:
44
  entry["details"] = details
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()