Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.venv
**/__pycache__
*.py[cod]
.git
.gitignore
tests
.pytest_cache
.ruff_cache
htmlcov
.coverage
ui/node_modules
dist
*.egg-info
**/*.egg-info
agents.md
.cursor
40 changes: 40 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
cache: npm
cache-dependency-path: ui/package-lock.json

- name: Set version from tag
run: uv version "${{ github.event.inputs.tag || github.ref_name }}" --package agentevals-cli

- name: Build core and bundled wheels
run: make release

Expand Down Expand Up @@ -89,3 +92,40 @@ jobs:
uv build --package agentevals-cli
uv publish dist/* --token ${{ secrets.PYPI_TOKEN }}
rm -rf src/agentevals/_static

push-docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6

- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Set appVersion in Chart.yaml
run: |
VERSION="${TAG#v}"
sed -i "s/^appVersion:.*/appVersion: \"$VERSION\"/" charts/agentevals/Chart.yaml
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}

- name: Build and push
run: |
VERSION="${TAG#v}"
make build-docker \
DOCKER_REGISTRY="ghcr.io/${{ github.repository_owner }}" \
DOCKER_TAG="$VERSION"
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}
38 changes: 38 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# syntax=docker/dockerfile:1

FROM node:25-bookworm-slim AS ui
WORKDIR /build/ui
COPY ui/package.json ui/package-lock.json ./
# Skip lifecycle scripts during ci, then rebuild esbuild in its own layer — avoids ETXTBSY when
# install.js execs the binary while overlayfs still has the file busy (common with BuildKit).
RUN npm ci --ignore-scripts
RUN npm rebuild esbuild
COPY ui/ ./
RUN npm run build

FROM python:3.14-slim-bookworm

WORKDIR /app

# Install uv binary only (no pip); same approach as astral-sh/uv's Dockerfile.
# https://github.com/astral-sh/uv/blob/6d889fd53d5c108d304c5a4085eb3140ec6a9cdb/Dockerfile#L21
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

COPY pyproject.toml uv.lock README.md ./
COPY packages ./packages
COPY src ./src

COPY --from=ui /build/ui/dist ./src/agentevals/_static

RUN uv sync --frozen --no-dev --extra live \
&& groupadd --gid 1000 app \
&& useradd --uid 1000 --gid app --home-dir /app --no-log-init app \
&& chown -R app:app /app

USER app
ENV PATH="/app/.venv/bin:$PATH"
ENV AGENTEVALS_SERVER_URL=http://127.0.0.1:8001

EXPOSE 8001 4318 8080

CMD ["agentevals", "serve", "--host", "0.0.0.0", "--port", "8001", "--otlp-port", "4318", "--mcp-port", "8080"]
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
VERSION := $(shell grep '^version' pyproject.toml | cut -d'"' -f2)
WHEEL := dist/agentevals_cli-$(VERSION)-py3-none-any.whl

.PHONY: build build-bundle build-ui release clean dev-backend dev-frontend dev-bundle test test-unit test-integration test-e2e
DOCKER_REGISTRY ?= soloio
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll need to set up the image builds too -- I think this will end up being ghcr.io (instead lof soloio). cc @EItanya as he's the master GH registry setter-upper.

DOCKER_IMAGE ?= agentevals
DOCKER_TAG ?= $(VERSION)
DOCKER_IMAGE_REF := $(if $(DOCKER_REGISTRY),$(DOCKER_REGISTRY:%/=%)/$(DOCKER_IMAGE),$(DOCKER_IMAGE))

# Multi-arch build (requires docker buildx). Manifest lists must be pushed — use build-docker-local for a single-arch --load.
PLATFORMS ?= linux/amd64,linux/arm64

.PHONY: build build-bundle build-docker build-ui release clean dev-backend dev-frontend dev-bundle test test-unit test-integration test-e2e

build:
uv build

build-docker:
docker buildx build --platform $(PLATFORMS) -t $(DOCKER_IMAGE_REF):$(DOCKER_TAG) --push .

build-ui:
cd ui && npm ci && npm run build

Expand Down
6 changes: 6 additions & 0 deletions charts/agentevals/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: agentevals
description: agentevals web UI, OTLP HTTP receiver, and MCP (Streamable HTTP)
type: application
version: 0.1.0
appVersion: "0.5.2"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we auto-generete this from Makefile?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if modifying the chart from the makefile is a good idea, I think the helm chart should be modified with a new versions not from the build

