├── .github └── workflows │ └── helix.yml ├── README.md ├── aispecs └── exchangerates.yaml ├── images ├── 3-terminals.png ├── complete_user_registeration_screenshot.png ├── exchangerates.png ├── helix_login_page_screenshot.png ├── helix_login_register_link_screenshot.png ├── ref.png ├── try_out_helix_ui_screenshot.png └── user_registeration_link_screenshot.png └── kind_helm_install.sh /.github/workflows/helix.yml: -------------------------------------------------------------------------------- 1 | name: CI for GenAI 2 | on: 3 | push: 4 | branches: [ "main" ] 5 | pull_request: 6 | branches: [ "main" ] 7 | workflow_dispatch: 8 | 9 | env: 10 | HELIX_URL: ${{ vars.HELIX_URL }} 11 | HELIX_API_KEY: ${{ secrets.HELIX_API_KEY }} 12 | 13 | jobs: 14 | test-and-comment: 15 | runs-on: ubuntu-latest 16 | if: github.event_name == 'pull_request' 17 | permissions: 18 | pull-requests: write 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Install helix CLI 23 | run: curl -sL -O https://get.helix.ml/install.sh && bash install.sh --cli -y 24 | 25 | - name: Test the helix app 26 | run: helix test -f aispecs/exchangerates.yaml --evaluation-model meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo 27 | 28 | - name: PR comment with file 29 | if: always() 30 | uses: thollander/actions-comment-pull-request@v3 31 | with: 32 | file-path: summary_latest.md 33 | 34 | test-on-main: 35 | runs-on: ubuntu-latest 36 | if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Install helix CLI 41 | run: curl -sL -O https://get.helix.ml/install.sh && bash install.sh --cli -y 42 | 43 | - name: Test the helix app 44 | run: helix test -f aispecs/exchangerates.yaml --evaluation-model meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CI/CD for cloud-native GenAI reference architecture 2 | 3 | Walkthrough: https://youtu.be/noilYeBqNUA?si=7awZFw5g_upbkPKc 4 | 5 | This repo will show you how to create a complete CI/CD architecture for GenAI apps running on Kubernetes, supporting a fully private deployment. 6 | 7 | This example will run entirely locally on a `kind` cluster on your laptop. 8 | In development, we will use external LLM provider `together.ai`. 9 | Helix will provide the versioned AI app implementation (prompt management, knowledge/RAG and API integrations) and evals (testing). GitHub actions will run the evals in CI. Flux will manage deployment. 10 | 11 | ![Reference Architecture](images/ref.png) 12 | 13 | There are two main flows: the CI (testing) flow where you can run `helix test` locally or in CI. And the CD flow where changes to AI apps merged to the main branch get reconciled into the cluster using Flux and deployed to Helix by the helix k8s operator. 14 | 15 | # Setup 16 | 17 | ## 1. Fork this repo 18 | 19 | Start by forking this repo. This is because part of the workflow is pushing changes to the repo and having GitHub Actions and Flux react to changes, so you'll need write access to the repo. 20 | 21 | Then check out the repo on your local machine: 22 | 23 | ``` 24 | export GITHUB_USERNAME= 25 | ``` 26 | 27 | ``` 28 | git clone git@github.com:${GITHUB_USERNAME}/genai-cicd-ref 29 | cd genai-cicd-ref 30 | ``` 31 | 32 | ## 2. Install helix in kind 33 | 34 | Requirements: 35 | * [docker](https://www.docker.com/) 36 | * [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or `brew install kind` 37 | * [kubectl](https://kubernetes.io/docs/tasks/tools/) or `brew install kubectl` 38 | * [helm](https://helm.sh/docs/intro/install/) or `brew install helm` 39 | * [flux cli](https://fluxcd.io/flux/installation/) or `brew install fluxcd/tap/flux` 40 | * [helix cli](https://docs.helix.ml/helix/private-deployment/controlplane/#just-install-the-cli) or `curl -sL -O https://get.helix.ml/install.sh && bash install.sh --cli` 41 | * [ngrok](https://ngrok.com/docs/getting-started/) or `brew install ngrok/ngrok/ngrok` 42 | 43 | We will run the `kind_helm_install.sh` script which will create a kind cluster and install helix in it via helm. 44 | 45 | For this deployment, to simplify things, we'll use [Together.ai](https://together.ai) as an external LLM provider (which provides free credit for new accounts), but you can later attach a Helix GPU runner [in Kubernetes](https://docs.helix.ml/helix/private-deployment/manual-install/kubernetes/#deploying-a-runner) or [otherwise](https://docs.helix.ml/helix/private-deployment/manual-install/). 46 | 47 | ``` 48 | export TOGETHER_API_KEY= 49 | bash kind_helm_install.sh 50 | ``` 51 | If at any point you need to start over, you can just re-run the script (it will tear down the kind cluster and recreate it from scratch). 52 | 53 | ``` 54 | watch kubectl get po 55 | ``` 56 | 57 | This will show helix starting up and running in your local kind cluster. 58 | Once all the pods are running, `ctrl+c` the `watch` and run the four commands the script printed at the end of the install to start a port-forward session: 59 | ``` 60 | export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=helix-controlplane,app.kubernetes.io/instance=my-helix-controlplane" -o jsonpath="{.items[0].metadata.name}") 61 | export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 62 | echo "Visit http://localhost:8080 to use your application" 63 | kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT 64 | ``` 65 | Leave that running. 66 | 67 | Load [http://localhost:8080](http://localhost:8080) and you should see Helix. It takes a few minutes to boot. 68 | 69 | Register for a new account (in your local helix install, through the web interface) and log in. 70 | 71 |
72 | Detailed steps for registering and logging in 73 | 74 | 1. In a web browser go to: http://localhost:8080/ 75 | ![helix_login_page_screenshot](images/helix_login_page_screenshot.png) 76 | 2. Register local user 77 | 1. Bottom left pane - click on "Login/Register" 78 | 79 | ![helix_login_register_link_screenshot](images/helix_login_register_link_screenshot.png) 80 | 81 | 2. Click on "Register" to begin the user registeration process 82 | 83 | ![user_registeration_link_screenshot](images/user_registeration_link_screenshot.png) 84 | 85 | 3. Complete user registration 86 | 87 | ![complete_user_registeration_screenshot](images/complete_user_registeration_screenshot.png) 88 | 89 | 2. To access the app, log in to the local HelixML UI with your registered user credentials. Test out creating a chat session 90 | 91 | ![try_out_helix_ui_screenshot](images/try_out_helix_ui_screenshot.png) 92 |
93 | 94 | Install the aispec CRDs and start the Helix Kubernetes Operator. For now we do this by cloning the helix repo, but these will be properly packaged and released as container images soon. In a new terminal session (you will need go installed - e.g `brew install go`): 95 | 96 | ``` 97 | git clone https://github.com/helixml/helix 98 | cd helix/operator 99 | make install 100 | ``` 101 | 102 | Go to your [helix account page](http://localhost:8080/account) (click the ... button in the bottom left and go to Account & API section) then copy and paste the `export` commands for `HELIX_URL` and `HELIX_API_KEY` from the "Set authentication credentials" section. Run them, then run the Helix Kubernetes Operator: 103 | 104 | ``` 105 | make run 106 | ``` 107 | 108 | Leave the operator running in this terminal window. You should have two terminal windows now: one with the `port-forward` running in it and another with the helix operator running in it. 109 | 110 | Test that the operator is working by deploying an aispec just with `kubectl` in a new terminal window: 111 | ``` 112 | kubectl apply -f aispecs/exchangerates.yaml 113 | ``` 114 | 115 | It should look like this: 116 | 117 | ![3 terminals showing portforward and operator running side by side](images/3-terminals.png) 118 | 119 | Inside helix, the app should now be working. Go to the [app store](http://localhost:8080/appstore) on the homepage, then launch the exchange rates app: 120 | 121 | ![Screenshot of Exchange Rates Chatbot](images/exchangerates.png) 122 | 123 | You can use it to query live currency exchange rates. 124 | 125 | Clean up the app: 126 | ``` 127 | kubectl delete -f aispecs/exchangerates.yaml 128 | ``` 129 | 130 | ## 3. Install Flux 131 | 132 | We will use Flux to automate GitOps deployments of changes to this app, rather than manually using `kubectl`. 133 | 134 | Install flux in the kind cluster: 135 | ``` 136 | flux install 137 | ``` 138 | 139 | Add your fork of this repo to flux: 140 | 141 | ``` 142 | export GITHUB_USERNAME= 143 | ``` 144 | 145 | ``` 146 | flux create source git aispecs \ 147 | --url=https://github.com/${GITHUB_USERNAME}/genai-cicd-ref \ 148 | --branch=main 149 | ``` 150 | 151 | Set up flux to reconcile aispecs in your fork: 152 | ``` 153 | flux create kustomization aispecs \ 154 | --source=GitRepository/aispecs \ 155 | --path="./aispecs" --prune=true \ 156 | --interval=1m --target-namespace=default 157 | ``` 158 | 159 | ## 4. Set up GitHub Actions 160 | 161 | So that the GitHub Actions in this repository can run against your local kind cluster, we'll run ngrok and configure GitHub Actions with the appropriate HELIX_URL variable and HELIX_API_KEY secret. 162 | 163 | Start ngrok forwarding to your local Helix server 164 | ``` 165 | ngrok http 8080 166 | ``` 167 | 168 | In a new terminal, get the public URL: 169 | ``` 170 | curl -s localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url' 171 | ``` 172 | 173 | The output will look something like: `https://abc123.ngrok.io` 174 | 175 | Add the URL as a variable and API key as a secret to your GitHub repository: 176 | 177 | 1. Go to your GitHub repository settings for this repo 178 | 2. Click on "Settings" tab 179 | 3. In the left sidebar, click "Secrets and variables" -> "Actions" 180 | 4. Click the "Variables" tab 181 | 5. Click "New repository variable" 182 | - Name: `HELIX_URL` 183 | - Value: The ngrok URL from above (e.g. `https://abc123.ngrok.io`) 184 | 6. Click the "Secrets" tab 185 | 7. Click "New repository secret" 186 | - Name: `HELIX_API_KEY` 187 | - Value: The API key from your Helix account page 188 | 189 | These credentials will be used by the GitHub Actions workflows to authenticate with your local Helix instance. 190 | 191 | Go to your Actions tab, find a failing run and re-run it to check that it works and tests the Helix app on the main branch. 192 | 193 | 194 | # Continuous Integration: Testing 195 | 196 | Go to your [helix account page](http://localhost:8080/account) (click the ... button in the bottom left and go to Account & API section, then copy and paste the `export` commands for `HELIX_URL` and `HELIX_API_KEY`). 197 | 198 | ``` 199 | git checkout -b new-feature 200 | ``` 201 | 202 | Edit the aispec `aispec/exchangerates.yaml` to add a feature or test. 203 | 204 | Run tests locally: 205 | ``` 206 | helix test -f aispecs/exchangerates.yaml --evaluation-model meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo 207 | ``` 208 | 209 | Push to CI: 210 | ``` 211 | git commit -am "update" 212 | git push 213 | ``` 214 | 215 | You will see the tests run in github actions when you make a pull request. 216 | 217 | # Continuous Delivery: Deployment via GitOps 218 | 219 | If the tests are green, you can merge to main. 220 | On push to main, Flux will pick up the new manifest and deploy it to your cluster. 221 | 222 | You can run: 223 | ``` 224 | flux get kustomizations --watch 225 | ``` 226 | Flux can take up to a minute to notice the change in the repo. 227 | 228 | Open the app in your browser by navigating to the "App Store" in your local helix install web UI, and observe the new improved GenAI capabilities! 229 | -------------------------------------------------------------------------------- /aispecs/exchangerates.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: app.aispec.org/v1alpha1 2 | kind: AIApp 3 | metadata: 4 | name: exchangerates 5 | spec: 6 | assistants: 7 | - model: meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo 8 | type: text 9 | # Tests for this assistant 10 | tests: 11 | - name: check usd to gbp rate 12 | steps: 13 | - prompt: what is the usd to gbp exchange rate? 14 | expected_output: the usd to gbp exchange rate. it's ok if it includes additional information such as the rate being based on the latest data. 15 | - name: usdgbp 16 | steps: 17 | - prompt: usdgbp 18 | expected_output: the usd to gbp exchange rate only. it should specifically mention both usd and gbp and not other currencies. if it mentions other currencies, FAIL the test 19 | apis: 20 | - name: Exchange Rates API 21 | description: Get latest currency exchange rates 22 | url: https://open.er-api.com/v6 23 | schema: |- 24 | openapi: 3.0.0 25 | info: 26 | title: Exchange Rates API 27 | description: Get latest currency exchange rates 28 | version: "1.0.0" 29 | servers: 30 | - url: https://open.er-api.com/v6 31 | paths: 32 | /latest/{currency}: 33 | get: 34 | operationId: getExchangeRates 35 | summary: Get latest exchange rates 36 | description: Get current exchange rates for a base currency 37 | parameters: 38 | - name: currency 39 | in: path 40 | required: true 41 | description: Base currency code (e.g., USD, EUR, GBP) 42 | schema: 43 | type: string 44 | responses: 45 | '200': 46 | description: Successful response with exchange rates 47 | content: 48 | application/json: 49 | schema: 50 | type: object 51 | properties: 52 | result: 53 | type: string 54 | example: "success" 55 | provider: 56 | type: string 57 | example: "Open Exchange Rates" 58 | base_code: 59 | type: string 60 | example: "USD" 61 | time_last_update_utc: 62 | type: string 63 | example: "2024-01-19 00:00:01" 64 | rates: 65 | type: object 66 | properties: 67 | EUR: 68 | type: number 69 | example: 0.91815 70 | GBP: 71 | type: number 72 | example: 0.78543 73 | JPY: 74 | type: number 75 | example: 148.192 76 | AUD: 77 | type: number 78 | example: 1.51234 79 | CAD: 80 | type: number 81 | example: 1.34521 82 | 83 | request_prep_template: | 84 | Your output must be a valid json, without any commentary or additional formatting. 85 | 86 | Examples: 87 | 88 | **User Input:** Get project prj_1234 details 89 | **OpenAPI schema path:** /projects/{projectId} 90 | **Verdict:** response should be 91 | 92 | ```json 93 | { 94 | "projectId": "prj_1234" 95 | } 96 | ``` 97 | 98 | **User Input:** What job is Marcus applying for? 99 | **OpenAPI schema path:** /jobvacancies/v1/list 100 | **OpenAPI schema parameters:** [ 101 | { 102 | "in": "query", 103 | "name": "candidate_name", 104 | "schema": { 105 | "type": "string" 106 | }, 107 | "required": false, 108 | "description": "Filter vacancies by candidate name" 109 | } 110 | ] 111 | **Verdict:** response should be: 112 | 113 | ```json 114 | { 115 | "candidate_name": "Marcus" 116 | } 117 | ``` 118 | 119 | **User Input:** List all users with status "active" 120 | **OpenAPI schema path:** /users/findByStatus 121 | **OpenAPI schema parameters:** [ 122 | { 123 | "name": "status", 124 | "in": "query", 125 | "description": "Status values that need to be considered for filter", 126 | "required": true, 127 | "type": "array", 128 | "items": { 129 | "type": "string", 130 | "enum": ["active", "pending", "sold"], 131 | "default": "active" 132 | } 133 | } 134 | ] 135 | **Verdict:** response should be: 136 | 137 | ```json 138 | { 139 | "status": "active" 140 | } 141 | ``` 142 | 143 | **Response Format:** Always respond with JSON without any commentary, wrapped in markdown json tags, for example: 144 | ```json 145 | { 146 | "parameterName": "parameterValue", 147 | "parameterName2": "parameterValue2" 148 | } 149 | ``` 150 | 151 | ===END EXAMPLES=== 152 | 153 | OpenAPI schema: 154 | 155 | {{.Schema}} 156 | 157 | ===END OPENAPI SCHEMA=== 158 | 159 | Based on conversation below, construct a valid JSON object. In cases where user input does not contain information for a query, DO NOT add that specific query parameter to the output. If a user doesn't provide a required parameter, use sensible defaults for required params, and leave optional params out. Do not pass parameters as null, instead just don't include them. 160 | ONLY use search parameters from the user messages below - do NOT use search parameters provided in the examples. 161 | 162 | If the user asks for a currency pair like "usdgbp", then split it apart into "usd" and "gbp", and search for the non-USD currency for example {"currency": "gbp"} 163 | 164 | response_success_template: | 165 | Present the key information in a concise manner and perform any actions requested by the user. 166 | Include relevant details, references, and links if present. Format the summary in Markdown for clarity and readability where appropriate, but don't mention formatting in your response unless it's relevant to the user's query. 167 | Make sure to NEVER mention technical terms like "APIs, JSON, Request, etc..." and use first person pronoun (say it as if you performed the action) 168 | 169 | If the user presented a currency pair e.g. usdgbp, and we searched for the non-USD currency, then present ONLY the USD to given currency rate, NOT including any other currencies other than the two in the currency pair the user provided. 170 | 171 | 172 | response_error_template: | 173 | As an AI chat assistant, your job is to help the user understand and resolve API error messages. 174 | When offering solutions, You will clarify without going into unnecessary detail. You must respond in less than 100 words. 175 | You should commence by saying "An error occurred while trying to process your request ..." also, if you think it's auth error, ask the user to read this doc https://docs.helix.ml/helix/develop/helix-tools/ (format as markdown) 176 | -------------------------------------------------------------------------------- /images/3-terminals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/3-terminals.png -------------------------------------------------------------------------------- /images/complete_user_registeration_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/complete_user_registeration_screenshot.png -------------------------------------------------------------------------------- /images/exchangerates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/exchangerates.png -------------------------------------------------------------------------------- /images/helix_login_page_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/helix_login_page_screenshot.png -------------------------------------------------------------------------------- /images/helix_login_register_link_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/helix_login_register_link_screenshot.png -------------------------------------------------------------------------------- /images/ref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/ref.png -------------------------------------------------------------------------------- /images/try_out_helix_ui_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/try_out_helix_ui_screenshot.png -------------------------------------------------------------------------------- /images/user_registeration_link_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helixml/genai-cicd-ref/a5ddc542bf5e5d1ac444eaca2feb6f241e6de0ec/images/user_registeration_link_screenshot.png -------------------------------------------------------------------------------- /kind_helm_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # This script can be used to test local changes to the Helix Helm chart. To do 5 | # this, export USE_LOCAL_HELM_CHART=1 and run the script. 6 | 7 | USE_LOCAL_HELM_CHART=${USE_LOCAL_HELM_CHART:-""} 8 | 9 | # Function to check if a command is installed 10 | check_command() { 11 | if ! command -v $1 &> /dev/null 12 | then 13 | echo "$1 could not be found, please install it before proceeding." 14 | exit 1 15 | else 16 | echo "$1 is installed." 17 | fi 18 | } 19 | 20 | # Check if kind is installed 21 | check_command "kind" 22 | 23 | # Check if Docker is running 24 | check_command "docker" 25 | 26 | # Check if kubectl is installed 27 | check_command "kubectl" 28 | 29 | # Check if helm is installed 30 | check_command "helm" 31 | 32 | # Check that user has TOGETHER_API_KEY set 33 | if [ -z "${TOGETHER_API_KEY:-}" ]; then 34 | echo "TOGETHER_API_KEY is not set. Please set it before proceeding." 35 | exit 1 36 | fi 37 | 38 | # Set the cluster name 39 | CLUSTER_NAME="helix-cluster" 40 | 41 | # Get a temporary working directory 42 | DIR=$(mktemp -d) 43 | 44 | # If the cluster already exists, delete it 45 | if kind get clusters | grep -q $CLUSTER_NAME; then 46 | echo "Deleting existing cluster named $CLUSTER_NAME..." 47 | kind delete cluster --name $CLUSTER_NAME 48 | fi 49 | 50 | # Create a kind cluster with the specified name 51 | echo "Creating a kind cluster named $CLUSTER_NAME..." 52 | kind create cluster --name $CLUSTER_NAME 53 | 54 | # Set kubectl context to the newly created cluster 55 | echo "Setting kubectl context to $CLUSTER_NAME..." 56 | kubectl cluster-info --context kind-$CLUSTER_NAME 57 | 58 | # Install Keycloak using Helm 59 | helm upgrade --install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \ 60 | --set auth.adminUser=admin \ 61 | --set auth.adminPassword=oh-hallo-insecure-password \ 62 | --set image.tag="23.0.7" \ 63 | --set httpRelativePath="/auth/" 64 | 65 | # Wait for pod to exist 66 | echo "Waiting for Keycloak pod to exist..." 67 | until echo $(kubectl get pod -l app.kubernetes.io/name=keycloak) | grep "keycloak"; do 68 | sleep 2 69 | echo -n "." 70 | done 71 | 72 | # Wait for Keycloak to be ready 73 | echo "Waiting for Keycloak to be ready..." 74 | kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=keycloak --timeout=300s 75 | 76 | if [ -n "$USE_LOCAL_HELM_CHART" ] && [ "$USE_LOCAL_HELM_CHART" != "false" ] && [ "$USE_LOCAL_HELM_CHART" != "" ]; then 77 | echo "Using local Helm chart..." 78 | 79 | # Change to the root directory of the Helix project 80 | cd "$(dirname "$0")/.." 81 | 82 | # Ensure we're in the correct directory 83 | if [ ! -d "./charts/helix-controlplane" ]; then 84 | echo "Error: charts/helix-controlplane directory not found. Make sure you're running this script from the root of the Helix project." 85 | exit 1 86 | fi 87 | 88 | cp charts/helix-controlplane/values-example.yaml $DIR/values-example.yaml 89 | 90 | CHART=./charts/helix-controlplane 91 | else 92 | # Add the Helix Helm chart repository 93 | echo "Adding the Helix Helm chart repository..." 94 | helm repo add helix https://charts.helix.ml 95 | helm repo update 96 | # Grab the latest values-example.yaml 97 | echo "Downloading the latest values-example.yaml..." 98 | curl -o $DIR/values-example.yaml https://raw.githubusercontent.com/helixml/helix/main/charts/helix-controlplane/values-example.yaml 99 | 100 | CHART=helix/helix-controlplane 101 | fi 102 | 103 | # Install Helix using Helm 104 | echo "Installing Helix..." 105 | export HELIX_VERSION=${HELIX_VERSION:-$(curl -s https://get.helix.ml/latest.txt)} 106 | helm upgrade --install my-helix-controlplane $CHART \ 107 | -f $DIR/values-example.yaml \ 108 | --set image.tag="${HELIX_VERSION}" \ 109 | --set envVariables.TOGETHER_API_KEY=${TOGETHER_API_KEY} 110 | --------------------------------------------------------------------------------