Repository navigation
feat(guardrails): add guardrailed banking agent example (RHAIENG-6262) #282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a89a9ff
3c3bf96
c4cc1f7
d0914f1
6013672
5c581d5
eafa1ff
7c57083
8614e1e
f056049
b7b5354
49539b4
765b6fe
08463c8
f555d20
949f959
08d8a6c
05abe1a
99f4583
d284800
4f9dba0
ed9ee1c
9b4dd10
1b1dcc4
847d8f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| .env | ||
| .env.* | ||
| !.env.example | ||
| .venv | ||
| __pycache__ | ||
| *.pyc | ||
| *.pyo | ||
| .pytest_cache | ||
| .mypy_cache | ||
| .ruff_cache | ||
| .git | ||
| .gitignore | ||
| .helm-secrets.yaml | ||
| .DS_Store | ||
| tests/ | ||
| examples/ | ||
| docs/ | ||
| *.md |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # Required — agent connects to NeMo Guardrails proxy, not directly to the LLM | ||
| API_KEY=not-needed-for-local-development | ||
| BASE_URL=http://localhost:8090/v1 | ||
| MODEL_ID=llama3.1:8b | ||
|
|
||
| # NeMo Guardrails — the proxy server that sits between the agent and the LLM | ||
| # Start with: make guardrails-server (generates config.yaml from these values) | ||
| # GUARDRAILS_PORT=8090 | ||
| # LLM_BASE_URL=http://localhost:11434/v1 | ||
|
|
||
| # Deployment | ||
| #CONTAINER_IMAGE= | ||
|
|
||
| # Auth (local default off; comma-separate multiple allowlist entries) | ||
| # AUTH_ENABLED=false | ||
| # AUTH_AUDIENCE=langgraph-guardrailed-agent | ||
| # AUTH_ALLOWED_SERVICEACCOUNTS=ci-testing:langgraph-guardrailed-agent-caller | ||
|
|
||
| # Optional — MLflow Tracing | ||
| # Tracing is opt-in: if MLFLOW_TRACKING_URI is not set, the agent runs without MLflow. | ||
| # Note: MLflow traces show the overall request but not guardrails internals. | ||
| # NeMo Guardrails appears as a regular ChatOpenAI endpoint in MLflow. | ||
|
|
||
| # --- Local MLflow server (no auth, no TLS) --- | ||
| # MLFLOW_TRACKING_URI=http://localhost:5000 | ||
| # MLFLOW_EXPERIMENT_NAME=langgraph-guardrailed-agent |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Local environment (may contain API keys) | ||
| .env | ||
|
|
||
| # NeMo Guardrails config is generated at startup by `make guardrails-server` | ||
| # from .env values — may contain API_KEY, never commit. | ||
| guardrails/safety/config.yaml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # Use Red Hat UBI9 Python 3.12 image (no Docker Hub rate limits on OpenShift) | ||
| # To update: docker pull registry.access.redhat.com/ubi9/python-312:latest | ||
| # docker inspect --format='{{index .RepoDigests 0}}' registry.access.redhat.com/ubi9/python-312:latest | ||
| FROM registry.access.redhat.com/ubi9/python-312@sha256:e95978812895b9abb2bdc109b501078da2a47c8dbb9fa23758af40ed50ab6023 | ||
| WORKDIR /opt/app-root/src/agents/langgraph/examples/guardrailed_agent | ||
|
|
||
| # Switch to root for installing dependencies | ||
| USER 0 | ||
|
|
||
| # Install uv for fast dependency management (v0.11.1) | ||
| # To update: docker pull ghcr.io/astral-sh/uv:latest | ||
| # docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/astral-sh/uv:latest | ||
| COPY --from=ghcr.io/astral-sh/uv@sha256:fc93e9ecd7218e9ec8fba117af89348eef8fd2463c50c13347478769aaedd0ce /uv /usr/local/bin/uv | ||
|
|
||
| # Copy project files for dependency installation. | ||
| # Keep the repo-relative layout so ../../../../components/auth resolves. | ||
| COPY pyproject.toml . | ||
| COPY src/ ./src/ | ||
| COPY components/auth/ /opt/app-root/src/components/auth/ | ||
|
|
||
| # Install the project and its dependencies using uv | ||
| # NeMo Guardrails runs as a separate pod in production (managed by NemoGuardrails CRD), | ||
| # so the [guardrails] extra is intentionally omitted from the agent container image. | ||
| RUN uv pip install --no-cache ".[tracing,auth]" | ||
|
|
||
| # Copy the application entrypoint, playground UI, and images | ||
| COPY main.py . | ||
| COPY playground/ ./playground/ | ||
| COPY images/ ./images/ | ||
|
|
||
| # Make everything group-writable (GID 0) for OpenShift arbitrary UID support | ||
| RUN chown -R 1001:0 /opt/app-root/src \ | ||
| && chmod -R g=u /opt/app-root/src | ||
|
|
||
| # Switch back to default non-root user | ||
| USER 1001 | ||
|
|
||
| # Expose port 8080 (OpenShift standard) | ||
| EXPOSE 8080 | ||
|
|
||
| # Set environment variables | ||
| ENV PORT=8080 | ||
| ENV HOME=/opt/app-root | ||
| ENV PYTHONPATH=/opt/app-root/src/agents/langgraph/examples/guardrailed_agent | ||
|
|
||
| # Run the application — reads PORT at runtime | ||
| CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT}"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| 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 := ../../deployment | ||
| 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 run-app run-app-fresh build push build-openshift deploy undeploy test dry-run help guardrails-server | ||
|
|
||
| help: ## Show this help | ||
| @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %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 | ||
|
tarun-etikala marked this conversation as resolved.
Resolved
|
||
|
|
||
| env: ## Create venv, load .env, and install dependencies (including NeMo Guardrails) | ||
| @echo "==> Creating virtual environment and installing dependencies..." && \ | ||
| rm -rf .venv && \ | ||
| uv sync --python 3.12 --extra guardrails && \ | ||
| 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 guardrails-server # start NeMo Guardrails proxy" && \ | ||
| echo " make run-app # start the agent" | ||
|
|
||
| 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; \ | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| 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 guardrails-server' to start NeMo Guardrails." | ||
|
|
||
| guardrails-server: ## Generate config from .env and start NeMo Guardrails proxy | ||
| @source .venv/bin/activate && set -a && source .env && set +a && \ | ||
| cp guardrails/safety/config.yaml.example guardrails/safety/config.yaml && \ | ||
| python3 -c "\ | ||
| import yaml; \ | ||
| f='guardrails/safety/config.yaml'; \ | ||
| c=yaml.safe_load(open(f)); \ | ||
| url='$${LLM_BASE_URL:-http://localhost:11434/v1}'; \ | ||
| model='$${MODEL_ID:-llama3.1:8b}'; \ | ||
| key='$${API_KEY:-not-needed}'; \ | ||
| [m['parameters'].update(base_url=url, api_key=key) or m.update(model=model) for m in c['models']]; \ | ||
| yaml.dump(c, open(f,'w'), default_flow_style=False, sort_keys=False); \ | ||
|
tarun-etikala marked this conversation as resolved.
Resolved
|
||
| print(f'Config generated: LLM_BASE_URL={url} MODEL_ID={model}')" && \ | ||
| echo "Starting NeMo Guardrails server on port $${GUARDRAILS_PORT:-8090}..." && \ | ||
| nemoguardrails server --config guardrails --default-config-id safety --port $${GUARDRAILS_PORT:-8090} --disable-chat-ui | ||
|
|
||
| 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 | ||
|
|
||
| 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 && \ | ||
| mkdir -p ./components && cp -r ../../../../components/auth ./components/auth && \ | ||
| trap 'rm -rf ./images ./components/auth; rmdir ./components 2>/dev/null || true' 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 && \ | ||
| mkdir -p ./components && cp -r ../../../../components/auth ./components/auth && \ | ||
| trap 'rm -rf ./images ./components/auth; rmdir ./components 2>/dev/null || true' 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 && \ | ||
| AUTH_ALLOWLIST="$${AUTH_ALLOWED_SERVICEACCOUNTS:-$${AUTH_ALLOWED_SERVICEACCOUNT}}" && \ | ||
| AUTH_ALLOWLIST_ARGS="" && \ | ||
| if [ -n "$$AUTH_ALLOWLIST" ]; then \ | ||
| IFS=',' read -r -a AUTH_ALLOWLIST_ITEMS <<< "$$AUTH_ALLOWLIST"; \ | ||
| for idx in "$${!AUTH_ALLOWLIST_ITEMS[@]}"; do \ | ||
| account="$${AUTH_ALLOWLIST_ITEMS[$$idx]}"; \ | ||
| account="$$(echo -n "$$account" | tr -d '[:space:]')"; \ | ||
| [ -n "$$account" ] || continue; \ | ||
| AUTH_ALLOWLIST_ARGS="$$AUTH_ALLOWLIST_ARGS --set-string auth.allowedServiceAccounts[$$idx]=$$account"; \ | ||
| done; \ | ||
| fi && \ | ||
| [ -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 && \ | ||
|
Comment on lines
+140
to
+144
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Serialize Helm secrets without manual YAML interpolation. API keys and MLflow tokens may legally contain 🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — same pattern in all agents' Makefiles across the repo. Will track as a cross-agent improvement separately.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+142
to
+144
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Helm secrets still serialized via manual
🤖 Prompt for AI Agents |
||
| 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}" \ | ||
| $${AUTH_ENABLED:+--set "auth.enabled=$${AUTH_ENABLED}"} \ | ||
| $${AUTH_ENABLED:+--set "serviceAccount.create=$${AUTH_ENABLED}"} \ | ||
| $${AUTH_AUDIENCE:+--set-string "auth.audience=$${AUTH_AUDIENCE}"} \ | ||
| $${AUTH_ALLOWLIST_ARGS} \ | ||
| $${MLFLOW_TRACKING_URI:+--set env.MLFLOW_TRACKING_URI="$${MLFLOW_TRACKING_URI}"} \ | ||
| $${MLFLOW_EXPERIMENT_NAME:+--set env.MLFLOW_EXPERIMENT_NAME="$${MLFLOW_EXPERIMENT_NAME}"} \ | ||
| $${MLFLOW_TRACKING_AUTH:+--set env.MLFLOW_TRACKING_AUTH="$${MLFLOW_TRACKING_AUTH}"} \ | ||
| $${MLFLOW_TRACKING_INSECURE_TLS:+--set env.MLFLOW_TRACKING_INSECURE_TLS="$${MLFLOW_TRACKING_INSECURE_TLS}"} \ | ||
| $${MLFLOW_TRACKING_SERVER_CERT_PATH:+--set env.MLFLOW_TRACKING_SERVER_CERT_PATH="$${MLFLOW_TRACKING_SERVER_CERT_PATH}"} \ | ||
| $${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 && \ | ||
| AUTH_ALLOWLIST="$${AUTH_ALLOWED_SERVICEACCOUNTS:-$${AUTH_ALLOWED_SERVICEACCOUNT}}" && \ | ||
| AUTH_ALLOWLIST_ARGS="" && \ | ||
| if [ -n "$$AUTH_ALLOWLIST" ]; then \ | ||
| IFS=',' read -r -a AUTH_ALLOWLIST_ITEMS <<< "$$AUTH_ALLOWLIST"; \ | ||
| for idx in "$${!AUTH_ALLOWLIST_ITEMS[@]}"; do \ | ||
| account="$${AUTH_ALLOWLIST_ITEMS[$$idx]}"; \ | ||
| account="$$(echo -n "$$account" | tr -d '[:space:]')"; \ | ||
| [ -n "$$account" ] || continue; \ | ||
| AUTH_ALLOWLIST_ARGS="$$AUTH_ALLOWLIST_ARGS --set-string auth.allowedServiceAccounts[$$idx]=$$account"; \ | ||
| done; \ | ||
| fi && \ | ||
| [ -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}" \ | ||
| $${AUTH_ENABLED:+--set "auth.enabled=$${AUTH_ENABLED}"} \ | ||
| $${AUTH_ENABLED:+--set "serviceAccount.create=$${AUTH_ENABLED}"} \ | ||
| $${AUTH_AUDIENCE:+--set-string "auth.audience=$${AUTH_AUDIENCE}"} \ | ||
| $${AUTH_ALLOWLIST_ARGS} \ | ||
| $${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_AUTH:+--set env.MLFLOW_TRACKING_AUTH="$${MLFLOW_TRACKING_AUTH}"} \ | ||
| $${MLFLOW_TRACKING_INSECURE_TLS:+--set env.MLFLOW_TRACKING_INSECURE_TLS="$${MLFLOW_TRACKING_INSECURE_TLS}"} \ | ||
| $${MLFLOW_TRACKING_SERVER_CERT_PATH:+--set env.MLFLOW_TRACKING_SERVER_CERT_PATH="$${MLFLOW_TRACKING_SERVER_CERT_PATH}"} \ | ||
| $${MLFLOW_WORKSPACE:+--set env.MLFLOW_WORKSPACE="$${MLFLOW_WORKSPACE}"} | ||
|
|
||
| undeploy: ## Remove deployment from cluster | ||
| helm uninstall $(AGENT_NAME) | ||
|
|
||
| test: ## Run unit tests | ||
| uv run --extra dev python -m pytest tests/ $(PYTEST_ARGS) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Mark
_check-envas phony.deploydepends on_check-env, but this target is absent from.PHONY. If a file named_check-envexists,makecan skip the required environment validation and continue with missing deployment settings. Add_check-envto the.PHONYlist.🤖 Prompt for AI Agents