diff --git a/agents/langflow/README.md b/agents/langflow/README.md new file mode 100644 index 00000000..465edfc6 --- /dev/null +++ b/agents/langflow/README.md @@ -0,0 +1,11 @@ +# Langflow Agents + +Agent templates built with [Langflow](https://docs.langflow.org/)'s visual flow builder. + +> **Note:** Langflow agents are different from other agents in this repo. They do not deploy a custom container — the "agent" is a JSON flow definition imported into an existing Langflow instance. See each agent's README for details. + +## Available Agents + +| Agent | Description | +|-------|-------------| +| [Simple Tool Calling Agent](simple_tool_calling_agent/) | Tool-calling agent that calls external APIs (weather, parks) and reasons over results. Includes Langfuse v3 tracing. | diff --git a/agents/langflow/simple_tool_calling_agent/.env.example b/agents/langflow/simple_tool_calling_agent/.env.example new file mode 100644 index 00000000..fed3ed2d --- /dev/null +++ b/agents/langflow/simple_tool_calling_agent/.env.example @@ -0,0 +1,8 @@ +# PostgreSQL +POSTGRES_USER=langflow +POSTGRES_PASSWORD=langflow +POSTGRES_DB=langflow + +# Langfuse — admin password and encryption key are auto-generated on first run by make init +LANGFUSE_ADMIN_PASSWORD= +LANGFUSE_ENCRYPTION_KEY= diff --git a/agents/langflow/simple_tool_calling_agent/Makefile b/agents/langflow/simple_tool_calling_agent/Makefile new file mode 100644 index 00000000..0516a59e --- /dev/null +++ b/agents/langflow/simple_tool_calling_agent/Makefile @@ -0,0 +1,42 @@ +SHELL := bash +COMPOSE_FILE := local/podman-compose.yml +ENV_FILE := local/.env + +.PHONY: init run stop clean status logs help + +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-12s %s\n", $$1, $$2}' + +init: ## Create local/.env from .env.example and generate secrets + @if [ ! -f $(ENV_FILE) ]; then \ + cp .env.example $(ENV_FILE) && echo "Created local/.env from .env.example"; \ + else \ + echo "local/.env already exists — skipping"; \ + fi + @if ! grep -q '^LANGFUSE_ADMIN_PASSWORD=.\+' $(ENV_FILE) 2>/dev/null; then \ + GENERATED_PWD=$$(openssl rand -base64 12) && \ + sed -i.bak "s/^LANGFUSE_ADMIN_PASSWORD=.*/LANGFUSE_ADMIN_PASSWORD=$$GENERATED_PWD/" $(ENV_FILE) && \ + rm -f $(ENV_FILE).bak && \ + echo "Generated Langfuse admin password."; \ + fi + @if ! grep -q '^LANGFUSE_ENCRYPTION_KEY=.\+' $(ENV_FILE) 2>/dev/null; then \ + GENERATED_KEY=$$(openssl rand -hex 32) && \ + sed -i.bak "s/^LANGFUSE_ENCRYPTION_KEY=.*/LANGFUSE_ENCRYPTION_KEY=$$GENERATED_KEY/" $(ENV_FILE) && \ + rm -f $(ENV_FILE).bak && \ + echo "Generated Langfuse encryption key."; \ + fi + +run: init ## Start the local Langflow stack (Langflow + PostgreSQL + Langfuse) + @./deploy-local.sh + +stop: ## Stop services (data preserved) + @./cleanup-local.sh + +clean: ## Stop services and remove all data + @./cleanup-local.sh --force + +status: ## Show status of running services + @podman-compose -f $(COMPOSE_FILE) ps + +logs: ## Tail logs from all services + @podman-compose -f $(COMPOSE_FILE) logs -f diff --git a/agents/langflow/simple_tool_calling_agent/README.md b/agents/langflow/simple_tool_calling_agent/README.md index 853eb42b..4fda514d 100644 --- a/agents/langflow/simple_tool_calling_agent/README.md +++ b/agents/langflow/simple_tool_calling_agent/README.md @@ -1,6 +1,6 @@
-# Langflow Simple Tool Calling Agent +# Simple Tool Calling Agent
@@ -8,11 +8,7 @@ ## What this agent does -A tool-calling agent built with Langflow's visual flow builder. It calls external APIs as tools (weather forecasts, national park data) and reasons over the results to answer user questions. Includes Langfuse v3 tracing out of the box. Runs locally via `podman-compose`. - -### Included demo flow - -The shipped flow is an outdoor activity assistant — it checks weather conditions and national park alerts to help decide if conditions are good for outdoor activities. +Tool-calling agent built with Langflow's visual flow builder. It calls external APIs as tools (weather forecasts, national park data) and reasons over the results to answer user questions. Includes Langfuse v3 tracing out of the box. **Example queries:** - *"Can I go walking in Boston tomorrow at 3 PM?"* @@ -26,103 +22,115 @@ The shipped flow is an outdoor activity assistant — it checks weather conditio | NPS Search Parks | NPS API | Search national parks by state | | NPS Park Alerts | NPS API | Active alerts and closures for a park | +> **Note:** Unlike other agents in this repo, Langflow agents do not deploy a custom container. The "agent" is a JSON flow definition imported into an existing Langflow instance. There is no Dockerfile, Helm chart, or FastAPI application. + --- -## Run locally +## Prerequisites -### Prerequisites +- [Podman](https://podman.io/) + [podman-compose](https://github.com/containers/podman-compose) — for running the local stack +- [GNU Make](https://www.gnu.org/software/make/) and a bash-compatible shell — on Windows, use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) (recommended) or [Git Bash](https://git-scm.com/downloads) -- **Podman** + **podman-compose** installed +### Installing Podman - **macOS:** - ```bash - brew install podman # install podman runtime - brew install podman-compose # install compose plugin - podman machine init # create a Linux VM (podman runs containers in a VM on macOS) - podman machine start # start the VM - ``` +**macOS:** +```bash +brew install podman # install podman runtime +brew install podman-compose # install compose plugin +podman machine init # create a Linux VM (podman runs containers in a VM on macOS) +podman machine start # start the VM +``` - **Linux:** - ```bash - sudo dnf install -y podman # install podman runtime - pip install podman-compose # install compose plugin - sudo systemctl start podman # start the podman service - ``` +**Linux:** +```bash +sudo dnf install -y podman # install podman runtime +pip install podman-compose # install compose plugin +sudo systemctl start podman # start the podman service +``` -### Start the local stack +## Deploying Locally + +### Setup ```bash cd agents/langflow/simple_tool_calling_agent -chmod +x deploy-local.sh cleanup-local.sh -./deploy-local.sh +make init # creates local/.env from .env.example, generates Langfuse secrets +make run # starts Langflow + PostgreSQL + Langfuse v3 (ClickHouse, MinIO, Redis) ``` -This starts: -- **Langflow** on http://localhost:7860 — the agent UI -- **PostgreSQL** — shared database server. Hosts two databases: `langflow` (flows, users, settings) and `langfuse` (metadata). The `langflow` database is created automatically by PostgreSQL; the `langfuse` database is created by `local/init-db.sh` on first startup -- **Ollama** on http://localhost:11434 — local LLM (qwen2.5:7b), runs natively on host for GPU acceleration -- **Langfuse v3** on http://localhost:3000 — tracing (admin@langflow.local / password auto-generated in `local/.env`), backed by ClickHouse, MinIO, and Redis - -### Import and configure the flow +### Import the flow 1. Open http://localhost:7860 2. On first launch, Langflow asks you to create a flow — create a **Blank Flow** (this is just to get past the initial screen) 3. Click the **Langflow icon** (top left) to go to the projects page 4. Click **Upload Flow** and select `flows/outdoor-activity-agent.json` -5. Configure the flow components: - | Component | Field | Value | - |-----------|-------|-------| - | KServe vLLM | api_base | http://host.containers.internal:11434/v1 | - | KServe vLLM | model_name | qwen2.5:7b | - | KServe vLLM | api_key | dummy | - | NPS Search Parks | api_key | Get one free at https://developer.nps.gov | - | NPS Park Alerts | api_key | Same NPS key as above | -6. Run the agent from the Langflow UI +### Configuration -> **Note:** Ollama runs natively on your machine (not in a container) to leverage GPU acceleration. On Apple Silicon Macs, this uses Metal for faster inference. Response times vary by hardware — expect 30 seconds to a few minutes per tool call. For faster responses, point the agent to a GPU-backed cluster endpoint (see [Remote model](#remote-model-instead-of-ollama)). +Configure the flow components: -### Query the agent via API +| Component | Field | Value | +|-----------|-------|-------| +| KServe vLLM | api_base | http://host.containers.internal:8321/v1 | +| KServe vLLM | model_name | ollama/qwen2.5:7b | +| KServe vLLM | api_key | not-needed | +| NPS Search Parks | api_key | Get one free at https://developer.nps.gov | +| NPS Park Alerts | api_key | Same NPS key as above | -Once the flow is running, you can query it via the Langflow API: +#### Pointing to a locally hosted model -```bash -curl -X POST http://localhost:7860/api/v1/run/ \ - -H "Content-Type: application/json" \ - -d '{"input_value": "What is the best day to hike near Denver?", "output_type": "chat", "input_type": "chat"}' -``` +See [Local Development](../../../docs/local-development.md) for Ollama + Llama Stack setup for local model serving. -Replace `` with the flow ID from the Langflow UI. You can find it in the browser URL bar when you open the flow — e.g., `http://localhost:7860/flow/27e7203d-b2b1-4700-962a-144a66155f14` → the flow ID is `27e7203d-b2b1-4700-962a-144a66155f14`. +**Notes:** +- `api_base` — use `host.containers.internal` instead of `localhost` so containerized Langflow can reach Llama Stack running on the host +- `api_key` — Llama Stack doesn't require authentication, so any non-empty string works +- `model_name` — not all models handle tool calling well. `qwen2.5:7b` and `llama3.1:8b` are known to work -On the cluster, replace `localhost:7860` with your cluster's Langflow route URL. +#### Pointing to a remotely hosted model + +Update the **KServe vLLM** component in the Langflow UI: -### Stop the local stack +| Field | Value | +|-------|-------| +| api_base | your-model-endpoint/v1 | +| model_name | your-model-id | +| api_key | your-api-key | + +### Running the Agent + +Run the agent from the Langflow UI by clicking the **Play** button. + +### Tracing + +Langfuse v3 tracing is included in the local stack and starts automatically. No additional setup needed. + +- **Langfuse UI**: http://localhost:3000 +- **Login**: admin@langflow.local / password auto-generated in `local/.env` + +After running the agent, select the **Langflow Agent** project and click **Traces** to see agent executions — LLM calls, tool invocations, inputs, and outputs. + +#### Stopping the stack ```bash -./cleanup-local.sh # stop services, keep data -./cleanup-local.sh --force # stop services, remove all data +make stop # stop services, keep data +make clean # stop services, remove all data ``` -**What gets preserved vs wiped:** - -| Data | `cleanup-local.sh` | `cleanup-local.sh --force` | -|------|---------------------|----------------------------| -| Downloaded Ollama models (e.g., qwen2.5:7b) | Kept | Kept (stored on host, not in containers) | +| Data | `make stop` | `make clean` | +|------|-------------|--------------| | Imported Langflow flows | Kept | **Deleted** (re-import needed) | | Langfuse traces (ClickHouse + MinIO) | Kept | **Deleted** | | PostgreSQL data | Kept | **Deleted** | | Redis cache | Kept | **Deleted** | -| `.env` and `.ollama-enabled` config | Kept | **Deleted** | +| `local/.env` config | Kept | **Deleted** | ---- - -## Deploy to cluster +## Deploying to Cluster There is nothing to build or deploy — no Dockerfiles, no Helm charts, no k8s manifests. This agent assumes Langflow, Langfuse, and an LLM (LlamaStack/KServe) are already running on the cluster. You just import the flow JSON into the existing Langflow instance and configure it. ### Finding cluster endpoints -Reach out to your cluster admin for the Langflow URL and LlamaStack endpoint/model names. However, if you have `oc` CLI access, you can find them yourself: +Reach out to your cluster admin for the Langflow URL and LlamaStack endpoint/model names. If you have `oc` CLI access, you can find them yourself: ```bash # Langflow UI URL @@ -153,85 +161,29 @@ oc get inferenceservice --all-namespaces - **NPS Park Alerts**: set `api_key` (same NPS key) 4. Run the agent +## API Endpoints ---- - -## Pointing agent to different models - -### Local model (Ollama) - -By default, the local stack runs **qwen2.5:7b** on Ollama. After importing the flow, set these values in the **KServe vLLM** component: - -| Field | Value | -|-------|-------| -| api_base | http://host.containers.internal:11434/v1 | -| model_name | qwen2.5:7b | -| api_key | dummy | - -> Ollama runs natively on your host machine (not in a container) for GPU acceleration. Use `host.containers.internal` so containerized Langflow can reach it. Ollama doesn't require authentication, so api_key can be any non-empty string (e.g., dummy). - -If you want to use a different model: - -1. Pull the model on Ollama: - ```bash - ollama pull - ``` - Or edit `local/.env` to change the default model and restart: - ``` - OLLAMA_MODEL=llama3.1:8b - ``` - Then: `./cleanup-local.sh && ./deploy-local.sh` - -2. Update the **KServe vLLM** component in the Langflow UI: - - | Field | Value | - |-------|-------| - | api_base | http://host.containers.internal:11434/v1 | - | model_name | your-model-name | - -> **Note:** Make sure you have enough CPU/GPU resources locally to run the model. Also, not all models handle tool calling well — for example, smaller models like Llama 3.2 1B may fail at tool calling, causing connection errors when the agent tries to invoke tools. Models like `qwen2.5:7b` and `llama3.1:8b` are known to work well with tool calling. - -### Remote model (instead of Ollama) - -If you primarily work with a remote model endpoint, or don't have enough local CPU/GPU resources to host a model, you can skip installing Ollama entirely by answering **n** when prompted by `deploy-local.sh`. - -Then update the **KServe vLLM** component in the Langflow UI to point to your remote endpoint: - -| Field | Value | -|-------|-------| -| api_base | your-model-endpoint/v1 | -| model_name | your-model-id | -| api_key | your-api-key | - ---- - -## Viewing traces in Langfuse - -After running the agent, traces are automatically sent to Langfuse. +### POST /api/v1/run/\ -**Locally:** -1. Open http://localhost:3000 (login: admin@langflow.local / password auto-generated in `local/.env`) -2. Select the **Langflow Agent** project -3. Click **Traces** in the left sidebar -4. Click on any trace to see the full agent execution — LLM calls, tool invocations, inputs, and outputs +```bash +curl -X POST http://localhost:7860/api/v1/run/ \ + -H "Content-Type: application/json" \ + -d '{"input_value": "What is the best day to hike near Denver?", "output_type": "chat", "input_type": "chat"}' +``` -**On cluster:** -1. Open the Langfuse route on your cluster -2. Select the **Langflow Agent** project -3. Click **Traces** in the left sidebar +Replace `` with the flow ID from the Langflow UI. You can find it in the browser URL bar when you open the flow — e.g., `http://localhost:7860/flow/27e7203d-b2b1-4700-962a-144a66155f14` → the flow ID is `27e7203d-b2b1-4700-962a-144a66155f14`. ---- +On the cluster, replace `localhost:7860` with your cluster's Langflow route URL. -## Exporting flows +## Exporting Flows When exporting a flow from Langflow, API keys and secrets can be embedded in the exported JSON file. To avoid leaking secrets: 1. In the export dialog, **uncheck "Save with API keys"** — this excludes all API keys from the exported file 2. If you already exported with keys included, you can strip them manually by searching for `"api_key"` fields in the JSON and clearing their `"value"` entries ---- - -## APIs Used +## Resources +- [Langflow Documentation](https://docs.langflow.org/) - [Open-Meteo](https://open-meteo.com/) — Weather and air quality (free, no key required) - [National Park Service API](https://developer.nps.gov) — Park search and alerts (free key required) diff --git a/agents/langflow/simple_tool_calling_agent/agent.yaml b/agents/langflow/simple_tool_calling_agent/agent.yaml new file mode 100644 index 00000000..d785e03f --- /dev/null +++ b/agents/langflow/simple_tool_calling_agent/agent.yaml @@ -0,0 +1,5 @@ +name: langflow-simple-tool-calling-agent +displayName: "Langflow Simple Tool Calling Agent" +framework: langflow +description: "Tool-calling agent built with Langflow's visual flow builder. Calls external APIs as tools and reasons over results." +deploymentModel: flow-import diff --git a/agents/langflow/simple_tool_calling_agent/cleanup-local.sh b/agents/langflow/simple_tool_calling_agent/cleanup-local.sh index 680748a0..cde6b5b4 100755 --- a/agents/langflow/simple_tool_calling_agent/cleanup-local.sh +++ b/agents/langflow/simple_tool_calling_agent/cleanup-local.sh @@ -7,17 +7,6 @@ LOCAL_DIR="$SCRIPT_DIR/local" cd "$LOCAL_DIR" || { echo "ERROR: Directory $LOCAL_DIR not found."; exit 1; } -stop_ollama() { - if pgrep -f "ollama serve" >/dev/null 2>&1; then - pkill -f "ollama serve" || true - if pgrep -f "ollama serve" >/dev/null 2>&1; then - echo "WARNING: Could not stop Ollama. Stop it manually: pkill -f 'ollama serve'" - else - echo "Ollama stopped." - fi - fi -} - if [ "${1:-}" = "--force" ]; then echo "Stopping services and removing all data..." # Stop containerized services @@ -32,16 +21,11 @@ if [ "${1:-}" = "--force" ]; then done # Remove project network podman network rm local_default || true - # Stop native Ollama - stop_ollama # Clean up config - rm -f "$LOCAL_DIR/.ollama-enabled" rm -f "$LOCAL_DIR/.env" echo "Done. All services stopped and data removed." else echo "Stopping services (data preserved)..." podman-compose down --remove-orphans || true - # Stop native Ollama - stop_ollama - echo "Done. Run './deploy-local.sh' to start again." + echo "Done. Run 'make run' to start again." fi diff --git a/agents/langflow/simple_tool_calling_agent/deploy-local.sh b/agents/langflow/simple_tool_calling_agent/deploy-local.sh index 2cf77f0c..afef7f0e 100755 --- a/agents/langflow/simple_tool_calling_agent/deploy-local.sh +++ b/agents/langflow/simple_tool_calling_agent/deploy-local.sh @@ -20,76 +20,10 @@ if ! command -v podman-compose &>/dev/null; then exit 1 fi -# Copy .env if it doesn't exist +# Ensure init has been run if [ ! -f "$LOCAL_DIR/.env" ]; then - cp "$LOCAL_DIR/.env.example" "$LOCAL_DIR/.env" - echo "Created .env from .env.example" -fi - -# Generate Langfuse secrets if not already set -if ! grep -q '^LANGFUSE_ADMIN_PASSWORD=.\+' "$LOCAL_DIR/.env" 2>/dev/null; then - GENERATED_PWD=$(openssl rand -base64 12) - sed -i.bak "s/^LANGFUSE_ADMIN_PASSWORD=.*/LANGFUSE_ADMIN_PASSWORD=$GENERATED_PWD/" "$LOCAL_DIR/.env" - rm -f "$LOCAL_DIR/.env.bak" - echo "Generated Langfuse admin password." -fi - -if ! grep -q '^LANGFUSE_ENCRYPTION_KEY=.\+' "$LOCAL_DIR/.env" 2>/dev/null; then - GENERATED_KEY=$(openssl rand -hex 32) - sed -i.bak "s/^LANGFUSE_ENCRYPTION_KEY=.*/LANGFUSE_ENCRYPTION_KEY=$GENERATED_KEY/" "$LOCAL_DIR/.env" - rm -f "$LOCAL_DIR/.env.bak" - echo "Generated Langfuse encryption key." -fi - -# Ask about Ollama on first run -OLLAMA_FLAG="$LOCAL_DIR/.ollama-enabled" -if [ ! -f "$OLLAMA_FLAG" ]; then - echo "" - read -p "Do you want to use Ollama as a local LLM? (Y/n) " -n 1 -r - echo "" - if [[ ! $REPLY =~ ^[Nn]$ ]]; then - echo "yes" > "$OLLAMA_FLAG" - else - echo "no" > "$OLLAMA_FLAG" - echo "Ollama disabled. You can point Langflow to an external model endpoint instead." - fi -fi - -USE_OLLAMA=$(cat "$OLLAMA_FLAG") - -# Start Ollama natively (not in a container) for GPU acceleration -if [ "$USE_OLLAMA" = "yes" ]; then - if ! command -v ollama &>/dev/null; then - echo "Ollama is not installed. Installing..." - if [[ "$(uname)" == "Darwin" ]]; then - brew install ollama - else - curl -fsSL https://ollama.com/install.sh | sh - fi - fi - - # Start ollama if not already running - if ! curl -s http://localhost:11434/api/tags &>/dev/null; then - echo "Starting Ollama..." - ollama serve &>/dev/null & - sleep 3 - else - echo "Ollama is already running." - fi - - # Pull model if needed - if [ -f "$LOCAL_DIR/.env" ]; then - OLLAMA_MODEL=$(grep -E '^OLLAMA_MODEL=' "$LOCAL_DIR/.env" | cut -d= -f2- || true) - fi - OLLAMA_MODEL="${OLLAMA_MODEL:-qwen2.5:7b}" - - if ollama list | grep -q "$OLLAMA_MODEL"; then - echo "Ollama model '$OLLAMA_MODEL' already available." - else - echo "Pulling Ollama model: $OLLAMA_MODEL (first time only)..." - ollama pull "$OLLAMA_MODEL" || \ - echo "Warning: Could not pull model. Run manually: ollama pull $OLLAMA_MODEL" - fi + echo "ERROR: local/.env not found. Run 'make init' first." + exit 1 fi cd "$LOCAL_DIR" || { echo "ERROR: Directory $LOCAL_DIR not found."; exit 1; } @@ -139,18 +73,15 @@ echo "=== Local environment is ready ===" echo "" echo " Langflow UI: http://localhost:7860" echo " Langfuse: http://localhost:3000 (login: admin@langflow.local / password auto-generated in local/.env)" -if [ "$USE_OLLAMA" = "yes" ]; then - echo " Ollama API: http://localhost:11434 (running natively on host)" -fi echo "" echo " Next steps:" echo " 1. Open http://localhost:7860" echo " 2. Import the flow: flows/outdoor-activity-agent.json" -echo " 3. Configure the flow components:" -echo " - KServe vLLM: set api_base=http://host.containers.internal:11434/v1 and model_name=qwen2.5:7b" +echo " 3. Configure the flow components (see README for details):" +echo " - KServe vLLM: set api_base and model_name (Ollama, LlamaStack, or remote endpoint)" echo " - NPS Search Parks: set api_key (get one at https://developer.nps.gov)" echo " - NPS Park Alerts: set api_key (same NPS key)" echo " 4. Run the agent from the Langflow UI" echo "" -echo " To stop: ./cleanup-local.sh" -echo " To stop and wipe data: ./cleanup-local.sh --force" +echo " To stop: make stop" +echo " To stop and wipe data: make clean" diff --git a/docs/adding-a-new-agent.md b/docs/adding-a-new-agent.md index ffb4f71b..dbae0085 100644 --- a/docs/adding-a-new-agent.md +++ b/docs/adding-a-new-agent.md @@ -41,6 +41,20 @@ Every agent must have: | `tests/` | Tests directory | | `examples/` | Example scripts | +### Flow-based agents (e.g., Langflow) + +Some agents do not deploy a custom container. Instead, the "agent" is a flow definition (e.g., JSON) imported into a pre-existing platform instance. These agents should still have: + +| File | Purpose | +|------|---------| +| `agent.yaml` | Same as above, but add `deploymentModel: flow-import` and adjust env vars to match the infrastructure stack | +| `Makefile` | Targets: `init`, `run`, `stop`, `clean`, `status`, `logs`, `help` | +| `.env.example` | Environment variables for the local infrastructure stack | +| `README.md` | Setup, usage, and deployment instructions | +| `flows/` | Flow definition files (JSON, YAML, etc.) | + +These agents do **not** need: `Dockerfile`, `values.yaml`, `.dockerignore`, `pyproject.toml`, `main.py`, `src/`, `tests/`, `examples/`, `playground/`. + ## 4. API Contract All agents must expose these endpoints: diff --git a/docs/local-development.md b/docs/local-development.md index 4ae306af..0cf32db9 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -114,17 +114,6 @@ cd agents/langgraph/react_agent make test ``` -## Building Container Images Locally - -The Makefiles auto-detect [Podman](https://podman.io/) (preferred) or Docker for container builds: - -```bash -make build # builds image locally using podman (or docker) -make push # pushes image to registry -``` - -To install Podman, see [podman.io/docs/installation](https://podman.io/docs/installation). On RHEL/Fedora it is pre-installed. - ## Dependencies All agents use [uv](https://docs.astral.sh/uv/) for dependency management: