Priyanshi Saxena commited on
Commit
f104fee
Β·
1 Parent(s): 20eee66

feat: added api tools

Browse files
.env.example CHANGED
@@ -1,5 +1,5 @@
1
  # Google Gemini API Key (Required)
2
- GEMINI_API_KEY=your_gemini_api_key_here
3
 
4
  # CoinGecko API Key (Optional - for higher rate limits)
5
  COINGECKO_API_KEY=your_coingecko_api_key_here
 
1
  # Google Gemini API Key (Required)
2
+ GEMINI_API_KEY=AIzaSyCgOdLBoDEeG7vONzCZgm1l9dJFyIYrZOw
3
 
4
  # CoinGecko API Key (Optional - for higher rate limits)
5
  COINGECKO_API_KEY=your_coingecko_api_key_here
.gitignore ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ env/
7
+ venv/
8
+ .venv/
9
+ pip-log.txt
10
+ pip-delete-this-directory.txt
11
+ .tox/
12
+ .coverage
13
+ .coverage.*
14
+ .cache
15
+ nosetests.xml
16
+ coverage.xml
17
+ *.cover
18
+ *.log
19
+ .git
20
+ .mypy_cache
21
+ .pytest_cache
22
+ .hypothesis
23
+
24
+ .DS_Store
25
+ .env
26
+ .flaskenv
27
+ *.env
28
+
29
+ gradio_queue.db
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
6
+
7
+ COPY requirements.txt .
8
+ RUN pip install --no-cache-dir -r requirements.txt
9
+
10
+ COPY . .
11
+
12
+ EXPOSE 7860
13
+
14
+ ENV PYTHONPATH=/app
15
+ ENV GRADIO_SERVER_NAME=0.0.0.0
16
+ ENV GRADIO_SERVER_PORT=7860
17
+
18
+ CMD ["python", "app.py"]
README.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Web3 Research Co-Pilot
2
+
3
+ AI-powered cryptocurrency research assistant built with LangChain and Gradio.
4
+
5
+ ## Features
6
+
7
+ - **Real-time Market Analysis**: CoinGecko, DeFiLlama, Etherscan integration
8
+ - **AI Research Agent**: Powered by Google Gemini
9
+ - **Interactive Interface**: Modern Gradio UI
10
+ - **Data Visualization**: Price charts and market overviews
11
+ - **AIRAA Integration**: Webhook support for external platforms
12
+
13
+ ## Quick Start
14
+
15
+ 1. **Clone and Setup**
16
+ ```bash
17
+ git clone <repository-url>
18
+ cd web3-research-agent
19
+ pip install -r requirements.txt
20
+ ```
21
+
22
+ 2. **Environment Configuration**
23
+ ```bash
24
+ cp .env.example .env
25
+ # Edit .env with your API keys
26
+ ```
27
+
28
+ 3. **Run Application**
29
+ ```bash
30
+ python app.py
31
+ ```
32
+
33
+ ## Required API Keys
34
+
35
+ - `GEMINI_API_KEY`: Google Gemini AI (required)
36
+ - `ETHERSCAN_API_KEY`: Ethereum blockchain data
37
+ - `COINGECKO_API_KEY`: Cryptocurrency market data (optional)
38
+ - `AIRAA_WEBHOOK_URL`: External integration (optional)
39
+
40
+ ## Deployment
41
+
42
+ ### Docker
43
+ ```bash
44
+ docker build -t web3-research-agent .
45
+ docker run -p 7860:7860 --env-file .env web3-research-agent
46
+ ```
47
+
48
+ ### Hugging Face Spaces
49
+ Upload repository to HF Spaces with environment variables configured.
50
+
51
+ ## Architecture
52
+
53
+ - **Agent**: LangChain-based research agent with memory
54
+ - **Tools**: Modular API integrations (CoinGecko, DeFiLlama, Etherscan)
55
+ - **UI**: Gradio interface with chat and visualization
56
+ - **Cache**: Optimized caching for API responses
57
+ - **Integration**: AIRAA webhook support
58
+
59
+ ## Usage Examples
60
+
61
+ - "Bitcoin price analysis and market sentiment"
62
+ - "Top DeFi protocols by TVL"
63
+ - "Ethereum gas prices and network stats"
64
+ - "Compare BTC vs ETH performance"
65
+
66
+ Built with ❀️ for Web3 research
app.py CHANGED
@@ -1,312 +1,151 @@
1
  import gradio as gr
2
- import os
3
- import json
4
- from src.enhanced_agent import EnhancedResearchAgent
5
- from src.portfolio_analyzer import portfolio_analyzer
6
- from src.visualizations import create_price_chart, create_market_overview, create_comparison_chart
7
- from src.cache_manager import cache_manager
8
  import asyncio
 
 
 
9
 
10
- research_agent = EnhancedResearchAgent()
11
-
12
- async def process_research_query(query, history):
13
- try:
14
- if not query.strip():
15
- return history + [["Please enter a research query.", ""]]
16
-
17
- response = await research_agent.research_with_context(query)
18
- return history + [[query, response]]
19
- except Exception as e:
20
- error_msg = f"Enhanced research failed: {str(e)}"
21
- return history + [[query, error_msg]]
22
-
23
- def research_query_sync(query, history):
24
- return asyncio.run(process_research_query(query, history))
25
-
26
- async def get_market_data():
27
- try:
28
- data = await research_agent.get_comprehensive_market_data()
29
- chart = create_market_overview(data)
30
- return chart
31
- except Exception as e:
32
- return f"Enhanced market data unavailable: {str(e)}"
33
-
34
- def get_market_data_sync():
35
- return asyncio.run(get_market_data())
36
-
37
- async def get_price_chart(symbol):
38
- try:
39
- if not symbol.strip():
40
- return "Please enter a cryptocurrency symbol"
41
-
42
- data = await research_agent.get_price_history(symbol)
43
- chart = create_price_chart(data, symbol)
44
- return chart
45
- except Exception as e:
46
- return f"Chart generation failed: {str(e)}"
47
-
48
- def get_price_chart_sync(symbol):
49
- return asyncio.run(get_price_chart(symbol))
50
 
51
- async def analyze_portfolio_async(portfolio_text):
52
- try:
53
- if not portfolio_text.strip():
54
- return "Please enter your portfolio holdings in JSON format"
55
-
56
- holdings = json.loads(portfolio_text)
57
- analysis = await portfolio_analyzer.analyze_portfolio(holdings)
58
-
59
- result = f"πŸ“Š PORTFOLIO ANALYSIS\n\n"
60
- result += f"πŸ’° Total Value: ${analysis['total_value']:,.2f}\n"
61
- result += f"πŸ“ˆ 24h Change: ${analysis['change_24h']:+,.2f} ({analysis['change_24h_percentage']:+.2f}%)\n\n"
62
-
63
- result += "🏦 ASSET ALLOCATION:\n"
64
- for asset in analysis['asset_allocation'][:10]:
65
- result += f"β€’ {asset['name']} ({asset['symbol']}): {asset['percentage']:.1f}% (${asset['value']:,.2f})\n"
66
-
67
- result += f"\n⚠️ RISK ASSESSMENT:\n"
68
- result += f"Overall Risk: {analysis['risk_metrics']['overall_risk']}\n"
69
- result += f"Diversification Score: {analysis['risk_metrics']['diversification_score']}/10\n"
70
- result += f"Largest Position: {analysis['risk_metrics']['largest_holding_percentage']:.1f}%\n\n"
71
-
72
- result += "πŸ’‘ RECOMMENDATIONS:\n"
73
- for i, rec in enumerate(analysis['recommendations'], 1):
74
- result += f"{i}. {rec}\n"
75
-
76
- return result
77
-
78
- except json.JSONDecodeError:
79
- return "❌ Invalid JSON format. Please use format: [{'symbol': 'BTC', 'amount': 1.0}, {'symbol': 'ETH', 'amount': 10.0}]"
80
- except Exception as e:
81
- return f"❌ Portfolio analysis failed: {str(e)}"
82
 
83
- async def get_defi_analysis_async():
84
- try:
85
- data = await research_agent.get_defi_analysis()
86
-
87
- result = "🏦 DeFi ECOSYSTEM ANALYSIS\n\n"
88
-
89
- if "top_protocols" in data:
90
- result += "πŸ“Š TOP PROTOCOLS BY TVL:\n"
91
- for i, protocol in enumerate(data["top_protocols"][:10], 1):
92
- name = protocol.get("name", "Unknown")
93
- tvl = protocol.get("tvl", 0)
94
- chain = protocol.get("chain", "Unknown")
95
- change = protocol.get("change_1d", 0)
96
- result += f"{i:2d}. {name} ({chain}): ${tvl/1e9:.2f}B TVL ({change:+.2f}%)\n"
97
-
98
- if "top_yields" in data:
99
- result += "\nπŸ’° HIGH YIELD OPPORTUNITIES:\n"
100
- for i, pool in enumerate(data["top_yields"][:5], 1):
101
- symbol = pool.get("symbol", "Unknown")
102
- apy = pool.get("apy", 0)
103
- tvl = pool.get("tvlUsd", 0)
104
- result += f"{i}. {symbol}: {apy:.2f}% APY (${tvl/1e6:.1f}M TVL)\n"
105
-
106
- return result
107
 
