Spaces:
Configuration error
Configuration error
Download main.py from D3AMON/hermes-agent: direct link, hf CLI and curl.
- Browser
- Download file 18.7 kB
-
https://huggingface.co/spaces/D3AMON/hermes-agent/resolve/main/main.py
- Command line
-
hf download hf://spaces/D3AMON/hermes-agent/main.py
-
curl -L -o main.py https://huggingface.co/spaces/D3AMON/hermes-agent/resolve/main/main.py
18.7 kB
| """ | |
| Hermes Agent - FastAPI Backend Server | |
| Serves both the API endpoints and Telegram webhook | |
| """ | |
| import os | |
| import logging | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI, Request, HTTPException, BackgroundTasks | |
| from fastapi.responses import JSONResponse, HTMLResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| import uvicorn | |
| import config | |
| from hermes_agent import process_query, get_agent | |
| from telegram_bot import get_bot, TelegramBot | |
| # Configure logging | |
| logging.basicConfig( | |
| level=getattr(logging, config.LOG_LEVEL), | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| logger = logging.getLogger("hermes-api") | |
| # Check if we have the API key set | |
| try: | |
| config.validate_config() | |
| logger.info("Configuration validated successfully") | |
| except ValueError as e: | |
| logger.warning(f"Configuration issue: {e}") | |
| # App metadata for documentation | |
| APP_METADATA = { | |
| "title": "Hermes Agent API", | |
| "description": """ | |
| ๐ค Hermes Agent - AI-powered assistant with 100+ API integrations | |
| Built with Nous Research Hermes function calling standard. | |
| ## Features | |
| - **Weather** - Global forecasts for any city | |
| - **Crypto** - Real-time prices for 1000+ coins | |
| - **Anime** - Search MyAnimeList database | |
| - **Games** - Pokemon, D&D, chess data | |
| - **Food** - 300,000+ recipes and cocktails | |
| - **Space** - ISS tracking, SpaceX launches, NASA APOD | |
| - **Sports** - Football, NBA, F1 standings | |
| - **Quotes** - Inspirational, movie, TV quotes | |
| - **Science** - Periodic table, number facts | |
| - **News** - Hacker News, space news | |
| - **Entertainment** - Jokes, memes, trivia | |
| - **Utility** - Translation, currency conversion, QR codes | |
| ## Usage | |
| Send a POST request to `/chat` with your message and get an AI response | |
| that intelligently uses tools when needed. | |
| """, | |
| "version": "1.0.0", | |
| "contact": { | |
| "name": "Hermes Agent", | |
| "url": "https://github.com/NousResearch/Hermes-Function-Calling" | |
| } | |
| } | |
| async def lifespan(app: FastAPI): | |
| """Lifespan context manager for startup/shutdown events""" | |
| # Startup | |
| logger.info("๐ Hermes Agent starting up...") | |
| logger.info(f"Model: {config.DEFAULT_MODEL}") | |
| logger.info(f"Max recursion depth: {config.MAX_RECURSION_DEPTH}") | |
| # Initialize agent | |
| agent = get_agent() | |
| logger.info("โ Agent initialized") | |
| # Setup Telegram bot in polling mode for maximum reliability on HF | |
| if config.TELEGRAM_BOT_TOKEN: | |
| try: | |
| bot = get_bot() | |
| # Run polling in a separate thread to not block FastAPI | |
| import threading | |
| def start_polling(): | |
| bot.run_polling() | |
| polling_thread = threading.Thread(target=start_polling, daemon=True) | |
| polling_thread.start() | |
| logger.info("โ Telegram bot started in polling mode (background thread)") | |
| except Exception as e: | |
| logger.warning(f"โ ๏ธ Could not start Telegram bot: {e}") | |
| yield | |
| # Shutdown | |
| logger.info("๐ Hermes Agent shutting down...") | |
| # Create FastAPI app | |
| app = FastAPI( | |
| title=APP_METADATA["title"], | |
| description=APP_METADATA["description"], | |
| version=APP_METADATA["version"], | |
| contact=APP_METADATA["contact"], | |
| lifespan=lifespan | |
| ) | |
| # Add CORS middleware | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ============================================================ | |
| # HTML DASHBOARD (Landing Page) | |
| # ============================================================ | |
| DASHBOARD_HTML = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Hermes Agent - AI with 100+ APIs</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); | |
| min-height: 100vh; | |
| color: #e94560; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 40px 20px; | |
| } | |
| header { | |
| text-align: center; | |
| padding: 60px 20px; | |
| } | |
| h1 { | |
| font-size: 3.5em; | |
| margin-bottom: 10px; | |
| background: linear-gradient(135deg, #e94560 0%, #ff6b6b 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-shadow: 0 0 30px rgba(233, 69, 96, 0.3); | |
| } | |
| .subtitle { | |
| font-size: 1.3em; | |
| color: #a0a0a0; | |
| margin-bottom: 30px; | |
| } | |
| .status { | |
| display: inline-block; | |
| padding: 10px 25px; | |
| background: rgba(0, 255, 100, 0.1); | |
| border: 1px solid rgba(0, 255, 100, 0.3); | |
| border-radius: 50px; | |
| color: #00ff64; | |
| font-weight: bold; | |
| } | |
| .status.online::before { | |
| content: "โ "; | |
| } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); | |
| gap: 25px; | |
| margin-top: 50px; | |
| } | |
| .card { | |
| background: rgba(255, 255, 255, 0.05); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 20px; | |
| padding: 30px; | |
| transition: all 0.3s ease; | |
| } | |
| .card:hover { | |
| transform: translateY(-5px); | |
| background: rgba(255, 255, 255, 0.08); | |
| border-color: rgba(233, 69, 96, 0.3); | |
| } | |
| .card h3 { | |
| font-size: 1.3em; | |
| margin-bottom: 15px; | |
| color: #e94560; | |
| } | |
| .card p { | |
| color: #b0b0b0; | |
| line-height: 1.6; | |
| font-size: 0.95em; | |
| } | |
| .card .icon { | |
| font-size: 2em; | |
| margin-bottom: 10px; | |
| } | |
| .endpoints { | |
| background: rgba(0, 0, 0, 0.2); | |
| border-radius: 15px; | |
| padding: 30px; | |
| margin-top: 50px; | |
| } | |
| .endpoints h2 { | |
| text-align: center; | |
| margin-bottom: 20px; | |
| color: #e94560; | |
| } | |
| .endpoint { | |
| display: flex; | |
| align-items: center; | |
| padding: 15px; | |
| margin: 10px 0; | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 10px; | |
| font-family: monospace; | |
| } | |
| .method { | |
| padding: 5px 15px; | |
| border-radius: 5px; | |
| margin-right: 15px; | |
| font-weight: bold; | |
| font-size: 0.85em; | |
| } | |
| .method.get { background: #4CAF50; color: white; } | |
| .method.post { background: #2196F3; color: white; } | |
| .path { | |
| color: #fff; | |
| } | |
| .footer { | |
| text-align: center; | |
| margin-top: 50px; | |
| padding: 30px; | |
| color: #666; | |
| } | |
| .footer a { | |
| color: #e94560; | |
| text-decoration: none; | |
| } | |
| .stats { | |
| display: flex; | |
| justify-content: center; | |
| gap: 50px; | |
| margin: 40px 0; | |
| flex-wrap: wrap; | |
| } | |
| .stat { | |
| text-align: center; | |
| } | |
| .stat .number { | |
| font-size: 2.5em; | |
| font-weight: bold; | |
| color: #e94560; | |
| } | |
| .stat .label { | |
| color: #888; | |
| font-size: 0.9em; | |
| text-transform: uppercase; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>Hermes Agent</h1> | |
| <p class="subtitle">AI-Powered Assistant with 100+ Real-World API Integrations</p> | |
| <div class="status online">Online</div> | |
| <div class="stats"> | |
| <div class="stat"> | |
| <div class="number">100+</div> | |
| <div class="label">APIs Integrated</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="number">50+</div> | |
| <div class="label">Categories</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="number">โ</div> | |
| <div class="label">Possibilities</div> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="grid"> | |
| <div class="card"> | |
| <div class="icon">๐ค๏ธ</div> | |
| <h3>Weather & Environment</h3> | |
| <p>Global weather forecasts, air quality, UV index, and environmental data for any location on Earth.</p> | |
| </div> | |
| <div class="card"> | |
| <div class="icon">๐ฐ</div> | |
| <h3>Crypto & Finance</h3> | |
| <p>Real-time cryptocurrency prices, market data, exchange rates, and financial information.</p> | |
| </div> | |
| <div class="card"> | |
| <div class="icon">๐ฎ</div> | |
| <h3>Games & Entertainment</h3> | |
| <p>Pokemon database, D&D references, anime search, game stats, and entertainment data.</p> | |
| </div> | |
| <div class="card"> | |
| <div class="icon">๐</div> | |
| <h3>Space & Science</h3> | |
| <p>ISS tracking, SpaceX launches, NASA imagery, periodic table, and scientific facts.</p> | |
| </div> | |
| <div class="card"> | |
| <div class="icon">๐</div> | |
| <h3>Food & Drink</h3> | |
| <p>300,000+ recipes, cocktail instructions, brewery database, and food information.</p> | |
| </div> | |
| <div class="card"> | |
| <div class="icon">โฝ</div> | |
| <h3>Sports</h3> | |
| <p>Football standings, NBA games, F1 results, chess ratings, and bike sharing data.</p> | |
| </div> | |
| <div class="card"> | |
| <div class="icon">๐</div> | |
| <h3>Books & Knowledge</h3> | |
| <p>Book search, dictionary definitions, quotes, trivia questions, and educational content.</p> | |
| </div> | |
| <div class="card"> | |
| <div class="icon">๐</div> | |
| <h3>Fun & Social</h3> | |
| <p>Jokes, memes, Reddit posts, Chuck Norris facts, Rick & Morty data, and more.</p> | |
| </div> | |
| </div> | |
| <div class="endpoints"> | |
| <h2>๐ API Endpoints</h2> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="path">/ - Dashboard (this page)</span> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="path">/health - Health check</span> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method post">POST</span> | |
| <span class="path">/chat - Send a message to Hermes</span> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method post">POST</span> | |
| <span class="path">/telegram/webhook - Telegram bot webhook</span> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="path">/tools - List all available tools</span> | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| <p>Powered by <a href="https://nousresearch.com">Nous Research Hermes</a> | | |
| Built with <a href="https://fastapi.tiangolo.com">FastAPI</a> | | |
| Deployed on <a href="https://huggingface.co/spaces">Hugging Face Spaces</a></p> | |
| <p style="margin-top: 10px;">Based on <a href="https://github.com/NousResearch/Hermes-Function-Calling">Hermes-Function-Calling</a></p> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| # ============================================================ | |
| # API ENDPOINTS | |
| # ============================================================ | |
| async def dashboard(): | |
| """Serve the dashboard HTML page""" | |
| return HTMLResponse(content=DASHBOARD_HTML) | |
| async def health_check(): | |
| """Health check endpoint""" | |
| return { | |
| "status": "healthy", | |
| "version": "1.0.0", | |
| "model": config.DEFAULT_MODEL, | |
| "features": { | |
| "agent": True, | |
| "telegram": bool(config.TELEGRAM_BOT_TOKEN), | |
| "tools_count": 100, | |
| "api_categories": 50 | |
| } | |
| } | |
| async def chat(request: Request): | |
| """ | |
| Main chat endpoint - send a message to Hermes Agent | |
| Request body: | |
| { | |
| "message": "Your question here", | |
| "user_id": "optional_user_id" | |
| } | |
| """ | |
| try: | |
| data = await request.json() | |
| message = data.get("message", "") | |
| user_id = data.get("user_id", "default") | |
| if not message: | |
| raise HTTPException(status_code=400, detail="Message is required") | |
| # Process through Hermes agent | |
| response = process_query(message, user_id) | |
| return { | |
| "response": response, | |
| "user_id": user_id, | |
| "status": "success" | |
| } | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"Chat error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def list_tools(): | |
| """List all available tools/functions""" | |
| from api_functions import get_all_function_names, get_function_schemas | |
| return { | |
| "functions": get_all_function_names(), | |
| "count": len(get_all_function_names()), | |
| "schemas": get_function_schemas() | |
| } | |
| async def telegram_webhook(request: Request): | |
| """ | |
| Telegram webhook endpoint | |
| Receives updates from Telegram when the bot is in webhook mode | |
| """ | |
| try: | |
| update_data = await request.json() | |
| bot = get_bot() | |
| bot.process_update(update_data) | |
| return {"status": "ok"} | |
| except Exception as e: | |
| logger.error(f"Webhook error: {e}") | |
| return {"status": "error", "message": str(e)} | |
| async def telegram_webhook_info(): | |
| """Get Telegram webhook information""" | |
| try: | |
| bot = get_bot() | |
| info = await bot.application.bot.get_webhook_info() | |
| return info.to_dict() | |
| except Exception as e: | |
| return {"error": str(e)} | |
| async def demo_page(): | |
| """Interactive demo page""" | |
| return HTMLResponse(content=""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Hermes Agent - Demo</title> | |
| <style> | |
| body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; background: #1a1a2e; color: #eee; } | |
| h1 { color: #e94560; } | |
| .chat { border: 1px solid #333; border-radius: 10px; padding: 20px; height: 400px; overflow-y: auto; background: rgba(255,255,255,0.05); } | |
| .input-area { display: flex; gap: 10px; margin-top: 20px; } | |
| input { flex: 1; padding: 15px; border-radius: 10px; border: 1px solid #333; background: rgba(255,255,255,0.1); color: #fff; font-size: 16px; } | |
| button { padding: 15px 30px; border-radius: 10px; border: none; background: #e94560; color: white; cursor: pointer; font-size: 16px; } | |
| button:hover { background: #ff6b6b; } | |
| .msg { padding: 10px; margin: 5px 0; border-radius: 10px; } | |
| .user { background: rgba(233, 69, 96, 0.2); text-align: right; } | |
| .bot { background: rgba(255, 255, 255, 0.1); text-align: left; } | |
| .typing { color: #888; font-style: italic; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>๐ค Hermes Agent Demo</h1> | |
| <div class="chat" id="chat"></div> | |
| <div class="input-area"> | |
| <input type="text" id="message" placeholder="Ask me anything..." onkeypress="if(event.key==='Enter') send()"> | |
| <button onclick="send()">Send</button> | |
| </div> | |
| <script> | |
| async function send() { | |
| const input = document.getElementById('message'); | |
| const chat = document.getElementById('chat'); | |
| const msg = input.value.trim(); | |
| if (!msg) return; | |
| chat.innerHTML += '<div class="msg user"><b>You:</b> ' + msg + '</div>'; | |
| chat.innerHTML += '<div class="msg bot typing">Hermes is thinking...</div>'; | |
| input.value = ''; | |
| chat.scrollTop = chat.scrollHeight; | |
| try { | |
| const res = await fetch('/chat', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({message: msg, user_id: 'demo'}) | |
| }); | |
| const data = await res.json(); | |
| // Remove typing indicator | |
| const typing = chat.querySelector('.typing'); | |
| if (typing) typing.remove(); | |
| chat.innerHTML += '<div class="msg bot"><b>Hermes:</b> ' + data.response.replace(/\n/g, '<br>') + '</div>'; | |
| } catch (e) { | |
| chat.innerHTML += '<div class="msg bot" style="color:#f66;">Error: ' + e.message + '</div>'; | |
| } | |
| chat.scrollTop = chat.scrollHeight; | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """) | |
| # Error handlers | |
| async def global_exception_handler(request: Request, exc: Exception): | |
| """Handle all unhandled exceptions""" | |
| logger.error(f"Unhandled exception: {exc}") | |
| return JSONResponse( | |
| status_code=500, | |
| content={"detail": "Internal server error", "error": str(exc)} | |
| ) | |
| # Startup validation | |
| async def startup_event(): | |
| """Run on startup""" | |
| logger.info("=" * 60) | |
| logger.info("HERMES AGENT STARTED") | |
| logger.info(f"Port: {config.PORT}") | |
| logger.info(f"Debug: {config.DEBUG}") | |
| logger.info("=" * 60) | |
| if __name__ == "__main__": | |
| # Run the server | |
| uvicorn.run( | |
| "main:app", | |
| host=config.HOST, | |
| port=config.PORT, | |
| reload=config.DEBUG, | |
| log_level="info" | |
| ) | |