Spaces:
Running
Running
RobertoBarrosoLuque
commited on
Commit
Β·
1062173
1
Parent(s):
001487b
Update with good function calling
Browse files- configs/prompt_library.yaml +89 -24
- src/modules/llm_completions.py +125 -57
configs/prompt_library.yaml
CHANGED
@@ -69,39 +69,104 @@ 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 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
|
86 |
RESPONSE FORMAT:
|
87 |
Return a JSON object with this structure:
|
88 |
-
{
|
89 |
"tools_needed": [
|
90 |
-
{
|
91 |
"function": "function_name",
|
92 |
-
"parameters": {
|
93 |
-
"reasoning": "
|
94 |
-
|
|
|
95 |
],
|
96 |
-
"query_analysis": "
|
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: "
|
104 |
-
Response: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
User Query: {user_query}
|
107 |
The date is {date}
|
|
|
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 |
+
|
73 |
+
1. search_meetings(query: str, limit: int = 3)
|
74 |
+
- Purpose: Search across all FOMC meeting fields for relevant information
|
75 |
+
- Parameters:
|
76 |
+
* query (required): Search term or phrase - searches across date, title, action, rate, magnitude, forward_guidance, economic_outlook, market_impact, key_economic_factors
|
77 |
+
* limit (optional): Maximum meetings to return (default: 3, recommend 3-5 for most queries)
|
78 |
+
- Best for: Thematic searches, finding patterns across meetings, keyword-based queries
|
79 |
+
- Returns: Ranked results with match scores and details about which fields matched
|
80 |
+
- Examples: "inflation expectations", "employment concerns", "rate cuts", "economic uncertainty"
|
81 |
+
|
82 |
+
2. get_latest_meeting()
|
83 |
+
- Purpose: Get the most recent FOMC meeting data
|
84 |
+
- Parameters: None
|
85 |
+
- Best for: "What's the latest?", "most recent decision", "current rate"
|
86 |
+
- Returns: Complete data for the newest meeting plus total meeting count
|
87 |
+
- Use when: User asks about current/recent/latest Fed actions without specifying dates
|
88 |
+
|
89 |
+
3. get_rate_decision(date: str)
|
90 |
+
- Purpose: Get specific meeting data by exact or approximate date
|
91 |
+
- Parameters:
|
92 |
+
* date (required): Meeting date in YYYY-MM-DD format (e.g., "2024-12-18")
|
93 |
+
- Best for: Date-specific queries, historical lookups
|
94 |
+
- Returns: Meeting data with exact_match flag; finds closest meeting within 30 days if no exact match
|
95 |
+
- Use when: User mentions specific dates, months, or "the meeting in [time period]"
|
96 |
+
|
97 |
+
4. compare_meetings(date1: str, date2: str)
|
98 |
+
- Purpose: Compare two meetings side by side with detailed analysis
|
99 |
+
- Parameters:
|
100 |
+
* date1 (required): First meeting date in YYYY-MM-DD format
|
101 |
+
* date2 (required): Second meeting date in YYYY-MM-DD format
|
102 |
+
- Best for: "How did X change between meetings?", trend analysis, policy evolution
|
103 |
+
- Returns: Differences, similarities, and factor analysis between meetings
|
104 |
+
- Use when: User explicitly asks for comparisons or asks about changes over time
|
105 |
+
|
106 |
+
TOOL SELECTION STRATEGY:
|
107 |
+
- Single recent query β get_latest_meeting()
|
108 |
+
- Specific date mention β get_rate_decision(date)
|
109 |
+
- Two dates or "compared to" β compare_meetings(date1, date2)
|
110 |
+
- Thematic/keyword search β search_meetings(query, limit)
|
111 |
+
- Complex queries may require multiple tools in sequence
|
112 |
+
|
113 |
+
DATE HANDLING:
|
114 |
+
- Convert relative dates to YYYY-MM-DD format (e.g., "December 2024" β "2024-12-18")
|
115 |
+
- For approximate dates, use typical FOMC meeting dates (usually 3rd Wednesday of month)
|
116 |
+
- Recent meeting schedule: typically 8 meetings per year
|
117 |
|
118 |
RESPONSE FORMAT:
|
119 |
Return a JSON object with this structure:
|
120 |
+
{
|
121 |
"tools_needed": [
|
122 |
+
{
|
123 |
"function": "function_name",
|
124 |
+
"parameters": {"param1": "value1", "param2": "value2"},
|
125 |
+
"reasoning": "Specific explanation of why this tool and these parameters are optimal",
|
126 |
+
"execution_order": 1
|
127 |
+
}
|
128 |
],
|
129 |
+
"query_analysis": "Detailed analysis of user intent and information needs",
|
130 |
+
"date_interpretations": "Any date conversions or assumptions made"
|
131 |
+
}
|
132 |
|
133 |
EXAMPLES:
|
|
|
|
|
134 |
|
135 |
+
User: "What was the latest rate decision?"
|
136 |
+
Response: {
|
137 |
+
"tools_needed": [{
|
138 |
+
"function": "get_latest_meeting",
|
139 |
+
"parameters": {},
|
140 |
+
"reasoning": "User wants most recent Fed decision; get_latest_meeting provides complete current data",
|
141 |
+
"execution_order": 1
|
142 |
+
}],
|
143 |
+
"query_analysis": "User seeking current Fed policy status - needs most recent meeting data",
|
144 |
+
"date_interpretations": "No dates specified - using latest available"
|
145 |
+
}
|
146 |
+
|
147 |
+
User: "How did inflation concerns change from June to December 2024?"
|
148 |
+
Response: {
|
149 |
+
"tools_needed": [{
|
150 |
+
"function": "compare_meetings",
|
151 |
+
"parameters": {"date1": "2024-06-12", "date2": "2024-12-18"},
|
152 |
+
"reasoning": "User wants evolution analysis between specific periods; compare_meetings provides detailed difference analysis including factor changes",
|
153 |
+
"execution_order": 1
|
154 |
+
}],
|
155 |
+
"query_analysis": "User seeking trend analysis of inflation concerns over 6-month period",
|
156 |
+
"date_interpretations": "June 2024 β 2024-06-12 (typical June FOMC date), December 2024 β 2024-12-18 (typical December FOMC date)"
|
157 |
+
}
|
158 |
+
|
159 |
+
User: "Tell me about employment data in recent Fed meetings"
|
160 |
+
Response: {
|
161 |
+
"tools_needed": [{
|
162 |
+
"function": "search_meetings",
|
163 |
+
"parameters": {"query": "employment", "limit": 4},
|
164 |
+
"reasoning": "Thematic search across multiple meetings for employment-related content; higher limit captures recent trend",
|
165 |
+
"execution_order": 1
|
166 |
+
}],
|
167 |
+
"query_analysis": "User wants employment-focused analysis across recent meetings - needs keyword search with sufficient results for pattern identification",
|
168 |
+
"date_interpretations": "No specific dates - search will return most relevant recent matches"
|
169 |
+
}
|
170 |
|
171 |
User Query: {user_query}
|
172 |
The date is {date}
|
src/modules/llm_completions.py
CHANGED
@@ -184,6 +184,114 @@ def execute_fed_tools(tools_decision: Dict[str, Any], fed_tools: Dict[str, calla
|
|
184 |
|
185 |
return results
|
186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
def stream_fed_agent_response(
|
188 |
message: str,
|
189 |
api_key: str,
|
@@ -192,6 +300,7 @@ def stream_fed_agent_response(
|
|
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
|
@@ -203,7 +312,6 @@ def stream_fed_agent_response(
|
|
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...",
|
@@ -214,78 +322,38 @@ def stream_fed_agent_response(
|
|
214 |
orchestrator_result = get_orchestrator_decision(message, api_key, prompt_library)
|
215 |
tools_decision = orchestrator_result["decision"]
|
216 |
|
217 |
-
|
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 |
-
|
228 |
-
|
229 |
-
|
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 |
-
|
240 |
-
|
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 |
-
|
253 |
-
|
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 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
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
|
|
|
184 |
|
185 |
return results
|
186 |
|
187 |
+
def extract_citations_from_tool_results(tool_results: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
188 |
+
"""Extract unique citations from tool results"""
|
189 |
+
citations = []
|
190 |
+
|
191 |
+
for result in tool_results:
|
192 |
+
if result["success"] and result["result"].get("success"):
|
193 |
+
# Check if result has meeting data with URLs
|
194 |
+
if "meeting" in result["result"]:
|
195 |
+
meeting = result["result"]["meeting"]
|
196 |
+
if meeting.get("url"):
|
197 |
+
citations.append({
|
198 |
+
"date": meeting.get("date", "Unknown date"),
|
199 |
+
"url": meeting["url"],
|
200 |
+
"title": meeting.get("title", f"FOMC Meeting {meeting.get('date', '')}")
|
201 |
+
})
|
202 |
+
elif "results" in result["result"]:
|
203 |
+
# Handle search results
|
204 |
+
for meeting in result["result"]["results"]:
|
205 |
+
if meeting.get("url"):
|
206 |
+
citations.append({
|
207 |
+
"date": meeting.get("date", "Unknown date"),
|
208 |
+
"url": meeting["url"],
|
209 |
+
"title": meeting.get("title", f"FOMC Meeting {meeting.get('date', '')}")
|
210 |
+
})
|
211 |
+
|
212 |
+
# Remove duplicate citations
|
213 |
+
unique_citations = []
|
214 |
+
seen_urls = set()
|
215 |
+
for citation in citations:
|
216 |
+
if citation["url"] not in seen_urls:
|
217 |
+
unique_citations.append(citation)
|
218 |
+
seen_urls.add(citation["url"])
|
219 |
+
|
220 |
+
return unique_citations
|
221 |
+
|
222 |
+
def format_response_with_citations(response: str, citations: List[Dict[str, str]]) -> str:
|
223 |
+
"""Format response with citations appended"""
|
224 |
+
if citations:
|
225 |
+
response += "\n\n**π Sources:**\n"
|
226 |
+
for citation in citations:
|
227 |
+
response += f"β’ [{citation['title']}]({citation['url']})\n"
|
228 |
+
return response
|
229 |
+
|
230 |
+
def create_planning_message(tools_decision: Dict[str, Any]) -> ChatMessage:
|
231 |
+
"""Create the planning/analysis message"""
|
232 |
+
return ChatMessage(
|
233 |
+
role="assistant",
|
234 |
+
content=f"Query Analysis: {tools_decision.get('query_analysis', 'Analyzing Fed data requirements')}\n\nTools needed: {len(tools_decision.get('tools_needed', []))}",
|
235 |
+
metadata={"title": "π§ Planning", "status": "done"}
|
236 |
+
)
|
237 |
+
|
238 |
+
def create_tool_messages(tools_decision: Dict[str, Any]) -> List[ChatMessage]:
|
239 |
+
"""Create initial tool execution messages"""
|
240 |
+
tool_messages = []
|
241 |
+
|
242 |
+
for i, tool in enumerate(tools_decision["tools_needed"]):
|
243 |
+
tool_msg = ChatMessage(
|
244 |
+
role="assistant",
|
245 |
+
content=f"Executing: {tool['function']}({', '.join([f'{k}={v}' for k, v in tool['parameters'].items()])})\n\nReasoning: {tool['reasoning']}",
|
246 |
+
metadata={"title": f"π§ Tool {i+1}: {tool['function']}", "status": "pending"}
|
247 |
+
)
|
248 |
+
tool_messages.append(tool_msg)
|
249 |
+
|
250 |
+
return tool_messages
|
251 |
+
|
252 |
+
def update_tool_messages_with_results(tool_results: List[Dict[str, Any]]) -> List[ChatMessage]:
|
253 |
+
"""Update tool messages with execution results"""
|
254 |
+
updated_messages = []
|
255 |
+
|
256 |
+
for i, tool_result in enumerate(tool_results):
|
257 |
+
execution_time = tool_result["execution_time"]
|
258 |
+
success_status = "β
" if tool_result["success"] else "β"
|
259 |
+
|
260 |
+
updated_msg = ChatMessage(
|
261 |
+
role="assistant",
|
262 |
+
content=f"{success_status} {tool_result['function']} completed\n\nExecution time: {execution_time:.2f}s\n\nResult summary: {str(tool_result['result'])[:200]}...",
|
263 |
+
metadata={"title": f"π§ Tool {i+1}: {tool_result['function']}", "status": "done", "duration": execution_time}
|
264 |
+
)
|
265 |
+
updated_messages.append(updated_msg)
|
266 |
+
|
267 |
+
return updated_messages
|
268 |
+
|
269 |
+
def build_context_from_tool_results(tool_results: List[Dict[str, Any]]) -> str:
|
270 |
+
"""Build combined context from successful tool results"""
|
271 |
+
combined_context = ""
|
272 |
+
for result in tool_results:
|
273 |
+
if result["success"]:
|
274 |
+
combined_context += f"\n\nFrom {result['function']}: {json.dumps(result['result'], indent=2)}"
|
275 |
+
return combined_context
|
276 |
+
|
277 |
+
def stream_final_response(message: str, system_prompt: str, api_key: str, citations: List[Dict[str, str]]):
|
278 |
+
"""Stream the final Fed Savant response with citations"""
|
279 |
+
llm = get_llm("large", api_key)
|
280 |
+
|
281 |
+
final_response = ""
|
282 |
+
for chunk in llm.chat.completions.create(
|
283 |
+
messages=[
|
284 |
+
{"role": "system", "content": system_prompt},
|
285 |
+
{"role": "user", "content": message}
|
286 |
+
],
|
287 |
+
temperature=0.2,
|
288 |
+
stream=True,
|
289 |
+
max_tokens=1000
|
290 |
+
):
|
291 |
+
if chunk.choices[0].delta.content:
|
292 |
+
final_response += chunk.choices[0].delta.content
|
293 |
+
yield format_response_with_citations(final_response, citations)
|
294 |
+
|
295 |
def stream_fed_agent_response(
|
296 |
message: str,
|
297 |
api_key: str,
|
|
|
300 |
):
|
301 |
"""Main orchestrator function that coordinates tools and generates responses with ChatMessage objects"""
|
302 |
|
303 |
+
# Input validation
|
304 |
if not message.strip():
|
305 |
yield [ChatMessage(role="assistant", content="Please enter a question about Federal Reserve policy or FOMC meetings.")]
|
306 |
return
|
|
|
312 |
messages = []
|
313 |
|
314 |
try:
|
|
|
315 |
messages.append(ChatMessage(
|
316 |
role="assistant",
|
317 |
content="Analyzing your query...",
|
|
|
322 |
orchestrator_result = get_orchestrator_decision(message, api_key, prompt_library)
|
323 |
tools_decision = orchestrator_result["decision"]
|
324 |
|
325 |
+
messages[0] = create_planning_message(tools_decision)
|
|
|
|
|
|
|
|
|
|
|
326 |
yield messages
|
327 |
|
|
|
328 |
if tools_decision.get("tools_needed"):
|
329 |
+
tool_messages = create_tool_messages(tools_decision)
|
330 |
+
messages.extend(tool_messages)
|
331 |
+
yield messages
|
|
|
|
|
|
|
|
|
|
|
332 |
|
|
|
333 |
tool_results = execute_fed_tools(tools_decision, fed_tools)
|
334 |
|
335 |
+
updated_tool_messages = update_tool_messages_with_results(tool_results)
|
336 |
+
messages[1:] = updated_tool_messages # Replace tool messages but keep planning
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
337 |
yield messages
|
338 |
|
339 |
+
combined_context = build_context_from_tool_results(tool_results)
|
340 |
+
citations = extract_citations_from_tool_results(tool_results)
|
|
|
|
|
|
|
341 |
|
|
|
342 |
system_prompt_template = prompt_library.get('fed_savant_chat', '')
|
343 |
system_prompt = system_prompt_template.format(
|
344 |
fed_data_context=combined_context,
|
345 |
user_question=message,
|
346 |
date=TODAY
|
347 |
)
|
348 |
+
|
349 |
+
expected_messages_count = 1 + len(tool_results)
|
350 |
|
351 |
+
for response_chunk in stream_final_response(message, system_prompt, api_key, citations):
|
352 |
+
if len(messages) < expected_messages_count + 1:
|
353 |
+
messages.append(ChatMessage(role="assistant", content=response_chunk))
|
354 |
+
else:
|
355 |
+
messages[-1] = ChatMessage(role="assistant", content=response_chunk)
|
356 |
+
yield messages
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
357 |
|
358 |
else:
|
359 |
# No tools needed, direct response
|