108
- except Exception as e:
109
- return f"❌ DeFi analysis failed: {str(e)}"
110
-
111
- def clear_cache():
112
- cache_manager.clear()
113
- return "Cache cleared successfully"
114
-
115
- def analyze_portfolio_sync(portfolio_text):
116
- return asyncio.run(analyze_portfolio_async(portfolio_text))
117
-
118
- def get_defi_analysis_sync():
119
- return asyncio.run(get_defi_analysis_async())
120
-
121
- with gr.Blocks(
122
- title="Web3 Research Co-Pilot",
123
- theme=gr.themes.Soft(primary_hue="blue", secondary_hue="gray"),
124
- css="""
125
- .container { max-width: 1200px; margin: 0 auto; }
126
- .header { text-align: center; padding: 20px; }
127
- .chat-container { min-height: 400px; }
128
- .chart-container { min-height: 500px; }
129
- """
130
- ) as app:
131
 
132
- gr.Markdown("# πŸš€ Web3 Research Co-Pilot", elem_classes=["header"])
133
- gr.Markdown("*AI-powered cryptocurrency research with real-time data integration*", elem_classes=["header"])
 
 
 
 
 
 
 
 
134
 
135
- with gr.Tabs():
136
-
137
- with gr.Tab("πŸ€– Research Chat"):
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  with gr.Row():
139
- with gr.Column(scale=3):
140
- chatbot = gr.Chatbot(
141
- value=[],
142
- height=400,
143
- elem_classes=["chat-container"],
144
- show_label=False
145
- )
146
 
147
  with gr.Row():
148
  query_input = gr.Textbox(
149
- placeholder="Ask about crypto markets, prices, trends, analysis...",
150
- scale=4,
151
- show_label=False
152
  )
153
- submit_btn = gr.Button("Research", variant="primary")
154
 
155
- gr.Examples(
156
- examples=[
157
- "What's the current Bitcoin price and trend?",
158
- "Compare Ethereum vs Solana DeFi ecosystems",
159
- "Analyze top DeFi protocols and TVL trends",
160
- "What are the trending coins and latest crypto news?",
161
- "Show me high-yield DeFi opportunities with risk analysis"
162
- ],
163
- inputs=query_input
164
- )
165
 
166
  with gr.Column(scale=1):
167
- gr.Markdown("### πŸ“Š Quick Actions")
168
- market_btn = gr.Button("Market Overview", size="sm")
169
- market_output = gr.HTML()
170
 
171
- clear_btn = gr.Button("Clear Cache", size="sm", variant="secondary")
172
- clear_output = gr.Textbox(show_label=False, interactive=False)
173
-
174
- with gr.Tab("πŸ“ˆ Price Charts"):
175
- with gr.Row():
176
- symbol_input = gr.Textbox(
177
- label="Cryptocurrency Symbol",
178
- placeholder="BTC, ETH, SOL, etc.",
179
- value="BTC"
180
- )
181
- chart_btn = gr.Button("Generate Chart", variant="primary")
182
-
183
- chart_output = gr.HTML(elem_classes=["chart-container"])
 
 
 
 
 
 
 
184
 
185
- gr.Examples(
186
- examples=["BTC", "ETH", "SOL", "ADA", "DOT"],
187
- inputs=symbol_input
 
188
  )
189
-
190
- with gr.Tab("πŸ’Ό Portfolio Analysis"):
191
- with gr.Row():
192
- with gr.Column(scale=1):
193
- portfolio_input = gr.Textbox(
194
- label="Portfolio Holdings (JSON Format)",
195
- placeholder='[{"symbol": "BTC", "amount": 1.0}, {"symbol": "ETH", "amount": 10.0}, {"symbol": "SOL", "amount": 50.0}]',
196
- lines=5,
197
- info="Enter your crypto holdings in JSON format with symbol and amount"
198
- )
199
- portfolio_btn = gr.Button("Analyze Portfolio", variant="primary")
200
-
201
- with gr.Column(scale=2):
202
- portfolio_output = gr.Textbox(
203
- label="Portfolio Analysis Results",
204
- lines=20,
205
- show_copy_button=True,
206
- interactive=False
207
- )
208
 
209
- gr.Examples(
210
- examples=[
211
- '[{"symbol": "BTC", "amount": 0.5}, {"symbol": "ETH", "amount": 5.0}]',
212
- '[{"symbol": "BTC", "amount": 1.0}, {"symbol": "ETH", "amount": 10.0}, {"symbol": "SOL", "amount": 100.0}]'
213
- ],
214
- inputs=portfolio_input
215
- )
216
-
217
- with gr.Tab("🏦 DeFi Analytics"):
218
- defi_btn = gr.Button("Get DeFi Ecosystem Analysis", variant="primary", size="lg")
219
- defi_output = gr.Textbox(
220
- label="DeFi Analysis Results",
221
- lines=25,
222
- show_copy_button=True,
223
- interactive=False,
224
- info="Comprehensive DeFi protocol analysis with TVL data and yield opportunities"
225
  )
226
-
227
- with gr.Tab("ℹ️ About"):
228
- gr.Markdown("""
229
- ## πŸš€ Enhanced Features
230
- - **Multi-API Integration**: CoinGecko, CryptoCompare, and DeFiLlama data sources
231
- - **AI-Powered Analysis**: Google Gemini 2.5 Flash with contextual market intelligence
232
- - **DeFi Analytics**: Protocol TVL analysis, yield farming opportunities, and ecosystem insights
233
- - **Portfolio Analysis**: Risk assessment, diversification scoring, and personalized recommendations
234
- - **News Integration**: Real-time crypto news aggregation and sentiment analysis
235
- - **Interactive Charts**: Advanced price visualizations with technical indicators
236
- - **Smart Caching**: Optimized performance with intelligent data caching (TTL-based)
237
- - **Rate Limiting**: Respectful API usage with automatic throttling
238
 
239
- ## 🎯 Core Capabilities
240
- 1. **Enhanced Research Chat**: Context-aware conversations with real-time market data integration
241
- 2. **Advanced Price Charts**: Interactive visualizations with 30-day historical data
242
- 3. **Portfolio Optimization**: Comprehensive portfolio analysis with risk metrics and recommendations
243
- 4. **DeFi Intelligence**: Protocol rankings, TVL trends, and high-yield opportunity identification
244
- 5. **Market Intelligence**: Global market metrics, trending assets, and breaking news analysis
245
 
246
- ## πŸ’‘ Query Examples
247
- - "Analyze Bitcoin vs Ethereum DeFi ecosystem performance"
248
- - "What are the top DeFi protocols by TVL with lowest risk?"
249
- - "Show me high-yield farming opportunities under 15% volatility"
250
- - "Compare my portfolio risk to market benchmarks"
251
- - "Latest crypto news impact on altcoin market sentiment"
252
- - "Which Layer 1 protocols have strongest DeFi adoption?"
253
 
254
- ## πŸ”§ Technical Architecture
255
- - **Async Processing**: Non-blocking operations for optimal performance
256
- - **Error Handling**: Comprehensive exception management with graceful degradation
257
- - **Symbol Mapping**: Intelligent cryptocurrency identifier resolution
258
- - **Data Validation**: Input sanitization and response formatting
259
- """)
260
-
261
- submit_btn.click(
262
- research_query_sync,
263
- inputs=[query_input, chatbot],
264
- outputs=chatbot
265
- ).then(lambda: "", outputs=query_input)
266
-
267
- query_input.submit(
268
- research_query_sync,
269
- inputs=[query_input, chatbot],
270
- outputs=chatbot
271
- ).then(lambda: "", outputs=query_input)
272
-
273
- chart_btn.click(
274
- get_price_chart_sync,
275
- inputs=symbol_input,
276
- outputs=chart_output
277
- )
278
-
279
- symbol_input.submit(
280
- get_price_chart_sync,
281
- inputs=symbol_input,
282
- outputs=chart_output
283
- )
284
-
285
- market_btn.click(
286
- get_market_data_sync,
287
- outputs=market_output
288
- )
289
-
290
- clear_btn.click(
291
- clear_cache,
292
- outputs=clear_output
293
- )
294
-
295
- portfolio_btn.click(
296
- analyze_portfolio_sync,
297
- inputs=portfolio_input,
298
- outputs=portfolio_output
299
- )
300
-
301
- defi_btn.click(
302
- get_defi_analysis_sync,
303
- outputs=defi_output
304
- )
305
 
306
  if __name__ == "__main__":
307
- app.launch(
 
 
308
  server_name="0.0.0.0",
309
- server_port=5000,
310
- share=False,
311
- show_error=True
312
  )
 
1
  import gradio as gr
 
 
 
 
 
 
2
  import asyncio
3
+ import json
4
+ from datetime import datetime
5
+ from typing import List, Tuple
6
 
