reason/main.py
egregore ebd78cf88e Add rate limiting to /process endpoint
- Uses slowapi for rate limiting
- Default: 10 requests/minute per IP
- Configurable via RATE_LIMIT env var
- Returns HTTP 429 when limit exceeded
2026-02-02 12:58:33 +00:00

102 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""
Egregore Reason Service - AI reasoning API
Provides HTTP API for conversation processing with Claude.
Runs on port 8081.
"""
import os
from typing import Optional
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel
from dotenv import load_dotenv
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
import anthropic
from tools import TOOLS, execute_tool
from prompts import get_system_prompt
from conversation import process_conversation
# Load environment
load_dotenv("/home/admin/.env")
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
if not ANTHROPIC_API_KEY:
raise ValueError("ANTHROPIC_API_KEY not set")
# Rate limiting configuration
RATE_LIMIT = os.getenv("RATE_LIMIT", "10/minute")
# Initialize rate limiter
limiter = Limiter(key_func=get_remote_address)
app = FastAPI(title="Egregore Reason Service", docs_url="/docs")
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# Initialize Anthropic client
client = anthropic.AsyncAnthropic(api_key=ANTHROPIC_API_KEY)
# Request models
class ProcessRequest(BaseModel):
model: str = "claude-sonnet-4-20250514"
history: list # Conversation history in Claude API format
max_iterations: int = 10
class ToolRequest(BaseModel):
name: str
input: dict
# Endpoints
@app.post("/process")
@limiter.limit(RATE_LIMIT)
async def api_process(request: Request, req: ProcessRequest):
"""Process a conversation with tool use loop"""
try:
response_blocks = await process_conversation(
client=client,
model=req.model,
history=req.history,
max_iterations=req.max_iterations
)
return {"blocks": response_blocks}
except anthropic.APIError as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/tool")
async def api_execute_tool(req: ToolRequest):
"""Execute a single tool directly"""
result = await execute_tool(req.name, req.input)
return {"result": result}
@app.get("/tools")
async def api_get_tools():
"""Get available tool definitions"""
return {"tools": TOOLS}
@app.get("/prompt")
async def api_get_prompt():
"""Get current system prompt with context"""
prompt = await get_system_prompt()
return {"prompt": prompt}
@app.get("/health")
async def health():
"""Health check endpoint"""
return {"status": "ok", "service": "reason"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8081)