""" LangCode Agent API Server Provides REST API for remote access to LangCode Agent """ import os import sys from typing import Optional, Dict, Any, List from fastapi import FastAPI, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from pydantic import BaseModel import uvicorn # Add current directory to path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from config_manager import config_manager from main import create_langcode_agent from langchain_core.messages import HumanMessage # Initialize FastAPI app app = FastAPI( title="LangCode Agent API", description="REST API for LangCode Agent - configurable AI agent", version="1.0.0" ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Mount static files for web UI web_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web") if os.path.exists(web_dir): app.mount("/static", StaticFiles(directory=web_dir), name="static") # Global agent instance agent = None class ChatRequest(BaseModel): """Chat request model""" message: str conversation_id: Optional[str] = None provider: Optional[str] = None model: Optional[str] = None class ChatResponse(BaseModel): """Chat response model""" response: str conversation_id: Optional[str] = None provider: Optional[str] = None model: Optional[str] = None class ConfigRequest(BaseModel): """Configuration request model""" name: str provider: str model_name: str api_key: Optional[str] = None base_url: Optional[str] = None set_active: bool = False class ConfigResponse(BaseModel): """Configuration response model""" name: str provider: str model_name: str is_active: bool def get_agent(): """Get or create agent instance""" global agent if agent is None: try: agent = create_langcode_agent() except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to create agent: {str(e)}") return agent @app.on_event("startup") async def startup_event(): """Initialize agent on startup""" global agent try: agent = create_langcode_agent() print("✅ LangCode Agent API server started") except Exception as e: print(f"❌ Failed to initialize agent: {e}") @app.get("/") async def root(): """Root endpoint - serve web UI""" web_index = os.path.join(web_dir, "index.html") if os.path.exists(web_index): return FileResponse(web_index) else: return { "message": "LangCode Agent API", "version": "1.0.0", "endpoints": { "chat": "/chat", "configurations": "/configurations", "health": "/health", "web_ui": "/static/index.html", "monitoring": "/monitoring" } } @app.get("/monitoring") async def monitoring(): """Monitoring dashboard endpoint""" monitoring_file = os.path.join(web_dir, "monitoring.html") if os.path.exists(monitoring_file): return FileResponse(monitoring_file) else: return { "message": "Monitoring dashboard not available", "version": "1.0.0", "note": "Monitoring dashboard file not found" } @app.get("/health") async def health_check(): """Health check endpoint""" return {"status": "healthy", "agent_initialized": agent is not None} @app.post("/chat", response_model=ChatResponse) async def chat(request: ChatRequest): """Chat with LangCode Agent""" try: agent_instance = get_agent() # Create message message = HumanMessage(content=request.message) # Invoke agent result = agent_instance.invoke({"messages": [message]}) # Extract response ai_message = result["messages"][-1] return ChatResponse( response=ai_message.content, conversation_id=request.conversation_id, provider=os.getenv("MODEL_PROVIDER", "unknown"), model=os.getenv("MODEL_NAME", "unknown") ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/configurations", response_model=List[ConfigResponse]) async def list_configurations(): """List all configurations""" try: configs = config_manager.get_configurations() active = config_manager.get_active_configuration() result = [] for name, config in configs.items(): result.append(ConfigResponse( name=name, provider=config.get("provider", "unknown"), model_name=config.get("model_name", "unknown"), is_active=(name == active) )) return result except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/configurations", response_model=ConfigResponse) async def add_configuration(request: ConfigRequest): """Add a new configuration""" try: config = { "provider": request.provider, "model_name": request.model_name } if request.api_key: config["api_key"] = request.api_key if request.base_url: config["base_url"] = request.base_url # Add configuration config_manager.add_configuration(request.name, config) # Set as active if requested if request.set_active: config_manager.set_active_configuration(request.name) return ConfigResponse( name=request.name, provider=request.provider, model_name=request.model_name, is_active=request.set_active ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.delete("/configurations/{name}") async def delete_configuration(name: str): """Delete a configuration""" try: if config_manager.remove_configuration(name): return {"message": f"Configuration '{name}' deleted successfully"} else: raise HTTPException(status_code=404, detail=f"Configuration '{name}' not found") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/configurations/{name}/activate") async def activate_configuration(name: str): """Set a configuration as active""" try: if config_manager.set_active_configuration(name): return {"message": f"Configuration '{name}' set as active"} else: raise HTTPException(status_code=404, detail=f"Configuration '{name}' not found") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/configurations/active") async def get_active_configuration(): """Get the active configuration""" try: active_name = config_manager.get_active_configuration() if not active_name: return {"message": "No active configuration set"} config = config_manager.get_configuration(active_name) if not config: raise HTTPException(status_code=404, detail=f"Active configuration '{active_name}' not found") return { "name": active_name, "provider": config.get("provider", "unknown"), "model_name": config.get("model_name", "unknown") } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/configurations/{name}/test") async def test_configuration(name: str): """Test connection to a configuration""" try: success, message = config_manager.test_configuration_connection(name) return { "success": success, "message": message } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/providers") async def list_providers(): """List available providers""" return { "providers": [ { "name": "openai", "description": "OpenAI GPT models", "required_env": ["OPENAI_API_KEY"], "optional_env": [] }, { "name": "ollama", "description": "Local Ollama models", "required_env": [], "optional_env": ["OLLAMA_BASE_URL"] }, { "name": "openrouter", "description": "OpenRouter multi-provider access", "required_env": ["OPENROUTER_API_KEY"], "optional_env": ["OPENROUTER_BASE_URL"] }, { "name": "anthropic", "description": "Anthropic Claude models", "required_env": ["ANTHROPIC_API_KEY"], "optional_env": [] }, { "name": "google", "description": "Google Gemini models", "required_env": ["GOOGLE_API_KEY"], "optional_env": [] } ] } if __name__ == "__main__": # Run the API server uvicorn.run( "api_server:app", host="0.0.0.0", port=8000, reload=True, log_level="info" )