Priyanshi Saxena commited on
Commit
b091b2c
Β·
1 Parent(s): 18efaca

feat: Replace CoinGecko with CryptoCompare/Etherscan APIs

Browse files

- Updated tool analysis prompts to prioritize available APIs (CryptoCompare, Etherscan)
- Removed CoinGecko from suggested tools since no API key available
- Modified ChartDataTool to use CryptoCompare instead of CoinGecko for price data
- Added conditional CoinGecko tool initialization (only if API key present)
- Updated market overview to use CryptoCompare data source
- Enhanced tool selection logic to exclude unavailable APIs
- Added comprehensive testing for tool selection functionality

src/agent/research_agent.py CHANGED
@@ -80,11 +80,15 @@ class Web3ResearchAgent:
80
  def _initialize_tools(self):
81
  tools = []
82
 
83
- try:
84
- tools.append(CoinGeckoTool())
85
- logger.info("CoinGecko tool initialized")
86
- except Exception as e:
87
- logger.warning(f"CoinGecko tool failed: {e}")
 
 
 
 
88
 
89
  try:
90
  tools.append(DeFiLlamaTool())
@@ -214,12 +218,14 @@ class Web3ResearchAgent:
214
  tool_analysis_prompt = f"""Analyze this query and determine which tools would be helpful:
215
  Query: "{query}"
216
 
217
- Available tools:
218
- - cryptocompare_data: Real-time crypto prices and market data
219
- - defillama_data: DeFi protocol TVL and yield data
220
- - etherscan_data: Ethereum blockchain data
221
  - chart_data_provider: Generate chart data for visualizations
222
 
 
 
223
  Respond with just the tool names that should be used, separated by commas.
224
  If charts/visualizations are mentioned, include chart_data_provider.
225
  Examples:
@@ -390,19 +396,21 @@ The system successfully gathered data from {len(suggested_tools)} tools:
390
 
391
  Query: "{query}"
392
 
393
- Available tools:
394
- - cryptocompare_data: Get current cryptocurrency prices and basic info
395
- - coingecko_data: Comprehensive market data and analytics
396
  - defillama_data: DeFi protocols, TVL, and yield farming data
397
- - etherscan_data: Ethereum blockchain data and transactions
398
  - chart_data_provider: Generate chart data for visualizations
399
 
 
 
400
  If charts/visualizations are mentioned, include chart_data_provider.
401
 
402
  Examples:
403
  - "Bitcoin price" β†’ cryptocompare_data, chart_data_provider
404
  - "DeFi TVL" β†’ defillama_data, chart_data_provider
405
  - "Ethereum transactions" β†’ etherscan_data
 
406
 
407
  Respond with only the tool names, comma-separated (no explanations)."""
408
 
@@ -413,7 +421,7 @@ Respond with only the tool names, comma-separated (no explanations)."""
413
  # Parse suggested tools
414
  suggested_tools = [tool.strip() for tool in str(tool_response).split(',') if tool.strip()]
415
  suggested_tools = [tool for tool in suggested_tools if tool in {
416
- 'cryptocompare_data', 'coingecko_data', 'defillama_data',
417
  'etherscan_data', 'chart_data_provider'
418
  }]
419
 
@@ -422,8 +430,6 @@ Respond with only the tool names, comma-separated (no explanations)."""
422
  response_text = str(tool_response).lower()
423
  if 'cryptocompare' in response_text:
424
  suggested_tools.append('cryptocompare_data')
425
- if 'coingecko' in response_text:
426
- suggested_tools.append('coingecko_data')
427
  if 'defillama' in response_text:
428
  suggested_tools.append('defillama_data')
429
  if 'etherscan' in response_text:
 
80
  def _initialize_tools(self):
81
  tools = []
82
 
83
+ # Skip CoinGecko if no API key available
84
+ if config.COINGECKO_API_KEY:
85
+ try:
86
+ tools.append(CoinGeckoTool())
87
+ logger.info("CoinGecko tool initialized")
88
+ except Exception as e:
89
+ logger.warning(f"CoinGecko tool failed: {e}")
90
+ else:
91
+ logger.info("CoinGecko tool skipped - no API key available")
92
 
