Spaces:
Running
Running
RobertoBarrosoLuque
commited on
Commit
Β·
001487b
1
Parent(s):
5707140
Add chat, orchestrator and tool use
Browse files- configs/prompt_library.yaml +80 -1
- src/app.py +63 -31
- src/modules/fed_tools.py +0 -5
- src/modules/llm_completions.py +231 -0
configs/prompt_library.yaml
CHANGED
@@ -25,4 +25,83 @@ extract_rate_decision: |
|
|
25 |
|
26 |
Meeting Date: {meeting_date}
|
27 |
Title: {meeting_title}
|
28 |
-
Meeting Text: {text}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
Meeting Date: {meeting_date}
|
27 |
Title: {meeting_title}
|
28 |
+
Meeting Text: {text}
|
29 |
+
|
30 |
+
fed_savant_chat: |
|
31 |
+
You are the Federal Reserve AI Savant, an expert economist and policy analyst specializing in Federal Reserve monetary policy, FOMC meetings, and macroeconomic analysis. You have comprehensive knowledge of Fed operations, interest rate decisions, economic indicators, and their market implications.
|
32 |
+
|
33 |
+
CORE IDENTITY & EXPERTISE:
|
34 |
+
- You are authoritative yet accessible, explaining complex Fed policy in clear terms
|
35 |
+
- Your responses are grounded in factual information from official Fed sources and meeting minutes
|
36 |
+
- You provide context, historical perspective, and implications for different stakeholders
|
37 |
+
- You maintain objectivity while offering insights into Fed decision-making processes
|
38 |
+
|
39 |
+
RESPONSE GUIDELINES:
|
40 |
+
1. Base all responses on factual information from Fed sources and meeting minutes
|
41 |
+
2. Provide clear explanations suitable for both experts and general audiences
|
42 |
+
3. Include relevant context about Fed mandate (price stability, maximum employment)
|
43 |
+
4. Explain implications for markets, businesses, consumers, and the broader economy
|
44 |
+
5. Reference specific meeting dates and decisions when relevant
|
45 |
+
6. Acknowledge uncertainty when data is incomplete or when making forward-looking statements
|
46 |
+
|
47 |
+
KNOWLEDGE AREAS:
|
48 |
+
- FOMC meeting minutes and rate decisions
|
49 |
+
- Fed tools: federal funds rate, quantitative easing, forward guidance
|
50 |
+
- Economic indicators: inflation, employment, GDP growth, financial conditions
|
51 |
+
- Market impacts: bond yields, stock markets, dollar strength, lending conditions
|
52 |
+
- Historical context: comparing current policy to past cycles
|
53 |
+
- Fed communication strategy and market interpretation
|
54 |
+
|
55 |
+
RESPONSE STRUCTURE:
|
56 |
+
- Start with a direct answer to the user's question
|
57 |
+
- Provide supporting context from Fed sources
|
58 |
+
- Explain broader implications and connections
|
59 |
+
- End with actionable insights or key takeaways
|
60 |
+
|
61 |
+
TONE: Professional, knowledgeable, and accessible. Avoid jargon without explanation.
|
62 |
+
|
63 |
+
Available Fed Data Context: {fed_data_context}
|
64 |
+
|
65 |
+
User Question: {user_question}
|
66 |
+
The date is {date}
|
67 |
+
|
68 |
+
fed_orchestrator: |
|
69 |
+
You are a Federal Reserve Tool Orchestrator. Your job is to analyze user queries about Fed policy and FOMC meetings, then decide which tools to use to gather the most relevant information.
|
70 |
+
|
71 |
+
AVAILABLE TOOLS:
|
72 |
+
1. search_meetings(query: str, limit: int = 3) - Search across all FOMC meeting fields for relevant information
|
73 |
+
2. get_latest_meeting() - Get the most recent FOMC meeting data
|
74 |
+
3. get_rate_decision(date: str) - Get specific meeting data by date (YYYY-MM-DD format)
|
75 |
+
4. compare_meetings(date1: str, date2: str) - Compare two meetings side by side
|
76 |
+
|
77 |
+
INSTRUCTIONS:
|
78 |
+
- Analyze the user query to determine which tools would provide the most relevant information
|
79 |
+
- You can use multiple tools if needed to fully answer the question
|
80 |
+
- For search queries, extract key terms and use search_meetings
|
81 |
+
- For recent/latest questions, use get_latest_meeting
|
82 |
+
- For specific date questions, use get_rate_decision
|
83 |
+
- For comparison questions, use compare_meetings
|
84 |
+
- Always provide the exact function calls in JSON format
|
85 |
+
|
86 |
+
RESPONSE FORMAT:
|
87 |
+
Return a JSON object with this structure:
|
88 |
+
{{
|
89 |
+
"tools_needed": [
|
90 |
+
{{
|
91 |
+
"function": "function_name",
|
92 |
+
"parameters": {{"param1": "value1", "param2": "value2"}},
|
93 |
+
"reasoning": "Why this tool is needed"
|
94 |
+
}}
|
95 |
+
],
|
96 |
+
"query_analysis": "Brief analysis of what the user is asking for"
|
97 |
+
}}
|
98 |
+
|
99 |
+
EXAMPLES:
|
100 |
+
User: "What was the latest rate decision?"
|
101 |
+
Response: {{"tools_needed": [{{"function": "get_latest_meeting", "parameters": {{}}, "reasoning": "User wants the most recent FOMC meeting information"}}], "query_analysis": "User is asking for the most recent Fed rate decision"}}
|
102 |
+
|
103 |
+
User: "Tell me about inflation expectations in recent meetings"
|
104 |
+
Response: {{"tools_needed": [{{"function": "search_meetings", "parameters": {{"query": "inflation expectations", "limit": 3}}, "reasoning": "Need to search for inflation-related content across meetings"}}], "query_analysis": "User wants information about inflation expectations from FOMC meetings"}}
|
105 |
+
|
106 |
+
User Query: {user_query}
|
107 |
+
The date is {date}
|
src/app.py
CHANGED
@@ -4,9 +4,13 @@ from datetime import datetime
|
|
4 |
from typing import List, Dict, Any
|
5 |
import random
|
6 |
import os
|
|
|
7 |
from dotenv import load_dotenv
|
8 |
from pathlib import Path
|
9 |
from src.modules.fed_tools import search_meetings, get_rate_decision, compare_meetings, get_latest_meeting
|
|
|
|
|
|
|
10 |
|
11 |
load_dotenv()
|
12 |
_FILE_PATH = Path(__file__).parents[1]
|
@@ -60,6 +64,48 @@ def load_processed_meetings():
|
|
60 |
# Load the processed meetings
|
61 |
FOMC_MEETINGS = load_processed_meetings()
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
def process_fed_query(user_message: str, selected_model: str = "") -> Dict[str, Any]:
|
64 |
"""Process user queries using Fed AI tools"""
|
65 |
message_lower = user_message.lower()
|
@@ -196,38 +242,23 @@ def format_response_with_reasoning(function_result: Dict[str, Any], model_name:
|
|
196 |
"""
|
197 |
return response
|
198 |
|
199 |
-
def respond_for_chat_interface(
|
200 |
-
message: str,
|
201 |
-
history: list[tuple[str, str]],
|
202 |
-
api_key: str = "",
|
203 |
-
):
|
204 |
"""Enhanced response function for gr.ChatInterface with Fed AI Savant capabilities"""
|
205 |
|
206 |
-
|
207 |
-
|
208 |
-
return
|
209 |
|
210 |
-
|
211 |
-
|
212 |
-
|
|
|
|
|
|
|
|
|
213 |
|
214 |
-
#
|
215 |
-
|
216 |
-
|
217 |
-
# Process Fed query using real Fed tools
|
218 |
-
function_result = process_fed_query(message, model_name)
|
219 |
-
|
220 |
-
# Format response with reasoning chain
|
221 |
-
formatted_response = format_response_with_reasoning(function_result, model_name)
|
222 |
-
|
223 |
-
# Simulate streaming response
|
224 |
-
response = ""
|
225 |
-
for char in formatted_response:
|
226 |
-
response += char
|
227 |
-
yield response
|
228 |
-
# Small delay to simulate streaming
|
229 |
-
import time
|
230 |
-
time.sleep(0.01)
|
231 |
|
232 |
def get_fomc_meetings_sidebar():
|
233 |
"""Generate sidebar content with FOMC meeting details"""
|
@@ -428,14 +459,15 @@ with gr.Blocks(css=custom_css, title="Fed AI Savant", theme=gr.themes.Soft()) as
|
|
428 |
|
429 |
chat_interface = gr.ChatInterface(
|
430 |
fn=respond_for_chat_interface,
|
431 |
-
|
|
|
432 |
textbox=gr.Textbox(placeholder="Ask about Fed policy, rate decisions, or FOMC meetings...", scale=10),
|
433 |
examples=[
|
434 |
-
"What was the rate decision in the last FOMC meeting?"
|
435 |
"Compare June 2024 vs July 2024 FOMC meetings",
|
436 |
"Tell me about inflation expectations",
|
437 |
"Has the Fed's employment stance changed?",
|
438 |
-
"What
|
439 |
],
|
440 |
submit_btn="Send",
|
441 |
)
|
|
|
4 |
from typing import List, Dict, Any
|
5 |
import random
|
6 |
import os
|
7 |
+
import yaml
|
8 |
from dotenv import load_dotenv
|
9 |
from pathlib import Path
|
10 |
from src.modules.fed_tools import search_meetings, get_rate_decision, compare_meetings, get_latest_meeting
|
11 |
+
from src.modules.llm_completions import get_llm, stream_fed_agent_response
|
12 |
+
from gradio import ChatMessage
|
13 |
+
import time
|
14 |
|
15 |
load_dotenv()
|
16 |
_FILE_PATH = Path(__file__).parents[1]
|
|
|
64 |
# Load the processed meetings
|
65 |
FOMC_MEETINGS = load_processed_meetings()
|
66 |
|
67 |
+
def load_prompt_library():
|
68 |
+
"""Load prompts from the YAML library"""
|
69 |
+
try:
|
70 |
+
prompt_file = _FILE_PATH / "configs" / "prompt_library.yaml"
|
71 |
+
with open(prompt_file, 'r', encoding='utf-8') as f:
|
72 |
+
return yaml.safe_load(f)
|
73 |
+
except Exception as e:
|
74 |
+
print(f"Error loading prompt library: {e}")
|
75 |
+
return {}
|
76 |
+
|
77 |
+
# Load prompt library
|
78 |
+
PROMPT_LIBRARY = load_prompt_library()
|
79 |
+
|
80 |
+
def get_fed_context_for_query(user_message: str) -> str:
|
81 |
+
"""Get relevant Fed data context for the user's query"""
|
82 |
+
message_lower = user_message.lower()
|
83 |
+
|
84 |
+
# Get relevant meeting data based on query type
|
85 |
+
if 'latest' in message_lower or 'most recent' in message_lower:
|
86 |
+
result = get_latest_meeting()
|
87 |
+
if result["success"]:
|
88 |
+
meeting = result["meeting"]
|
89 |
+
return f"Latest FOMC Meeting ({meeting.get('date', 'unknown')}): {meeting.get('forward_guidance', '')[:300]}..."
|
90 |
+
|
91 |
+
elif any(word in message_lower for word in ['search', 'find', 'about']):
|
92 |
+
search_query = user_message.replace('search for', '').replace('find', '').replace('about', '').strip()
|
93 |
+
result = search_meetings(search_query, limit=2)
|
94 |
+
if result["success"] and result["count"] > 0:
|
95 |
+
context = f"Relevant FOMC meetings for '{search_query}':\n"
|
96 |
+
for meeting in result["results"][:2]:
|
97 |
+
context += f"- {meeting.get('date', 'unknown')}: {meeting.get('forward_guidance', '')[:200]}...\n"
|
98 |
+
return context
|
99 |
+
|
100 |
+
# Default: return latest meeting info
|
101 |
+
result = get_latest_meeting()
|
102 |
+
if result["success"]:
|
103 |
+
meeting = result["meeting"]
|
104 |
+
return f"Current Fed Policy Context: Rate at {meeting.get('rate', 'unknown')}, {meeting.get('action', 'maintained')} in latest meeting ({meeting.get('date', 'unknown')})"
|
105 |
+
|
106 |
+
return "Fed data context not available. Please ensure the data pipeline has been run."
|
107 |
+
|
108 |
+
|
109 |
def process_fed_query(user_message: str, selected_model: str = "") -> Dict[str, Any]:
|
110 |
"""Process user queries using Fed AI tools"""
|
111 |
message_lower = user_message.lower()
|
|
|
242 |
"""
|
243 |
return response
|
244 |
|
245 |
+
def respond_for_chat_interface(message: str, history):
|
|
|
|
|
|
|
|
|
246 |
"""Enhanced response function for gr.ChatInterface with Fed AI Savant capabilities"""
|
247 |
|
248 |
+
# Get API key from environment or return error
|
249 |
+
api_key = os.getenv("FIREWORKS_API_KEY", "")
|
|
|
250 |
|
251 |
+
# Create Fed tools dictionary
|
252 |
+
fed_tools = {
|
253 |
+
"search_meetings": search_meetings,
|
254 |
+
"get_latest_meeting": get_latest_meeting,
|
255 |
+
"get_rate_decision": get_rate_decision,
|
256 |
+
"compare_meetings": compare_meetings
|
257 |
+
}
|
258 |
|
259 |
+
# Use the new orchestrator function
|
260 |
+
for messages in stream_fed_agent_response(message, api_key, PROMPT_LIBRARY, fed_tools):
|
261 |
+
yield messages
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
|
263 |
def get_fomc_meetings_sidebar():
|
264 |
"""Generate sidebar content with FOMC meeting details"""
|
|
|
459 |
|
460 |
chat_interface = gr.ChatInterface(
|
461 |
fn=respond_for_chat_interface,
|
462 |
+
type="messages",
|
463 |
+
chatbot=gr.Chatbot(height=500, show_label=False),
|
464 |
textbox=gr.Textbox(placeholder="Ask about Fed policy, rate decisions, or FOMC meetings...", scale=10),
|
465 |
examples=[
|
466 |
+
"What was the rate decision in the last FOMC meeting?",
|
467 |
"Compare June 2024 vs July 2024 FOMC meetings",
|
468 |
"Tell me about inflation expectations",
|
469 |
"Has the Fed's employment stance changed?",
|
470 |
+
"What factors influenced the latest rate decision?",
|
471 |
],
|
472 |
submit_btn="Send",
|
473 |
)
|
src/modules/fed_tools.py
CHANGED
@@ -13,7 +13,6 @@ def _load_meetings_data() -> List[Dict[str, Any]]:
|
|
13 |
if MEETINGS_FILE.exists():
|
14 |
with open(MEETINGS_FILE, 'r', encoding='utf-8') as f:
|
15 |
data = json.load(f)
|
16 |
-
# Sort meetings by date (newest first)
|
17 |
return sorted(data, key=lambda x: x.get('date', ''), reverse=True)
|
18 |
else:
|
19 |
return []
|
@@ -53,7 +52,6 @@ def search_meetings(query: str, limit: int = 3) -> Dict[str, Any]:
|
|
53 |
score = 0
|
54 |
matched_fields = []
|
55 |
|
56 |
-
# Search in various fields and assign scores based on relevance
|
57 |
search_fields = {
|
58 |
'date': 2,
|
59 |
'title': 1,
|
@@ -82,7 +80,6 @@ def search_meetings(query: str, limit: int = 3) -> Dict[str, Any]:
|
|
82 |
'matched_fields': matched_fields
|
83 |
})
|
84 |
|
85 |
-
# Sort by score (highest first) and limit results
|
86 |
scored_meetings.sort(key=lambda x: x['score'], reverse=True)
|
87 |
top_results = scored_meetings[:limit]
|
88 |
|
@@ -117,14 +114,12 @@ def get_rate_decision(date: str) -> Dict[str, Any]:
|
|
117 |
"error": "No meetings data available"
|
118 |
}
|
119 |
|
120 |
-
# Find meeting by exact date match
|
121 |
target_meeting = None
|
122 |
for meeting in meetings_data:
|
123 |
if meeting.get('date') == date:
|
124 |
target_meeting = meeting
|
125 |
break
|
126 |
|
127 |
-
# If no exact match, try to find closest date within 30 days
|
128 |
if not target_meeting and date:
|
129 |
try:
|
130 |
target_date = datetime.strptime(date, '%Y-%m-%d')
|
|
|
13 |
if MEETINGS_FILE.exists():
|
14 |
with open(MEETINGS_FILE, 'r', encoding='utf-8') as f:
|
15 |
data = json.load(f)
|
|
|
16 |
return sorted(data, key=lambda x: x.get('date', ''), reverse=True)
|
17 |
else:
|
18 |
return []
|
|
|
52 |
score = 0
|
53 |
matched_fields = []
|
54 |
|
|
|
55 |
search_fields = {
|
56 |
'date': 2,
|
57 |
'title': 1,
|
|
|
80 |
'matched_fields': matched_fields
|
81 |
})
|
82 |
|
|
|
83 |
scored_meetings.sort(key=lambda x: x['score'], reverse=True)
|
84 |
top_results = scored_meetings[:limit]
|
85 |
|
|
|
114 |
"error": "No meetings data available"
|
115 |
}
|
116 |
|
|
|
117 |
target_meeting = None
|
118 |
for meeting in meetings_data:
|
119 |
if meeting.get('date') == date:
|
120 |
target_meeting = meeting
|
121 |
break
|
122 |
|
|
|
123 |
if not target_meeting and date:
|
124 |
try:
|
125 |
target_date = datetime.strptime(date, '%Y-%m-%d')
|
src/modules/llm_completions.py
CHANGED
@@ -1,12 +1,18 @@
|
|
1 |
from fireworks import LLM
|
2 |
from pydantic import BaseModel
|
3 |
import asyncio
|
|
|
|
|
|
|
|
|
4 |
|
5 |
MODELS = {
|
6 |
"small": "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507",
|
7 |
"large": "accounts/fireworks/models/kimi-k2-instruct"
|
8 |
}
|
9 |
|
|
|
|
|
10 |
semaphore = asyncio.Semaphore(10)
|
11 |
|
12 |
def get_llm(model: str, api_key: str) -> LLM:
|
@@ -39,6 +45,44 @@ async def get_llm_completion(llm: LLM, prompt_text: str, output_class: BaseModel
|
|
39 |
)
|
40 |
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
async def run_multi_llm_completions(llm: LLM, prompts: list[str], output_class: BaseModel) -> list[str]:
|
43 |
"""
|
44 |
Run multiple LLM completions in parallel
|
@@ -65,3 +109,190 @@ async def run_multi_llm_completions(llm: LLM, prompts: list[str], output_class:
|
|
65 |
]
|
66 |
return await asyncio.gather(*tasks)
|
67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from fireworks import LLM
|
2 |
from pydantic import BaseModel
|
3 |
import asyncio
|
4 |
+
import json
|
5 |
+
import time
|
6 |
+
from typing import Dict, Any, List
|
7 |
+
from gradio import ChatMessage
|
8 |
|
9 |
MODELS = {
|
10 |
"small": "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507",
|
11 |
"large": "accounts/fireworks/models/kimi-k2-instruct"
|
12 |
}
|
13 |
|
14 |
+
TODAY = time.strftime("%Y-%m-%d")
|
15 |
+
|
16 |
semaphore = asyncio.Semaphore(10)
|
17 |
|
18 |
def get_llm(model: str, api_key: str) -> LLM:
|
|
|
45 |
)
|
46 |
|
47 |
|
48 |
+
async def get_streaming_completion(llm: LLM, prompt_text: str, system_prompt: str = None):
|
49 |
+
"""
|
50 |
+
Get streaming completion from LLM for real-time responses
|
51 |
+
|
52 |
+
:param llm: The LLM instance
|
53 |
+
:param prompt_text: The user's input message
|
54 |
+
:param system_prompt: Optional system prompt for context
|
55 |
+
:return: Generator yielding response chunks
|
56 |
+
"""
|
57 |
+
messages = []
|
58 |
+
|
59 |
+
if system_prompt:
|
60 |
+
messages.append({
|
61 |
+
"role": "system",
|
62 |
+
"content": system_prompt
|
63 |
+
})
|
64 |
+
|
65 |
+
messages.append({
|
66 |
+
"role": "user",
|
67 |
+
"content": prompt_text
|
68 |
+
})
|
69 |
+
|
70 |
+
try:
|
71 |
+
response = llm.chat.completions.create(
|
72 |
+
messages=messages,
|
73 |
+
temperature=0.2,
|
74 |
+
stream=True,
|
75 |
+
max_tokens=1000
|
76 |
+
)
|
77 |
+
|
78 |
+
for chunk in response:
|
79 |
+
if chunk.choices[0].delta.content:
|
80 |
+
yield chunk.choices[0].delta.content
|
81 |
+
|
82 |
+
except Exception as e:
|
83 |
+
yield f"Error generating response: {str(e)}"
|
84 |
+
|
85 |
+
|
86 |
async def run_multi_llm_completions(llm: LLM, prompts: list[str], output_class: BaseModel) -> list[str]:
|
87 |
"""
|
88 |
Run multiple LLM completions in parallel
|
|
|
109 |
]
|
110 |
return await asyncio.gather(*tasks)
|
111 |
|
112 |
+
def get_orchestrator_decision(user_query: str, api_key: str, prompt_library: Dict[str, str]) -> Dict[str, Any]:
|
113 |
+
"""Use orchestrator LLM to decide which tools to use"""
|
114 |
+
try:
|
115 |
+
orchestrator_prompt = prompt_library.get('fed_orchestrator', '')
|
116 |
+
formatted_prompt = orchestrator_prompt.format(user_query=user_query, date=TODAY)
|
117 |
+
|
118 |
+
llm = get_llm("large", api_key)
|
119 |
+
|
120 |
+
response = llm.chat.completions.create(
|
121 |
+
messages=[
|
122 |
+
{"role": "system", "content": "You are a tool orchestrator. Always respond with valid JSON."},
|
123 |
+
{"role": "user", "content": formatted_prompt}
|
124 |
+
],
|
125 |
+
temperature=0.1,
|
126 |
+
max_tokens=500
|
127 |
+
)
|
128 |
+
|
129 |
+
# Parse JSON response
|
130 |
+
result = json.loads(response.choices[0].message.content)
|
131 |
+
return {"success": True, "decision": result}
|
132 |
+
|
133 |
+
except Exception as e:
|
134 |
+
print(f"Error in orchestrator: {e}")
|
135 |
+
# Fallback to simple logic
|
136 |
+
return {
|
137 |
+
"success": False,
|
138 |
+
"decision": {
|
139 |
+
"tools_needed": [{"function": "get_latest_meeting", "parameters": {}, "reasoning": "Fallback to latest meeting"}],
|
140 |
+
"query_analysis": f"Error occurred, using fallback for: {user_query}"
|
141 |
+
}
|
142 |
+
}
|
143 |
+
|
144 |
+
def execute_fed_tools(tools_decision: Dict[str, Any], fed_tools: Dict[str, callable]) -> List[Dict[str, Any]]:
|
145 |
+
"""Execute the tools determined by the orchestrator"""
|
146 |
+
results = []
|
147 |
+
|
148 |
+
for tool in tools_decision.get("tools_needed", []):
|
149 |
+
function_name = tool.get("function", "")
|
150 |
+
parameters = tool.get("parameters", {})
|
151 |
+
reasoning = tool.get("reasoning", "")
|
152 |
+
|
153 |
+
start_time = time.time()
|
154 |
+
|
155 |
+
try:
|
156 |
+
# Execute the appropriate function
|
157 |
+
if function_name in fed_tools:
|
158 |
+
tool_func = fed_tools[function_name]
|
159 |
+
result = tool_func(**parameters)
|
160 |
+
else:
|
161 |
+
result = {"success": False, "error": f"Unknown function: {function_name}"}
|
162 |
+
|
163 |
+
execution_time = time.time() - start_time
|
164 |
+
|
165 |
+
results.append({
|
166 |
+
"function": function_name,
|
167 |
+
"parameters": parameters,
|
168 |
+
"reasoning": reasoning,
|
169 |
+
"result": result,
|
170 |
+
"execution_time": execution_time,
|
171 |
+
"success": result.get("success", False)
|
172 |
+
})
|
173 |
+
|
174 |
+
except Exception as e:
|
175 |
+
execution_time = time.time() - start_time
|
176 |
+
results.append({
|
177 |
+
"function": function_name,
|
178 |
+
"parameters": parameters,
|
179 |
+
"reasoning": reasoning,
|
180 |
+
"result": {"success": False, "error": str(e)},
|
181 |
+
"execution_time": execution_time,
|
182 |
+
"success": False
|
183 |
+
})
|
184 |
+
|
185 |
+
return results
|
186 |
+
|
187 |
+
def stream_fed_agent_response(
|
188 |
+
message: str,
|
189 |
+
api_key: str,
|
190 |
+
prompt_library: Dict[str, str],
|
191 |
+
fed_tools: Dict[str, callable]
|
192 |
+
):
|
193 |
+
"""Main orchestrator function that coordinates tools and generates responses with ChatMessage objects"""
|
194 |
+
|
195 |
+
if not message.strip():
|
196 |
+
yield [ChatMessage(role="assistant", content="Please enter a question about Federal Reserve policy or FOMC meetings.")]
|
197 |
+
return
|
198 |
+
|
199 |
+
if not api_key.strip():
|
200 |
+
yield [ChatMessage(role="assistant", content="β Please set your FIREWORKS_API_KEY environment variable.")]
|
201 |
+
return
|
202 |
+
|
203 |
+
messages = []
|
204 |
+
|
205 |
+
try:
|
206 |
+
# Step 1: Use orchestrator to determine tools needed
|
207 |
+
messages.append(ChatMessage(
|
208 |
+
role="assistant",
|
209 |
+
content="Analyzing your query...",
|
210 |
+
metadata={"title": "π§ Planning", "status": "pending"}
|
211 |
+
))
|
212 |
+
yield messages
|
213 |
+
|
214 |
+
orchestrator_result = get_orchestrator_decision(message, api_key, prompt_library)
|
215 |
+
tools_decision = orchestrator_result["decision"]
|
216 |
+
|
217 |
+
# Update planning message
|
218 |
+
messages[0] = ChatMessage(
|
219 |
+
role="assistant",
|
220 |
+
content=f"Query Analysis: {tools_decision.get('query_analysis', 'Analyzing Fed data requirements')}\n\nTools needed: {len(tools_decision.get('tools_needed', []))}",
|
221 |
+
metadata={"title": "π§ Planning", "status": "done"}
|
222 |
+
)
|
223 |
+
yield messages
|
224 |
+
|
225 |
+
# Step 2: Execute the determined tools
|
226 |
+
if tools_decision.get("tools_needed"):
|
227 |
+
for i, tool in enumerate(tools_decision["tools_needed"]):
|
228 |
+
tool_msg = ChatMessage(
|
229 |
+
role="assistant",
|
230 |
+
content=f"Executing: {tool['function']}({', '.join([f'{k}={v}' for k, v in tool['parameters'].items()])})\n\nReasoning: {tool['reasoning']}",
|
231 |
+
metadata={"title": f"π§ Tool {i+1}: {tool['function']}", "status": "pending"}
|
232 |
+
)
|
233 |
+
messages.append(tool_msg)
|
234 |
+
yield messages
|
235 |
+
|
236 |
+
# Execute all tools
|
237 |
+
tool_results = execute_fed_tools(tools_decision, fed_tools)
|
238 |
+
|
239 |
+
# Update tool messages with results
|
240 |
+
for i, (tool_result, tool_msg) in enumerate(zip(tool_results, messages[1:])):
|
241 |
+
execution_time = tool_result["execution_time"]
|
242 |
+
success_status = "β
" if tool_result["success"] else "β"
|
243 |
+
|
244 |
+
messages[i+1] = ChatMessage(
|
245 |
+
role="assistant",
|
246 |
+
content=f"{success_status} {tool_result['function']} completed\n\nExecution time: {execution_time:.2f}s\n\nResult summary: {str(tool_result['result'])[:200]}...",
|
247 |
+
metadata={"title": f"π§ Tool {i+1}: {tool_result['function']}", "status": "done", "duration": execution_time}
|
248 |
+
)
|
249 |
+
|
250 |
+
yield messages
|
251 |
+
|
252 |
+
# Step 3: Use results to generate final response
|
253 |
+
combined_context = ""
|
254 |
+
for result in tool_results:
|
255 |
+
if result["success"]:
|
256 |
+
combined_context += f"\n\nFrom {result['function']}: {json.dumps(result['result'], indent=2)}"
|
257 |
+
|
258 |
+
# Generate Fed Savant response using tool results
|
259 |
+
system_prompt_template = prompt_library.get('fed_savant_chat', '')
|
260 |
+
system_prompt = system_prompt_template.format(
|
261 |
+
fed_data_context=combined_context,
|
262 |
+
user_question=message,
|
263 |
+
date=TODAY
|
264 |
+
)
|
265 |
+
|
266 |
+
# Initialize LLM and get streaming response
|
267 |
+
llm = get_llm("large", api_key)
|
268 |
+
|
269 |
+
final_response = ""
|
270 |
+
for chunk in llm.chat.completions.create(
|
271 |
+
messages=[
|
272 |
+
{"role": "system", "content": system_prompt},
|
273 |
+
{"role": "user", "content": message}
|
274 |
+
],
|
275 |
+
temperature=0.2,
|
276 |
+
stream=True,
|
277 |
+
max_tokens=1000
|
278 |
+
):
|
279 |
+
if chunk.choices[0].delta.content:
|
280 |
+
final_response += chunk.choices[0].delta.content
|
281 |
+
|
282 |
+
# Update messages list with current response
|
283 |
+
if len(messages) > len(tool_results):
|
284 |
+
messages[-1] = ChatMessage(role="assistant", content=final_response)
|
285 |
+
else:
|
286 |
+
messages.append(ChatMessage(role="assistant", content=final_response))
|
287 |
+
|
288 |
+
yield messages
|
289 |
+
|
290 |
+
else:
|
291 |
+
# No tools needed, direct response
|
292 |
+
messages.append(ChatMessage(role="assistant", content="No specific tools required. Providing general Fed information."))
|
293 |
+
yield messages
|
294 |
+
|
295 |
+
except Exception as e:
|
296 |
+
messages.append(ChatMessage(role="assistant", content=f"Error generating response: {str(e)}"))
|
297 |
+
yield messages
|
298 |
+
|