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)