- 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>
99 lines
3.4 KiB
Python
99 lines
3.4 KiB
Python
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))
|