diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b255d731 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM python:3.11-slim + +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 + +WORKDIR /app + +# curl is used for the container healthcheck +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* + +# Rogue uses uv + uv.lock for reproducible installs +RUN pip install --no-cache-dir uv + +# Copy dependency manifests first for better caching +COPY pyproject.toml uv.lock ./ +COPY .python-version* ./ + +# Copy the repo +COPY . . + +# Install deps from lockfile +RUN uv sync --locked --no-dev + +# AgentCore HTTP runtime expects the service to listen on 8080 +EXPOSE 8080 + +# Optional but recommended: container-level healthcheck uses AgentCore /ping +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ + CMD curl -fsS http://localhost:8080/ping || exit 1 + +# Start the existing server on the required host/port +CMD ["uv", "run", "python", "-m", "rogue.run_server", "--host", "0.0.0.0", "--port", "8080"] \ No newline at end of file diff --git a/rogue/server/api/agentcore.py b/rogue/server/api/agentcore.py new file mode 100644 index 00000000..55f10866 --- /dev/null +++ b/rogue/server/api/agentcore.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter, BackgroundTasks, Depends +from rogue_sdk.types import EvaluationRequest, EvaluationResponse +from .evaluation import get_evaluation_service, enqueue_evaluation +from ..services.evaluation_service import EvaluationService + +router = APIRouter(tags=["agentcore"]) + + +@router.get("/ping") +def ping(): + return {"status": "Healthy"} + + +@router.post("/invocations", response_model=EvaluationResponse) +async def invocations( + request: EvaluationRequest, + background_tasks: BackgroundTasks, + evaluation_service: EvaluationService = Depends(get_evaluation_service), +): + # different entrypoint to /evaluations to meet agentcore convention + return await enqueue_evaluation( + request=request, + background_tasks=background_tasks, + evaluation_service=evaluation_service, + endpoint="/invocations", + ) diff --git a/rogue/server/api/evaluation.py b/rogue/server/api/evaluation.py index 7cea63eb..57e7c22c 100644 --- a/rogue/server/api/evaluation.py +++ b/rogue/server/api/evaluation.py @@ -24,11 +24,11 @@ def get_evaluation_service(): return EvaluationService() -@router.post("", response_model=EvaluationResponse) -async def create_evaluation( +async def enqueue_evaluation( request: EvaluationRequest, background_tasks: BackgroundTasks, - evaluation_service: EvaluationService = Depends(get_evaluation_service), + evaluation_service: EvaluationService, + endpoint: str, ): job_id = str(uuid.uuid4()) @@ -46,7 +46,7 @@ async def create_evaluation( # Build extra logging info extra_info = { - "endpoint": "/evaluations", + "endpoint": endpoint, "method": "POST", "agent_url": str(request.agent_config.evaluated_agent_url), "scenario_count": scenario_count, @@ -82,6 +82,20 @@ async def create_evaluation( ) +@router.post("", response_model=EvaluationResponse) +async def create_evaluation( + request: EvaluationRequest, + background_tasks: BackgroundTasks, + evaluation_service: EvaluationService = Depends(get_evaluation_service), +): + return await enqueue_evaluation( + request=request, + background_tasks=background_tasks, + evaluation_service=evaluation_service, + endpoint="/evaluations", + ) + + @router.get("", response_model=JobListResponse) async def list_evaluations( status: Optional[EvaluationStatus] = None, diff --git a/rogue/server/main.py b/rogue/server/main.py index bd1a4c65..0c4b372f 100644 --- a/rogue/server/main.py +++ b/rogue/server/main.py @@ -28,6 +28,7 @@ from .api.llm import router as llm_router from .api.red_team import router as red_team_router from .websocket.manager import websocket_router +from .api.agentcore import router as ac_router logger = get_logger(__name__) @@ -67,6 +68,7 @@ def create_app() -> FastAPI: app.include_router(llm_router, prefix="/api/v1") app.include_router(interview_router, prefix="/api/v1") app.include_router(websocket_router, prefix="/api/v1") + app.include_router(ac_router, prefix="") return app