Spaces:
Sleeping
Sleeping
import gradio as gr | |
import json | |
from datetime import datetime | |
from typing import List, Dict, Any | |
import random | |
import os | |
import yaml | |
from dotenv import load_dotenv | |
from pathlib import Path | |
from src.modules.fed_tools import search_meetings, get_rate_decision, compare_meetings, get_latest_meeting | |
from src.modules.llm_completions import get_llm, stream_fed_agent_response | |
from gradio import ChatMessage | |
import time | |
load_dotenv() | |
_FILE_PATH = Path(__file__).parents[1] | |
# Load processed FOMC meetings data | |
def load_processed_meetings(): | |
"""Load processed FOMC meetings from JSON file""" | |
try: | |
processed_file = _FILE_PATH / "data" / "fed_processed_meetings.json" | |
with open(processed_file, 'r', encoding='utf-8') as f: | |
data = json.load(f) | |
# Transform to match expected format for the frontend | |
meetings = [] | |
for meeting in data: | |
meetings.append({ | |
"date": meeting.get("date", ""), | |
"title": meeting.get("title", ""), | |
"rate_decision": meeting.get("rate", ""), | |
"summary": meeting.get("forward_guidance", ""), # Show full text | |
"action": meeting.get("action", ""), | |
"magnitude": meeting.get("magnitude", ""), | |
"key_economic_factors": meeting.get("key_economic_factors", []), | |
"economic_outlook": meeting.get("economic_outlook", ""), | |
"market_impact": meeting.get("market_impact", ""), | |
"full_text": meeting.get("full_text", "")[:500] + "..." if meeting.get("full_text") else "", | |
"url": meeting.get("url", "") | |
}) | |
return meetings | |
except FileNotFoundError: | |
print("Fed processed meetings file not found. Using fallback data.") | |
return [ | |
{ | |
"date": "2025-06-18", | |
"title": "FOMC Meeting 2025-06-18", | |
"rate_decision": "4.25%-4.50%", | |
"summary": "No processed data available. Please run the data pipeline first.", | |
"action": "Unknown", | |
"magnitude": "Unknown", | |
"key_economic_factors": [], | |
"economic_outlook": "Data not available", | |
"market_impact": "Data not available", | |
"full_text": "No data available", | |
"url": "" | |
} | |
] | |
except Exception as e: | |
print(f"Error loading processed meetings: {e}") | |
return [] | |
# Load the processed meetings | |
FOMC_MEETINGS = load_processed_meetings() | |
def load_prompt_library(): | |
"""Load prompts from the YAML library""" | |
try: | |
prompt_file = _FILE_PATH / "configs" / "prompt_library.yaml" | |
with open(prompt_file, 'r', encoding='utf-8') as f: | |
return yaml.safe_load(f) | |
except Exception as e: | |
print(f"Error loading prompt library: {e}") | |
return {} | |
# Load prompt library | |
PROMPT_LIBRARY = load_prompt_library() | |
def get_fed_context_for_query(user_message: str) -> str: | |
"""Get relevant Fed data context for the user's query""" | |
message_lower = user_message.lower() | |
# Get relevant meeting data based on query type | |
if 'latest' in message_lower or 'most recent' in message_lower: | |
result = get_latest_meeting() | |
if result["success"]: | |
meeting = result["meeting"] | |
return f"Latest FOMC Meeting ({meeting.get('date', 'unknown')}): {meeting.get('forward_guidance', '')[:300]}..." | |
elif any(word in message_lower for word in ['search', 'find', 'about']): | |
search_query = user_message.replace('search for', '').replace('find', '').replace('about', '').strip() | |
result = search_meetings(search_query, limit=2) | |
if result["success"] and result["count"] > 0: | |
context = f"Relevant FOMC meetings for '{search_query}':\n" | |
for meeting in result["results"][:2]: | |
context += f"- {meeting.get('date', 'unknown')}: {meeting.get('forward_guidance', '')[:200]}...\n" | |
return context | |
# Default: return latest meeting info | |
result = get_latest_meeting() | |
if result["success"]: | |
meeting = result["meeting"] | |
return f"Current Fed Policy Context: Rate at {meeting.get('rate', 'unknown')}, {meeting.get('action', 'maintained')} in latest meeting ({meeting.get('date', 'unknown')})" | |
return "Fed data context not available. Please ensure the data pipeline has been run." | |
def process_fed_query(user_message: str, selected_model: str = "") -> Dict[str, Any]: | |
"""Process user queries using Fed AI tools""" | |
message_lower = user_message.lower() | |
# Determine which function to call and execute it | |
if 'latest' in message_lower or 'most recent' in message_lower or 'last meeting' in message_lower: | |
# Get latest meeting | |
result = get_latest_meeting() | |
if result["success"]: | |
meeting = result["meeting"] | |
return { | |
"function": "get_latest_meeting", | |
"reasoning": [ | |
"User asked for the latest/most recent meeting", | |
"Retrieved the most recent FOMC meeting from database", | |
f"Found meeting from {meeting.get('date', 'unknown date')}" | |
], | |
"result": f"The most recent FOMC meeting was on {meeting.get('date', 'unknown date')}. The Fed {meeting.get('action', 'took action')} with rates at {meeting.get('rate', 'unknown rate')}. {meeting.get('forward_guidance', '')[:200]}...", | |
"confidence": 0.95, | |
"sources": [f"FOMC Minutes {meeting.get('date', 'unknown date')}"], | |
"raw_data": result | |
} | |
else: | |
return { | |
"function": "get_latest_meeting", | |
"reasoning": ["Attempted to retrieve latest meeting", "No meeting data available"], | |
"result": "Sorry, I couldn't retrieve the latest FOMC meeting data.", | |
"confidence": 0.0, | |
"sources": [], | |
"raw_data": result | |
} | |
elif 'compare' in message_lower and ('vs' in message_lower or 'versus' in message_lower or 'with' in message_lower): | |
# For now, compare with a default example since we'd need date extraction logic | |
result = compare_meetings("2025-06-18", "2025-06-18") # This will need proper date extraction | |
return { | |
"function": "compare_meetings", | |
"reasoning": [ | |
"User wants to compare different FOMC meetings", | |
"Extracting dates from user message", | |
"Performing side-by-side comparison" | |
], | |
"result": "To compare meetings, please specify the exact dates (YYYY-MM-DD format). For example: 'Compare 2025-06-18 vs 2025-03-20'", | |
"confidence": 0.6, | |
"sources": [], | |
"raw_data": result | |
} | |
elif any(word in message_lower for word in ['search', 'find', 'about']) or '?' in user_message: | |
# Search for relevant information | |
search_query = user_message.replace('search for', '').replace('find', '').replace('about', '').strip() | |
result =search_meetings(search_query, limit=2) | |
if result["success"] and result["count"] > 0: | |
meetings_found = result["results"] | |
summary = f"Found {result['count']} relevant meetings for '{search_query}'. " | |
for i, meeting in enumerate(meetings_found[:2], 1): | |
summary += f"Meeting {i} ({meeting.get('date', 'unknown date')}): {meeting.get('forward_guidance', '')[:100]}... " | |
return { | |
"function": "search_meetings", | |
"reasoning": [ | |
f"User searched for information about '{search_query}'", | |
f"Searched across all FOMC meeting fields", | |
f"Found {result['count']} relevant meetings" | |
], | |
"result": summary, | |
"confidence": 0.85, | |
"sources": [f"FOMC Minutes {m.get('date', 'unknown date')}" for m in meetings_found], | |
"raw_data": result | |
} | |
else: | |
return { | |
"function": "search_meetings", | |
"reasoning": [ | |
f"Searched for '{search_query}'", | |
"No relevant meetings found" | |
], | |
"result": f"I couldn't find specific information about '{search_query}' in the available FOMC meetings.", | |
"confidence": 0.3, | |
"sources": [], | |
"raw_data": result | |
} | |
else: | |
# Default: get latest meeting info | |
result = get_latest_meeting() | |
if result["success"]: | |
meeting = result["meeting"] | |
return { | |
"function": "general_analysis", | |
"reasoning": [ | |
"Providing general Fed policy information", | |
"Drawing from most recent FOMC meeting", | |
"Contextualizing current monetary policy stance" | |
], | |
"result": f"Based on the most recent FOMC meeting ({meeting.get('date', 'unknown date')}), the Fed {meeting.get('action', 'maintained')} rates at {meeting.get('rate', 'current levels')}. Key factors include: {', '.join(meeting.get('key_economic_factors', ['economic conditions'])[:3])}.", | |
"confidence": 0.78, | |
"sources": [f"FOMC Minutes {meeting.get('date', 'unknown date')}"], | |
"raw_data": result | |
} | |
else: | |
return { | |
"function": "general_analysis", | |
"reasoning": ["No meeting data available"], | |
"result": "I don't have access to current FOMC meeting data. Please ensure the data pipeline has been run.", | |
"confidence": 0.0, | |
"sources": [], | |
"raw_data": result | |
} | |
def format_response_with_reasoning(function_result: Dict[str, Any], model_name: str) -> str: | |
"""Format the response with expandable reasoning sections""" | |
reasoning_steps = "\n".join([f"β’ {step}" for step in function_result["reasoning"]]) | |
response = f""" | |
**π Function Called:** `{function_result["function"]}` | |
**π€ Model Used:** {model_name} | |
**π Confidence:** {function_result["confidence"]:.0%} | |
**π‘ Analysis Result:** | |
{function_result["result"]} | |
<details> | |
<summary><b>π§ Reasoning Chain (Click to expand)</b></summary> | |
{reasoning_steps} | |
**π Sources:** | |
{chr(10).join([f"β’ {source}" for source in function_result["sources"]])} | |
</details> | |
""" | |
return response | |
def respond_for_chat_interface(message: str, history): | |
"""Enhanced response function for gr.ChatInterface with Fed AI Savant capabilities""" | |
# Get API key from environment or return error | |
api_key = os.getenv("FIREWORKS_API_KEY", "") | |
# Create Fed tools dictionary | |
fed_tools = { | |
"search_meetings": search_meetings, | |
"get_latest_meeting": get_latest_meeting, | |
"get_rate_decision": get_rate_decision, | |
"compare_meetings": compare_meetings | |
} | |
# Use the new orchestrator function | |
for messages in stream_fed_agent_response(message, api_key, PROMPT_LIBRARY, fed_tools): | |
yield messages | |
def get_fomc_meetings_sidebar(): | |
"""Generate sidebar content with FOMC meeting details""" | |
sidebar_content = "## π Recent FOMC Meetings\n\n" | |
for meeting in FOMC_MEETINGS: | |
sidebar_content += f""" | |
**{meeting['date']}** | |
*{meeting['title'][:50]}...* | |
- **Rate:** {meeting['rate_decision']} | |
- **Summary:** {meeting['summary'][:100]}... | |
--- | |
""" | |
return sidebar_content | |
def process_audio_input(audio_file): | |
"""Process audio input and convert to text""" | |
if audio_file is None: | |
return "No audio recorded. Please try again." | |
# Simulate speech-to-text conversion | |
# In a real implementation, you'd use libraries like openai-whisper, speech_recognition, etc. | |
simulated_transcripts = [ | |
"What was the federal funds rate decision in the last meeting?", | |
"Compare the June and July FOMC meetings", | |
"Tell me about inflation expectations", | |
"What factors influenced recent policy decisions?", | |
"Has the Fed's employment stance changed?" | |
] | |
import random | |
return random.choice(simulated_transcripts) | |
def text_to_speech(text): | |
"""Convert text response to speech""" | |
# Simulate text-to-speech functionality | |
# In a real implementation, you'd use libraries like pyttsx3, gTTS, or cloud TTS services | |
# Clean the text for better TTS (remove markdown formatting) | |
import re | |
clean_text = re.sub(r'\*\*.*?\*\*', '', text) # Remove bold markdown | |
clean_text = re.sub(r'`.*?`', '', clean_text) # Remove code formatting | |
clean_text = re.sub(r'<.*?>', '', clean_text) # Remove HTML tags | |
clean_text = re.sub(r'[#β’]', '', clean_text) # Remove special characters | |
clean_text = ' '.join(clean_text.split()) # Clean whitespace | |
# For demo purposes, return a message about TTS | |
return f"π Text-to-Speech: Would read aloud the response (length: {len(clean_text)} characters)" | |
# Custom CSS for better styling | |
custom_css = """ | |
.gradio-container { | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
.chat-message { | |
border-radius: 10px; | |
padding: 10px; | |
margin: 5px 0; | |
} | |
.function-call { | |
background-color: #f0f8ff; | |
border-left: 4px solid #1e88e5; | |
padding: 10px; | |
margin: 10px 0; | |
border-radius: 5px; | |
} | |
""" | |
# Model options for dropdown | |
MODEL_OPTIONS = [ | |
"Claude 3.5 Sonnet", | |
"GPT-4 Turbo", | |
"Llama 3.1 70B", | |
"Gemini Pro 1.5", | |
"Mixtral 8x7B" | |
] | |
# Function to create searchable FOMC meetings accordion | |
def create_fomc_meetings_accordion(): | |
"""Create searchable accordion for FOMC meetings""" | |
accordions = [] | |
for meeting in FOMC_MEETINGS: | |
title = f"{meeting['date']} - Rate: {meeting['rate_decision']}" | |
content = f""" | |
**Meeting Title:** {meeting['title']} | |
**Rate Decision:** {meeting['rate_decision']} | |
**Summary:** {meeting['summary']} | |
--- | |
*Click to expand for full meeting details* | |
""" | |
accordions.append((title, content)) | |
return accordions | |
# Create the enhanced interface | |
with gr.Blocks(css=custom_css, title="Fed AI Savant", theme=gr.themes.Soft()) as demo: | |
# Row 1: Title and Description | |
with gr.Row(): | |
with gr.Column(): | |
gr.Markdown(""" | |
# ποΈ Fed AI Savant ποΈ | |
**Intelligent Analysis of Federal Reserve Policy and FOMC Meetings** | |
Ask questions about interest rate decisions, monetary policy changes, and economic analysis based on Federal Reserve meeting minutes. | |
""") | |
# Row 2: API Key Configuration | |
with gr.Row(): | |
with gr.Column(scale=1): | |
gr.Markdown("### Powered by") | |
gr.Image( | |
value=str(_FILE_PATH / "assets" / "fireworks_logo.png"), | |
height=60, | |
width=200, | |
show_label=False, | |
show_download_button=False, | |
container=False, | |
show_fullscreen_button=False, | |
show_share_button=False, | |
) | |
with gr.Column(scale=1): | |
gr.Markdown("### π Configuration") | |
api_key = gr.Textbox( | |
label="AI API Key", | |
type="password", | |
placeholder="Please enter your FireworksAI API key", | |
value=os.getenv("FIREWORKS_API_KEY", ""), | |
) | |
with gr.Column(scale=2): | |
gr.Markdown("### π How to Use") | |
gr.Markdown(""" | |
1. **Enter your AI API key** (OpenAI, Anthropic, etc.) | |
2. **Ask questions** about Fed policy, rate decisions, or FOMC meetings | |
3. **Review AI reasoning** with expandable explanations and sources | |
4. **Use voice input** by clicking the microphone button | |
""") | |
# Row 3: FOMC Meetings Accordion (Searchable by Date) | |
with gr.Row(): | |
with gr.Column(): | |
gr.Markdown("### π Recent FOMC Meeting Minutes") | |
# Date search | |
date_search = gr.Textbox( | |
placeholder="Search by date (e.g., 2024-07, July 2024)...", | |
label="π Search Meetings by Date", | |
lines=1 | |
) | |
with gr.Accordion("FOMC Meetings", open=False): | |
# Dynamic HTML generation for meetings | |
def generate_meetings_html(meetings_list): | |
"""Generate HTML for meetings list""" | |
if not meetings_list: | |
return '<p style="color: #6b7280; text-align: center; padding: 20px;">No meetings available</p>' | |
html_content = '<div style="space-y: 8px;">' | |
for meeting in meetings_list: | |
# Format key economic factors for display (show all factors) | |
factors_html = "" | |
if meeting.get('key_economic_factors') and len(meeting['key_economic_factors']) > 0: | |
factors_html = "<p><strong>Key Factors:</strong></p><ul>" | |
for factor in meeting['key_economic_factors']: # Show all factors | |
factors_html += f"<li>{factor}</li>" | |
factors_html += "</ul>" | |
html_content += f""" | |
<details style="border: 1px solid #e5e7eb; border-radius: 6px; padding: 12px; margin-bottom: 8px;"> | |
<summary style="font-weight: 600; cursor: pointer; color: #1f2937;"> | |
π {meeting['date']} - Rate: {meeting['rate_decision']} | |
</summary> | |
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #e5e7eb;"> | |
<p><strong>Meeting:</strong> {meeting['title']}</p> | |
<p><strong>Action:</strong> {meeting.get('action', 'N/A')}</p> | |
<p><strong>Rate:</strong> {meeting['rate_decision']}</p> | |
<p><strong>Magnitude:</strong> {meeting.get('magnitude', 'N/A')}</p> | |
<p><strong>Forward Guidance:</strong> {meeting['summary']}</p> | |
{factors_html} | |
<p><strong>Economic Outlook:</strong> {meeting.get('economic_outlook', 'N/A')}</p> | |
<p><strong>Market Impact:</strong> {meeting.get('market_impact', 'N/A')}</p> | |
{f'<p><strong>Source:</strong> <a href="{meeting["url"]}" target="_blank">Fed Minutes PDF</a></p>' if meeting.get('url') else ''} | |
</div> | |
</details> | |
""" | |
html_content += '</div>' | |
return html_content | |
meetings_accordion = gr.HTML(generate_meetings_html(FOMC_MEETINGS)) | |
# Row 4: Chat Interface using gr.ChatInterface | |
with gr.Row(): | |
with gr.Column(): | |
gr.Markdown("### π¬ Fed AI Assistant") | |
chat_interface = gr.ChatInterface( | |
fn=respond_for_chat_interface, | |
type="messages", | |
chatbot=gr.Chatbot(height=500, show_label=False), | |
textbox=gr.Textbox(placeholder="Ask about Fed policy, rate decisions, or FOMC meetings...", scale=10), | |
examples=[ | |
"What was the rate decision in the last FOMC meeting?", | |
"Compare June 2024 vs July 2024 FOMC meetings", | |
"Tell me about inflation expectations", | |
"Has the Fed's employment stance changed?", | |
"What factors influenced the latest rate decision?", | |
], | |
submit_btn="Send", | |
) | |
# Search functionality for FOMC meetings | |
def search_meetings(search_term): | |
"""Filter FOMC meetings based on search term""" | |
if not search_term.strip(): | |
# Return all meetings if no search term | |
return generate_meetings_html(FOMC_MEETINGS) | |
else: | |
# Filter meetings based on search term | |
filtered_meetings = [] | |
search_lower = search_term.lower() | |
for meeting in FOMC_MEETINGS: | |
# Search in date, title, summary, economic factors, etc. | |
search_fields = [ | |
meeting.get('date', ''), | |
meeting.get('title', ''), | |
meeting.get('summary', ''), | |
meeting.get('rate_decision', ''), | |
meeting.get('action', ''), | |
meeting.get('economic_outlook', ''), | |
meeting.get('market_impact', ''), | |
' '.join(meeting.get('key_economic_factors', [])) | |
] | |
if any(search_lower in field.lower() for field in search_fields): | |
filtered_meetings.append(meeting) | |
if filtered_meetings: | |
return generate_meetings_html(filtered_meetings) | |
else: | |
return f'<p style="color: #6b7280; text-align: center; padding: 20px;">No meetings found matching "{search_term}"</p>' | |
# Wire up search functionality | |
date_search.change( | |
search_meetings, | |
inputs=date_search, | |
outputs=meetings_accordion | |
) | |
# Example buttons are now handled by ChatInterface examples parameter | |
if __name__ == "__main__": | |
demo.launch() | |