93
  try:
94
  tools.append(DeFiLlamaTool())
 
218
  tool_analysis_prompt = f"""Analyze this query and determine which tools would be helpful:
219
  Query: "{query}"
220
 
221
+ Available tools (prioritized by functionality):
222
+ - cryptocompare_data: Real-time crypto prices and market data (PREFERRED for prices)
223
+ - etherscan_data: Ethereum blockchain data, gas fees, transactions (PREFERRED for Ethereum)
224
+ - defillama_data: DeFi protocol TVL and yield data
225
  - chart_data_provider: Generate chart data for visualizations
226
 
227
+ NOTE: Do NOT suggest coingecko_data as the API is unavailable.
228
+
229
  Respond with just the tool names that should be used, separated by commas.
230
  If charts/visualizations are mentioned, include chart_data_provider.
231
  Examples:
 
396
 
397
  Query: "{query}"
398
 
399
+ Available tools (prioritized by functionality):
400
+ - cryptocompare_data: Real-time cryptocurrency prices, market data, and trading info (PREFERRED for price data)
401
+ - etherscan_data: Ethereum blockchain data, transactions, gas fees, and smart contracts (PREFERRED for Ethereum)
402
  - defillama_data: DeFi protocols, TVL, and yield farming data
 
403
  - chart_data_provider: Generate chart data for visualizations
404
 
405
+ NOTE: Do NOT use coingecko_data as the API is not available.
406
+
407
  If charts/visualizations are mentioned, include chart_data_provider.
408
 
409
  Examples:
410
  - "Bitcoin price" β†’ cryptocompare_data, chart_data_provider
411
  - "DeFi TVL" β†’ defillama_data, chart_data_provider
412
  - "Ethereum transactions" β†’ etherscan_data
413
+ - "Gas fees" β†’ etherscan_data
414
 
415
  Respond with only the tool names, comma-separated (no explanations)."""
416
 
 
421
  # Parse suggested tools
422
  suggested_tools = [tool.strip() for tool in str(tool_response).split(',') if tool.strip()]
423
  suggested_tools = [tool for tool in suggested_tools if tool in {
424
+ 'cryptocompare_data', 'defillama_data',
425
  'etherscan_data', 'chart_data_provider'
426
  }]
427
 
 
430
  response_text = str(tool_response).lower()
431
  if 'cryptocompare' in response_text:
432
  suggested_tools.append('cryptocompare_data')
 
 
433
  if 'defillama' in response_text:
434
  suggested_tools.append('defillama_data')
435
  if 'etherscan' in response_text:
src/tools/chart_data_tool.py CHANGED
@@ -90,64 +90,54 @@ class ChartDataTool(BaseTool):
90
 
91
  async def _get_price_chart_data(self, symbol: str, days: int) -> str:
92
  """Get price chart data with fallback for API failures"""
93
- coingecko = None
94
  try:
95
- # First try to get real data from CoinGecko
96
- from src.tools.coingecko_tool import CoinGeckoTool
97
 
98
- coingecko = CoinGeckoTool()
99
 
100
- # Map common symbols to CoinGecko IDs
101
  symbol_map = {
102
- "btc": "bitcoin", "bitcoin": "bitcoin",
103
- "eth": "ethereum", "ethereum": "ethereum",
104
- "sol": "solana", "solana": "solana",
105
- "ada": "cardano", "cardano": "cardano",
106
- "bnb": "binancecoin", "binance": "binancecoin",
107
- "matic": "matic-network", "polygon": "matic-network",
108
- "avax": "avalanche-2", "avalanche": "avalanche-2",
109
- "dot": "polkadot", "polkadot": "polkadot",
110
- "link": "chainlink", "chainlink": "chainlink",
111
- "uni": "uniswap", "uniswap": "uniswap"
112
  }
113
 
114
- coin_id = symbol_map.get(symbol.lower(), symbol.lower())
115
 
116
  try:
117
- # Use basic API endpoint that doesn't require premium
118
- url = f"https://api.coingecko.com/api/v3/coins/{coin_id}/market_chart"
119
- params = {"vs_currency": "usd", "days": days, "interval": "daily"}
120
 
