Files
truetype eda22811ce Rename project: maxclaw → langcode
- Renamed all references from maxclaw to langcode across all files
- Renamed maxclaw.py → langcode.py
- Renamed completion scripts: maxclaw.bash/zsh → langcode.bash/zsh
- Updated Dockerfile, README, all Python files, HTML, JSON, YAML
- New Gitea repository: http://192.168.0.183:8085/truetype/langcode_agent.git
2026-03-21 08:36:35 +01:00

326 lines
9.4 KiB
Python

"""
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"
)