File size: 10,292 Bytes
0f81d99 1fa6961 0f81d99 1fa6961 0f81d99 1fa6961 cc467c2 0f81d99 0420d27 0f81d99 0420d27 0f81d99 b102339 0f81d99 b102339 0f81d99 b102339 0f81d99 0420d27 cc467c2 0f81d99 0420d27 0f81d99 1fa6961 0f81d99 1fa6961 0f81d99 1fa6961 0f81d99 1fa6961 0420d27 0f81d99 0420d27 cc467c2 0f81d99 cc467c2 1fa6961 0420d27 7c04f3e 0420d27 7c04f3e 0f81d99 7c04f3e 1fa6961 0420d27 cc467c2 0420d27 cc467c2 0f81d99 1fa6961 0f81d99 1fa6961 0f81d99 0420d27 0f81d99 cc467c2 0f81d99 b102339 0f81d99 0420d27 0f81d99 cc467c2 0f81d99 b102339 0f81d99 0420d27 0f81d99 1fa6961 0f81d99 0420d27 0f81d99 0420d27 0f81d99 7c04f3e 0f81d99 7c04f3e 0f81d99 7c04f3e 0f81d99 0420d27 0f81d99 7c04f3e 0f81d99 0420d27 0f81d99 0420d27 1fa6961 0420d27 0f81d99 7c04f3e 0f81d99 7c04f3e 0420d27 0f81d99 7c04f3e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
import os, json, time, random, asyncio
from dotenv import load_dotenv
from typing import Optional, Dict, Any
# Load environment variables
load_dotenv()
# Agno imports (corrected based on search results)
from agno.agent import Agent
from agno.models.groq import Groq
from agno.models.google import Gemini
from agno.tools.yfinance import YFinanceTools
# Tavily import (replacing DuckDuckGo)
from tavily import TavilyClient
# Additional imports for custom tools
from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
# Advanced Rate Limiter with exponential backoff (SILENT)
class AdvancedRateLimiter:
def __init__(self, requests_per_minute: int, tokens_per_minute: int = None):
self.requests_per_minute = requests_per_minute
self.tokens_per_minute = tokens_per_minute
self.request_times = []
self.token_usage = []
self.consecutive_failures = 0
def wait_if_needed(self, estimated_tokens: int = 1000):
current_time = time.time()
# Clean old requests (older than 1 minute)
self.request_times = [t for t in self.request_times if current_time - t < 60]
self.token_usage = [(t, tokens) for t, tokens in self.token_usage if current_time - t < 60]
# Calculate wait time for requests (SILENT)
if len(self.request_times) >= self.requests_per_minute:
wait_time = 60 - (current_time - self.request_times[0]) + random.uniform(2, 8)
time.sleep(wait_time) # Changed from asyncio.sleep to time.sleep
# Record this request
self.request_times.append(current_time)
if self.tokens_per_minute:
self.token_usage.append((current_time, estimated_tokens))
def record_success(self):
self.consecutive_failures = 0
def record_failure(self):
self.consecutive_failures += 1
# Initialize rate limiters for free tiers
groq_limiter = AdvancedRateLimiter(requests_per_minute=30, tokens_per_minute=6000)
gemini_limiter = AdvancedRateLimiter(requests_per_minute=2, tokens_per_minute=32000)
tavily_limiter = AdvancedRateLimiter(requests_per_minute=50)
# Initialize Tavily client
tavily_client = TavilyClient(os.getenv("TAVILY_API_KEY"))
# Custom tool functions - ALL SYNCHRONOUS (SILENT)
def multiply_tool(a: float, b: float) -> float:
"""Multiply two numbers."""
return a * b
def add_tool(a: float, b: float) -> float:
"""Add two numbers."""
return a + b
def subtract_tool(a: float, b: float) -> float:
"""Subtract two numbers."""
return a - b
def divide_tool(a: float, b: float) -> float:
"""Divide two numbers."""
if b == 0:
raise ValueError("Cannot divide by zero.")
return a / b
def tavily_search_tool(query: str) -> str:
"""Search using Tavily with rate limiting - SYNCHRONOUS."""
try:
tavily_limiter.wait_if_needed()
response = tavily_client.search(
query=query,
max_results=3,
search_depth="basic",
include_answer=False
)
# Format results
results = []
for result in response.get('results', []):
results.append(f"Title: {result.get('title', '')}\nContent: {result.get('content', '')}")
return "\n\n---\n\n".join(results)
except Exception as e:
return f"Tavily search failed: {str(e)}"
def wiki_search_tool(query: str) -> str:
"""Search Wikipedia with rate limiting - SYNCHRONOUS."""
try:
time.sleep(random.uniform(1, 3)) # Changed from asyncio.sleep
loader = WikipediaLoader(query=query, load_max_docs=1)
data = loader.load()
return "\n\n---\n\n".join([doc.page_content[:1000] for doc in data])
except Exception as e:
return f"Wikipedia search failed: {str(e)}"
def arxiv_search_tool(query: str) -> str:
"""Search ArXiv with rate limiting - SYNCHRONOUS."""
try:
time.sleep(random.uniform(1, 4)) # Changed from asyncio.sleep
search_docs = ArxivLoader(query=query, load_max_docs=2).load()
return "\n\n---\n\n".join([doc.page_content[:800] for doc in search_docs])
except Exception as e:
return f"ArXiv search failed: {str(e)}"
# Create specialized Agno agents (SILENT)
def create_agno_agents():
"""Create specialized Agno agents with the best free models"""
# Math specialist agent (using Groq for speed)
math_agent = Agent(
name="Math Specialist",
model=Groq(
id="llama-3.3-70b-versatile",
api_key=os.getenv("GROQ_API_KEY"),
temperature=0
),
tools=[multiply_tool, add_tool, subtract_tool, divide_tool],
instructions=[
"You are a mathematical specialist with access to calculation tools.",
"Use the appropriate math tools for calculations.",
"Show your work step by step.",
"Always provide precise numerical answers.",
"Finish with: FINAL ANSWER: [numerical result]"
],
show_tool_calls=False, # SILENT
markdown=False
)
# Research specialist agent (using Gemini for capability)
research_agent = Agent(
name="Research Specialist",
model=Gemini(
id="gemini-2.0-flash-thinking-exp",
api_key=os.getenv("GOOGLE_API_KEY"),
temperature=0
),
tools=[tavily_search_tool, wiki_search_tool, arxiv_search_tool], # All synchronous now
instructions=[
"You are a research specialist with access to multiple search tools.",
"Use Tavily search for current web information, Wikipedia for encyclopedic content, and ArXiv for academic papers.",
"Always cite sources and provide well-researched answers.",
"Synthesize information from multiple sources when possible.",
"Finish with: FINAL ANSWER: [your researched answer]"
],
show_tool_calls=False, # SILENT
markdown=False
)
# Coordinator agent (using Groq for fast coordination)
coordinator_agent = Agent(
name="Coordinator",
model=Groq(
id="llama-3.3-70b-versatile",
api_key=os.getenv("GROQ_API_KEY"),
temperature=0
),
tools=[tavily_search_tool, wiki_search_tool], # All synchronous now
instructions=[
"You are the main coordinator agent.",
"Analyze queries and provide comprehensive responses.",
"Use Tavily search for current information and Wikipedia for background context.",
"Always finish with: FINAL ANSWER: [your final answer]"
],
show_tool_calls=False, # SILENT
markdown=False
)
return {
"math": math_agent,
"research": research_agent,
"coordinator": coordinator_agent
}
# Main Agno multi-agent system (SIMPLIFIED - NO ASYNC)
class AgnoMultiAgentSystem:
"""Agno multi-agent system with comprehensive rate limiting"""
def __init__(self):
self.agents = create_agno_agents()
self.request_count = 0
self.last_request_time = time.time()
def process_query(self, query: str, max_retries: int = 5) -> str:
"""Process query using Agno agents with rate limiting (SYNCHRONOUS)"""
# Global rate limiting (SILENT)
current_time = time.time()
if current_time - self.last_request_time > 3600:
self.request_count = 0
self.last_request_time = current_time
self.request_count += 1
# Add delay between requests (SILENT)
if self.request_count > 1:
time.sleep(random.uniform(3, 10)) # Changed from asyncio.sleep
for attempt in range(max_retries):
try:
# Route to appropriate agent based on query type (SILENT)
if any(word in query.lower() for word in ['calculate', 'math', 'multiply', 'add', 'subtract', 'divide', 'compute']):
response = self.agents["math"].run(query, stream=False)
elif any(word in query.lower() for word in ['search', 'find', 'research', 'what is', 'who is', 'when', 'where']):
response = self.agents["research"].run(query, stream=False)
else:
response = self.agents["coordinator"].run(query, stream=False)
return response.content if hasattr(response, 'content') else str(response)
except Exception as e:
error_msg = str(e).lower()
if any(keyword in error_msg for keyword in ['rate limit', '429', 'quota', 'too many requests']):
wait_time = (2 ** attempt) + random.uniform(15, 45)
time.sleep(wait_time) # Changed from asyncio.sleep
continue
elif attempt == max_retries - 1:
try:
return self.agents["coordinator"].run(f"Answer this as best you can: {query}", stream=False)
except:
return f"Error: {str(e)}"
else:
wait_time = (2 ** attempt) + random.uniform(2, 8)
time.sleep(wait_time) # Changed from asyncio.sleep
return "Maximum retries exceeded. Please try again later."
# SIMPLIFIED main function (NO ASYNC)
def main(query: str) -> str:
"""Main function using Agno multi-agent system (SYNCHRONOUS)"""
agno_system = AgnoMultiAgentSystem()
return agno_system.process_query(query)
def get_final_answer(query: str) -> str:
"""Extract only the FINAL ANSWER from the response"""
full_response = main(query)
if "FINAL ANSWER:" in full_response:
final_answer = full_response.split("FINAL ANSWER:")[-1].strip()
return final_answer
else:
return full_response.strip()
if __name__ == "__main__":
# Test the Agno system - CLEAN OUTPUT ONLY
result = get_final_answer("What are the names of the US presidents who were assassinated?")
print(result)
|