Skip to content

Commit 3704ac5

Browse files
Merge branch 'main' into dev-add-a2a-protocol-example
2 parents a741dd7 + b6bf854 commit 3704ac5

46 files changed

Lines changed: 792 additions & 899 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.env
2+
.env.*
3+
!.env.example
4+
.venv
5+
__pycache__
6+
*.pyc
7+
*.pyo
8+
.pytest_cache
9+
.mypy_cache
10+
.ruff_cache
11+
.git
12+
.gitignore
13+
.helm-secrets.yaml
14+
.DS_Store
15+
tests/
16+
examples/
17+
docs/
18+
*.md
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Required
2+
API_KEY=
3+
BASE_URL=
4+
MODEL_ID=
5+
6+
# Deployment
7+
CONTAINER_IMAGE=
8+
9+
# Optional — MLflow Tracing
10+
# MLFLOW_TRACKING_URI=
11+
# MLFLOW_EXPERIMENT_NAME=
12+
# MLFLOW_HEALTH_CHECK_TIMEOUT=5
13+
# MLFLOW_HTTP_REQUEST_TIMEOUT=2
14+
# MLFLOW_HTTP_REQUEST_MAX_RETRIES=0
15+
# MLFLOW_TRACKING_TOKEN=
16+
# MLFLOW_TRACKING_INSECURE_TLS=
17+
# MLFLOW_WORKSPACE=default
18+
# LLM_PROVIDER=litellm
19+
# MLFLOW_TRACKING_AUTH= # Use Kubernetes service account for authentication (if running inside the cluster)
20+
Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
# Use Python 3.12 slim image as base
2-
FROM python:3.12-slim
1+
# Use Red Hat UBI9 Python 3.12 image (no Docker Hub rate limits on OpenShift)
2+
# To update: docker pull registry.access.redhat.com/ubi9/python-312:latest
3+
# docker inspect --format='{{index .RepoDigests 0}}' registry.access.redhat.com/ubi9/python-312:latest
4+
FROM registry.access.redhat.com/ubi9/python-312@sha256:e95978812895b9abb2bdc109b501078da2a47c8dbb9fa23758af40ed50ab6023
5+
WORKDIR /opt/app-root/src
36

4-
# Set working directory
5-
WORKDIR /app
7+
# Switch to root for installing dependencies
8+
USER 0
69

7-
# Create a non-privileged user for OpenShift
8-
# UID 1001 is commonly used in OpenShift deployments
9-
# Adding to root group (GID 0) is the OpenShift convention for arbitrary UIDs
10-
RUN groupadd -r appuser && useradd -r -u 1001 -g 0 -m -d /home/appuser appuser
11-
12-
# Install uv for fast dependency management
13-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
10+
# Install uv for fast dependency management (v0.11.1)
11+
# To update: docker pull ghcr.io/astral-sh/uv:latest
12+
# docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/astral-sh/uv:latest
13+
COPY --from=ghcr.io/astral-sh/uv@sha256:fc93e9ecd7218e9ec8fba117af89348eef8fd2463c50c13347478769aaedd0ce /uv /usr/local/bin/uv
1414

1515
# Copy project files for dependency installation
1616
COPY pyproject.toml .
1717
COPY src/ ./src/
1818

1919
# Install the project and its dependencies using uv
20-
RUN uv pip install --system --no-cache .
20+
# pysqlite3-binary (installed inline) provides SQLite >= 3.35 for ChromaDB on UBI9
21+
RUN uv pip install --no-cache ".[tracing]" pysqlite3-binary \
22+
&& SITE=$(python3 -c "import site; print(site.getsitepackages()[0])") \
23+
&& printf '__import__("pysqlite3")\nimport sys\nsys.modules["sqlite3"] = sys.modules.pop("pysqlite3")\n' > "$SITE/sitecustomize.py"
2124

2225
# Copy the application entrypoint, playground UI, and images
2326
COPY main.py .
@@ -27,20 +30,20 @@ COPY images/ ./images/
2730
# Pre-create the directory that CrewAI writes to at import time
2831
# (crewai.utilities.paths.db_storage_path -> ~/.local/share/app/)
2932
# Make everything group-writable (GID 0) for OpenShift arbitrary UID support
30-
RUN mkdir -p /home/appuser/.local/share/app \
31-
&& chown -R appuser:0 /app /home/appuser \
32-
&& chmod -R g=u /app /home/appuser
33+
RUN mkdir -p /opt/app-root/.local/share/app \
34+
&& chown -R 1001:0 /opt/app-root/src /opt/app-root/.local \
35+
&& chmod -R g=u /opt/app-root/src /opt/app-root/.local
3336