12 changes: 12 additions & 0 deletions charts/agentevals/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
1. UI and API are available at port {{ .Values.service.http.port }} (Service port name: http).
2. OTLP HTTP receiver: port {{ .Values.service.otlpHttp.port }} (OTEL_EXPORTER_OTLP_ENDPOINT=http://<service>:{{ .Values.service.otlpHttp.port }}).
3. MCP (Streamable HTTP): port {{ .Values.service.mcp.port }}, path /mcp (e.g. http://<service>:{{ .Values.service.mcp.port }}/mcp).
{{- if .Values.ephemeralVolume.enabled }}
4. An emptyDir is mounted at /tmp with HOME=/tmp/agentevals-home (ephemeral; lost on pod restart). Set ephemeralVolume.enabled=false and readOnlyRootFilesystem=false if you need a writable root without this mount.
{{- end }}

Get the Service URL:
export POD_NAME=$(kubectl get pods --namespace {{ include "agentevals.namespace" . }} -l "app.kubernetes.io/name={{ include "agentevals.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace {{ include "agentevals.namespace" . }} port-forward $POD_NAME {{ .Values.service.http.port }}:{{ .Values.service.http.port }}

Health check: GET http://<pod-ip>:{{ .Values.service.http.containerPort }}/api/health
57 changes: 57 additions & 0 deletions charts/agentevals/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{{- define "agentevals.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "agentevals.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{- define "agentevals.namespace" -}}
{{- default .Release.Namespace .Values.namespaceOverride }}
{{- end }}

{{- define "agentevals.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "agentevals.image" -}}
{{- $registry := .Values.image.registry | default .Values.registry -}}
{{- $tag := .Values.image.tag | default .Values.tag | default .Chart.AppVersion -}}
{{- if $registry -}}
{{- printf "%s/%s:%s" $registry .Values.image.repository $tag -}}
{{- else -}}
{{- printf "%s:%s" .Values.image.repository $tag -}}
{{- end -}}
{{- end }}

{{- define "agentevals.labels" -}}
helm.sh/chart: {{ include "agentevals.chart" . }}
{{ include "agentevals.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: agentevals
{{- end }}

{{- define "agentevals.selectorLabels" -}}
app.kubernetes.io/name: {{ include "agentevals.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "agentevals.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "agentevals.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
128 changes: 128 additions & 0 deletions charts/agentevals/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "agentevals.fullname" . }}
namespace: {{ include "agentevals.namespace" . }}
labels:
{{- include "agentevals.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "agentevals.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "agentevals.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "agentevals.serviceAccountName" . }}
{{- if .Values.ephemeralVolume.enabled }}
volumes:
- name: agentevals-tmp
{{- if or .Values.ephemeralVolume.sizeLimit (eq .Values.ephemeralVolume.medium "Memory") }}
emptyDir:
{{- if eq .Values.ephemeralVolume.medium "Memory" }}
medium: Memory
{{- end }}
{{- with .Values.ephemeralVolume.sizeLimit }}
sizeLimit: {{ . }}
{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}
containers:
- name: agentevals
image: {{ include "agentevals.image" . | quote }}
imagePullPolicy: {{ .Values.image.pullPolicy | default .Values.imagePullPolicy }}
{{- if .Values.command }}
command:
{{- toYaml .Values.command | nindent 12 }}
{{- end }}
{{- if .Values.args }}
args:
{{- toYaml .Values.args | nindent 12 }}
{{- end }}
env:
- name: AGENTEVALS_SERVER_URL
value: "http://127.0.0.1:{{ .Values.service.http.containerPort }}"
{{- if .Values.ephemeralVolume.enabled }}
- name: TMPDIR
value: "/tmp"
- name: HOME
value: "/tmp/agentevals-home"
{{- end }}
{{- with .Values.env }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.envFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.http.containerPort }}
protocol: TCP
- name: otlp-http
containerPort: {{ .Values.service.otlpHttp.containerPort }}
protocol: TCP
- name: mcp
containerPort: {{ .Values.service.mcp.containerPort }}
protocol: TCP
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
{{- $sc := deepCopy .Values.securityContext }}
{{- if not .Values.ephemeralVolume.enabled }}
{{- $_ := set $sc "readOnlyRootFilesystem" false }}
{{- end }}
{{- toYaml $sc | nindent 12 }}
startupProbe:
httpGet:
path: /api/health
port: http
failureThreshold: 60
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 15
periodSeconds: 20
{{- if .Values.ephemeralVolume.enabled }}
volumeMounts:
- name: agentevals-tmp
mountPath: /tmp
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
24 changes: 24 additions & 0 deletions charts/agentevals/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "agentevals.fullname" . }}
namespace: {{ include "agentevals.namespace" . }}
labels:
{{- include "agentevals.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- name: http
port: {{ .Values.service.http.port }}
targetPort: http
protocol: TCP
- name: otlp-http
port: {{ .Values.service.otlpHttp.port }}
targetPort: otlp-http
protocol: TCP
- name: mcp
port: {{ .Values.service.mcp.port }}
targetPort: mcp
protocol: TCP
selector:
{{- include "agentevals.selectorLabels" . | nindent 4 }}
14 changes: 14 additions & 0 deletions charts/agentevals/templates/serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "agentevals.serviceAccountName" . }}
namespace: {{ include "agentevals.namespace" . }}
labels:
{{- include "agentevals.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}
Loading