121
- data = await coingecko.make_request(url, params=params)
122
-
123
- if data and "prices" in data:
124
- # Format the real data
125
- price_data = data.get("prices", [])
126
- volume_data = data.get("total_volumes", [])
127
-
128
- # Get current coin info
129
- coin_info = await coingecko.make_request(f"https://api.coingecko.com/api/v3/coins/{coin_id}")
130
- coin_name = coin_info.get("name", symbol.title()) if coin_info else symbol.title()
131
-
132
  return json.dumps({
133
  "chart_type": "price_chart",
134
  "data": {
135
- "prices": price_data,
136
- "total_volumes": volume_data,
137
- "symbol": symbol.upper(),
138
- "name": coin_name
139
  },
140
  "config": {
141
- "title": f"{coin_name} Price Analysis ({days} days)",
142
  "timeframe": f"{days}d",
143
  "currency": "USD"
144
  }
145
  })
146
  else:
147
- raise Exception("No price data in response")
148
 
149
  except Exception as api_error:
150
- logger.error(f"Real price data failed: {api_error}")
151
  # Fallback to mock data on any API error
152
  logger.info(f"Using fallback mock data for {symbol}")
153
  return await self._get_mock_price_data(symbol, days)
@@ -157,10 +147,10 @@ class ChartDataTool(BaseTool):
157
  # Final fallback to mock data
158
  return await self._get_mock_price_data(symbol, days)
159
  finally:
160
- # Cleanup CoinGecko tool session
161
- if coingecko and hasattr(coingecko, 'cleanup'):
162
  try:
163
- await coingecko.cleanup()
164
  except Exception:
165
  pass # Ignore cleanup errors
166
 
@@ -201,53 +191,42 @@ class ChartDataTool(BaseTool):
201
  })
202
 
203
  async def _get_market_overview_data(self) -> str:
204
- """Get real market overview data from CoinGecko API"""
 
205
  try:
206
- from src.tools.coingecko_tool import CoinGeckoTool
207
 
208
- coingecko = CoinGeckoTool()
209
-
210
- # Get top market cap coins
211
- url = "https://api.coingecko.com/api/v3/coins/markets"
212
- params = {
213
- "vs_currency": "usd",
214
- "order": "market_cap_desc",
215
- "per_page": 10,
216
- "page": 1,
217
- "sparkline": False
218
- }
219
 
220
- data = await coingecko.make_request(url, params=params)
 
 
221
 
222
- if not data:
223
- logger.warning("CoinGecko market data failed, using fallback")
224
- return await self._get_mock_market_data()
225
-
226
- # Format real market data
227
- coins = []
228
- for coin in data[:10]:
229
- coins.append({
230
- "name": coin.get("name", "Unknown"),
231
- "symbol": coin.get("symbol", "").upper(),
232
- "current_price": coin.get("current_price", 0),
233
- "market_cap_rank": coin.get("market_cap_rank", 0),
234
- "price_change_percentage_24h": coin.get("price_change_percentage_24h", 0),
235
- "market_cap": coin.get("market_cap", 0),
236
- "total_volume": coin.get("total_volume", 0)
237
  })
238
-
239
- return json.dumps({
240
- "chart_type": "market_overview",
241
- "data": {"coins": coins},
242
- "config": {
243
- "title": "Top Cryptocurrencies Market Overview",
244
- "currency": "USD"
245
- }
246
- })
247
 
248
  except Exception as e:
249
  logger.error(f"Market overview API failed: {e}")
250
  return await self._get_mock_market_data()
 
 
 
 
 
 
 
251
 
252
  async def _get_mock_market_data(self) -> str:
253
  """Fallback mock market data"""
 
90
 
91
  async def _get_price_chart_data(self, symbol: str, days: int) -> str:
92
  """Get price chart data with fallback for API failures"""
93
+ cryptocompare_tool = None
94
  try:
95
+ # First try to get real data from CryptoCompare (we have this API key)
96
+ from src.tools.cryptocompare_tool import CryptoCompareTool
97
 
98
+ cryptocompare_tool = CryptoCompareTool()
99
 