34-
# Switch to non-privileged user
35-
USER appuser
37+
# Switch back to default non-root user
38+
USER 1001
3639

3740
# Expose port 8080 (OpenShift standard)
3841
EXPOSE 8080
3942

4043
# Set environment variables
4144
ENV PORT=8080
42-
ENV HOME=/home/appuser
43-
ENV PYTHONPATH=/app:/app/src
45+
ENV HOME=/opt/app-root
46+
ENV PYTHONPATH=/opt/app-root/src
4447

45-
# Run the application
46-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
48+
# Run the application — reads PORT at runtime
49+
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT}"]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
SHELL := bash
2+
AGENT_NAME := $(shell python3 -c "import re; print(re.search(r'^name:\s*(.+)', open('agent.yaml').read(), re.M).group(1).strip())")
3+
CHART_DIR := ../../../charts/agent
4+
VALUES_FILE := values.yaml
5+
CONTAINER_CLI := $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null)
6+
7+
.PHONY: init run run-cli build push build-openshift deploy undeploy test dry-run help
8+
9+
help: ## Show this help
10+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-12s %s\n", $$1, $$2}'
11+
12+
init: ## Copy .env.example to .env for configuration
13+
@if [ ! -f .env ]; then cp .env.example .env && echo "Created .env from .env.example — edit it with your configuration"; else echo ".env already exists — skipping"; fi
14+
15+
run: ## Run agent locally with hot-reload
16+
@set -a && source .env && set +a && \
17+
uv run uvicorn main:app --host 0.0.0.0 --port $${PORT:-8000} --reload --reload-exclude .venv
18+
19+
run-cli: ## Run interactive CLI chat (no web server)
20+
@set -a && source .env && set +a && \
21+
cd examples && uv run python execute_ai_service_locally.py
22+
23+
build: ## Build container image locally (podman/docker)
24+
@[ -n "$(CONTAINER_CLI)" ] || { echo "ERROR: neither podman nor docker found in PATH"; exit 1; } && \
25+
source .env && \
26+
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
27+
cp -r ../../../images ./images && trap 'rm -rf ./images' EXIT && \
28+
$(CONTAINER_CLI) build --platform linux/amd64 -t "$${CONTAINER_IMAGE}" -f Dockerfile .
29+
30+
push: ## Push container image to registry
31+
@[ -n "$(CONTAINER_CLI)" ] || { echo "ERROR: neither podman nor docker found in PATH"; exit 1; } && \
32+
source .env && \
33+
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
34+
$(CONTAINER_CLI) push "$${CONTAINER_IMAGE}"
35+
36+
build-openshift: ## Build image in-cluster via OpenShift BuildConfig (no podman/docker needed)
37+
@cp -r ../../../images ./images && trap 'rm -rf ./images' EXIT && \
38+
oc new-build --strategy=docker --binary --name=$(AGENT_NAME) --to=$(AGENT_NAME):latest 2>/dev/null || true && \
39+
oc start-build $(AGENT_NAME) --from-dir=. --follow && \
40+
NS=$$(oc project -q) && \
41+
echo "" && \
42+
echo "Image built. To deploy, set in .env:" && \
43+
echo " CONTAINER_IMAGE=image-registry.openshift-image-registry.svc:5000/$$NS/$(AGENT_NAME):latest"
44+
45+
_check-env:
46+
@set -a && source .env 2>/dev/null && set +a; \
47+
missing=""; \
48+
for var in $$(sed -n '/^ *required:/,/^ *[a-z]/{ /^ *- /s/^ *- *//p; }' agent.yaml); do \
49+
eval val="\$$$$var"; \
50+
[ -n "$$val" ] || missing="$$missing $$var"; \
51+
done; \
52+
[ -z "$$missing" ] || { echo "ERROR: Missing required env vars:$$missing"; exit 1; }
53+
54+
deploy: _check-env ## Deploy to OpenShift/K8s via Helm
55+
@source .env && \
56+
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
57+
case "$${CONTAINER_IMAGE}" in *:*) IMAGE_REPO="$${CONTAINER_IMAGE%:*}"; IMAGE_TAG="$${CONTAINER_IMAGE##*:}";; *) IMAGE_REPO="$${CONTAINER_IMAGE}"; IMAGE_TAG="latest";; esac && \
58+
trap 'rm -f .helm-secrets.yaml' EXIT && \
59+
umask 077 && \
60+
{ printf 'secrets:\n apiKey: "%s"\n' "$${API_KEY}"; \
61+
[ -z "$${MLFLOW_TRACKING_TOKEN}" ] || printf ' mlflowTrackingToken: "%s"\n' "$${MLFLOW_TRACKING_TOKEN}"; \
62+
} > .helm-secrets.yaml && \
63+
helm upgrade --install $(AGENT_NAME) $(CHART_DIR) \
64+
-f $(VALUES_FILE) \
65+
-f .helm-secrets.yaml \
66+
--set image.repository="$${IMAGE_REPO}" \
67+
--set image.tag="$${IMAGE_TAG}" \
68+
--set env.BASE_URL="$${BASE_URL}" \
69+
--set env.MODEL_ID="$${MODEL_ID}" \
70+
$${MLFLOW_TRACKING_URI:+--set env.MLFLOW_TRACKING_URI="$${MLFLOW_TRACKING_URI}"} \
71+
$${MLFLOW_EXPERIMENT_NAME:+--set env.MLFLOW_EXPERIMENT_NAME="$${MLFLOW_EXPERIMENT_NAME}"} \
72+
$${MLFLOW_TRACKING_INSECURE_TLS:+--set env.MLFLOW_TRACKING_INSECURE_TLS="$${MLFLOW_TRACKING_INSECURE_TLS}"} \
73+
$${MLFLOW_WORKSPACE:+--set env.MLFLOW_WORKSPACE="$${MLFLOW_WORKSPACE}"} && \
74+
if command -v oc >/dev/null 2>&1; then \
75+
echo "" && echo "Waiting for rollout to complete..." && \
76+
if oc rollout status deployment/$(AGENT_NAME) --timeout=120s; then \
77+
ROUTE=$$(oc get route $(AGENT_NAME) -o jsonpath='{.spec.host}' 2>/dev/null || true); \
78+
if [ -n "$$ROUTE" ]; then echo "" && echo "Agent is available at: https://$$ROUTE"; fi; \
79+
else \
80+
echo "" && echo "WARNING: Rollout did not complete successfully. Check pod status with:" && \
81+
echo " oc get pods -l app.kubernetes.io/name=$(AGENT_NAME)" && \
82+
echo " oc logs deployment/$(AGENT_NAME)"; \
83+
fi; \
84+
fi
85+
86+
dry-run: _check-env ## Render Helm templates without deploying
87+
@source .env && \
88+
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
89+
case "$${CONTAINER_IMAGE}" in *:*) IMAGE_REPO="$${CONTAINER_IMAGE%:*}"; IMAGE_TAG="$${CONTAINER_IMAGE##*:}";; *) IMAGE_REPO="$${CONTAINER_IMAGE}"; IMAGE_TAG="latest";; esac && \
90+
helm template $(AGENT_NAME) $(CHART_DIR) \
91+
-f $(VALUES_FILE) \
92+
--set secrets.apiKey="REDACTED" \
93+
--set image.repository="$${IMAGE_REPO}" \
94+
--set image.tag="$${IMAGE_TAG}" \
95+
--set env.BASE_URL="$${BASE_URL}" \
96+
--set env.MODEL_ID="$${MODEL_ID}" \
97+
$${MLFLOW_TRACKING_URI:+--set env.MLFLOW_TRACKING_URI="$${MLFLOW_TRACKING_URI}"} \
98+
$${MLFLOW_TRACKING_TOKEN:+--set secrets.mlflowTrackingToken="REDACTED"} \
99+
$${MLFLOW_EXPERIMENT_NAME:+--set env.MLFLOW_EXPERIMENT_NAME="$${MLFLOW_EXPERIMENT_NAME}"} \
100+
$${MLFLOW_TRACKING_INSECURE_TLS:+--set env.MLFLOW_TRACKING_INSECURE_TLS="$${MLFLOW_TRACKING_INSECURE_TLS}"} \
101+
$${MLFLOW_WORKSPACE:+--set env.MLFLOW_WORKSPACE="$${MLFLOW_WORKSPACE}"}
102+
103+
undeploy: ## Remove deployment from cluster
104+
helm uninstall $(AGENT_NAME)
105+
106+
test: ## Run tests
107+
uv run --extra dev python -m pytest tests/

0 commit comments

Comments
 (0)