| """ |
| AgentForge - Hugging Face Space Template |
| This is a generic, reusable agent runner that reads configuration from environment variables. |
| """ |
| import os |
| import json |
| from fastapi import FastAPI, Request, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from pydantic import BaseModel, Field |
| from typing import Optional, List, Dict, Any |
| from agents import Agent, AsyncOpenAI as AgentsAsyncOpenAI, OpenAIChatCompletionsModel, function_tool, Runner, SQLiteSession |
| import aiosmtplib |
| from email.message import EmailMessage |
| |
| |
| |
| AGENT_CONFIG_STR = os.getenv("AGENT_CONFIG") |
| if not AGENT_CONFIG_STR: |
| raise ValueError("AGENT_CONFIG environment variable is required") |
|
|
| |
| try: |
| raw_config = json.loads(AGENT_CONFIG_STR) |
| except json.JSONDecodeError as e: |
| raise ValueError(f"Failed to parse AGENT_CONFIG as JSON: {e}") |
|
|
| |
| if isinstance(raw_config, dict): |
| |
| if "result" in raw_config and "agent_build" in raw_config.get("result", {}): |
| AGENT_CONFIG = raw_config["result"]["agent_build"] |
| |
| elif "agent_build" in raw_config: |
| AGENT_CONFIG = raw_config["agent_build"] |
| |
| else: |
| AGENT_CONFIG = raw_config |
| else: |
| AGENT_CONFIG = raw_config |
|
|
| |
| if not isinstance(AGENT_CONFIG, dict): |
| raise ValueError(f"AGENT_CONFIG must be a dictionary, got {type(AGENT_CONFIG)}") |
|
|
| |
| print(f"Loaded AGENT_CONFIG with keys: {list(AGENT_CONFIG.keys())[:10]}...") |
|
|
| |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") |
| GROK_API_KEY = os.getenv("GROK_API_KEY") |
|
|
| |
| |
| |
| app = FastAPI( |
| title=f"{AGENT_CONFIG.get('name', 'Agent')} API", |
| description=f"Deployed agent for {AGENT_CONFIG.get('business_context', {}).get('business_name', 'Business')}", |
| version="1.0.0" |
| ) |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| |
| |
| class ChatRequest(BaseModel): |
| message: str = Field(..., description="User message to the agent") |
| session_id: Optional[str] = Field(default="default", description="Session ID for conversation tracking") |
|
|
| class ChatResponse(BaseModel): |
| status: str |
| agent_name: Optional[str] = None |
| user_message: str |
| agent_response: str |
| tools_available: List[str] |
| timestamp: float |
|
|
| |
| |
| |
| def recreate_tools_from_config(domain: str, business_name: str): |
| """ |
| Recreate tools based on domain. |
| This mirrors the DynamicToolFactory logic from agent_architect.py |
| """ |
| |
| if domain == "pharmacy": |
| @function_tool |
| async def manage_prescription(action: str, prescription_id: str = None, patient_id: str = None, medication: str = None) -> dict: |
| """Manage prescriptions - check, refill, or create""" |
| from datetime import datetime |
| return {"prescription_id": prescription_id or f"RX-{datetime.now().strftime('%Y%m%d%H%M')}", |
| "action": action, "status": "Processed", "refills": 3} |
| |
| @function_tool |
| async def check_drug_inventory(medication_name: str) -> dict: |
| """Check medication stock and expiry""" |
| return {"medication": medication_name, "in_stock": True, "quantity": 250, "expiry": "2026-06-15"} |
| |
| @function_tool |
| async def get_patient_info(patient_id: str) -> dict: |
| """Retrieve patient records and allergies""" |
| return {"patient_id": patient_id, "allergies": ["Penicillin"], "medications": ["Metformin"]} |
| |
| @function_tool |
| def web_search(query: str) -> dict: |
| """Perform a web search for current information""" |
| return {"query": query, "results": "Web search functionality - integrate with real API"} |
| |
| return [manage_prescription, check_drug_inventory, get_patient_info, web_search] |
| |
| elif domain == "ecommerce": |
| @function_tool |
| async def search_products(query: str, category: str = None) -> dict: |
| """Search product catalog""" |
| return {"query": query, "results": [{"id": "P001", "name": query, "price": 49.99, "stock": 50}]} |
| |
| @function_tool |
| async def track_order(order_id: str) -> dict: |
| """Track order status and delivery""" |
| return {"order_id": order_id, "status": "In Transit", "eta": "2025-11-20", "location": "Distribution Center"} |
| |
| @function_tool |
| async def manage_cart(action: str, product_id: str = None, quantity: int = 1) -> dict: |
| """Add, remove, or view cart items""" |
| return {"action": action, "product_id": product_id, "cart_total": 149.99, "items": 3} |
| |
| @function_tool |
| def web_search(query: str) -> dict: |
| """Perform a web search for current information""" |
| return {"query": query, "results": "Web search functionality"} |
| |
| return [search_products, track_order, manage_cart, web_search] |
| |
| elif domain == "weather": |
| @function_tool |
| async def get_forecast(location: str, days: int = 7) -> dict: |
| """Get weather forecast""" |
| return {"location": location, "days": days, "forecast": [{"date": "2025-12-12", "high": 22, "low": 15, "condition": "partly cloudy"}]} |
| |
| @function_tool |
| async def severe_weather_alert(location: str) -> dict: |
| """Check for severe weather alerts""" |
| return {"location": location, "alerts": [], "severity": "none", "preparedness_tips": ["Normal precautions"]} |
| |
| @function_tool |
| async def historical_weather_comparison(location: str, date: str) -> dict: |
| """Compare current weather to historical data""" |
| return {"location": location, "date": date, "current_temp": 20, "historical_avg": 18, "difference": 2, "percentile": 65} |
| |
| @function_tool |
| def web_search(query: str) -> dict: |
| """Perform a web search for current information""" |
| return {"query": query, "results": "Web search functionality"} |
| |
| return [get_forecast, severe_weather_alert, historical_weather_comparison, web_search] |
| |
| elif domain == "email_marketing": |
| @function_tool |
| async def draft_cold_email(recipient_name: str, company: str, pain_point: str, solution_offer: str) -> dict: |
| """Draft a personalized cold email based on research and pain points""" |
| return { |
| "subject": f"Question regarding {company}'s {pain_point} strategy", |
| "body": f"Hi {recipient_name},\n\nI noticed {company} might be facing challenges with {pain_point}. Our solution for {solution_offer} has helped similar companies...\n\nBest regards,\nAgent", |
| "status": "drafted", |
| "quality_score": 0.95 |
| } |
|
|
| @function_tool |
| async def verify_email_format(email: str) -> dict: |
| """Verify if an email address is valid and formatted correctly""" |
| is_valid = "@" in email and "." in email.split("@")[-1] |
| return {"email": email, "is_valid": is_valid, "suggestion": None if is_valid else "Check format"} |
|
|
| @function_tool |
| async def send_email(to: str, subject: str, body: str, is_html: bool = True) -> dict: |
| """Actually send an email using SMTP configurations from environment.""" |
| host = os.getenv("SMTP_HOST") |
| port = int(os.getenv("SMTP_PORT", "587")) |
| username = os.getenv("SMTP_USER") |
| password = os.getenv("SMTP_PASSWORD") |
| from_email = os.getenv("SMTP_FROM_EMAIL", username) |
|
|
| if not all([host, username, password]): |
| return { |
| "status": "error", |
| "message": "SMTP credentials (SMTP_HOST, SMTP_USER, SMTP_PASSWORD) are not configured in environment." |
| } |
|
|
| message = EmailMessage() |
| message["From"] = from_email |
| message["To"] = to |
| message["Subject"] = subject |
| if is_html: |
| message.set_content(body, subtype="html") |
| else: |
| message.set_content(body) |
|
|
| try: |
| await aiosmtplib.send( |
| message, |
| hostname=host, |
| port=port, |
| username=username, |
| password=password, |
| use_tls=(port == 465), |
| start_tls=(port == 587), |
| ) |
| return {"to": to, "subject": subject, "status": "sent", "timestamp": "2024-02-09T12:00:00"} |
| except Exception as e: |
| return {"status": "error", "message": str(e)} |
|
|
| @function_tool |
| def web_search(query: str) -> dict: |
| """Perform a web search for prospect research""" |
| return {"query": query, "results": f"Research data for {query}"} |
| |
| return [draft_cold_email, verify_email_format, send_email, web_search] |
|
|
| |
| else: |
| @function_tool |
| async def generate_analytics(metric: str, time_range: str) -> dict: |
| """Generate business analytics""" |
| return {"metric": metric, "time_range": time_range, "value": 12500, "trend": "+15%", "insights": f"{metric} growing"} |
| |
| @function_tool |
| async def send_notification(recipient: str, message: str, channel: str = "email") -> dict: |
| """Send notifications""" |
| if channel == "email": |
| host = os.getenv("SMTP_HOST") |
| if host: |
| |
| return {"recipient": recipient, "message": "Notification sent via actual email", "status": "Sent"} |
| return {"recipient": recipient, "message": message, "channel": channel, "status": "Sent"} |
|
|
| @function_tool |
| async def send_email(to: str, subject: str, body: str, is_html: bool = True) -> dict: |
| """Actually send an email using SMTP configurations from environment.""" |
| host = os.getenv("SMTP_HOST") |
| port = int(os.getenv("SMTP_PORT", "587")) |
| username = os.getenv("SMTP_USER") |
| password = os.getenv("SMTP_PASSWORD") |
| from_email = os.getenv("SMTP_FROM_EMAIL", username) |
|
|
| if not all([host, username, password]): |
| return { |
| "status": "error", |
| "message": "SMTP credentials (SMTP_HOST, SMTP_USER, SMTP_PASSWORD) are not configured in environment." |
| } |
|
|
| message = EmailMessage() |
| message["From"] = from_email |
| message["To"] = to |
| message["Subject"] = subject |
| if is_html: |
| message.set_content(body, subtype="html") |
| else: |
| message.set_content(body) |
|
|
| try: |
| await aiosmtplib.send( |
| message, |
| hostname=host, |
| port=port, |
| username=username, |
| password=password, |
| use_tls=(port == 465), |
| start_tls=(port == 587), |
| ) |
| return {"to": to, "subject": subject, "status": "sent", "timestamp": "2024-02-09T12:00:00"} |
| except Exception as e: |
| return {"status": "error", "message": str(e)} |
| |
| @function_tool |
| def web_search(query: str) -> dict: |
| """Perform a web search for current information""" |
| return {"query": query, "results": "Web search functionality"} |
| |
| return [generate_analytics, send_notification, send_email, web_search] |
|
|
| |
| |
| |
| def initialize_agent(): |
| """Initialize the agent with configuration from environment""" |
| model = AGENT_CONFIG.get("model", "gpt-4o") |
| |
| |
| if "gemini" in model.lower(): |
| api_key = GEMINI_API_KEY |
| client = AgentsAsyncOpenAI(api_key=api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/") |
| model_name = "gemini-2.0-flash-exp" |
| elif "grok" in model.lower(): |
| api_key = GROK_API_KEY |
| client = AgentsAsyncOpenAI(api_key=api_key, base_url="https://api.x.ai/v1") |
| model_name = "grok-beta" |
| else: |
| api_key = OPENAI_API_KEY |
| client = AgentsAsyncOpenAI(api_key=api_key) |
| model_name = "gpt-4o" |
| |
| if not api_key: |
| raise ValueError(f"API key not found for model: {model}") |
| |
| MODEL = OpenAIChatCompletionsModel(model=model_name, openai_client=client) |
| |
| |
| business_context = AGENT_CONFIG.get("business_context", {}) |
| if not isinstance(business_context, dict): |
| business_context = {} |
| |
| domain = business_context.get("domain") or AGENT_CONFIG.get("domain", "generic") |
| business_name = business_context.get("business_name") or AGENT_CONFIG.get("business_name", "Business") |
| tools = recreate_tools_from_config(domain, business_name) |
| |
| |
| agent_name = AGENT_CONFIG.get("name") or AGENT_CONFIG.get("agent_name", "AI Agent") |
| |
| |
| instructions = AGENT_CONFIG.get("instructions", "You are a helpful AI assistant.") |
| |
| |
| agent = Agent( |
| name=agent_name, |
| instructions=instructions, |
| model=MODEL, |
| tools=tools |
| ) |
| |
| return agent, tools |
|
|
| |
| AGENT_INSTANCE, AGENT_TOOLS = initialize_agent() |
|
|
| |
| |
| |
| @app.get("/") |
| async def root(): |
| """Health check and agent info""" |
| |
| tool_names = [] |
| for tool in AGENT_TOOLS: |
| if hasattr(tool, '__name__'): |
| tool_names.append(tool.__name__) |
| elif hasattr(tool, 'name'): |
| tool_names.append(tool.name) |
| else: |
| |
| tool_str = str(tool) |
| if "name='" in tool_str: |
| try: |
| name_start = tool_str.index("name='") + 6 |
| name_end = tool_str.index("'", name_start) |
| tool_names.append(tool_str[name_start:name_end]) |
| except: |
| tool_names.append(str(tool)[:50]) |
| else: |
| tool_names.append(str(tool)[:50]) |
| |
| return { |
| "status": "online", |
| "agent_name": AGENT_CONFIG.get("name") or AGENT_CONFIG.get("agent_name") or "GenericAgent", |
| "agent_id": AGENT_CONFIG.get("agent_id"), |
| "business": AGENT_CONFIG.get("business_context", {}).get("business_name") if isinstance(AGENT_CONFIG.get("business_context"), dict) else None, |
| "domain": AGENT_CONFIG.get("business_context", {}).get("domain") if isinstance(AGENT_CONFIG.get("business_context"), dict) else AGENT_CONFIG.get("domain"), |
| "tools_count": len(AGENT_TOOLS), |
| "tools": tool_names, |
| "model": AGENT_CONFIG.get("model"), |
| "deployment": "Hugging Face Space" |
| } |
|
|
| @app.post("/run", response_model=ChatResponse) |
| async def run_agent(request: ChatRequest) -> ChatResponse: |
| """ |
| Main endpoint to interact with the agent. |
| This is the primary interface for users. |
| """ |
| import time |
| |
| try: |
| |
| runner = Runner() |
| temp_session = SQLiteSession(":memory:") |
| |
| response = await runner.run(AGENT_INSTANCE, request.message, session=temp_session) |
| final_output = str(response.final_output) if hasattr(response, 'final_output') else str(response) |
| |
| return ChatResponse( |
| status="success", |
| agent_name=AGENT_CONFIG.get("name", "GenericAgent"), |
| user_message=request.message, |
| agent_response=final_output, |
| tools_available=[tool.__name__ if hasattr(tool, '__name__') else str(tool) for tool in AGENT_TOOLS], |
| timestamp=time.time() |
| ) |
| |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=f"Agent execution error: {str(e)}") |
|
|
| @app.get("/config") |
| async def get_config(): |
| """Get agent configuration (without sensitive data)""" |
| safe_config = { |
| "agent_id": AGENT_CONFIG.get("agent_id"), |
| "name": AGENT_CONFIG.get("name"), |
| "model": AGENT_CONFIG.get("model"), |
| "business_context": AGENT_CONFIG.get("business_context"), |
| "tools_count": len(AGENT_TOOLS), |
| "deployment_ready": AGENT_CONFIG.get("deployment_ready") |
| } |
| return safe_config |
|
|
| @app.get("/health") |
| async def health_check(): |
| """Health check endpoint""" |
| return {"status": "healthy", "agent": AGENT_CONFIG.get("name") or AGENT_CONFIG.get("agent_name")} |
|
|
| @app.get("/debug/config") |
| async def debug_config(): |
| """Debug endpoint to see what config is loaded (without sensitive data)""" |
| safe_config = { |
| "has_config": bool(AGENT_CONFIG), |
| "config_keys": list(AGENT_CONFIG.keys()) if isinstance(AGENT_CONFIG, dict) else [], |
| "agent_name": AGENT_CONFIG.get("name") or AGENT_CONFIG.get("agent_name"), |
| "agent_id": AGENT_CONFIG.get("agent_id"), |
| "model": AGENT_CONFIG.get("model"), |
| "has_business_context": "business_context" in AGENT_CONFIG, |
| "business_context_type": type(AGENT_CONFIG.get("business_context")).__name__, |
| "domain": AGENT_CONFIG.get("business_context", {}).get("domain") if isinstance(AGENT_CONFIG.get("business_context"), dict) else AGENT_CONFIG.get("domain"), |
| "business_name": AGENT_CONFIG.get("business_context", {}).get("business_name") if isinstance(AGENT_CONFIG.get("business_context"), dict) else None, |
| "tools_count_from_config": len(AGENT_CONFIG.get("tools", [])), |
| "tools_count_loaded": len(AGENT_TOOLS), |
| } |
| return safe_config |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|