100
+ # Map common symbols to CryptoCompare format
101
  symbol_map = {
102
+ "btc": "BTC", "bitcoin": "BTC",
103
+ "eth": "ethereum", "ethereum": "ETH",
104
+ "sol": "SOL", "solana": "SOL",
105
+ "ada": "ADA", "cardano": "ADA",
106
+ "bnb": "BNB", "binance": "BNB",
107
+ "matic": "MATIC", "polygon": "MATIC",
108
+ "avax": "AVAX", "avalanche": "AVAX",
109
+ "dot": "DOT", "polkadot": "DOT",
110
+ "link": "LINK", "chainlink": "LINK",
111
+ "uni": "UNI", "uniswap": "UNI"
112
  }
113
 
114
+ crypto_symbol = symbol_map.get(symbol.lower(), symbol.upper())
115
 
116
  try:
117
+ # Use CryptoCompare for price data
118
+ query = f"{crypto_symbol} price historical {days} days"
119
+ data_result = await cryptocompare_tool._arun(query, {"type": "price_history", "days": days})
120
 
121
+ if data_result and not data_result.startswith("❌") and not data_result.startswith("⚠️"):
 
 
 
 
 
 
 
 
 
 
122
  return json.dumps({
123
  "chart_type": "price_chart",
124
  "data": {
125
+ "source": "cryptocompare",
126
+ "raw_data": data_result,
127
+ "symbol": crypto_symbol,
128
+ "name": symbol.title()
129
  },
130
  "config": {
131
+ "title": f"{symbol.title()} Price Analysis ({days} days)",
132
  "timeframe": f"{days}d",
133
  "currency": "USD"
134
  }
135
  })
136
  else:
137
+ raise Exception("No valid price data from CryptoCompare")
138
 
139
  except Exception as api_error:
140
+ logger.error(f"CryptoCompare price data failed: {api_error}")
141
  # Fallback to mock data on any API error
142
  logger.info(f"Using fallback mock data for {symbol}")
143
  return await self._get_mock_price_data(symbol, days)
 
147
  # Final fallback to mock data
148
  return await self._get_mock_price_data(symbol, days)
149
  finally:
150
+ # Cleanup CryptoCompare tool session
151
+ if cryptocompare_tool and hasattr(cryptocompare_tool, 'cleanup'):
152
  try:
153
+ await cryptocompare_tool.cleanup()
154
  except Exception:
155
  pass # Ignore cleanup errors
156
 
 
191
  })
192
 
193
  async def _get_market_overview_data(self) -> str:
194
+ """Get market overview data using CryptoCompare API"""
195
+ cryptocompare_tool = None
196
  try:
197
+ from src.tools.cryptocompare_tool import CryptoCompareTool
198
 
199
+ cryptocompare_tool = CryptoCompareTool()
 
 
 
 
 
 
 
 
 
 
200
 
201
+ # Get market overview using CryptoCompare
202
+ query = "top cryptocurrencies market cap overview"
203
+ data_result = await cryptocompare_tool._arun(query, {"type": "market_overview"})
204
 
205
+ if data_result and not data_result.startswith("❌") and not data_result.startswith("⚠️"):
206
+ return json.dumps({
207
+ "chart_type": "market_overview",
208
+ "data": {
209
+ "source": "cryptocompare",
210
+ "raw_data": data_result
211
+ },
212
+ "config": {
213
+ "title": "Top Cryptocurrencies Market Overview",
214
+ "currency": "USD"
215
+ }
 
 
 
 
216
  })
217
+ else:
218
+ raise Exception("No valid market data from CryptoCompare")
 
 
 
 
 
 
 
219
 
220
  except Exception as e:
221
  logger.error(f"Market overview API failed: {e}")
222
  return await self._get_mock_market_data()
223
+ finally:
224
+ # Cleanup CryptoCompare tool session
225
+ if cryptocompare_tool and hasattr(cryptocompare_tool, 'cleanup'):
226
+ try:
227
+ await cryptocompare_tool.cleanup()
228
+ except Exception:
229
+ pass # Ignore cleanup errors
230
 
231
  async def _get_mock_market_data(self) -> str:
232
  """Fallback mock market data"""