7
+ from src.agent.research_agent import Web3ResearchAgent
8
+ from src.api.airaa_integration import AIRAAIntegration
9
+ from src.visualizations import create_price_chart, create_market_overview
10
+ from src.utils.logger import get_logger
11
+ from src.utils.config import config
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ logger = get_logger(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ class Web3CoPilotApp:
16
+ def __init__(self):
17
+ try:
18
+ self.agent = Web3ResearchAgent()
19
+ self.airaa = AIRAAIntegration()
20
+ except Exception as e:
21
+ logger.error(f"App initialization failed: {e}")
22
+ raise
23
+
24
+ async def process_query(self, query: str, history: List[Tuple[str, str]]):
25
+ if not query.strip():
26
+ yield history, ""
27
+ return
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ try:
30
+ history.append((query, "πŸ” Researching..."))
31
+ yield history, ""
32
+
33
+ result = await self.agent.research_query(query)
34
+
35
+ if result["success"]:
36
+ response = result["result"]
37
+ sources = ", ".join(result.get("sources", []))
38
+ response += f"\n\n---\nπŸ“Š **Sources**: {sources}\n⏰ **Generated**: {datetime.now().strftime('%H:%M:%S')}"
39
+
40
+ if config.AIRAA_WEBHOOK_URL:
41
+ asyncio.create_task(self.airaa.send_research_data(result))
42
+ else:
43
+ response = f"❌ Error: {result.get('error', 'Research failed')}"
44
+
45
+ history[-1] = (query, response)
46
+ yield history, ""
47
+
48
+ except Exception as e:
49
+ logger.error(f"Query error: {e}")
50
+ history[-1] = (query, f"❌ System error: {str(e)}")
51
+ yield history, ""
52
 
53
+ def get_chart_data(self, symbol: str):
54
+ try:
55
+ if not symbol.strip():
56
+ return "Please enter a symbol"
57
+
58
+ data = asyncio.run(self.agent.get_price_history(symbol))
59
+ return create_price_chart(data, symbol)
60
+ except Exception as e:
61
+ logger.error(f"Chart error: {e}")
62
+ return f"Chart unavailable: {str(e)}"
63
 
64
+ def get_market_overview(self):
65
+ try:
66
+ data = asyncio.run(self.agent.get_comprehensive_market_data())
67
+ return create_market_overview(data)
68
+ except Exception as e:
69
+ logger.error(f"Market overview error: {e}")
70
+ return f"Market data unavailable: {str(e)}"
71
+
72
+ def create_interface(self):
73
+ with gr.Blocks(title=config.UI_TITLE, theme=gr.themes.Soft()) as demo:
74
+ gr.Markdown(f"""
75
+ # πŸš€ {config.UI_TITLE}
76
+ {config.UI_DESCRIPTION}
77
+ **Powered by**: Gemini AI β€’ CoinGecko β€’ DeFiLlama β€’ Etherscan
78
+ """)
79
+
80
  with gr.Row():
81
+ with gr.Column(scale=2):
82
+ chatbot = gr.Chatbot(label="Research Assistant", height=650)
 
 
 
 
 
83
 
84
  with gr.Row():
85
  query_input = gr.Textbox(
86
+ placeholder="Ask about crypto markets, DeFi protocols, or on-chain data...",
87
+ label="Research Query", lines=2
 
88
  )
89
+ submit_btn = gr.Button("πŸ” Research", variant="primary")
90
 
91
+ clear_btn = gr.Button("πŸ—‘οΈ Clear", variant="secondary")
 
 
 
 
 
 
 
 
 
92
 
93
  with gr.Column(scale=1):
94
+ gr.Markdown("### πŸ’‘ Example Queries")
 
 
95
 
96
+ examples = [
97
+ "Bitcoin price analysis",
98
+ "Top DeFi protocols by TVL",
99
+ "Ethereum vs Solana comparison",
100
+ "Trending cryptocurrencies",
101
+ "DeFi yield opportunities"
102
+ ]
103
+
104
+ for example in examples:
105
+ gr.Button(example, size="sm").click(
106
+ lambda x=example: x, outputs=query_input
107
+ )
108
+
109
+ gr.Markdown("### πŸ“ˆ Visualizations")
110
+ chart_output = gr.Plot(label="Charts")
111
+
112
+ symbol_input = gr.Textbox(placeholder="BTC, ETH, SOL...", label="Chart Symbol")
113
+ chart_btn = gr.Button("πŸ“Š Generate Chart")
114
+
115
+ market_btn = gr.Button("🌐 Market Overview")
116
 
117
+ submit_btn.click(
118
+ self.process_query,
119
+ inputs=[query_input, chatbot],
120
+ outputs=[chatbot, query_input]
121
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
+ query_input.submit(
124
+ self.process_query,
125
+ inputs=[query_input, chatbot],
126
+ outputs=[chatbot, query_input]
 
 
 
 
 
 
 
 
 
 
 
 
127
  )
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ clear_btn.click(lambda: ([], ""), outputs=[chatbot, query_input])
 
 
 
 
 
130
 
131
+ chart_btn.click(
132
+ self.get_chart_data,
133
+ inputs=symbol_input,
134
+ outputs=chart_output
135
+ )
 
 
136
 
137
+ market_btn.click(
138
+ self.get_market_overview,
139
+ outputs=chart_output
140
+ )
141
+
142
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  if __name__ == "__main__":
145
+ app = Web3CoPilotApp()
146
+ interface = app.create_interface()
147
+ interface.launch(
148
  server_name="0.0.0.0",
149
+ server_port=7860,
150
+ share=False
 
151
  )
minimal_test.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+
4
+ sys.path.insert(0, os.path.dirname(__file__))
5
+
6
+ def minimal_test():
7
+ try:
8
+ print("Testing minimal imports...")
9
+
10
+ from src.utils.config import config
11
+ print("βœ… Config imported")
12
+
13
+ from src.utils.logger import get_logger
14
+ print("βœ… Logger imported")
15
+
16
+ from src.tools.base_tool import BaseWeb3Tool
17
+ print("βœ… Base tool imported")
18
+
19
+ from src.tools.coingecko_tool import CoinGeckoTool
20
+ tool = CoinGeckoTool()
21
+ print("βœ… CoinGecko tool created")
22
+
23
+ from src.agent.research_agent import Web3ResearchAgent
24
+ print("βœ… Research agent imported")
25
+
26
+ print("πŸŽ‰ All core components working!")
27
+ return True
28
+
29
+ except Exception as e:
30
+ print(f"❌ Error: {e}")
31
+ import traceback
32
+ traceback.print_exc()
33
+ return False
34
+
35
+ if __name__ == "__main__":
36
+ minimal_test()
requirements.txt CHANGED
@@ -1,7 +1,14 @@
1
- aiohttp==3.10.11
2
- gradio==5.8.0
3
- google-generativeai==0.8.3
4
- plotly==5.24.1
5
- pandas==2.2.3
6
- python-dotenv==1.0.1
7
- asyncio-throttle==1.0.2
 
 
 
 
 
 
 
 
1
+ langchain
2
+ langchain-google-genai
3
+ langchain-community
4
+ gradio
5
+ aiohttp
6
+ tenacity
7
+ plotly
8
+ pandas
9
+ numpy
10
+ pydantic
11
+ python-dotenv
12
+ diskcache
13
+ google-generativeai
14
+ asyncio-throttle
run.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Web3 Research Co-Pilot Application
5
+ Complete production-ready crypto research assistant powered by AI
6
+ """
7
+
8
+ import sys
9
+ import os
10
+ import asyncio
11
+ from pathlib import Path
12
+
13
+ # Add project root to path
14
+ project_root = Path(__file__).parent
15
+ sys.path.insert(0, str(project_root))
16
+
17
+ def main():
18
+ print("πŸš€ Starting Web3 Research Co-Pilot...")
19
+
20
+ try:
21
+ from app import Web3CoPilotApp
22
+
23
+ app = Web3CoPilotApp()
24
+ interface = app.create_interface()
25
+
26
+ print("βœ… Application initialized successfully!")
27
+ print("🌐 Launching web interface...")
28
+ print("πŸ“ Local URL: http://localhost:7860")
29
+
30
+ interface.launch(
31
+ server_name="0.0.0.0",
32
+ server_port=7860,
33
+ share=False,
34
+ show_api=False,
35
+ quiet=False
36
+ )
37
+
38
+ except ImportError as e:
39
+ print(f"❌ Import error: {e}")
40
+ print("Please install dependencies: pip install -r requirements.txt")
41
+ sys.exit(1)
42
+ except Exception as e:
43
+ print(f"❌ Application error: {e}")
44
+ sys.exit(1)
45
+
46
+ if __name__ == "__main__":
47
+ main()
src/agent/__init__.py ADDED
File without changes
src/agent/query_planner.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any, List
2
+ import asyncio
3
+
4
+ class QueryPlanner:
5
+ def __init__(self, llm):
6
+ self.llm = llm
7
+
8
+ async def plan_research(self, query: str) -> Dict[str, Any]:
9
+ try:
10
+ planning_prompt = f"""
11
+ Analyze this research query and create a structured plan:
12
+ Query: {query}
13
+
14
+ Determine:
15
+ 1. What type of analysis is needed (price, market, defi, comparison, etc.)
16
+ 2. Which data sources would be most relevant
17
+ 3. What specific steps should be taken
18
+ 4. Priority focus area
19
+
20
+ Respond in JSON format with keys: type, steps, priority, data_sources
21
+ """
22
+
23
+ response = await asyncio.to_thread(
24
+ self.llm.invoke,
25
+ planning_prompt
26
+ )
27
+
28
+ # Simple categorization based on keywords
29
+ query_lower = query.lower()
30
+ plan = {
31
+ "type": self._categorize_query(query_lower),
32
+ "steps": self._generate_steps(query_lower),
33
+ "priority": self._determine_priority(query_lower),
34
+ "data_sources": self._identify_sources(query_lower)
35
+ }
36
+
37
+ return plan
38
+
39
+ except Exception:
40
+ return {
41
+ "type": "general",
42
+ "steps": ["Analyze query", "Gather data", "Provide insights"],
43
+ "priority": "general analysis",
44
+ "data_sources": ["coingecko", "defillama"]
45
+ }
46
+
47
+ def _categorize_query(self, query: str) -> str:
48
+ if any(word in query for word in ["price", "chart", "value"]):
49
+ return "price_analysis"
50
+ elif any(word in query for word in ["defi", "tvl", "protocol", "yield"]):
51
+ return "defi_analysis"
52
+ elif any(word in query for word in ["compare", "vs", "versus"]):
53
+ return "comparison"
54
+ elif any(word in query for word in ["market", "overview", "trending"]):
55
+ return "market_overview"
56
+ else:
57
+ return "general"
58
+
59
+ def _generate_steps(self, query: str) -> List[str]:
60
+ steps = ["Gather relevant data"]
61
+
62
+ if "price" in query:
63
+ steps.extend(["Get current price data", "Analyze price trends"])
64
+ if "defi" in query:
65
+ steps.extend(["Fetch DeFi protocol data", "Analyze TVL trends"])
66
+ if any(word in query for word in ["compare", "vs"]):
67
+ steps.append("Perform comparative analysis")
68
+
69
+ steps.append("Synthesize insights and recommendations")
70
+ return steps
71
+
72
+ def _determine_priority(self, query: str) -> str:
73
+ if "urgent" in query or "now" in query:
74
+ return "high"
75
+ elif "overview" in query:
76
+ return "comprehensive"
77
+ else:
78
+ return "standard"
79
+
80
+ def _identify_sources(self, query: str) -> List[str]:
81
+ sources = []
82
+ if any(word in query for word in ["price", "market", "coin", "token"]):
83
+ sources.append("coingecko")
84
+ if any(word in query for word in ["defi", "tvl", "protocol"]):
85
+ sources.append("defillama")
86
+ if any(word in query for word in ["transaction", "address", "gas"]):
87
+ sources.append("etherscan")
88
+
89
+ return sources if sources else ["coingecko", "defillama"]
src/agent/research_agent.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
2
+ from langchain_google_genai import ChatGoogleGenerativeAI
3
+ from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
4
+ from langchain.memory import ConversationBufferWindowMemory
5
+ from typing import List, Dict, Any
6
+ import asyncio
7
+ from datetime import datetime
8
+
9
+ from src.tools.coingecko_tool import CoinGeckoTool
10
+ from src.tools.defillama_tool import DeFiLlamaTool
11
+ from src.tools.etherscan_tool import EtherscanTool
12
+ from src.agent.query_planner import QueryPlanner
13
+ from src.config import config
14
+ from src.utils.logger import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+ class Web3ResearchAgent:
19
+ def __init__(self):
20
+ try:
21
+ if not config.GEMINI_API_KEY:
22
+ raise ValueError("GEMINI_API_KEY not configured")
23
+
24
+ self.llm = ChatGoogleGenerativeAI(
25
+ model="gemini-1.5-flash",
26
+ google_api_key=config.GEMINI_API_KEY,
27
+ temperature=0.1,
28
+ max_tokens=2048
29
+ )
30
+
31
+ self.tools = [CoinGeckoTool(), DeFiLlamaTool(), EtherscanTool()]
32
+ self.query_planner = QueryPlanner(self.llm)
33
+ self.memory = ConversationBufferWindowMemory(
34
+ memory_key="chat_history", return_messages=True, k=10
35
+ )
36
+
37
+ self.agent = self._create_agent()
38
+ self.executor = AgentExecutor(
39
+ agent=self.agent, tools=self.tools, memory=self.memory,
40
+ verbose=False, max_iterations=5, handle_parsing_errors=True
41
+ )
42
+ except Exception as e:
43
+ logger.error(f"Agent init failed: {e}")
44
+ raise
45
+
46
+ def _create_agent(self):
47
+ prompt = ChatPromptTemplate.from_messages([
48
+ ("system", """You are an expert Web3 research assistant. Use available tools to provide accurate,
49
+ data-driven insights about cryptocurrency markets, DeFi protocols, and blockchain data.
50
+
51
+ Format responses with clear sections, emojis, and actionable insights."""),
52
+ MessagesPlaceholder("chat_history"),
53
+ ("human", "{input}"),
54
+ MessagesPlaceholder("agent_scratchpad")
55
+ ])
56
+
57
+ return create_tool_calling_agent(self.llm, self.tools, prompt)
58
+
59
+ async def research_query(self, query: str) -> Dict[str, Any]:
60
+ try:
61
+ logger.info(f"Processing: {query}")
62
+
63
+ research_plan = await self.query_planner.plan_research(query)
64
+
65
+ enhanced_query = f"""
66
+ Research Query: {query}
67
+ Research Plan: {research_plan.get('steps', [])}
68
+ Priority: {research_plan.get('priority', 'general')}
69
+
70
+ Execute systematic research and provide comprehensive analysis.
71
+ """
72
+
73
+ result = await asyncio.to_thread(
74
+ self.executor.invoke, {"input": enhanced_query}
75
+ )
76
+
77
+ return {
78
+ "success": True,
79
+ "query": query,
80
+ "research_plan": research_plan,
81
+ "result": result.get("output", "No response"),
82
+ "sources": self._extract_sources(result.get("output", "")),
83
+ "metadata": {
84
+ "tools_used": [tool.name for tool in self.tools],
85
+ "timestamp": datetime.now().isoformat()
86
+ }
87
+ }
88
+
89
+ except Exception as e:
90
+ logger.error(f"Research error: {e}")
91
+ return {
92
+ "success": False,
93
+ "query": query,
94
+ "error": str(e),
95
+ "metadata": {"timestamp": datetime.now().isoformat()}
96
+ }
97
+
98
+ async def get_price_history(self, symbol: str, days: int = 30) -> Dict[str, Any]:
99
+ try:
100
+ coingecko_tool = next(t for t in self.tools if isinstance(t, CoinGeckoTool))
101
+ return await coingecko_tool._arun(symbol, {"type": "price_history", "days": days})
102
+ except Exception as e:
103
+ logger.error(f"Price history error: {e}")
104
+ return {}
105
+
106
+ async def get_comprehensive_market_data(self) -> Dict[str, Any]:
107
+ try:
108
+ tasks = []
109
+ for tool in self.tools:
110
+ if isinstance(tool, CoinGeckoTool):
111
+ tasks.append(tool._arun("", {"type": "market_overview"}))
112
+ elif isinstance(tool, DeFiLlamaTool):
113
+ tasks.append(tool._arun("", {"type": "tvl_overview"}))
114
+
115
+ results = await asyncio.gather(*tasks, return_exceptions=True)
116
+
117
+ data = {}
118
+ for i, result in enumerate(results):
119
+ if not isinstance(result, Exception):
120
+ if i == 0:
121
+ data["market"] = result
122
+ elif i == 1:
123
+ data["defi"] = result
124
+
125
+ return data
126
+ except Exception as e:
127
+ logger.error(f"Market data error: {e}")
128
+ return {}
129
+
130
+ def _extract_sources(self, result_text: str) -> List[str]:
131
+ sources = []
132
+ if "CoinGecko" in result_text or "coingecko" in result_text.lower():
133
+ sources.append("CoinGecko API")
134
+ if "DeFiLlama" in result_text or "defillama" in result_text.lower():
135
+ sources.append("DeFiLlama API")
136
+ if "Etherscan" in result_text or "etherscan" in result_text.lower():
137
+ sources.append("Etherscan API")
138
+ return sources
src/api/__init__.py ADDED
File without changes
src/api/airaa_integration.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import re
3
+ from typing import Dict, Any, List
4
+ from src.config import config
5
+ from src.utils.logger import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+ class AIRAAIntegration:
10
+ def __init__(self):
11
+ self.webhook_url = config.AIRAA_WEBHOOK_URL
12
+ self.api_key = config.AIRAA_API_KEY
13
+ self.enabled = bool(self.webhook_url)
14
+
15
+ async def send_research_data(self, research_result: Dict[str, Any]) -> bool:
16
+ if not self.enabled:
17
+ return False
18
+
19
+ try:
20
+ payload = self._format_for_airaa(research_result)
21
+
22
+ headers = {"Content-Type": "application/json"}
23
+ if self.api_key:
24
+ headers["Authorization"] = f"Bearer {self.api_key}"
25
+
26
+ timeout = aiohttp.ClientTimeout(total=30)
27
+ async with aiohttp.ClientSession(timeout=timeout) as session:
28
+ async with session.post(
29
+ self.webhook_url, json=payload, headers=headers
30
+ ) as response:
31
+ success = response.status == 200
32
+ if success:
33
+ logger.info("Data sent to AIRAA successfully")
34
+ else:
35
+ logger.warning(f"AIRAA webhook returned {response.status}")
36
+ return success
37
+
38
+ except Exception as e:
39
+ logger.error(f"AIRAA integration failed: {e}")
40
+ return False
41
+
42
+ def _format_for_airaa(self, result: Dict[str, Any]) -> Dict[str, Any]:
43
+ return {
44
+ "source": "web3-research-copilot",
45
+ "timestamp": result["metadata"]["timestamp"],
46
+ "query": result["query"],
47
+ "research_plan": result.get("research_plan", {}),
48
+ "findings": result["result"],
49
+ "data_sources": result["sources"],
50
+ "confidence_score": self._calculate_confidence(result),
51
+ "tags": self._extract_tags(result["query"]),
52
+ "structured_data": self._extract_structured_data(result["result"])
53
+ }
54
+
55
+ def _calculate_confidence(self, result: Dict[str, Any]) -> float:
56
+ base_score = 0.7
57
+ source_boost = min(len(result.get("sources", [])) * 0.1, 0.3)
58
+ error_penalty = 0.3 if not result.get("success", True) else 0
59
+ return max(0.0, min(1.0, base_score + source_boost - error_penalty))
60
+
61
+ def _extract_tags(self, query: str) -> List[str]:
62
+ tags = []
63
+ query_lower = query.lower()
64
+
65
+ token_patterns = {
66
+ "bitcoin": ["bitcoin", "btc"],
67
+ "ethereum": ["ethereum", "eth"],
68
+ "defi": ["defi", "defillama", "protocol", "tvl"],
69
+ "market-analysis": ["price", "market", "analysis"],
70
+ "trading-volume": ["volume", "trading"]
71
+ }
72
+
73
+ for tag, patterns in token_patterns.items():
74
+ if any(pattern in query_lower for pattern in patterns):
75
+ tags.append(tag)
76
+
77
+ return tags
78
+
79
+ def _extract_structured_data(self, result_text: str) -> Dict[str, Any]:
80
+ structured = {}
81
+
82
+ price_pattern = r'\$([0-9,]+\.?[0-9]*)'
83
+ percentage_pattern = r'([+-]?[0-9]+\.?[0-9]*)%'
84
+
85
+ prices = re.findall(price_pattern, result_text)
86
+ percentages = re.findall(percentage_pattern, result_text)
87
+
88
+ if prices:
89
+ structured["prices"] = [float(p.replace(',', '')) for p in prices[:5]]
90
+ if percentages:
91
+ structured["percentages"] = [float(p) for p in percentages[:5]]
92
+
93
+ return structured
src/config.py CHANGED
@@ -7,6 +7,7 @@ class Config:
7
  GEMINI_API_KEY: str = os.getenv("GEMINI_API_KEY", "")
8
  COINGECKO_API_KEY: Optional[str] = os.getenv("COINGECKO_API_KEY")
9
  CRYPTOCOMPARE_API_KEY: Optional[str] = os.getenv("CRYPTOCOMPARE_API_KEY")
 
10
 
11
  COINGECKO_BASE_URL: str = "https://api.coingecko.com/api/v3"
12
  CRYPTOCOMPARE_BASE_URL: str = "https://min-api.cryptocompare.com/data"
@@ -15,5 +16,11 @@ class Config:
15
  RATE_LIMIT_DELAY: float = 2.0
16
  MAX_RETRIES: int = 3
17
  REQUEST_TIMEOUT: int = 30
 
 
 
 
 
 
18
 
19
  config = Config()
 
7
  GEMINI_API_KEY: str = os.getenv("GEMINI_API_KEY", "")
8
  COINGECKO_API_KEY: Optional[str] = os.getenv("COINGECKO_API_KEY")
9
  CRYPTOCOMPARE_API_KEY: Optional[str] = os.getenv("CRYPTOCOMPARE_API_KEY")
10
+ ETHERSCAN_API_KEY: str = os.getenv("ETHERSCAN_API_KEY", "")
11
 
12
  COINGECKO_BASE_URL: str = "https://api.coingecko.com/api/v3"
13
  CRYPTOCOMPARE_BASE_URL: str = "https://min-api.cryptocompare.com/data"
 
16
  RATE_LIMIT_DELAY: float = 2.0
17
  MAX_RETRIES: int = 3
18
  REQUEST_TIMEOUT: int = 30
19
+
20
+ UI_TITLE: str = "Web3 Research Co-Pilot"
21
+ UI_DESCRIPTION: str = "AI-powered crypto research assistant"
22
+
23
+ AIRAA_WEBHOOK_URL: Optional[str] = os.getenv("AIRAA_WEBHOOK_URL")
24
+ AIRAA_API_KEY: Optional[str] = os.getenv("AIRAA_API_KEY")
25
 
26
  config = Config()
src/tools/__init__.py ADDED
File without changes
src/tools/base_tool.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+ from typing import Dict, Any, Optional
3
+ from langchain.tools import BaseTool
4
+ from pydantic import BaseModel, Field, PrivateAttr
5
+ import asyncio
6
+ import aiohttp
7
+ from tenacity import retry, stop_after_attempt, wait_exponential
8
+ from src.utils.logger import get_logger
9
+
10
+ logger = get_logger(__name__)
11
+
12
+ class Web3ToolInput(BaseModel):
13
+ query: str = Field(description="Search query or parameter")
14
+ filters: Optional[Dict[str, Any]] = Field(default=None, description="Additional filters")
15
+
16
+ class BaseWeb3Tool(BaseTool, ABC):
17
+ name: str = "base_web3_tool"
18
+ description: str = "Base Web3 tool"
19
+ args_schema: type[BaseModel] = Web3ToolInput
20
+
21
+ _session: Optional[aiohttp.ClientSession] = PrivateAttr(default=None)
22
+
23
+ def __init__(self, **kwargs):
24
+ super().__init__(**kwargs)
25
+ self._session = None
26
+
27
+ async def get_session(self):
28
+ if not self._session:
29
+ timeout = aiohttp.ClientTimeout(total=30)
30
+ self._session = aiohttp.ClientSession(timeout=timeout)
31
+ return self._session
32
+
33
+ @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=8))
34
+ async def make_request(self, url: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
35
+ session = await self.get_session()
36
+ try:
37
+ async with session.get(url, params=params or {}) as response:
38
+ if response.status == 200:
39
+ return await response.json()
40
+ elif response.status == 429:
41
+ await asyncio.sleep(2)
42
+ raise aiohttp.ClientResponseError(
43
+ request_info=response.request_info,
44
+ history=response.history,
45
+ status=response.status
46
+ )
47
+ else:
48
+ response.raise_for_status()
49
+ except Exception as e:
50
+ logger.error(f"Request failed: {e}")
51
+ raise
52
+
53
+ def _run(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
54
+ return asyncio.run(self._arun(query, filters))
55
+
56
+ @abstractmethod
57
+ async def _arun(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
58
+ pass
59
+
60
+ async def cleanup(self):
61
+ if self._session:
62
+ await self._session.close()
63
+ if self.session:
64
+ await self.session.close()
src/tools/coingecko_tool.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any, Optional
2
+ from pydantic import BaseModel, PrivateAttr
3
+ from src.tools.base_tool import BaseWeb3Tool, Web3ToolInput
4
+ from src.utils.config import config
5
+
6
+ class CoinGeckoTool(BaseWeb3Tool):
7
+ name: str = "coingecko_data"
8
+ description: str = """Get cryptocurrency price, volume, market cap and trend data from CoinGecko.
9
+ Useful for: price analysis, market rankings, volume trends, price changes.
10
+ Input: cryptocurrency name/symbol (bitcoin, ethereum, BTC, ETH) or market query."""
11
+ args_schema: type[BaseModel] = Web3ToolInput
12
+
13
+ _base_url: str = PrivateAttr(default="https://api.coingecko.com/api/v3")
14
+ _symbol_map: Dict[str, str] = PrivateAttr(default_factory=lambda: {
15
+ "btc": "bitcoin", "eth": "ethereum", "sol": "solana", "ada": "cardano",
16
+ "dot": "polkadot", "bnb": "binancecoin", "usdc": "usd-coin",
17
+ "usdt": "tether", "xrp": "ripple", "avax": "avalanche-2",
18
+ "link": "chainlink", "matic": "matic-network", "uni": "uniswap"
19
+ })
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+
24
+ async def _arun(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
25
+ try:
26
+ filters = filters or {}
27
+
28
+ if filters.get("type") == "trending":
29
+ return await self._get_trending()
30
+ elif filters.get("type") == "market_overview":
31
+ return await self._get_market_overview()
32
+ elif filters.get("type") == "price_history":
33
+ return await self._get_price_history(query, filters.get("days", 30))
34
+ else:
35
+ return await self._get_coin_data(query)
36
+
37
+ except Exception as e:
38
+ return f"CoinGecko error: {str(e)}"
39
+ async def _get_trending(self) -> str:
40
+ data = await self.make_request(f"{self._base_url}/search/trending")
41
+ data = await self.make_request(f"{self.base_url}/search/trending")
42
+
43
+ trending = data.get("coins", [])[:5]
44
+ result = "πŸ”₯ **Trending Cryptocurrencies:**\n\n"
45
+
46
+ for i, coin in enumerate(trending, 1):
47
+ item = coin.get("item", {})
48
+ name = item.get("name", "Unknown")
49
+ symbol = item.get("symbol", "").upper()
50
+ rank = item.get("market_cap_rank", "N/A")
51
+ result += f"{i}. **{name} ({symbol})** - Rank #{rank}\n"
52
+
53
+ return result
54
+
55
+ async def _get_market_overview(self) -> str:
56
+ data = await self.make_request(f"{self._base_url}/coins/markets", params)10, "page": 1}
57
+ data = await self.make_request(f"{self.base_url}/coins/markets", params)
58
+
59
+ result = "πŸ“Š **Top Cryptocurrencies by Market Cap:**\n\n"
60
+
61
+ for coin in data[:10]:
62
+ name = coin.get("name", "Unknown")
63
+ symbol = coin.get("symbol", "").upper()
64
+ price = coin.get("current_price", 0)
65
+ change = coin.get("price_change_percentage_24h", 0)
66
+ mcap = coin.get("market_cap", 0)
67
+
68
+ emoji = "πŸ“ˆ" if change >= 0 else "πŸ“‰"
69
+ result += f"{emoji} **{name} ({symbol})**: ${price:,.4f} ({change:+.2f}%) | MCap: ${mcap/1e9:.2f}B\n"
70
+
71
+ return result
72
+
73
+ coin_id = self._symbol_map.get(query.lower(), query.lower())
74
+ coin_id = self.symbol_map.get(query.lower(), query.lower())
75
+
76
+ params = {
77
+ "ids": coin_id,
78
+ "vs_currencies": "usd",
79
+ "include_24hr_change": "true",
80
+ "include_24hr_vol": "true",
81
+ "include_market_cap": "true"
82
+ }
83
+ data = await self.make_request(f"{self._base_url}/simple/price", params)
84
+ data = await self.make_request(f"{self.base_url}/simple/price", params)
85
+
86
+ if coin_id not in data:
87
+ return f"No data found for {query}"
88
+
89
+ coin_data = data[coin_id]
90
+ price = coin_data.get("usd", 0)
91
+ change = coin_data.get("usd_24h_change", 0)
92
+ volume = coin_data.get("usd_24h_vol", 0)
93
+ mcap = coin_data.get("usd_market_cap", 0)
94
+
95
+ emoji = "πŸ“ˆ" if change >= 0 else "πŸ“‰"
96
+
97
+ result = f"πŸ’° **{query.upper()} Market Data:**\n\n"
98
+ result += f"{emoji} **Price**: ${price:,.4f}\n"
99
+ result += f"πŸ“Š **24h Change**: {change:+.2f}%\n"
100
+ result += f"πŸ“ˆ **24h Volume**: ${volume:,.0f}\n"
101
+ result += f"🏦 **Market Cap**: ${mcap:,.0f}\n"
102
+
103
+ return result
104
+
105
+ coin_id = self._symbol_map.get(symbol.lower(), symbol.lower())
106
+
107
+ params = {"vs_currency": "usd", "days": days}
108
+ data = await self.make_request(f"{self._base_url}/coins/{coin_id}/market_chart", params)
109
+ data = await self.make_request(f"{self.base_url}/coins/{coin_id}/market_chart", params)
110
+
111
+ return {
112
+ "symbol": symbol.upper(),
113
+ "prices": data.get("prices", []),
114
+ "volumes": data.get("total_volumes", []),
115
+ "market_caps": data.get("market_caps", [])
116
+ }
src/tools/defillama_tool.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any, Optional
2
+ from pydantic import BaseModel, PrivateAttr
3
+ from src.tools.base_tool import BaseWeb3Tool, Web3ToolInput
4
+
5
+ class DeFiLlamaTool(BaseWeb3Tool):
6
+ name: str = "defillama_data"
7
+ description: str = """Get DeFi protocol data, TVL, and yields from DeFiLlama.
8
+ Useful for: DeFi analysis, protocol rankings, TVL trends, yield farming data.
9
+ Input: protocol name or general DeFi query."""
10
+ args_schema: type[BaseModel] = Web3ToolInput
11
+
12
+ _base_url: str = PrivateAttr(default="https://api.llama.fi")
13
+
14
+ def __init__(self):
15
+ super().__init__()
16
+
17
+ async def _arun(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
18
+ try:
19
+ filters = filters or {}
20
+
21
+ if filters.get("type") == "tvl_overview":
22
+ return await self._get_tvl_overview()
23
+ elif filters.get("type") == "protocol_data":
24
+ return await self._get_protocol_data(query)
25
+ elif query:
26
+ return await self._search_protocols(query)
27
+ else:
28
+ return await self._get_top_protocols()
29
+
30
+ except Exception as e:
31
+ return f"DeFiLlama error: {str(e)}"
32
+
33
+ async def _get_top_protocols(self) -> str:
34
+ data = await self.make_request(f"{self.base_url}/protocols")
35
+
36
+ if not data:
37
+ return "No DeFi protocol data available"
38
+
39
+ top_protocols = sorted(data, key=lambda x: x.get("tvl", 0), reverse=True)[:10]
40
+
41
+ result = "🏦 **Top DeFi Protocols by TVL:**\n\n"
42
+
43
+ for i, protocol in enumerate(top_protocols, 1):
44
+ name = protocol.get("name", "Unknown")
45
+ tvl = protocol.get("tvl", 0)
46
+ change = protocol.get("change_1d", 0)
47
+ chain = protocol.get("chain", "Multi-chain")
48
+
49
+ emoji = "πŸ“ˆ" if change >= 0 else "πŸ“‰"
50
+ result += f"{i}. **{name}** ({chain}): ${tvl/1e9:.2f}B TVL {emoji} ({change:+.2f}%)\n"
51
+
52
+ return result
53
+
54
+ async def _get_tvl_overview(self) -> str:
55
+ try:
56
+ protocols_data = await self.make_request(f"{self.base_url}/protocols")
57
+ chains_data = await self.make_request(f"{self.base_url}/chains")
58
+
59
+ if not protocols_data or not chains_data:
60
+ return "TVL overview data unavailable"
61
+
62
+ total_tvl = sum(p.get("tvl", 0) for p in protocols_data)
63
+ top_chains = sorted(chains_data, key=lambda x: x.get("tvl", 0), reverse=True)[:5]
64
+
65
+ result = "🌐 **DeFi TVL Overview:**\n\n"
66
+ result += f"πŸ’° **Total TVL**: ${total_tvl/1e9:.2f}B\n\n"
67
+ result += "**Top Chains by TVL:**\n"
68
+
69
+ for i, chain in enumerate(top_chains, 1):
70
+ name = chain.get("name", "Unknown")
71
+ tvl = chain.get("tvl", 0)
72
+ result += f"{i}. **{name}**: ${tvl/1e9:.2f}B\n"
73
+
74
+ return result
75
+
76
+ except Exception:
77
+ return await self._get_top_protocols()
78
+
79
+ async def _get_protocol_data(self, protocol: str) -> str:
80
+ protocols = await self.make_request(f"{self.base_url}/protocols")
81
+
82
+ if not protocols:
83
+ return f"No data available for {protocol}"
84
+
85
+ matching_protocol = None
86
+ for p in protocols:
87
+ if protocol.lower() in p.get("name", "").lower():
88
+ matching_protocol = p
89
+ break
90
+
91
+ if not matching_protocol:
92
+ return f"Protocol '{protocol}' not found"
93
+
94
+ name = matching_protocol.get("name", "Unknown")
95
+ tvl = matching_protocol.get("tvl", 0)
96
+ change_1d = matching_protocol.get("change_1d", 0)
97
+ change_7d = matching_protocol.get("change_7d", 0)
98
+ chain = matching_protocol.get("chain", "Multi-chain")
99
+ category = matching_protocol.get("category", "Unknown")
100
+
101
+ result = f"πŸ›οΈ **{name} Protocol Analysis:**\n\n"
102
+ result += f"πŸ’° **TVL**: ${tvl/1e9:.2f}B\n"
103
+ result += f"πŸ“Š **24h Change**: {change_1d:+.2f}%\n"
104
+ result += f"πŸ“ˆ **7d Change**: {change_7d:+.2f}%\n"
105
+ result += f"⛓️ **Chain**: {chain}\n"
106
+ result += f"🏷️ **Category**: {category}\n"
107
+
108
+ return result
109
+
110
+ async def _search_protocols(self, query: str) -> str:
111
+ protocols = await self.make_request(f"{self.base_url}/protocols")
112
+
113
+ if not protocols:
114
+ return "No protocol data available"
115
+
116
+ matching = [p for p in protocols if query.lower() in p.get("name", "").lower()][:5]
117
+
118
+ if not matching:
119
+ return f"No protocols found matching '{query}'"
120
+
121
+ result = f"πŸ” **Protocols matching '{query}':**\n\n"
122
+
123
+ for protocol in matching:
124
+ name = protocol.get("name", "Unknown")
125
+ tvl = protocol.get("tvl", 0)
126
+ chain = protocol.get("chain", "Multi-chain")
127
+ result += f"β€’ **{name}** ({chain}): ${tvl/1e9:.2f}B TVL\n"
128
+
129
+ return result
src/tools/etherscan_tool.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any, Optional
2
+ from pydantic import BaseModel, PrivateAttr
3
+ from src.tools.base_tool import BaseWeb3Tool, Web3ToolInput
4
+ from src.utils.config import config
5
+
6
+ class EtherscanTool(BaseWeb3Tool):
7
+ name: str = "etherscan_data"
8
+ description: str = """Get Ethereum blockchain data from Etherscan.
9
+ Useful for: transaction analysis, address information, gas prices, token data.
10
+ Input: Ethereum address, transaction hash, or general blockchain query."""
11
+ args_schema: type[BaseModel] = Web3ToolInput
12
+
13
+ _base_url: str = PrivateAttr(default="https://api.etherscan.io/api")
14
+ _api_key: Optional[str] = PrivateAttr(default=None)
15
+
16
+ def __init__(self):
17
+ super().__init__()
18
+ self._api_key = config.ETHERSCAN_API_KEY
19
+
20
+ async def _arun(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
21
+ if not self.api_key:
22
+ return "❌ Etherscan API key not configured"
23
+
24
+ try:
25
+ filters = filters or {}
26
+
27
+ if filters.get("type") == "gas_prices":
28
+ return await self._get_gas_prices()
29
+ elif filters.get("type") == "eth_stats":
30
+ return await self._get_eth_stats()
31
+ elif self._is_address(query):
32
+ return await self._get_address_info(query)
33
+ elif self._is_tx_hash(query):
34
+ return await self._get_transaction_info(query)
35
+ else:
36
+ return await self._get_gas_prices()
37
+
38
+ except Exception as e:
39
+ return f"Etherscan error: {str(e)}"
40
+
41
+ def _is_address(self, query: str) -> bool:
42
+ return len(query) == 42 and query.startswith("0x")
43
+
44
+ def _is_tx_hash(self, query: str) -> bool:
45
+ return len(query) == 66 and query.startswith("0x")
46
+
47
+ async def _get_gas_prices(self) -> str:
48
+ params = {
49
+ "module": "gastracker",
50
+ "action": "gasoracle",
51
+ "apikey": self.api_key
52
+ }
53
+
54
+ data = await self.make_request(self.base_url, params)
55
+
56
+ if data.get("status") != "1":
57
+ return "Gas price data unavailable"
58
+
59
+ result_data = data.get("result", {})
60
+ safe_gas = result_data.get("SafeGasPrice", "N/A")
61
+ standard_gas = result_data.get("StandardGasPrice", "N/A")
62
+ fast_gas = result_data.get("FastGasPrice", "N/A")
63
+
64
+ result = "β›½ **Ethereum Gas Prices:**\n\n"
65
+ result += f"🐌 **Safe**: {safe_gas} gwei\n"
66
+ result += f"⚑ **Standard**: {standard_gas} gwei\n"
67
+ result += f"πŸš€ **Fast**: {fast_gas} gwei\n"
68
+
69
+ return result
70
+
71
+ async def _get_eth_stats(self) -> str:
72
+ params = {
73
+ "module": "stats",
74
+ "action": "ethsupply",
75
+ "apikey": self.api_key
76
+ }
77
+
78
+ data = await self.make_request(self.base_url, params)
79
+
80
+ if data.get("status") != "1":
81
+ return "Ethereum stats unavailable"
82
+
83
+ eth_supply = int(data.get("result", 0)) / 1e18
84
+
85
+ result = "πŸ“Š **Ethereum Network Stats:**\n\n"
86
+ result += f"πŸ’Ž **ETH Supply**: {eth_supply:,.0f} ETH\n"
87
+
88
+ return result
89
+
90
+ async def _get_address_info(self, address: str) -> str:
91
+ params = {
92
+ "module": "account",
93
+ "action": "balance",
94
+ "address": address,
95
+ "tag": "latest",
96
+ "apikey": self.api_key
97
+ }
98
+
99
+ data = await self.make_request(self.base_url, params)
100
+
101
+ if data.get("status") != "1":
102
+ return f"Address information unavailable for {address}"
103
+
104
+ balance_wei = int(data.get("result", 0))
105
+ balance_eth = balance_wei / 1e18
106
+
107
+ result = f"πŸ“ **Address Information:**\n\n"
108
+ result += f"**Address**: {address}\n"
109
+ result += f"πŸ’° **Balance**: {balance_eth:.4f} ETH\n"
110
+
111
+ return result
112
+
113
+ async def _get_transaction_info(self, tx_hash: str) -> str:
114
+ params = {
115
+ "module": "proxy",
116
+ "action": "eth_getTransactionByHash",
117
+ "txhash": tx_hash,
118
+ "apikey": self.api_key
119
+ }
120
+
121
+ data = await self.make_request(self.base_url, params)
122
+
123
+ if not data.get("result"):
124
+ return f"Transaction not found: {tx_hash}"
125
+
126
+ tx = data.get("result", {})
127
+ value_wei = int(tx.get("value", "0x0"), 16)
128
+ value_eth = value_wei / 1e18
129
+ gas_price = int(tx.get("gasPrice", "0x0"), 16) / 1e9
130
+
131
+ result = f"πŸ“ **Transaction Information:**\n\n"
132
+ result += f"**Hash**: {tx_hash}\n"
133
+ result += f"**From**: {tx.get('from', 'N/A')}\n"
134
+ result += f"**To**: {tx.get('to', 'N/A')}\n"
135
+ result += f"πŸ’° **Value**: {value_eth:.4f} ETH\n"
136
+ result += f"β›½ **Gas Price**: {gas_price:.2f} gwei\n"
137
+
138
+ return result
src/utils/__init__.py ADDED
File without changes
src/utils/config.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from src.config import config
src/utils/exceptions.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any
2
+
3
+ class CustomException(Exception):
4
+ pass
5
+
6
+ class APIError(CustomException):
7
+ pass
8
+
9
+ class RateLimitError(APIError):
10
+ pass
11
+
12
+ class DataValidationError(CustomException):
13
+ pass
14
+
15
+ class ConfigurationError(CustomException):
16
+ pass
src/utils/logger.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sys
3
+ from typing import Optional
4
+
5
+ def get_logger(name: str, level: str = "INFO") -> logging.Logger:
6
+ logger = logging.getLogger(name)
7
+
8
+ if not logger.handlers:
9
+ handler = logging.StreamHandler(sys.stdout)
10
+ formatter = logging.Formatter(
11
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
12
+ )
13
+ handler.setFormatter(formatter)
14
+ logger.addHandler(handler)
15
+ logger.setLevel(getattr(logging, level.upper()))
16
+
17
+ return logger
src/visualizations.py CHANGED
@@ -1,238 +1,62 @@
1
  import plotly.graph_objects as go
2
- import plotly.express as px
3
- from plotly.subplots import make_subplots
4
- from typing import Dict, Any, List
5
- import pandas as pd
6
  from datetime import datetime
 
7
 
8
- def create_price_chart(data: Dict[str, Any], symbol: str) -> str:
9
  try:
10
  if not data or "prices" not in data:
11
- return f"<div style='padding: 20px; text-align: center;'>No price data available for {symbol.upper()}</div>"
12
 
13
  prices = data["prices"]
14
- volumes = data.get("total_volumes", [])
 
15
 
16
- if not prices:
17
- return f"<div style='padding: 20px; text-align: center;'>No price history found for {symbol.upper()}</div>"
18
-
19
- df = pd.DataFrame(prices, columns=["timestamp", "price"])
20
- df["datetime"] = pd.to_datetime(df["timestamp"], unit="ms")
21
-
22
- fig = make_subplots(
23
- rows=2, cols=1,
24
- shared_xaxes=True,
25
- vertical_spacing=0.05,
26
- subplot_titles=[f"{symbol.upper()} Price Chart", "Volume"],
27
- row_width=[0.7, 0.3]
28
- )
29
-
30
- fig.add_trace(
31
- go.Scatter(
32
- x=df["datetime"],
33
- y=df["price"],
34
- mode="lines",
35
- name="Price",
36
- line=dict(color="#00D4AA", width=2),
37
- hovertemplate="<b>%{y:$,.2f}</b><br>%{x}<extra></extra>"
38
- ),
39
- row=1, col=1
40
- )
41
-
42
- if volumes:
43
- vol_df = pd.DataFrame(volumes, columns=["timestamp", "volume"])
44
- vol_df["datetime"] = pd.to_datetime(vol_df["timestamp"], unit="ms")
45
-
46
- fig.add_trace(
47
- go.Bar(
48
- x=vol_df["datetime"],
49
- y=vol_df["volume"],
50
- name="Volume",
51
- marker_color="#FF6B6B",
52
- opacity=0.7,
53
- hovertemplate="<b>$%{y:,.0f}</b><br>%{x}<extra></extra>"
54
- ),
55
- row=2, col=1
56
- )
57
-
58
- current_price = df["price"].iloc[-1]
59
- price_change = ((df["price"].iloc[-1] - df["price"].iloc[0]) / df["price"].iloc[0]) * 100
60
 
61
  fig.update_layout(
62
- title=dict(
63
- text=f"{symbol.upper()} - ${current_price:,.4f} ({price_change:+.2f}%)",
64
- x=0.5,
65
- font=dict(size=20, color="#FFFFFF")
66
- ),
67
- xaxis_title="Date",
68
- yaxis_title="Price (USD)",
69
- template="plotly_dark",
70
- showlegend=False,
71
- height=600,
72
- margin=dict(l=60, r=60, t=80, b=60),
73
- plot_bgcolor="rgba(0,0,0,0)",
74
- paper_bgcolor="rgba(0,0,0,0)"
75
  )
76
 
77
- fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
78
- fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
79
-
80
- return fig.to_html(include_plotlyjs="cdn", div_id=f"chart_{symbol}")
81
-
82
- except Exception as e:
83
- return f"<div style='padding: 20px; text-align: center; color: #FF6B6B;'>Chart generation failed: {str(e)}</div>"
84
 
85
- def create_market_overview(data: Dict[str, Any]) -> str:
86
  try:
87
- if not data or "market_data" not in data:
88
- return "<div style='padding: 20px; text-align: center;'>Market data unavailable</div>"
89
-
90
- market_data = data["market_data"]
91
- if not market_data:
92
- return "<div style='padding: 20px; text-align: center;'>No market data found</div>"
93
-
94
- df = pd.DataFrame(market_data)
95
- df = df.head(20)
96
-
97
- fig = make_subplots(
98
- rows=2, cols=2,
99
- subplot_titles=[
100
- "Market Cap Distribution",
101
- "24h Price Changes",
102
- "Trading Volume",
103
- "Price vs Volume"
104
- ],
105
- specs=[[{"type": "pie"}, {"type": "bar"}],
106
- [{"type": "bar"}, {"type": "scatter"}]]
107
- )
108
-
109
- fig.add_trace(
110
- go.Pie(
111
- labels=df["symbol"].str.upper(),
112
- values=df["market_cap"],
113
- textinfo="label+percent",
114
- textposition="inside",
115
- marker=dict(colors=px.colors.qualitative.Set3),
116
- hovertemplate="<b>%{label}</b><br>Market Cap: $%{value:,.0f}<extra></extra>"
117
- ),
118
- row=1, col=1
119
- )
120
-
121
- colors = ["#00D4AA" if x >= 0 else "#FF6B6B" for x in df["price_change_percentage_24h"]]
122
- fig.add_trace(
123
- go.Bar(
124
- x=df["symbol"].str.upper(),
125
- y=df["price_change_percentage_24h"],
126
- marker_color=colors,
127
- hovertemplate="<b>%{x}</b><br>24h Change: %{y:+.2f}%<extra></extra>"
128
- ),
129
- row=1, col=2
130
- )
131
 
132
- fig.add_trace(
133
- go.Bar(
134
- x=df["symbol"].str.upper(),
135
- y=df["total_volume"],
136
- marker_color="#4ECDC4",
137
- hovertemplate="<b>%{x}</b><br>Volume: $%{y:,.0f}<extra></extra>"
138
- ),
139
- row=2, col=1
140
  )
141
 
142
- fig.add_trace(
143
- go.Scatter(
144
- x=df["current_price"],
145
- y=df["total_volume"],
146
- mode="markers+text",
147
- text=df["symbol"].str.upper(),
148
- textposition="top center",
149
- marker=dict(
150
- size=df["market_cap"] / df["market_cap"].max() * 50 + 10,
151
- color=df["price_change_percentage_24h"],
152
- colorscale="RdYlGn",
153
- colorbar=dict(title="24h Change %"),
154
- line=dict(width=1, color="white")
155
- ),
156
- hovertemplate="<b>%{text}</b><br>Price: $%{x:,.4f}<br>Volume: $%{y:,.0f}<extra></extra>"
157
- ),
158
- row=2, col=2
159
- )
160
-
161
- global_info = data.get("global_data", {}).get("data", {})
162
- total_mcap = global_info.get("total_market_cap", {}).get("usd", 0)
163
- total_volume = global_info.get("total_volume", {}).get("usd", 0)
164
- btc_dominance = global_info.get("market_cap_percentage", {}).get("btc", 0)
165
-
166
- title_text = f"Crypto Market Overview - Total MCap: ${total_mcap/1e12:.2f}T | 24h Vol: ${total_volume/1e9:.0f}B | BTC Dom: {btc_dominance:.1f}%"
167
-
168
  fig.update_layout(
169
- title=dict(
170
- text=title_text,
171
- x=0.5,
172
- font=dict(size=16, color="#FFFFFF")
173
- ),
174
- template="plotly_dark",
175
- showlegend=False,
176
- height=800,
177
- margin=dict(l=60, r=60, t=100, b=60),
178
- plot_bgcolor="rgba(0,0,0,0)",
179
- paper_bgcolor="rgba(0,0,0,0)"
180
  )
181
 
182
- fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
183
- fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor="rgba(255,255,255,0.1)")
184
-
185
- return fig.to_html(include_plotlyjs="cdn", div_id="market_overview")
186
-
187
- except Exception as e:
188
- return f"<div style='padding: 20px; text-align: center; color: #FF6B6B;'>Market overview failed: {str(e)}</div>"
189
 
190
- def create_comparison_chart(coins_data: List[Dict[str, Any]]) -> str:
191
- try:
192
- if not coins_data:
193
- return "<div style='padding: 20px; text-align: center;'>No comparison data available</div>"
194
-
195
- df = pd.DataFrame(coins_data)
196
-
197
- fig = make_subplots(
198
- rows=1, cols=2,
199
- subplot_titles=["Price Comparison", "Market Cap Comparison"]
200
- )
201
-
202
- colors = px.colors.qualitative.Set1[:len(df)]
203
-
204
- for i, (_, coin) in enumerate(df.iterrows()):
205
- fig.add_trace(
206
- go.Bar(
207
- name=coin["symbol"].upper(),
208
- x=[coin["symbol"].upper()],
209
- y=[coin["current_price"]],
210
- marker_color=colors[i],
211
- hovertemplate=f"<b>{coin['name']}</b><br>Price: $%{{y:,.4f}}<extra></extra>"
212
- ),
213
- row=1, col=1
214
- )
215
-
216
- fig.add_trace(
217
- go.Bar(
218
- name=coin["symbol"].upper(),
219
- x=[coin["symbol"].upper()],
220
- y=[coin["market_cap"]],
221
- marker_color=colors[i],
222
- showlegend=False,
223
- hovertemplate=f"<b>{coin['name']}</b><br>Market Cap: $%{{y:,.0f}}<extra></extra>"
224
- ),
225
- row=1, col=2
226
- )
227
-
228
- fig.update_layout(
229
- title="Cryptocurrency Comparison",
230
- template="plotly_dark",
231
- height=500,
232
- showlegend=True
233
- )
234
-
235
- return fig.to_html(include_plotlyjs="cdn", div_id="comparison_chart")
236
-
237
- except Exception as e:
238
- return f"<div style='padding: 20px; text-align: center; color: #FF6B6B;'>Comparison chart failed: {str(e)}</div>"
 
1
  import plotly.graph_objects as go
 
 
 
 
2
  from datetime import datetime
3
+ from typing import Dict, Any
4
 
5
+ def create_price_chart(data: Dict[str, Any], symbol: str) -> go.Figure:
6
  try:
7
  if not data or "prices" not in data:
8
+ return _empty_chart(f"No price data for {symbol}")
9
 
10
  prices = data["prices"]
11
+ timestamps = [datetime.fromtimestamp(p[0]/1000) for p in prices]
12
+ values = [p[1] for p in prices]
13
 
14
+ fig = go.Figure()
15
+ fig.add_trace(go.Scatter(
16
+ x=timestamps, y=values, mode='lines',
17
+ name=f'{symbol.upper()} Price',
18
+ line=dict(color='#00D4AA', width=2)
19
+ ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  fig.update_layout(
22
+ title=f'{symbol.upper()} Price History',
23
+ xaxis_title='Date', yaxis_title='Price (USD)',
24
+ template='plotly_dark', height=400
 
 
 
 
 
 
 
 
 
 
25
  )
26
 
27
+ return fig
28
+ except Exception:
29
+ return _empty_chart(f"Chart error for {symbol}")
 
 
 
 
30
 
31
+ def create_market_overview(data: Dict[str, Any]) -> go.Figure:
32
  try:
33
+ if not data:
34
+ return _empty_chart("No market data available")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ fig = go.Figure()
37
+ fig.add_annotation(
38
+ text="Market Overview\n" + str(data)[:200] + "...",
39
+ x=0.5, y=0.5, font=dict(size=12, color="white"),
40
+ showarrow=False, align="left"
 
 
 
41
  )
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  fig.update_layout(
44
+ title="Market Overview", template='plotly_dark', height=400,
45
+ xaxis=dict(visible=False), yaxis=dict(visible=False)
 
 
 
 
 
 
 
 
 
46
  )
47
 
48
+ return fig
49
+ except Exception:
50
+ return _empty_chart("Market overview error")
 
 
 
 
51
 
52
+ def _empty_chart(message: str) -> go.Figure:
53
+ fig = go.Figure()
54
+ fig.add_annotation(
55
+ text=message, x=0.5, y=0.5,
56
+ font=dict(size=16, color="white"), showarrow=False
57
+ )
58
+ fig.update_layout(
59
+ template='plotly_dark', height=400,
60
+ xaxis=dict(visible=False), yaxis=dict(visible=False)
61
+ )
62
+ return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_app.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import os
5
+
6
+ sys.path.insert(0, os.path.dirname(__file__))
7
+
8
+ def test_imports():
9
+ try:
10
+ print("Testing imports...")
11
+
12
+ # Test basic Python modules
13
+ import json
14
+ import asyncio
15
+ from datetime import datetime
16
+ from typing import List, Tuple
17
+ print("βœ… Basic Python modules imported successfully")
18
+
19
+ # Test installed packages
20
+ import gradio as gr
21
+ print("βœ… Gradio imported successfully")
22
+
23
+ import aiohttp
24
+ print("βœ… aiohttp imported successfully")
25
+
26
+ import plotly
27
+ print("βœ… Plotly imported successfully")
28
+
29
+ import pandas
30
+ print("βœ… Pandas imported successfully")
31
+
32
+ import pydantic
33
+ print("βœ… Pydantic imported successfully")
34
+
35
+ # Test LangChain
36
+ import langchain
37
+ from langchain.agents import AgentExecutor
38
+ from langchain_google_genai import ChatGoogleGenerativeAI
39
+ print("βœ… LangChain modules imported successfully")
40
+
41
+ # Test our modules
42
+ from src.utils.config import config
43
+ from src.utils.logger import get_logger
44
+ print("βœ… Config and logger imported successfully")
45
+
46
+ from src.tools.base_tool import BaseWeb3Tool
47
+ from src.tools.coingecko_tool import CoinGeckoTool
48
+ from src.tools.defillama_tool import DeFiLlamaTool
49
+ from src.tools.etherscan_tool import EtherscanTool
50
+ print("βœ… Tools imported successfully")
51
+
52
+ from src.agent.research_agent import Web3ResearchAgent
53
+ from src.agent.query_planner import QueryPlanner
54
+ print("βœ… Agent modules imported successfully")
55
+
56
+ from src.api.airaa_integration import AIRAAIntegration
57
+ print("βœ… AIRAA integration imported successfully")
58
+
59
+ from src.visualizations import create_price_chart, create_market_overview
60
+ print("βœ… Visualizations imported successfully")
61
+
62
+ # Test app import
63
+ from app import Web3CoPilotApp
64
+ print("βœ… Main app imported successfully")
65
+
66
+ print("\nπŸŽ‰ All imports successful! The application is ready to run.")
67
+ return True
68
+
69
+ except ImportError as e:
70
+ print(f"❌ Import error: {e}")
71
+ return False
72
+ except Exception as e:
73
+ print(f"❌ Unexpected error: {e}")
74
+ return False
75
+
76
+ def test_app_initialization():
77
+ try:
78
+ print("\nTesting app initialization...")
79
+ # This will test if we can create the app instance
80
+ # but won't actually run it
81
+ os.environ.setdefault('GEMINI_API_KEY', 'test_key_for_import_test')
82
+
83
+ from app import Web3CoPilotApp
84
+ print("βœ… App class imported successfully")
85
+
86
+ # Test if we can create the interface (but don't launch)
87
+ app = Web3CoPilotApp()
88
+ interface = app.create_interface()
89
+ print("βœ… App interface created successfully")
90
+
91
+ print("\nπŸš€ Application is fully functional and ready to launch!")
92
+ return True
93
+
94
+ except Exception as e:
95
+ print(f"❌ App initialization error: {e}")
96
+ return False
97
+
98
+ if __name__ == "__main__":
99
+ print("=" * 60)
100
+ print("Web3 Research Co-Pilot - Application Test")
101
+ print("=" * 60)
102
+
103
+ success = test_imports()
104
+ if success:
105
+ test_app_initialization()
106
+
107
+ print("=" * 60)
108
+ print("Test complete!")