TurkishCodeMan's picture
Upload folder using huggingface_hub
c71c58f verified
# -*- coding: utf-8 -*-
"""
Fintech Orchestrator Graph — HuggingFace Version
Adapted from orchestrator_v3.py for HuggingFace Spaces deployment.
Uses HF Inference API (Gemma 3) instead of local Qwen.
Uses mocked banking data instead of A2A remote agent.
"""
from __future__ import annotations
from typing import Optional
from pydantic import BaseModel, Field
from hf_model import generate_response
# ---------------------------------------------------------------------------
# Mock Banking Data (replaces A2A agent)
# ---------------------------------------------------------------------------
MOCK_BANKING_DATA = {
"net_worth": {
"total": 87500.00,
"assets": 142000.00,
"liabilities": 54500.00,
"currency": "USD",
},
"assets": {
"checking": 12500.00,
"savings": 35000.00,
"investments": 89500.00,
"other": 5000.00,
},
"liabilities": {
"credit_cards": 4500.00,
"student_loans": 25000.00,
"auto_loan": 15000.00,
"other": 10000.00,
},
"portfolio": {
"AAPL": 15200,
"GOOGL": 12800,
"MSFT": 18500,
"AMZN": 9000,
"bonds": 14000,
"ETFs": 18000,
},
"transactions": [
{"date": "2026-02-08", "description": "Salary Deposit", "amount": 5200.00},
{"date": "2026-02-07", "description": "Grocery Store", "amount": -127.43},
{"date": "2026-02-06", "description": "Electric Bill", "amount": -145.00},
{"date": "2026-02-05", "description": "Restaurant", "amount": -68.50},
{"date": "2026-02-04", "description": "Gas Station", "amount": -52.30},
],
}
def get_banking_data(query: str) -> str:
"""Mock banking query - returns relevant data based on query."""
query_lower = query.lower()
if "net worth" in query_lower or "toplam" in query_lower:
data = MOCK_BANKING_DATA["net_worth"]
return f"""Net Worth Summary:
- Total Net Worth: ${data['total']:,.2f}
- Total Assets: ${data['assets']:,.2f}
- Total Liabilities: ${data['liabilities']:,.2f}"""
elif "portfolio" in query_lower or "stocks" in query_lower:
portfolio = MOCK_BANKING_DATA["portfolio"]
lines = [f"- {k}: ${v:,.2f}" for k, v in portfolio.items()]
total = sum(portfolio.values())
return f"Portfolio (Total: ${total:,.2f}):\n" + "\n".join(lines)
elif "assets" in query_lower or "varlık" in query_lower:
assets = MOCK_BANKING_DATA["assets"]
lines = [f"- {k.title()}: ${v:,.2f}" for k, v in assets.items()]
total = sum(assets.values())
return f"Assets (Total: ${total:,.2f}):\n" + "\n".join(lines)
elif "liabilities" in query_lower or "borç" in query_lower:
liabilities = MOCK_BANKING_DATA["liabilities"]
lines = [f"- {k.replace('_', ' ').title()}: ${v:,.2f}" for k, v in liabilities.items()]
total = sum(liabilities.values())
return f"Liabilities (Total: ${total:,.2f}):\n" + "\n".join(lines)
elif "transaction" in query_lower or "işlem" in query_lower:
transactions = MOCK_BANKING_DATA["transactions"]
lines = [f"- {t['date']}: {t['description']} (${t['amount']:+,.2f})" for t in transactions]
return "Recent Transactions:\n" + "\n".join(lines)
else:
return f"""Account Summary:
- Net Worth: ${MOCK_BANKING_DATA['net_worth']['total']:,.2f}
- Checking: ${MOCK_BANKING_DATA['assets']['checking']:,.2f}
- Savings: ${MOCK_BANKING_DATA['assets']['savings']:,.2f}
- Investments: ${MOCK_BANKING_DATA['assets']['investments']:,.2f}"""
# ---------------------------------------------------------------------------
# Router Decision Schema
# ---------------------------------------------------------------------------
class RouterDecision(BaseModel):
"""Router decision with multi-step planning"""
needs_banking: bool = Field(
default=False,
description="True if real account data is needed"
)
needs_calculation: bool = Field(
default=False,
description="True if financial calculation is needed"
)
needs_graph: bool = Field(
default=False,
description="True if visualization/chart is needed"
)
task_description: str = Field(
default="",
description="Description of what needs to be done"
)
def route_query(query: str) -> RouterDecision:
"""Simple keyword-based router (fast, no LLM call)."""
query_lower = query.lower()
needs_banking = any(word in query_lower for word in [
'balance', 'net worth', 'portfolio', 'assets', 'liabilities',
'account', 'transaction', 'hesap', 'bakiye', 'varlık', 'borç'
])
needs_calculation = any(word in query_lower for word in [
'calculate', 'compute', 'roi', 'interest', 'compound', 'projection',
'hesapla', 'faiz', 'getiri'
])
needs_graph = any(word in query_lower for word in [
'chart', 'graph', 'visualize', 'plot', 'pie', 'bar', 'line',
'grafik', 'görselleştir', 'çiz'
])
return RouterDecision(
needs_banking=needs_banking,
needs_calculation=needs_calculation,
needs_graph=needs_graph,
task_description=query
)
# ---------------------------------------------------------------------------
# Calculator
# ---------------------------------------------------------------------------
def calculate(expression: str, banking_data: str = "") -> str:
"""Perform financial calculation using LLM."""
prompt = f"""You are a financial calculator. Perform the calculation requested.
Request: {expression}
{"Available account data:\n" + banking_data if banking_data else ""}
Provide:
1. The calculation formula used
2. Step-by-step calculation
3. Final result
For compound interest: A = P(1 + r)^t
For ROI: ((Final - Initial) / Initial) * 100
"""
messages = [{"role": "user", "content": prompt}]
return generate_response(messages, max_tokens=600)
# ---------------------------------------------------------------------------
# Orchestrator State
# ---------------------------------------------------------------------------
class OrchestratorState:
"""State container for orchestrator."""
def __init__(self):
self.banking_data: Optional[str] = None
self.calculation_result: Optional[str] = None
self.graph_data: Optional[dict] = None
self.output: str = ""
# ---------------------------------------------------------------------------
# Main Orchestrator Function
# ---------------------------------------------------------------------------
def run_orchestrator(query: str) -> tuple[str, Optional[dict]]:
"""
Main entry point for the orchestrator.
Args:
query: User's query string
Returns:
Tuple of (response_text, chart_data_dict_or_none)
"""
# Route the query
decision = route_query(query)
state = OrchestratorState()
response_parts = []
chart_data = None
# Step 1: Get banking data if needed
if decision.needs_banking:
state.banking_data = get_banking_data(query)
response_parts.append(f"📊 **Account Data:**\n{state.banking_data}")
# Step 2: Perform calculation if needed
if decision.needs_calculation:
state.calculation_result = calculate(query, state.banking_data or "")
response_parts.append(f"\n🧮 **Calculation:**\n{state.calculation_result}")
# Step 3: Prepare chart data if needed
if decision.needs_graph:
query_lower = query.lower()
if 'portfolio' in query_lower or 'pie' in query_lower:
chart_data = {
"type": "pie",
"title": "Portfolio Distribution",
"data": MOCK_BANKING_DATA["portfolio"]
}
elif 'assets' in query_lower:
chart_data = {
"type": "bar",
"title": "Assets Breakdown",
"data": MOCK_BANKING_DATA["assets"]
}
elif 'liabilities' in query_lower:
chart_data = {
"type": "bar",
"title": "Liabilities Breakdown",
"data": MOCK_BANKING_DATA["liabilities"]
}
else:
# Default: net worth projection
initial = MOCK_BANKING_DATA["net_worth"]["total"]
rate = 0.08
chart_data = {
"type": "line",
"title": "Net Worth Projection (8% Annual Growth)",
"data": {f"Year {i}": initial * (1 + rate) ** i for i in range(6)}
}
response_parts.append(f"\n📈 **Chart:** {chart_data['title']}")
# Step 4: If no specific action, use LLM for general response
if not any([decision.needs_banking, decision.needs_calculation, decision.needs_graph]):
context = f"""You are a fintech assistant. Answer the user's question about finance.
Available account data:
- Net Worth: ${MOCK_BANKING_DATA['net_worth']['total']:,.2f}
- Assets: ${MOCK_BANKING_DATA['net_worth']['assets']:,.2f}
- Liabilities: ${MOCK_BANKING_DATA['net_worth']['liabilities']:,.2f}
User question: {query}
Provide a helpful, concise response."""
messages = [{"role": "user", "content": context}]
llm_response = generate_response(messages, max_tokens=800)
response_parts.append(llm_response)
# Combine response
state.output = "\n\n".join(response_parts)
return state.output, chart_data