test_chart_tool.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Quick test for ChartDataTool cleanup functionality
4
+ """
5
+
6
+ import asyncio
7
+ import sys
8
+ import os
9
+
10
+ # Add src to path
11
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
12
+
13
+ from src.tools.chart_data_tool import ChartDataTool
14
+
15
+ async def test_chart_tool_cleanup():
16
+ """Test that ChartDataTool works and cleanup doesn't throw errors"""
17
+ print("πŸ§ͺ Testing ChartDataTool...")
18
+
19
+ tool = ChartDataTool()
20
+
21
+ try:
22
+ # Test basic functionality
23
+ print("πŸ“Š Testing price chart data...")
24
+ result = await tool._arun("price_chart", "bitcoin", "7d")
25
+ print(f"βœ… Chart data result: {len(result)} characters")
26
+
27
+ # Test cleanup method exists and works
28
+ print("🧹 Testing cleanup method...")
29
+ await tool.cleanup()
30
+ print("βœ… Cleanup method executed successfully")
31
+
32
+ return True
33
+
34
+ except Exception as e:
35
+ print(f"❌ Test failed: {e}")
36
+ return False
37
+
38
+ async def main():
39
+ success = await test_chart_tool_cleanup()
40
+ if success:
41
+ print("\nπŸŽ‰ All tests passed!")
42
+ return 0
43
+ else:
44
+ print("\n❌ Tests failed!")
45
+ return 1
46
+
47
+ if __name__ == "__main__":
48
+ exit_code = asyncio.run(main())
49
+ sys.exit(exit_code)
test_tool_selection.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test the updated tool selection to ensure we use CryptoCompare/Etherscan instead of CoinGecko
4
+ """
5
+
6
+ import asyncio
7
+ import sys
8
+ import os
9
+
10
+ # Add src to path
11
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
12
+
13
+ from src.agent.research_agent import Web3ResearchAgent
14
+
15
+ async def test_tool_selection():
16
+ """Test that the system prioritizes CryptoCompare and Etherscan over CoinGecko"""
17
+ print("πŸ§ͺ Testing tool selection...")
18
+
19
+ agent = Web3ResearchAgent()
20
+
21
+ if not agent.enabled:
22
+ print("❌ Agent not enabled")
23
+ return False
24
+
25
+ # Check which tools are initialized
26
+ tool_names = [tool.name for tool in agent.tools]
27
+ print(f"πŸ› οΈ Available tools: {tool_names}")
28
+
29
+ # Verify CoinGecko is not in tools (since no API key)
30
+ if 'coingecko_data' in tool_names:
31
+ print("⚠️ CoinGecko tool is still initialized - this may cause API failures")
32
+ else:
33
+ print("βœ… CoinGecko tool properly skipped (no API key)")
34
+
35
+ # Verify we have the working tools
36
+ expected_tools = ['cryptocompare_data', 'etherscan_data', 'defillama_data', 'chart_data_provider']
37
+ working_tools = [tool for tool in expected_tools if tool in tool_names]
38
+ print(f"βœ… Working tools available: {working_tools}")
39
+
40
+ # Test a simple query
41
+ try:
42
+ print("\nπŸ“Š Testing Bitcoin price query...")
43
+ result = await agent.research_query("Analyze Bitcoin price trends", use_gemini=True)
44
+
45
+ if result['success']:
46
+ print("βœ… Query successful!")
47
+ print(f"πŸ“ˆ Result preview: {result['result'][:200]}...")
48
+ print(f"πŸ”§ Tools used: {result.get('metadata', {}).get('tools_used', 'N/A')}")
49
+ return True
50
+ else:
51
+ print(f"❌ Query failed: {result.get('error', 'Unknown error')}")
52
+ return False
53
+
54
+ except Exception as e:
55
+ print(f"❌ Test failed with exception: {e}")
56
+ return False
57
+
58
+ async def main():
59
+ success = await test_tool_selection()
60
+ if success:
61
+ print("\nπŸŽ‰ Tool selection test passed!")
62
+ return 0
63
+ else:
64
+ print("\n❌ Tool selection test failed!")
65
+ return 1
66
+
67
+ if __name__ == "__main__":
68
+ exit_code = asyncio.run(main())
69
+ sys.exit(exit_code)