-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathMakefile
More file actions
176 lines (157 loc) · 9.08 KB
/
Makefile
File metadata and controls
176 lines (157 loc) · 9.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
SHELL := bash
AGENT_NAME := $(shell python3 -c "import re; print(re.search(r'^name:\s*(.+)', open('agent.yaml').read(), re.M).group(1).strip())")
CHART_DIR := ../../../charts/agent
VALUES_FILE := values.yaml
CONTAINER_CLI := $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null)
MODEL ?= llama3.1:8b
.PHONY: init re-init env ollama llama-server run-app run-app-fresh run-cli build push build-openshift deploy undeploy test test-integration dry-run help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-12s %s\n", $$1, $$2}'
re-init: ## Activate venv and reload .env variables
@source .venv/bin/activate && set -a && source .env && set +a && \
echo "Venv activated and .env variables loaded."
init: ## Copy .env.example to .env for configuration
@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
env: ## Create venv, load .env, and install dependencies
@echo "==> Creating virtual environment and installing dependencies..." && \
rm -rf .venv && \
uv sync --python 3.12 && \
echo "==> Load .env variables..." && \
source .venv/bin/activate && set -a && source .env && set +a && \
echo "" && \
echo "Done. Next steps:" && \
echo " make ollama # install Ollama and pull models" && \
echo " make llama-server # start Llama Stack server"
ollama: ## Install Ollama, start it, and pull models
@source .venv/bin/activate && set -a && source .env && set +a && \
if command -v ollama >/dev/null 2>&1; then \
echo "==> Ollama already installed, skipping..."; \
else \
echo "==> Installing Ollama..." && \
curl -fsSL https://ollama.com/install.sh | sh; \
fi && \
echo "==> Starting Ollama service..." && \
ollama serve &>/dev/null & \
OLLAMA_PID=$$! && \
echo "Waiting for Ollama to be ready (max 30s)..." && \
OLLAMA_WAIT=0 && \
until ollama list >/dev/null 2>&1; do \
OLLAMA_WAIT=$$((OLLAMA_WAIT + 1)); \
if [ $$OLLAMA_WAIT -ge 30 ]; then \
echo "ERROR: Ollama failed to start within 30 seconds." && \
kill $$OLLAMA_PID 2>/dev/null; \
exit 1; \
fi; \
sleep 1; \
done && \
echo "==> Pulling $(MODEL)..." && \
ollama pull $(MODEL) && \
echo "" && \
echo "Done. Run 'make llama-server' to start the Llama Stack server."
llama-server: ## Install llama-stack and start Llama Stack server
@source .venv/bin/activate && set -a && source .env && set +a && \
echo "==> Installing llama-stack..." && \
uv pip install "llama-stack==0.5.0" "llama-stack-api==0.5.0" ollama "pymilvus==2.6.9" "milvus-lite>=2.5.1" "setuptools>=80.9.0,<82.0.0" && \
mkdir -p ../../../milvus_data && \
echo "==> Killing any existing process on port 8321..." && \
lsof -ti:8321 | xargs kill -9 2>/dev/null; true && \
echo "==> Starting Llama Stack server on port 8321..." && \
llama stack run ../../../run_llama_server.yaml
run-app: ## Run agent locally with hot-reload
@source .venv/bin/activate && set -a && source .env && set +a && \
if lsof -ti:$${PORT:-8000} >/dev/null 2>&1; then \
echo "ERROR: Port $${PORT:-8000} is already in use." && \
echo " Change PORT in .env or run: make run-app-fresh" && \
exit 1; \
fi && \
uv run $${MLFLOW_TRACKING_URI:+--extra tracing} uvicorn main:app --host 127.0.0.1 --port $${PORT:-8000} --reload --reload-exclude .venv
run-app-fresh: ## Kill existing process on port and run agent with hot-reload
@source .venv/bin/activate && set -a && source .env && set +a && \
echo "==> Killing existing process on port $${PORT:-8000}..." && \
lsof -ti:$${PORT:-8000} | xargs kill -9 2>/dev/null; true && \
uv run $${MLFLOW_TRACKING_URI:+--extra tracing} uvicorn main:app --host 127.0.0.1 --port $${PORT:-8000} --reload --reload-exclude .venv
run-cli: ## Run interactive CLI chat (no web server)
@source .venv/bin/activate && set -a && source .env && set +a && \
cd examples && uv run $${MLFLOW_TRACKING_URI:+--extra tracing} python execute_ai_service_locally.py
build: ## Build container image locally (podman/docker)
@[ -n "$(CONTAINER_CLI)" ] || { echo "ERROR: neither podman nor docker found in PATH"; exit 1; } && \
source .env && \
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
cp -r ../../../images ./images && trap 'rm -rf ./images' EXIT && \
$(CONTAINER_CLI) build --platform linux/amd64 -t "$${CONTAINER_IMAGE}" -f Dockerfile .
push: ## Push container image to registry
@[ -n "$(CONTAINER_CLI)" ] || { echo "ERROR: neither podman nor docker found in PATH"; exit 1; } && \
source .env && \
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
$(CONTAINER_CLI) push "$${CONTAINER_IMAGE}"
build-openshift: ## Build image in-cluster via OpenShift BuildConfig (no podman/docker needed)
@cp -r ../../../images ./images && trap 'rm -rf ./images' EXIT && \
oc new-build --strategy=docker --binary --name=$(AGENT_NAME) --to=$(AGENT_NAME):latest 2>/dev/null || true && \
oc start-build $(AGENT_NAME) --from-dir=. --follow && \
NS=$$(oc project -q) && \
echo "" && \
echo "Image built. To deploy, set in .env:" && \
echo " CONTAINER_IMAGE=image-registry.openshift-image-registry.svc:5000/$$NS/$(AGENT_NAME):latest"
_check-env:
@set -a && source .env 2>/dev/null && set +a; \
missing=""; \
for var in $$(sed -n '/^ *required:/,/^ *[a-z]/{ /^ *- /s/^ *- *//p; }' agent.yaml); do \
eval val="\$$$$var"; \
[ -n "$$val" ] || missing="$$missing $$var"; \
done; \
[ -z "$$missing" ] || { echo "ERROR: Missing required env vars:$$missing"; exit 1; }
deploy: _check-env ## Deploy to OpenShift/K8s via Helm
@source .env && \
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
LAST_SEG="$${CONTAINER_IMAGE##*/}" && if [[ "$$LAST_SEG" == *:* ]]; then IMAGE_REPO="$${CONTAINER_IMAGE%:*}"; IMAGE_TAG="$${LAST_SEG##*:}"; else IMAGE_REPO="$${CONTAINER_IMAGE}"; IMAGE_TAG="latest"; fi && \
trap 'rm -f .helm-secrets.yaml' EXIT && \
umask 077 && \
{ printf 'secrets:\n apiKey: "%s"\n' "$${API_KEY}"; \
[ -z "$${MLFLOW_TRACKING_TOKEN}" ] || printf ' mlflowTrackingToken: "%s"\n' "$${MLFLOW_TRACKING_TOKEN}"; \
} > .helm-secrets.yaml && \
helm upgrade --install $(AGENT_NAME) $(CHART_DIR) \
-f $(VALUES_FILE) \
-f .helm-secrets.yaml \
--set image.repository="$${IMAGE_REPO}" \
--set image.tag="$${IMAGE_TAG}" \
--set env.BASE_URL="$${BASE_URL}" \
--set env.MODEL_ID="$${MODEL_ID}" \
$${MLFLOW_TRACKING_URI:+--set env.MLFLOW_TRACKING_URI="$${MLFLOW_TRACKING_URI}"} \
$${MLFLOW_EXPERIMENT_NAME:+--set env.MLFLOW_EXPERIMENT_NAME="$${MLFLOW_EXPERIMENT_NAME}"} \
$${MLFLOW_TRACKING_INSECURE_TLS:+--set env.MLFLOW_TRACKING_INSECURE_TLS="$${MLFLOW_TRACKING_INSECURE_TLS}"} \
$${MLFLOW_WORKSPACE:+--set env.MLFLOW_WORKSPACE="$${MLFLOW_WORKSPACE}"} && \
if command -v oc >/dev/null 2>&1; then \
echo "" && echo "Waiting for rollout to complete..." && \
if oc rollout status deployment/$(AGENT_NAME) --timeout=120s; then \
ROUTE=$$(oc get route $(AGENT_NAME) -o jsonpath='{.spec.host}' 2>/dev/null || true); \
if [ -n "$$ROUTE" ]; then echo "" && echo "Agent is available at: https://$$ROUTE"; fi; \
else \
echo "" && echo "WARNING: Rollout did not complete successfully. Check pod status with:" && \
echo " oc get pods -l app.kubernetes.io/name=$(AGENT_NAME)" && \
echo " oc logs deployment/$(AGENT_NAME)"; \
fi; \
fi
dry-run: _check-env ## Render Helm templates without deploying
@source .env && \
[ -n "$${CONTAINER_IMAGE}" ] || { echo "ERROR: CONTAINER_IMAGE is not set in .env"; exit 1; } && \
LAST_SEG="$${CONTAINER_IMAGE##*/}" && if [[ "$$LAST_SEG" == *:* ]]; then IMAGE_REPO="$${CONTAINER_IMAGE%:*}"; IMAGE_TAG="$${LAST_SEG##*:}"; else IMAGE_REPO="$${CONTAINER_IMAGE}"; IMAGE_TAG="latest"; fi && \
helm template $(AGENT_NAME) $(CHART_DIR) \
-f $(VALUES_FILE) \
--set secrets.apiKey="REDACTED" \
--set image.repository="$${IMAGE_REPO}" \
--set image.tag="$${IMAGE_TAG}" \
--set env.BASE_URL="$${BASE_URL}" \
--set env.MODEL_ID="$${MODEL_ID}" \
$${MLFLOW_TRACKING_URI:+--set env.MLFLOW_TRACKING_URI="$${MLFLOW_TRACKING_URI}"} \
$${MLFLOW_TRACKING_TOKEN:+--set secrets.mlflowTrackingToken="REDACTED"} \
$${MLFLOW_EXPERIMENT_NAME:+--set env.MLFLOW_EXPERIMENT_NAME="$${MLFLOW_EXPERIMENT_NAME}"} \
$${MLFLOW_TRACKING_INSECURE_TLS:+--set env.MLFLOW_TRACKING_INSECURE_TLS="$${MLFLOW_TRACKING_INSECURE_TLS}"} \
$${MLFLOW_WORKSPACE:+--set env.MLFLOW_WORKSPACE="$${MLFLOW_WORKSPACE}"}
undeploy: ## Remove deployment from cluster
helm uninstall $(AGENT_NAME)
test: ## Run tests
uv run --extra dev python -m pytest tests/ --ignore=tests/integration
test-integration: ## Run integration deployment test
PYTHONPATH=$$(git rev-parse --show-toplevel)/tests \
uv run --extra dev python -m pytest tests/integration/test_deployment.py \
-v --tb=long --junitxml=results.xml