feat(service): add FastAPI agent service with 5 routers and Docker setup
- config.py: pydantic-settings with all env vars, privacy mode, per-agent overrides - app_state.py: global singletons (pool, master agent, registry, llm_router, sweep) - main.py: FastAPI lifespan startup — DB pool, LLM router, Odoo client, agents, master - routers/dispatch.py: POST /dispatch with rate limiting and webhook secret auth - routers/approval.py: GET /approval/pending, POST /approval/respond - routers/registry.py: GET/POST /registry/agents, POST /registry/backend overrides - routers/sweep.py: POST /sweep trigger, GET /sweep/status - routers/health.py: GET /health, GET /health/detailed (DB/Odoo/Ollama checks) - requirements.txt: pinned deps (fastapi, uvicorn, asyncpg, anthropic, alembic) - Dockerfile: python:3.11-slim, single uvicorn worker - docker-compose.yml: agent-service + postgres:15, bound to 192.168.2.47:8001 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
98
agent_service/routers/registry.py
Normal file
98
agent_service/routers/registry.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix='/registry', tags=['registry'])
|
||||
|
||||
|
||||
class AgentInfo(BaseModel):
|
||||
name: str
|
||||
domain: str
|
||||
active: bool
|
||||
backend: str = 'ollama'
|
||||
last_seen: Optional[str] = None
|
||||
|
||||
|
||||
class BackendOverride(BaseModel):
|
||||
agent_name: str
|
||||
backend: str # ollama | claude
|
||||
set_by: str
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
@router.get('/agents', response_model=list[AgentInfo])
|
||||
async def list_agents():
|
||||
from ..app_state import get_agent_registry, get_llm_router
|
||||
registry = get_agent_registry()
|
||||
llm_router = get_llm_router()
|
||||
if registry is None:
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Registry not ready')
|
||||
agents = registry.get_all()
|
||||
result = []
|
||||
for agent in agents:
|
||||
backend = 'ollama'
|
||||
if llm_router:
|
||||
try:
|
||||
backend = await llm_router.get_backend(agent['name'])
|
||||
except Exception:
|
||||
pass
|
||||
result.append(AgentInfo(
|
||||
name=agent['name'],
|
||||
domain=agent.get('domain', ''),
|
||||
active=agent.get('active', True),
|
||||
backend=backend,
|
||||
last_seen=agent.get('last_seen'),
|
||||
))
|
||||
return result
|
||||
|
||||
|
||||
@router.post('/sync', status_code=status.HTTP_200_OK)
|
||||
async def sync_registry():
|
||||
from ..app_state import get_agent_registry
|
||||
registry = get_agent_registry()
|
||||
if registry is None:
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Registry not ready')
|
||||
try:
|
||||
count = await registry.sync()
|
||||
return {'synced': count}
|
||||
except Exception as exc:
|
||||
logger.error('registry sync failed: %s', exc)
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc))
|
||||
|
||||
|
||||
@router.post('/backend', status_code=status.HTTP_200_OK)
|
||||
async def set_backend_override(req: BackendOverride):
|
||||
from ..app_state import get_llm_router
|
||||
llm_router = get_llm_router()
|
||||
if llm_router is None:
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='LLM router not ready')
|
||||
if req.backend not in ('ollama', 'claude'):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='backend must be ollama or claude')
|
||||
try:
|
||||
await llm_router.set_backend_override(
|
||||
caller=req.agent_name,
|
||||
backend=req.backend,
|
||||
set_by=req.set_by,
|
||||
note=req.note,
|
||||
)
|
||||
return {'agent': req.agent_name, 'backend': req.backend}
|
||||
except Exception as exc:
|
||||
logger.error('set_backend_override failed: %s', exc)
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc))
|
||||
|
||||
|
||||
@router.delete('/backend/{agent_name}', status_code=status.HTTP_200_OK)
|
||||
async def reset_backend_override(agent_name: str):
|
||||
from ..app_state import get_llm_router
|
||||
llm_router = get_llm_router()
|
||||
if llm_router is None:
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='LLM router not ready')
|
||||
try:
|
||||
await llm_router.reset_backend_override(caller=agent_name)
|
||||
return {'agent': agent_name, 'reset': True}
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc))
|
||||
Reference in New Issue
Block a user