Abid Ali Awan
commited on
Commit
Β·
563fd53
1
Parent(s):
ea3677f
Refactor financial tools: removed expense tracker functionality, updated budget planner keywords, and improved README with new tags and demo video link.
Browse files- README.md +10 -7
- agents/financial_agent.py +2 -4
- agents/tools.py +15 -169
- app.py +6 -12
README.md
CHANGED
@@ -6,6 +6,11 @@ colorTo: blue
|
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.33.0
|
8 |
app_file: app.py
|
|
|
|
|
|
|
|
|
|
|
9 |
pinned: false
|
10 |
license: apache-2.0
|
11 |
short_description: Financial analysis, investments, budget planning, and more.
|
@@ -16,6 +21,11 @@ short_description: Financial analysis, investments, budget planning, and more.
|
|
16 |
|
17 |
An intelligent financial advisory agent powered by OpenAI's GPT-4.1 and specialized financial tools. This agent provides comprehensive financial analysis, investment recommendations, budget planning, and market insights through an intuitive web interface.
|
18 |
|
|
|
|
|
|
|
|
|
|
|
19 |
## π Features
|
20 |
|
21 |
### Core Financial Tools
|
@@ -92,13 +102,6 @@ python app.py
|
|
92 |
"Detailed portfolio analysis for my current investments"
|
93 |
```
|
94 |
|
95 |
-
#### Expense Tracker
|
96 |
-
```
|
97 |
-
"Track my expenses: [{'category': 'food', 'amount': 150}, {'category': 'gas', 'amount': 80}]"
|
98 |
-
"Analyze my spending patterns from last month"
|
99 |
-
"Help me categorize and analyze my expenses"
|
100 |
-
```
|
101 |
-
|
102 |
### Response Type Control
|
103 |
|
104 |
#### Short Responses (Under 200 words)
|
|
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.33.0
|
8 |
app_file: app.py
|
9 |
+
tags:
|
10 |
+
- mcp-server-track
|
11 |
+
- financial-analysis
|
12 |
+
- openai
|
13 |
+
- tavily
|
14 |
pinned: false
|
15 |
license: apache-2.0
|
16 |
short_description: Financial analysis, investments, budget planning, and more.
|
|
|
21 |
|
22 |
An intelligent financial advisory agent powered by OpenAI's GPT-4.1 and specialized financial tools. This agent provides comprehensive financial analysis, investment recommendations, budget planning, and market insights through an intuitive web interface.
|
23 |
|
24 |
+
<!-- **Watch the demo video:** [Code Analysis MCP Demo (Agents MCP Hackathon)](https://www.youtube.com/watch?v=A4YWMMyJRsA)
|
25 |
+
|
26 |
+
|
27 |
+
[](https://youtu.be/A4YWMMyJRsA) -->
|
28 |
+
|
29 |
## π Features
|
30 |
|
31 |
### Core Financial Tools
|
|
|
102 |
"Detailed portfolio analysis for my current investments"
|
103 |
```
|
104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
### Response Type Control
|
106 |
|
107 |
#### Short Responses (Under 200 words)
|
agents/financial_agent.py
CHANGED
@@ -29,7 +29,6 @@ class FinancialAdvisorAgent:
|
|
29 |
Available tools:
|
30 |
- budget_planner: Use when users ask about budgeting, income allocation, or expense planning. Input should be JSON with 'income' and 'expenses' keys.
|
31 |
- investment_analyzer: Use when users ask about specific stocks or investments. Input should be a stock symbol (e.g., AAPL).
|
32 |
-
- expense_tracker: Use when users want to track or analyze expenses. Input should be JSON with 'expenses' array.
|
33 |
- market_trends: Use when users ask about market trends or financial news. Input should be a search query.
|
34 |
- portfolio_analyzer: Use when users want to analyze their portfolio. Input should be JSON with 'holdings' array.
|
35 |
|
@@ -134,11 +133,10 @@ When a user asks a question:
|
|
134 |
# Check if this is a multi-tool query (contains keywords for multiple tools)
|
135 |
message_lower = message.lower()
|
136 |
tool_keywords = {
|
137 |
-
"budget_planner": ["budget", "income", "expense", "spending", "allocat"],
|
138 |
"investment_analyzer": ["stock", "invest", "buy", "sell", "analyze"],
|
139 |
"portfolio_analyzer": ["portfolio", "holdings", "allocation", "diversif"],
|
140 |
-
"market_trends": ["market", "trend", "news", "sector", "economic"]
|
141 |
-
"expense_tracker": ["track", "expense", "spending", "categoriz"]
|
142 |
}
|
143 |
|
144 |
detected_tools = []
|
|
|
29 |
Available tools:
|
30 |
- budget_planner: Use when users ask about budgeting, income allocation, or expense planning. Input should be JSON with 'income' and 'expenses' keys.
|
31 |
- investment_analyzer: Use when users ask about specific stocks or investments. Input should be a stock symbol (e.g., AAPL).
|
|
|
32 |
- market_trends: Use when users ask about market trends or financial news. Input should be a search query.
|
33 |
- portfolio_analyzer: Use when users want to analyze their portfolio. Input should be JSON with 'holdings' array.
|
34 |
|
|
|
133 |
# Check if this is a multi-tool query (contains keywords for multiple tools)
|
134 |
message_lower = message.lower()
|
135 |
tool_keywords = {
|
136 |
+
"budget_planner": ["budget", "income", "expense", "spending", "allocat", "track", "categoriz"],
|
137 |
"investment_analyzer": ["stock", "invest", "buy", "sell", "analyze"],
|
138 |
"portfolio_analyzer": ["portfolio", "holdings", "allocation", "diversif"],
|
139 |
+
"market_trends": ["market", "trend", "news", "sector", "economic"]
|
|
|
140 |
}
|
141 |
|
142 |
detected_tools = []
|
agents/tools.py
CHANGED
@@ -16,8 +16,21 @@ class FinancialTools:
|
|
16 |
def budget_planner(input_str: str) -> str:
|
17 |
"""Create a personalized budget plan with advanced features"""
|
18 |
try:
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
expenses = data.get("expenses", {})
|
22 |
goals = data.get("savings_goals", {})
|
23 |
debt = data.get("debt", {})
|
@@ -300,172 +313,6 @@ class FinancialTools:
|
|
300 |
func=investment_analyzer,
|
301 |
)
|
302 |
|
303 |
-
def create_expense_tracker(self) -> Tool:
|
304 |
-
def expense_tracker(input_str: str) -> str:
|
305 |
-
"""Track and analyze expenses with advanced insights, trends, and predictions"""
|
306 |
-
try:
|
307 |
-
data = json.loads(input_str)
|
308 |
-
expenses = data.get("expenses", [])
|
309 |
-
historical_data = data.get("historical_expenses", [])
|
310 |
-
budget_limits = data.get("budget_limits", {})
|
311 |
-
|
312 |
-
# Combine current and historical data
|
313 |
-
all_expenses = expenses + historical_data
|
314 |
-
df = pd.DataFrame(all_expenses)
|
315 |
-
|
316 |
-
if df.empty:
|
317 |
-
return "No expense data provided"
|
318 |
-
|
319 |
-
# Ensure date column exists and is properly formatted
|
320 |
-
if "date" in df.columns:
|
321 |
-
df["date"] = pd.to_datetime(df["date"])
|
322 |
-
df = df.sort_values("date")
|
323 |
-
else:
|
324 |
-
# Add current date for expenses without dates
|
325 |
-
df["date"] = datetime.now()
|
326 |
-
|
327 |
-
# Current period analysis
|
328 |
-
current_df = pd.DataFrame(expenses) if expenses else pd.DataFrame()
|
329 |
-
total_current = current_df["amount"].sum() if not current_df.empty else 0
|
330 |
-
|
331 |
-
# Category analysis
|
332 |
-
category_summary = df.groupby("category")["amount"].agg(["sum", "mean", "count", "std"]).to_dict("index")
|
333 |
-
|
334 |
-
# Trend analysis (if historical data available)
|
335 |
-
trends = {}
|
336 |
-
predictions = {}
|
337 |
-
|
338 |
-
if len(df) > 1 and "date" in df.columns:
|
339 |
-
# Monthly spending trends
|
340 |
-
df["month"] = df["date"].dt.to_period("M")
|
341 |
-
monthly_spending = df.groupby("month")["amount"].sum()
|
342 |
-
|
343 |
-
if len(monthly_spending) > 1:
|
344 |
-
# Calculate month-over-month growth
|
345 |
-
mom_growth = monthly_spending.pct_change().iloc[-1] * 100
|
346 |
-
trends["monthly_growth"] = f"{mom_growth:.1f}%"
|
347 |
-
|
348 |
-
# Simple linear trend prediction for next month
|
349 |
-
if len(monthly_spending) >= 3:
|
350 |
-
recent_trend = monthly_spending.tail(3).mean()
|
351 |
-
predictions["next_month_estimate"] = f"${recent_trend:.2f}"
|
352 |
-
|
353 |
-
# Category trends
|
354 |
-
for category in df["category"].unique():
|
355 |
-
cat_data = df[df["category"] == category].groupby("month")["amount"].sum()
|
356 |
-
if len(cat_data) > 1:
|
357 |
-
cat_trend = cat_data.pct_change().iloc[-1] * 100
|
358 |
-
trends[f"{category}_trend"] = f"{cat_trend:.1f}%"
|
359 |
-
|
360 |
-
# Spending pattern analysis
|
361 |
-
if "date" in df.columns:
|
362 |
-
df["day_of_week"] = df["date"].dt.day_name()
|
363 |
-
df["hour"] = df["date"].dt.hour
|
364 |
-
|
365 |
-
spending_patterns = {
|
366 |
-
"busiest_day": df.groupby("day_of_week")["amount"].sum().idxmax(),
|
367 |
-
"peak_spending_hour": df.groupby("hour")["amount"].sum().idxmax(),
|
368 |
-
}
|
369 |
-
else:
|
370 |
-
spending_patterns = {}
|
371 |
-
|
372 |
-
# Budget analysis
|
373 |
-
budget_analysis = {}
|
374 |
-
if budget_limits:
|
375 |
-
for category, limit in budget_limits.items():
|
376 |
-
cat_spending = category_summary.get(category, {}).get("sum", 0)
|
377 |
-
budget_analysis[category] = {
|
378 |
-
"limit": f"${limit:.2f}",
|
379 |
-
"spent": f"${cat_spending:.2f}",
|
380 |
-
"remaining": f"${max(0, limit - cat_spending):.2f}",
|
381 |
-
"percentage_used": f"{(cat_spending / limit * 100):.1f}%" if limit > 0 else "N/A",
|
382 |
-
"status": "Over Budget" if cat_spending > limit else "Within Budget"
|
383 |
-
}
|
384 |
-
|
385 |
-
# Anomaly detection (expenses significantly above average)
|
386 |
-
anomalies = []
|
387 |
-
if not df.empty:
|
388 |
-
for category in df["category"].unique():
|
389 |
-
cat_data = df[df["category"] == category]["amount"]
|
390 |
-
if len(cat_data) > 2:
|
391 |
-
mean_spending = cat_data.mean()
|
392 |
-
std_spending = cat_data.std()
|
393 |
-
threshold = mean_spending + (2 * std_spending)
|
394 |
-
|
395 |
-
recent_anomalies = cat_data[cat_data > threshold]
|
396 |
-
if not recent_anomalies.empty:
|
397 |
-
anomalies.append({
|
398 |
-
"category": category,
|
399 |
-
"unusual_amount": f"${recent_anomalies.iloc[-1]:.2f}",
|
400 |
-
"typical_range": f"${mean_spending:.2f} Β± ${std_spending:.2f}"
|
401 |
-
})
|
402 |
-
|
403 |
-
# Generate insights and recommendations
|
404 |
-
insights = []
|
405 |
-
recommendations = []
|
406 |
-
|
407 |
-
# Top spending categories
|
408 |
-
top_categories = sorted(category_summary.items(), key=lambda x: x[1]["sum"], reverse=True)[:3]
|
409 |
-
category_strings = [f'{cat} (${data["sum"]:.2f})' for cat, data in top_categories]
|
410 |
-
insights.append(f"Top 3 spending categories: {', '.join(category_strings)}")
|
411 |
-
|
412 |
-
# Spending frequency analysis
|
413 |
-
if not df.empty:
|
414 |
-
avg_transaction = df["amount"].mean()
|
415 |
-
insights.append(f"Average transaction: ${avg_transaction:.2f}")
|
416 |
-
|
417 |
-
if avg_transaction > 100:
|
418 |
-
recommendations.append("Consider breaking down large expenses into smaller, more frequent transactions for better budget control")
|
419 |
-
|
420 |
-
# Trend-based recommendations
|
421 |
-
if "monthly_growth" in trends:
|
422 |
-
growth = float(trends["monthly_growth"].rstrip("%"))
|
423 |
-
if growth > 10:
|
424 |
-
recommendations.append(f"Spending increased {growth:.1f}% this month. Review discretionary expenses.")
|
425 |
-
elif growth < -10:
|
426 |
-
recommendations.append(f"Good job! Spending decreased {abs(growth):.1f}% this month.")
|
427 |
-
|
428 |
-
# Budget recommendations
|
429 |
-
for category, analysis in budget_analysis.items():
|
430 |
-
if "Over Budget" in analysis["status"]:
|
431 |
-
recommendations.append(f"Reduce {category} spending - currently over budget")
|
432 |
-
elif float(analysis["percentage_used"].rstrip("%")) > 80:
|
433 |
-
recommendations.append(f"Approaching {category} budget limit ({analysis['percentage_used']})")
|
434 |
-
|
435 |
-
analysis_result = {
|
436 |
-
"current_period": {
|
437 |
-
"total_expenses": f"${total_current:.2f}",
|
438 |
-
"transaction_count": len(current_df) if not current_df.empty else 0,
|
439 |
-
"average_transaction": f"${(total_current / len(current_df)):.2f}" if not current_df.empty else "$0.00",
|
440 |
-
},
|
441 |
-
"category_analysis": {
|
442 |
-
category: {
|
443 |
-
"total": f"${data['sum']:.2f}",
|
444 |
-
"average": f"${data['mean']:.2f}",
|
445 |
-
"transactions": int(data['count']),
|
446 |
-
"variability": f"${data.get('std', 0):.2f}"
|
447 |
-
} for category, data in category_summary.items()
|
448 |
-
},
|
449 |
-
"trends": trends,
|
450 |
-
"predictions": predictions,
|
451 |
-
"spending_patterns": spending_patterns,
|
452 |
-
"budget_analysis": budget_analysis,
|
453 |
-
"anomalies": anomalies,
|
454 |
-
"insights": insights,
|
455 |
-
"recommendations": recommendations,
|
456 |
-
"analysis_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
457 |
-
}
|
458 |
-
|
459 |
-
return json.dumps(analysis_result, indent=2)
|
460 |
-
|
461 |
-
except Exception as e:
|
462 |
-
return f"Error tracking expenses: {str(e)}"
|
463 |
-
|
464 |
-
return Tool(
|
465 |
-
name="expense_tracker",
|
466 |
-
description="Track and analyze expenses with detailed insights",
|
467 |
-
func=expense_tracker,
|
468 |
-
)
|
469 |
|
470 |
def create_market_trends_analyzer(self) -> Tool:
|
471 |
def market_trends(query: str) -> str:
|
@@ -690,7 +537,6 @@ class FinancialTools:
|
|
690 |
return [
|
691 |
self.create_budget_planner(),
|
692 |
self.create_investment_analyzer(),
|
693 |
-
self.create_expense_tracker(),
|
694 |
self.create_market_trends_analyzer(),
|
695 |
self.create_portfolio_analyzer(),
|
696 |
]
|
|
|
16 |
def budget_planner(input_str: str) -> str:
|
17 |
"""Create a personalized budget plan with advanced features"""
|
18 |
try:
|
19 |
+
# Handle empty or invalid input
|
20 |
+
if not input_str or input_str.strip() == "":
|
21 |
+
input_str = '{"income": 5000, "expenses": {}}'
|
22 |
+
|
23 |
+
# Try to parse JSON, if it fails, try to extract values from text
|
24 |
+
try:
|
25 |
+
data = json.loads(input_str)
|
26 |
+
except json.JSONDecodeError:
|
27 |
+
# Fallback: extract income and expenses from text
|
28 |
+
import re
|
29 |
+
income_match = re.search(r'(\$?[\d,]+(?:\.\d{2})?)', input_str)
|
30 |
+
income = float(income_match.group(1).replace('$', '').replace(',', '')) if income_match else 5000
|
31 |
+
data = {"income": income, "expenses": {}}
|
32 |
+
|
33 |
+
income = data.get("income", 5000)
|
34 |
expenses = data.get("expenses", {})
|
35 |
goals = data.get("savings_goals", {})
|
36 |
debt = data.get("debt", {})
|
|
|
313 |
func=investment_analyzer,
|
314 |
)
|
315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
316 |
|
317 |
def create_market_trends_analyzer(self) -> Tool:
|
318 |
def market_trends(query: str) -> str:
|
|
|
537 |
return [
|
538 |
self.create_budget_planner(),
|
539 |
self.create_investment_analyzer(),
|
|
|
540 |
self.create_market_trends_analyzer(),
|
541 |
self.create_portfolio_analyzer(),
|
542 |
]
|
app.py
CHANGED
@@ -414,17 +414,15 @@ def determine_intended_tool(message):
|
|
414 |
message_lower = message.lower()
|
415 |
|
416 |
tool_detection_map = {
|
417 |
-
"budget_planner": ["budget", "income", "expense", "spending", "allocat", "monthly", "plan", "financial plan", "money"],
|
418 |
"investment_analyzer": ["stock", "invest", "buy", "sell", "analyze", "AAPL", "GOOGL", "TSLA", "share", "equity"],
|
419 |
"portfolio_analyzer": ["portfolio", "holdings", "allocation", "diversif", "asset", "position"],
|
420 |
-
"market_trends": ["market", "trend", "news", "sector", "economic", "latest", "current"]
|
421 |
-
"expense_tracker": ["track", "expense", "spending", "categoriz", "cost"]
|
422 |
}
|
423 |
|
424 |
tool_names = {
|
425 |
"budget_planner": "Budget Planner",
|
426 |
-
"investment_analyzer": "Investment Analyzer",
|
427 |
-
"expense_tracker": "Expense Tracker",
|
428 |
"market_trends": "Market Trends Analyzer",
|
429 |
"portfolio_analyzer": "Portfolio Analyzer",
|
430 |
}
|
@@ -512,8 +510,6 @@ def process_financial_query(message, history):
|
|
512 |
status_msg = f"π° Processing budget analysis (estimated 5-10 seconds)..."
|
513 |
elif intended_tool_key == "portfolio_analyzer":
|
514 |
status_msg = f"π Analyzing portfolio data (estimated 8-12 seconds)..."
|
515 |
-
elif intended_tool_key == "expense_tracker":
|
516 |
-
status_msg = f"π³ Processing expense analysis (estimated 5-10 seconds)..."
|
517 |
else:
|
518 |
status_msg = f"π Using {intended_tool_name} (estimated 5-15 seconds)..."
|
519 |
|
@@ -539,16 +535,14 @@ def process_financial_query(message, history):
|
|
539 |
|
540 |
tool_names = {
|
541 |
"budget_planner": "Budget Planner",
|
542 |
-
"investment_analyzer": "Investment Analyzer",
|
543 |
-
"expense_tracker": "Expense Tracker",
|
544 |
"market_trends": "Market Trends Analyzer",
|
545 |
"portfolio_analyzer": "Portfolio Analyzer",
|
546 |
}
|
547 |
|
548 |
tool_emojis = {
|
549 |
"Budget Planner": "π°",
|
550 |
-
"Investment Analyzer": "π",
|
551 |
-
"Expense Tracker": "π³",
|
552 |
"Market Trends Analyzer": "π°",
|
553 |
"Portfolio Analyzer": "π",
|
554 |
}
|
@@ -744,4 +738,4 @@ with gr.Blocks(theme=gr.themes.Base(), title="Financial Advisory Agent") as demo
|
|
744 |
chatbot.like(like_handler)
|
745 |
|
746 |
if __name__ == "__main__":
|
747 |
-
demo.launch(
|
|
|
414 |
message_lower = message.lower()
|
415 |
|
416 |
tool_detection_map = {
|
417 |
+
"budget_planner": ["budget", "income", "expense", "spending", "allocat", "monthly", "plan", "financial plan", "money", "track", "categoriz", "cost"],
|
418 |
"investment_analyzer": ["stock", "invest", "buy", "sell", "analyze", "AAPL", "GOOGL", "TSLA", "share", "equity"],
|
419 |
"portfolio_analyzer": ["portfolio", "holdings", "allocation", "diversif", "asset", "position"],
|
420 |
+
"market_trends": ["market", "trend", "news", "sector", "economic", "latest", "current"]
|
|
|
421 |
}
|
422 |
|
423 |
tool_names = {
|
424 |
"budget_planner": "Budget Planner",
|
425 |
+
"investment_analyzer": "Investment Analyzer",
|
|
|
426 |
"market_trends": "Market Trends Analyzer",
|
427 |
"portfolio_analyzer": "Portfolio Analyzer",
|
428 |
}
|
|
|
510 |
status_msg = f"π° Processing budget analysis (estimated 5-10 seconds)..."
|
511 |
elif intended_tool_key == "portfolio_analyzer":
|
512 |
status_msg = f"π Analyzing portfolio data (estimated 8-12 seconds)..."
|
|
|
|
|
513 |
else:
|
514 |
status_msg = f"π Using {intended_tool_name} (estimated 5-15 seconds)..."
|
515 |
|
|
|
535 |
|
536 |
tool_names = {
|
537 |
"budget_planner": "Budget Planner",
|
538 |
+
"investment_analyzer": "Investment Analyzer",
|
|
|
539 |
"market_trends": "Market Trends Analyzer",
|
540 |
"portfolio_analyzer": "Portfolio Analyzer",
|
541 |
}
|
542 |
|
543 |
tool_emojis = {
|
544 |
"Budget Planner": "π°",
|
545 |
+
"Investment Analyzer": "π",
|
|
|
546 |
"Market Trends Analyzer": "π°",
|
547 |
"Portfolio Analyzer": "π",
|
548 |
}
|
|
|
738 |
chatbot.like(like_handler)
|
739 |
|
740 |
if __name__ == "__main__":
|
741 |
+
demo.launch()
|