| | |
| | """ |
| | 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 = { |
| | "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}""" |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | 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 |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | 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) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | 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 = "" |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | 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) |
| | """ |
| | |
| | decision = route_query(query) |
| | state = OrchestratorState() |
| | response_parts = [] |
| | chart_data = None |
| | |
| | |
| | if decision.needs_banking: |
| | state.banking_data = get_banking_data(query) |
| | response_parts.append(f"📊 **Account Data:**\n{state.banking_data}") |
| | |
| | |
| | if decision.needs_calculation: |
| | state.calculation_result = calculate(query, state.banking_data or "") |
| | response_parts.append(f"\n🧮 **Calculation:**\n{state.calculation_result}") |
| | |
| | |
| | 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: |
| | |
| | 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']}") |
| | |
| | |
| | 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) |
| | |
| | |
| | state.output = "\n\n".join(response_parts) |
| | |
| | return state.output, chart_data |
| |
|