From 20cbb84ef1360dcb623c577e9c4456bb220396b4 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Fri, 6 Sep 2024 16:31:05 -0400 Subject: [PATCH 01/28] fixed Makefile for integration tests --- .gitignore | 2 +- Makefile | 3 ++- tests/Makefile | 72 ++++++++++++++++++++++++++++++++------------------ 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 2b9d60287..abde59662 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ build/ **/*.whl .model/ *.gguf -.env +.env* .ruff_cache .branches .temp diff --git a/Makefile b/Makefile index 732abe4e5..1938eb8db 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ help: ## Display this help information | sort | awk 'BEGIN {FS = ":.*?## "}; \ {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -clean: ## Clean up all the things (packages, build dirs, compiled .whl files, python eggs) +clean: ## Clean up all the things (test artifacts, packages, build dirs, compiled .whl files, python eggs) + -rm .env.email .env.password .pytest_cache -rm -rf .logs -rm zarf-package-*.tar.zst -rm packages/**/zarf-package-*.tar.zst diff --git a/tests/Makefile b/tests/Makefile index 53441cd0f..03c9709de 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,41 +1,63 @@ -SUPABASE_URL := https://supabase-kong.uds.dev +SUPABASE_URL ?= https://supabase-kong.uds.dev set-supabase: - SUPABASE_URL := ${SUPABASE_URL} - SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d) + $(eval SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d)) + @echo "Supabase URL: $(SUPABASE_URL)" + @echo "Supabase Anon Key: $(SUPABASE_ANON_KEY)" define get_jwt_token - echo "Getting JWT token from ${SUPABASE_URL}..."; \ - TOKEN_RESPONSE=$$(curl -s -X POST $(1) \ - -H "apikey: ${SUPABASE_ANON_KEY}" \ + echo "Getting JWT token from $(SUPABASE_URL)..."; \ + echo "Email: $(1)"; \ + echo "Password: $(2)"; \ + TOKEN_RESPONSE=$$(curl -s -X POST $(3) \ + -H "apikey: $(SUPABASE_ANON_KEY)" \ -H "Content-Type: application/json" \ - -d '{ "email": "admin@localhost", "password": "$$SUPABASE_PASS"}'); \ - echo "Extracting token from $${TOKEN_RESPONSE}"; \ - JWT=$$(echo $${TOKEN_RESPONSE} | grep -o '"access_token":"[^"]*' | cut -d '"' -f 4); \ + -d "{\"email\": \"$(1)\", \"password\": \"$(2)\"}"); \ + echo "Extracting token from $$TOKEN_RESPONSE"; \ + JWT=$$(echo $$TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d '"' -f 4); \ echo "SUPABASE_USER_JWT=$$JWT" > .env; \ - echo "SUPABASE_URL=$$SUPABASE_URL" >> .env; \ - echo "SUPABASE_ANON_KEY=$$SUPABASE_ANON_KEY" >> .env; \ + echo "SUPABASE_URL=$(SUPABASE_URL)" >> .env; \ + echo "SUPABASE_ANON_KEY=$(SUPABASE_ANON_KEY)" >> .env; \ echo "DONE - variables exported to .env file" endef -test-user: set-supabase - @read -s -p "Enter a new DEV API password: " SUPABASE_PASS; echo; \ - echo "Creating new supabase user..."; \ - $(call get_jwt_token,"${SUPABASE_URL}/auth/v1/signup") - -test-env: set-supabase - @read -s -p "Enter your DEV API password: " SUPABASE_PASS; echo; \ - $(call get_jwt_token,"${SUPABASE_URL}/auth/v1/token?grant_type=password") - -test-api-integration: set-supabase - source .env; PYTHONPATH=$$(pwd) pytest -vv -s tests/integration/api +prompt-email: + @echo "Enter your email address: "; \ + read SUPABASE_EMAIL; \ + echo $$SUPABASE_EMAIL > .env.email + +prompt-password: + @echo "Enter your DEV API password: "; \ + read SUPABASE_PASS; \ + echo $$SUPABASE_PASS > .env.password + +test-user: set-supabase prompt-email prompt-password + SUPABASE_EMAIL=$$(cat .env.email | tr -d '\n'); \ + SUPABASE_PASS=$$(cat .env.password | tr -d '\n'); \ + $(call get_jwt_token,$$SUPABASE_EMAIL,$$SUPABASE_PASS,"$(SUPABASE_URL)/auth/v1/signup") + +test-env: set-supabase prompt-email prompt-password + SUPABASE_EMAIL=$$(cat .env.email | tr -d '\n'); \ + SUPABASE_PASS=$$(cat .env.password | tr -d '\n'); \ + $(call get_jwt_token,$$SUPABASE_EMAIL,$$SUPABASE_PASS,"$(SUPABASE_URL)/auth/v1/token?grant_type=password") + +test-api-integration: + @if [ ! -f .env ]; then \ + echo ".env file not found!"; \ + exit 1; \ + fi + @if ! grep -q SUPABASE_USER_JWT .env || ! grep -q SUPABASE_URL .env || ! grep -q SUPABASE_ANON_KEY .env; then \ + echo "Required environment variables (SUPABASE_USER_JWT, SUPABASE_URL, SUPABASE_ANON_KEY) are missing in .env!"; \ + exit 1; \ + fi + @env $$(cat .env | xargs) PYTHONPATH=$$(pwd) pytest -vv -s tests/integration/api test-api-unit: set-supabase PYTHONPATH=$$(pwd) pytest -vv -s tests/unit test-load: - locust -f ${PWD}/tests/load/loadtest.py --web-port 8089 + locust -f $$(pwd)/tests/load/loadtest.py --web-port 8089 debug: set-supabase - @echo ${SUPABASE_URL} - @echo ${SUPABASE_ANON_KEY} + @echo $(SUPABASE_URL) + @echo $(SUPABASE_ANON_KEY) From 3955adc0762a1f129f299a8caacab53e401398e8 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Fri, 6 Sep 2024 17:44:26 -0400 Subject: [PATCH 02/28] more docs for running tests --- tests/README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/README.md b/tests/README.md index 46951f643..e42438ae3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -4,7 +4,7 @@ This document outlines tests related to the LeapfrogAI API and backends. Please see the [documentation in the LeapfrogAI UI sub-directory](../src/leapfrogai_ui/README.md) for Svelte UI Playwright tests. -## API Tests +## API For the unit and integration tests within this directory, the following components must be running and accessible: @@ -12,9 +12,20 @@ For the unit and integration tests within this directory, the following componen - [Repeater](../packages/repeater/README.md) - [Supabase](../packages/supabase/README.md) -Please see the [Makefile](./Makefile) for more details. Below is a quick synopsis of the available Make targets: +If you are running everything in a [UDS Kubernetes cluster](../k3d-gpu/README.md), you must port-forward your model (e.g., Repeater, vLLM, etc.) using the following command: ```bash +uds zarf connect --name=vllm-model --namespace=leapfrogai --local-port=50051 --remote-port=50051 +``` + +If running everything via Docker containers or in a local Python environment, then ensure they are accessible based on the test configurations in each testing target's sub-directory. + +Please see the [Makefile](./Makefile) for more details. Below is a quick synopsis of the available Make targets that are **run from the root of the entire repository**: + +```bash +# Install the python dependencies +python -m pip install ".[dev]" + # create a test user for the tests make test-user SUPABASE_URL=https://supabase-kong.uds.dev @@ -34,7 +45,7 @@ Please see the [Load Test documentation](./load/README.md) and directory for mor ## End-To-End Tests -End-to-End (E2E) tests are located in the `e2e/` sub-directory. Each E2E test runs independently based on the model backend that we are trying to test. +End-to-End (E2E) tests are located in the `e2e/` sub-directory. Each E2E test runs independently based on the model backend that is to be tested. ### Running Tests From 32e31660348dd669f455268572951d3b7e25e038 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 13:54:09 -0400 Subject: [PATCH 03/28] WIP integration workflow --- .github/workflows/pytest-shim.yaml | 8 +++- .github/workflows/pytest.yaml | 71 +++++++++++++++++++++++++++--- Makefile | 2 +- tests/Makefile | 7 ++- tests/README.md | 10 +++-- 5 files changed, 85 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pytest-shim.yaml b/.github/workflows/pytest-shim.yaml index 0f5478754..af216a0ab 100644 --- a/.github/workflows/pytest-shim.yaml +++ b/.github/workflows/pytest-shim.yaml @@ -42,7 +42,6 @@ concurrency: group: pytest-skip-${{ github.ref }} cancel-in-progress: true - jobs: pytest: runs-on: ubuntu-latest @@ -50,3 +49,10 @@ jobs: - name: Skipped run: | echo skipped + + integration: + runs-on: ubuntu-latest + steps: + - name: Skipped + run: | + echo skipped diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 87cac7d56..e5c5aaca6 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -1,12 +1,13 @@ name: pytest + on: pull_request: types: - - opened # default trigger - - reopened # default trigger - - synchronize # default trigger - - ready_for_review # don't run on draft PRs - - milestoned # allows us to trigger on bot PRs + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs paths: - "**" - "!.github/**" @@ -46,7 +47,7 @@ jobs: - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: - python-version-file: 'pyproject.toml' + python-version-file: "pyproject.toml" - name: Build Repeater env: @@ -63,3 +64,61 @@ jobs: run: python -m pytest tests/pytest -v env: LFAI_RUN_REPEATER_TESTS: true + + integration: + runs-on: ai-ubuntu-big-boy-8-core + + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Python + uses: ./.github/actions/python + + - name: Setup UDS Cluster + uses: ./.github/actions/uds-cluster + with: + registry1Username: ${{ secrets.IRON_BANK_ROBOT_USERNAME }} + registry1Password: ${{ secrets.IRON_BANK_ROBOT_PASSWORD }} + ghToken: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup API and Supabase + uses: ./.github/actions/lfai-core + + - name: Deploy text-embeddings + run: | + make build-text-embeddings LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst -l=trace --confirm + rm packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst + + - name: Build Repeater + env: + LOCAL_VERSION: dev + run: | + make docker-repeater + - name: Run Repeater + run: docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev + + - name: Generate Secrets + id: generate_secrets + run: | + SUPABASE_PASS=$(cat <(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9!@#$%^&*()_+-=[]{}|;:,.<>?' | head -c 20) <(echo '!@1Aa') | fold -w1 | shuf | tr -d '\n') + echo "::add-mask::$SUPABASE_PASS" + echo "SUPABASE_PASS=$SUPABASE_PASS" >> $GITHUB_OUTPUT + SUPABASE_ANON_KEY=$(uds zarf tools kubectl get secret supabase-bootstrap-jwt -n leapfrogai -o jsonpath='{.data.anon-key}' | base64 -d) + echo P"::add-mask::$SUPABASE_ANON_KEY" + echo "SUPABASE_ANON_KEY=$SUPABASE_ANON_KEY" >> $GITHUB_OUTPUT + + - name: Verify Secrets + run: | + echo "SUPABASE_ANON_KEY is set: ${{ steps.generate_secrets.outputs.SUPABASE_ANON_KEY != '' }}" + echo "SUPABASE_PASS is set: ${{ steps.generate_secrets.outputs.SUPABASE_PASS != '' }}" + + - name: Run Integration Tests + env: + SUPABASE_ANON_KEY: ${{ steps.generate_secrets.outputs.SUPABASE_ANON_KEY }} + SUPABASE_PASS: ${{ steps.generate_secrets.outputs.SUPABASE_PASS }} + SUPABASE_EMAIL: "test@test.test" + run: | + env $$(cat .env | xargs) python -m pytest -v -s tests/integration/api diff --git a/Makefile b/Makefile index 1938eb8db..78718d0b9 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ help: ## Display this help information {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' clean: ## Clean up all the things (test artifacts, packages, build dirs, compiled .whl files, python eggs) - -rm .env.email .env.password .pytest_cache + -rm .env .env.email .env.password .pytest_cache -rm -rf .logs -rm zarf-package-*.tar.zst -rm packages/**/zarf-package-*.tar.zst diff --git a/tests/Makefile b/tests/Makefile index 03c9709de..474f24f7f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -36,7 +36,12 @@ test-user: set-supabase prompt-email prompt-password SUPABASE_PASS=$$(cat .env.password | tr -d '\n'); \ $(call get_jwt_token,$$SUPABASE_EMAIL,$$SUPABASE_PASS,"$(SUPABASE_URL)/auth/v1/signup") -test-env: set-supabase prompt-email prompt-password +# Setup for pipeline tests with no interactive terminal +test-user-pipeline: set-supabase + $(call get_jwt_token,$(SUPABASE_EMAIL),$(SUPABASE_PASS),"$(SUPABASE_URL)/auth/v1/signup") + $(call get_jwt_token,$(SUPABASE_EMAIL),$(SUPABASE_PASS),"$(SUPABASE_URL)/auth/v1/token?grant_type=password") + +test-env: test-user SUPABASE_EMAIL=$$(cat .env.email | tr -d '\n'); \ SUPABASE_PASS=$$(cat .env.password | tr -d '\n'); \ $(call get_jwt_token,$$SUPABASE_EMAIL,$$SUPABASE_PASS,"$(SUPABASE_URL)/auth/v1/token?grant_type=password") diff --git a/tests/README.md b/tests/README.md index e42438ae3..0ca5a58c0 100644 --- a/tests/README.md +++ b/tests/README.md @@ -27,16 +27,18 @@ Please see the [Makefile](./Makefile) for more details. Below is a quick synopsi python -m pip install ".[dev]" # create a test user for the tests -make test-user SUPABASE_URL=https://supabase-kong.uds.dev +# prompts for a password and email +make test-user # setup the environment variables for the tests -make test-env SUPABASE_URL=https://supabase-kong.uds.dev +# prompts for the previous step's password and email +make test-env # run the unit tests -make test-api-unit SUPABASE_URL=https://supabase-kong.uds.dev +make test-api-unit # run the integration tests -make test-api-integration SUPABASE_URL=https://supabase-kong.uds.dev +make test-api-integration ``` ## Load Tests From d921d08b130b0e423af657bfbec3b26efd4ea106 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 13:55:59 -0400 Subject: [PATCH 04/28] stop integration on failing unit tests --- .github/workflows/pytest.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index e5c5aaca6..1a836c27e 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -68,6 +68,9 @@ jobs: integration: runs-on: ai-ubuntu-big-boy-8-core + # If basic unit tests fail, do not run this job + needs: pytest + steps: - name: Checkout Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 From 6b237b669a2d3e0e1d81ed344635c6e5711c862f Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:05:36 -0400 Subject: [PATCH 05/28] optimize job deps --- .github/workflows/pytest.yaml | 50 ++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 1a836c27e..764136ad5 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -45,20 +45,35 @@ jobs: - name: Checkout Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Cache Python Dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cache/pip + **/src/leapfrogai_api + **/src/leapfrogai_sdk + key: ${{ pytest }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ pytest }}-pip- + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: python-version-file: "pyproject.toml" + - name: Install Python Deps + run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" + - name: Build Repeater env: LOCAL_VERSION: dev run: | make docker-repeater - - name: Run Repeater - run: docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev - - name: Install Python Deps - run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" + - name: Run Repeater + id: run_repeater + run: | + docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev + echo "REPEATER_CONTAINER_ID=${REPEATER_CONTAINER_ID}" >> $GITHUB_ENV - name: Run Pytest run: python -m pytest tests/pytest -v @@ -75,8 +90,23 @@ jobs: - name: Checkout Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Setup Python - uses: ./.github/actions/python + - name: Use Cached Python Dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cache/pip + **/src/leapfrogai_api + **/src/leapfrogai_sdk + key: ${{ pytest }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ pytest }}-pip- + + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 + with: + python-version-file: "pyproject.toml" + + - name: Install Python Deps + run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" - name: Setup UDS Cluster uses: ./.github/actions/uds-cluster @@ -95,13 +125,9 @@ jobs: uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst -l=trace --confirm rm packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst - - name: Build Repeater - env: - LOCAL_VERSION: dev + - name: Use Repeater from Previous job run: | - make docker-repeater - - name: Run Repeater - run: docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev + docker start $REPEATER_CONTAINER_ID - name: Generate Secrets id: generate_secrets From 866137477d358565ee033608333bb3941c84ca77 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:09:11 -0400 Subject: [PATCH 06/28] cancel-in-progress --- .github/workflows/pytest.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 764136ad5..187e52132 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -33,12 +33,13 @@ on: # Declare default permissions as read only. permissions: read-all -concurrency: - group: pytest-${{ github.ref }} - cancel-in-progress: true - jobs: pytest: + + concurrency: + group: pytest-${{ github.ref }} + cancel-in-progress: true + runs-on: ubuntu-latest steps: @@ -86,6 +87,10 @@ jobs: # If basic unit tests fail, do not run this job needs: pytest + concurrency: + group: integration-${{ github.ref }} + cancel-in-progress: true + steps: - name: Checkout Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 From 6bcc1b41e549abb60a213e07663a5dc42a44dd2e Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:12:00 -0400 Subject: [PATCH 07/28] run in draft WIP --- .github/workflows/pytest.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 187e52132..1d6ab22dd 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -6,7 +6,6 @@ on: - opened # default trigger - reopened # default trigger - synchronize # default trigger - - ready_for_review # don't run on draft PRs - milestoned # allows us to trigger on bot PRs paths: - "**" @@ -35,7 +34,6 @@ permissions: read-all jobs: pytest: - concurrency: group: pytest-${{ github.ref }} cancel-in-progress: true From 9d2c21fb654cff6e48a2b21674c6174ac39d364a Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:16:37 -0400 Subject: [PATCH 08/28] fix workflow yaml --- .github/workflows/pytest-shim.yaml | 9 ++++++--- .github/workflows/pytest.yaml | 11 ++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pytest-shim.yaml b/.github/workflows/pytest-shim.yaml index af216a0ab..30fd638b4 100644 --- a/.github/workflows/pytest-shim.yaml +++ b/.github/workflows/pytest-shim.yaml @@ -38,12 +38,12 @@ permissions: # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks # Abort prior jobs in the same workflow / PR -concurrency: - group: pytest-skip-${{ github.ref }} - cancel-in-progress: true jobs: pytest: + concurrency: + group: pytest-skip-${{ github.ref }} + cancel-in-progress: true runs-on: ubuntu-latest steps: - name: Skipped @@ -51,6 +51,9 @@ jobs: echo skipped integration: + concurrency: + group: integration-skip-${{ github.ref }} + cancel-in-progress: true runs-on: ubuntu-latest steps: - name: Skipped diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 1d6ab22dd..9706f8f69 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -6,6 +6,7 @@ on: - opened # default trigger - reopened # default trigger - synchronize # default trigger + - ready_for_review # don't run on draft PRs - milestoned # allows us to trigger on bot PRs paths: - "**" @@ -25,7 +26,7 @@ on: - "!.gitignore" - "!LICENSE" - # Ignore LFAI-UI things (no Python) + # Ignore UI things (no Python) - "!src/leapfrogai_ui/**" - "!packages/ui/**" @@ -51,9 +52,9 @@ jobs: ~/.cache/pip **/src/leapfrogai_api **/src/leapfrogai_sdk - key: ${{ pytest }}-pip-${{ hashFiles('**/requirements.txt') }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | - ${{ pytest }}-pip- + ${{ runner.os }}-pip- - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: @@ -100,9 +101,9 @@ jobs: ~/.cache/pip **/src/leapfrogai_api **/src/leapfrogai_sdk - key: ${{ pytest }}-pip-${{ hashFiles('**/requirements.txt') }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | - ${{ pytest }}-pip- + ${{ runner.os }}-pip- - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: From a44b721d883065d7a250dce2546ec4064dd9634e Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:21:53 -0400 Subject: [PATCH 09/28] cache miss fix --- .github/workflows/pytest.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 9706f8f69..7a60398d8 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -46,15 +46,13 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Cache Python Dependencies - uses: actions/cache@v3 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: | ~/.cache/pip **/src/leapfrogai_api **/src/leapfrogai_sdk - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + key: pytest-integration-pip-${{ github.ref }} - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: @@ -95,15 +93,13 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Use Cached Python Dependencies - uses: actions/cache@v3 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: | ~/.cache/pip **/src/leapfrogai_api **/src/leapfrogai_sdk - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + key: pytest-integration-pip-${{ github.ref }} - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: From 2cfd6bcadb2168c5531795a5858878deda9fafc3 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:22:57 -0400 Subject: [PATCH 10/28] repeater earlier in integration --- .github/workflows/pytest.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 7a60398d8..91e03e800 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -108,6 +108,10 @@ jobs: - name: Install Python Deps run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" + - name: Use Repeater from Previous job + run: | + docker start $REPEATER_CONTAINER_ID + - name: Setup UDS Cluster uses: ./.github/actions/uds-cluster with: @@ -125,10 +129,6 @@ jobs: uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst -l=trace --confirm rm packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst - - name: Use Repeater from Previous job - run: | - docker start $REPEATER_CONTAINER_ID - - name: Generate Secrets id: generate_secrets run: | From 1253836bd2d8ab8ab3b950f04e05a860e770f9d5 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:24:15 -0400 Subject: [PATCH 11/28] skip shim fix --- .github/workflows/pytest-shim.yaml | 12 ++++++------ .github/workflows/pytest.yaml | 11 ++++------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pytest-shim.yaml b/.github/workflows/pytest-shim.yaml index 30fd638b4..a75bc7fd6 100644 --- a/.github/workflows/pytest-shim.yaml +++ b/.github/workflows/pytest-shim.yaml @@ -39,22 +39,22 @@ permissions: # Abort prior jobs in the same workflow / PR +concurrency: + group: pytest-integration-skip-${{ github.ref }} + cancel-in-progress: true + jobs: pytest: - concurrency: - group: pytest-skip-${{ github.ref }} - cancel-in-progress: true runs-on: ubuntu-latest + steps: - name: Skipped run: | echo skipped integration: - concurrency: - group: integration-skip-${{ github.ref }} - cancel-in-progress: true runs-on: ubuntu-latest + steps: - name: Skipped run: | diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 91e03e800..3695fd200 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -33,11 +33,12 @@ on: # Declare default permissions as read only. permissions: read-all +concurrency: + group: pytest-integration-${{ github.ref }} + cancel-in-progress: true + jobs: pytest: - concurrency: - group: pytest-${{ github.ref }} - cancel-in-progress: true runs-on: ubuntu-latest @@ -84,10 +85,6 @@ jobs: # If basic unit tests fail, do not run this job needs: pytest - concurrency: - group: integration-${{ github.ref }} - cancel-in-progress: true - steps: - name: Checkout Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 From e1b7bab08462cc67e835a4dce11d1ab007d89dc7 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:32:54 -0400 Subject: [PATCH 12/28] pass repeater ID differently --- .github/workflows/pytest.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 3695fd200..c05109f62 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -42,6 +42,9 @@ jobs: runs-on: ubuntu-latest + outputs: + repeater_container_id: ${{ steps.run_repeater.outputs.REPEATER_CONTAINER_ID }} + steps: - name: Checkout Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -71,8 +74,8 @@ jobs: - name: Run Repeater id: run_repeater run: | - docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev - echo "REPEATER_CONTAINER_ID=${REPEATER_CONTAINER_ID}" >> $GITHUB_ENV + REPEATER_CONTAINER_ID=$(docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev) + echo "REPEATER_CONTAINER_ID=$REPEATER_CONTAINER_ID" >> $GITHUB_OUTPUT - name: Run Pytest run: python -m pytest tests/pytest -v @@ -107,7 +110,7 @@ jobs: - name: Use Repeater from Previous job run: | - docker start $REPEATER_CONTAINER_ID + docker start ${{ needs.pytest.outputs.REPEATER_CONTAINER_ID }} - name: Setup UDS Cluster uses: ./.github/actions/uds-cluster @@ -145,6 +148,6 @@ jobs: env: SUPABASE_ANON_KEY: ${{ steps.generate_secrets.outputs.SUPABASE_ANON_KEY }} SUPABASE_PASS: ${{ steps.generate_secrets.outputs.SUPABASE_PASS }} - SUPABASE_EMAIL: "test@test.test" + SUPABASE_EMAIL: "doug@uds.dev" run: | env $$(cat .env | xargs) python -m pytest -v -s tests/integration/api From 73fc929eb4c8035c25302d7714137a7961208dd1 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:37:48 -0400 Subject: [PATCH 13/28] revert non-working repeater passoff --- .github/workflows/pytest.yaml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index c05109f62..e855960de 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -6,7 +6,7 @@ on: - opened # default trigger - reopened # default trigger - synchronize # default trigger - - ready_for_review # don't run on draft PRs + - ready_for_review # don't run on draft PRs - milestoned # allows us to trigger on bot PRs paths: - "**" @@ -39,12 +39,8 @@ concurrency: jobs: pytest: - runs-on: ubuntu-latest - outputs: - repeater_container_id: ${{ steps.run_repeater.outputs.REPEATER_CONTAINER_ID }} - steps: - name: Checkout Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -74,8 +70,7 @@ jobs: - name: Run Repeater id: run_repeater run: | - REPEATER_CONTAINER_ID=$(docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev) - echo "REPEATER_CONTAINER_ID=$REPEATER_CONTAINER_ID" >> $GITHUB_OUTPUT + docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:e2e-test - name: Run Pytest run: python -m pytest tests/pytest -v @@ -108,9 +103,10 @@ jobs: - name: Install Python Deps run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" - - name: Use Repeater from Previous job + - name: Run Repeater + id: run_repeater run: | - docker start ${{ needs.pytest.outputs.REPEATER_CONTAINER_ID }} + docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:e2e-test - name: Setup UDS Cluster uses: ./.github/actions/uds-cluster From e09df4da4c619bdb150cffeefa206b71411a51c6 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 14:41:12 -0400 Subject: [PATCH 14/28] fix repeater steps --- .github/workflows/pytest.yaml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index e855960de..153332494 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -61,16 +61,12 @@ jobs: - name: Install Python Deps run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" - - name: Build Repeater + - name: Build and Run Repeater env: - LOCAL_VERSION: dev + LOCAL_VERSION: e2e-test run: | make docker-repeater - - - name: Run Repeater - id: run_repeater - run: | - docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:e2e-test + docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:$LOCAL_VERSION - name: Run Pytest run: python -m pytest tests/pytest -v @@ -103,10 +99,12 @@ jobs: - name: Install Python Deps run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" - - name: Run Repeater - id: run_repeater + - name: Build and Run Repeater + env: + LOCAL_VERSION: e2e-test run: | - docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:e2e-test + make docker-repeater + docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:$LOCAL_VERSION - name: Setup UDS Cluster uses: ./.github/actions/uds-cluster From 947b725b729f44d18e4a405d3738d865c398b160 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 15:21:57 -0400 Subject: [PATCH 15/28] fix all integration tests --- .github/workflows/pytest.yaml | 28 +++++++++++++++------------- src/leapfrogai_api/pyproject.toml | 3 ++- tests/Makefile | 2 ++ tests/utils/client.py | 8 +++++--- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 153332494..0681a9938 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -54,14 +54,15 @@ jobs: **/src/leapfrogai_sdk key: pytest-integration-pip-${{ github.ref }} - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 + - name: Setup Python + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: python-version-file: "pyproject.toml" - - name: Install Python Deps + - name: Install Python Dependencies run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" - - name: Build and Run Repeater + - name: Setup Repeater env: LOCAL_VERSION: e2e-test run: | @@ -92,14 +93,15 @@ jobs: **/src/leapfrogai_sdk key: pytest-integration-pip-${{ github.ref }} - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 + - name: Setup Python + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 with: python-version-file: "pyproject.toml" - name: Install Python Deps run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" - - name: Build and Run Repeater + - name: Setup Repeater env: LOCAL_VERSION: e2e-test run: | @@ -116,13 +118,6 @@ jobs: - name: Setup API and Supabase uses: ./.github/actions/lfai-core - - name: Deploy text-embeddings - run: | - make build-text-embeddings LOCAL_VERSION=e2e-test - docker image prune -af - uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst -l=trace --confirm - rm packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst - - name: Generate Secrets id: generate_secrets run: | @@ -138,10 +133,17 @@ jobs: echo "SUPABASE_ANON_KEY is set: ${{ steps.generate_secrets.outputs.SUPABASE_ANON_KEY != '' }}" echo "SUPABASE_PASS is set: ${{ steps.generate_secrets.outputs.SUPABASE_PASS != '' }}" + - name: Setup Text-Embeddings + run: | + make build-text-embeddings LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst -l=trace --confirm + rm packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst + - name: Run Integration Tests env: SUPABASE_ANON_KEY: ${{ steps.generate_secrets.outputs.SUPABASE_ANON_KEY }} SUPABASE_PASS: ${{ steps.generate_secrets.outputs.SUPABASE_PASS }} SUPABASE_EMAIL: "doug@uds.dev" run: | - env $$(cat .env | xargs) python -m pytest -v -s tests/integration/api + env $(cat .env | xargs) python -m pytest -v -s tests/integration/api diff --git a/src/leapfrogai_api/pyproject.toml b/src/leapfrogai_api/pyproject.toml index 8a46a46e2..7e9921825 100644 --- a/src/leapfrogai_api/pyproject.toml +++ b/src/leapfrogai_api/pyproject.toml @@ -18,7 +18,8 @@ dependencies = [ "supabase == 2.6.0", "langchain == 0.2.1", "langchain-community == 0.2.1", - "unstructured[md,xlsx,pptx] == 0.15.3", # Only specify necessary filetypes to prevent package bloat (e.g. 130MB vs 6GB) + "unstructured[md,xlsx,pptx] == 0.15.9", # Only specify necessary filetypes to prevent package bloat (e.g. 130MB vs 6GB) + "nltk == 3.9.1", # Required for pickled code containing .pptx parsing dependencies "pylibmagic == 0.5.0", # Resolves issue with libmagic not being bundled with OS - https://github.com/ahupp/python-magic/issues/233, may not be needed after this is merged https://github.com/ahupp/python-magic/pull/294 "python-magic == 0.4.27", "storage3==0.7.6", # required by supabase, bug when using previous versions diff --git a/tests/Makefile b/tests/Makefile index 474f24f7f..4bb81ccc6 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,4 +1,5 @@ SUPABASE_URL ?= https://supabase-kong.uds.dev +LEAPFROGAI_MODEL ?= llama-cpp-python set-supabase: $(eval SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d)) @@ -18,6 +19,7 @@ define get_jwt_token echo "SUPABASE_USER_JWT=$$JWT" > .env; \ echo "SUPABASE_URL=$(SUPABASE_URL)" >> .env; \ echo "SUPABASE_ANON_KEY=$(SUPABASE_ANON_KEY)" >> .env; \ + echo "LEAPFROGAI_MODEL=$(LEAPFROGAI_MODEL)" >> .env echo "DONE - variables exported to .env file" endef diff --git a/tests/utils/client.py b/tests/utils/client.py index 7a58b02f5..0baf6c0dc 100644 --- a/tests/utils/client.py +++ b/tests/utils/client.py @@ -3,7 +3,7 @@ from pathlib import Path -LEAPFROGAI_MODEL = "llama-cpp-python" +LEAPFROGAI_MODEL = os.getenv("LEAPFROGAI_MODEL", "llama-cpp-python") OPENAI_MODEL = "gpt-4o-mini" @@ -17,7 +17,9 @@ def openai_client(): def leapfrogai_client(): return OpenAI( - base_url=os.getenv("LEAPFROGAI_API_URL"), + base_url=os.getenv( + "LEAPFROGAI_API_URL", "https://leapfrogai-api.uds.dev/openai/v1" + ), api_key=os.getenv("SUPABASE_USER_JWT"), ) @@ -31,7 +33,7 @@ def __init__(self, client: OpenAI, model: str): self.model = model -def client_config_factory(client_name) -> ClientConfig: +def client_config_factory(client_name: str) -> ClientConfig: if client_name == "openai": return ClientConfig(client=openai_client(), model=OPENAI_MODEL) elif client_name == "leapfrogai": From 11c78de33527ad23251f3f8a2a611211036e795b Mon Sep 17 00:00:00 2001 From: Justin Law Date: Sat, 7 Sep 2024 16:01:06 -0400 Subject: [PATCH 16/28] pipeline and docs fixes --- .github/workflows/pytest.yaml | 5 ++++- tests/Makefile | 10 ++++++---- tests/README.md | 2 +- tests/integration/api/test_rag_files.py | 5 +++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 0681a9938..f42ae87c5 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -145,5 +145,8 @@ jobs: SUPABASE_ANON_KEY: ${{ steps.generate_secrets.outputs.SUPABASE_ANON_KEY }} SUPABASE_PASS: ${{ steps.generate_secrets.outputs.SUPABASE_PASS }} SUPABASE_EMAIL: "doug@uds.dev" + # Turn off non-deterministic tests that require a working LLM in-cluster for evaluation + LFAI_RUN_NIAH_TESTS: "false" run: | - env $(cat .env | xargs) python -m pytest -v -s tests/integration/api + make test-user-pipeline + env $(cat .env | xargs) python -m pytest -v -s tests/integration/api diff --git a/tests/Makefile b/tests/Makefile index 4bb81ccc6..9abf6a64c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,5 +1,7 @@ SUPABASE_URL ?= https://supabase-kong.uds.dev LEAPFROGAI_MODEL ?= llama-cpp-python +LFAI_RUN_NIAH_TESTS ?= false +LFAI_RUN_REPEATER_TESTS ?= true set-supabase: $(eval SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d)) @@ -7,7 +9,7 @@ set-supabase: @echo "Supabase Anon Key: $(SUPABASE_ANON_KEY)" define get_jwt_token - echo "Getting JWT token from $(SUPABASE_URL)..."; \ + echo "Getting JWT token from $(3)..."; \ echo "Email: $(1)"; \ echo "Password: $(2)"; \ TOKEN_RESPONSE=$$(curl -s -X POST $(3) \ @@ -17,7 +19,7 @@ define get_jwt_token echo "Extracting token from $$TOKEN_RESPONSE"; \ JWT=$$(echo $$TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d '"' -f 4); \ echo "SUPABASE_USER_JWT=$$JWT" > .env; \ - echo "SUPABASE_URL=$(SUPABASE_URL)" >> .env; \ + echo "SUPABASE_URL=$(3)" >> .env; \ echo "SUPABASE_ANON_KEY=$(SUPABASE_ANON_KEY)" >> .env; \ echo "LEAPFROGAI_MODEL=$(LEAPFROGAI_MODEL)" >> .env echo "DONE - variables exported to .env file" @@ -57,10 +59,10 @@ test-api-integration: echo "Required environment variables (SUPABASE_USER_JWT, SUPABASE_URL, SUPABASE_ANON_KEY) are missing in .env!"; \ exit 1; \ fi - @env $$(cat .env | xargs) PYTHONPATH=$$(pwd) pytest -vv -s tests/integration/api + @env $$(cat .env | xargs) LFAI_RUN_NIAH_TESTS=$(LFAI_RUN_NIAH_TESTS) PYTHONPATH=$$(pwd) pytest -vv -s tests/integration/api test-api-unit: set-supabase - PYTHONPATH=$$(pwd) pytest -vv -s tests/unit + LFAI_RUN_REPEATER_TESTS=$(LFAI_RUN_REPEATER_TESTS) PYTHONPATH=$$(pwd) pytest -vv -s tests/unit test-load: locust -f $$(pwd)/tests/load/loadtest.py --web-port 8089 diff --git a/tests/README.md b/tests/README.md index 0ca5a58c0..d2b410fff 100644 --- a/tests/README.md +++ b/tests/README.md @@ -20,7 +20,7 @@ uds zarf connect --name=vllm-model --namespace=leapfrogai --local-port=50051 --r If running everything via Docker containers or in a local Python environment, then ensure they are accessible based on the test configurations in each testing target's sub-directory. -Please see the [Makefile](./Makefile) for more details. Below is a quick synopsis of the available Make targets that are **run from the root of the entire repository**: +Please see the [Makefile](./Makefile) for more details on turning tests on/off and for setting test parameters like the default model to use. Below is a quick synopsis of the available Make targets that are **run from the root of the entire repository**: ```bash # Install the python dependencies diff --git a/tests/integration/api/test_rag_files.py b/tests/integration/api/test_rag_files.py index a5a743f6e..4c436c6ab 100644 --- a/tests/integration/api/test_rag_files.py +++ b/tests/integration/api/test_rag_files.py @@ -1,6 +1,7 @@ import os from pathlib import Path from openai.types.beta.threads.text import Text +import pytest from ...utils.client import client_config_factory @@ -23,6 +24,10 @@ def make_test_run(client, assistant, thread): return run +@pytest.mark.skipif( + os.environ.get("LFAI_RUN_NIAH_TESTS").lower() != "true", + reason="LFAI_RUN_NIAH_TESTS envvar was not set to true", +) def test_rag_needle_haystack(): config = client_config_factory("leapfrogai") client = config.client From 37ced30fdad5782967eb8a00c3bf673fc07c3d7e Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 10:49:47 -0400 Subject: [PATCH 17/28] fix Makefile for local and pipeline --- .github/workflows/pytest.yaml | 9 +++++---- Makefile | 2 +- tests/Makefile | 7 +------ tests/integration/api/test_rag_files.py | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index f42ae87c5..744c0dc24 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -125,7 +125,7 @@ jobs: echo "::add-mask::$SUPABASE_PASS" echo "SUPABASE_PASS=$SUPABASE_PASS" >> $GITHUB_OUTPUT SUPABASE_ANON_KEY=$(uds zarf tools kubectl get secret supabase-bootstrap-jwt -n leapfrogai -o jsonpath='{.data.anon-key}' | base64 -d) - echo P"::add-mask::$SUPABASE_ANON_KEY" + echo "::add-mask::$SUPABASE_ANON_KEY" echo "SUPABASE_ANON_KEY=$SUPABASE_ANON_KEY" >> $GITHUB_OUTPUT - name: Verify Secrets @@ -144,9 +144,10 @@ jobs: env: SUPABASE_ANON_KEY: ${{ steps.generate_secrets.outputs.SUPABASE_ANON_KEY }} SUPABASE_PASS: ${{ steps.generate_secrets.outputs.SUPABASE_PASS }} - SUPABASE_EMAIL: "doug@uds.dev" - # Turn off non-deterministic tests that require a working LLM in-cluster for evaluation + SUPABASE_EMAIL: doug@uds.dev + SUPABASE_URL: https://supabase-kong.uds.dev + # Turn off NIAH tests that are not applicable for integration testing using the Repeater model LFAI_RUN_NIAH_TESTS: "false" run: | make test-user-pipeline - env $(cat .env | xargs) python -m pytest -v -s tests/integration/api + env $(cat .env | xargs) python -m pytest -v -s tests/integration/api diff --git a/Makefile b/Makefile index 78718d0b9..cef9bc20d 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ help: ## Display this help information {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' clean: ## Clean up all the things (test artifacts, packages, build dirs, compiled .whl files, python eggs) - -rm .env .env.email .env.password .pytest_cache + -rm -rf .env .env.email .env.password .pytest_cache -rm -rf .logs -rm zarf-package-*.tar.zst -rm packages/**/zarf-package-*.tar.zst diff --git a/tests/Makefile b/tests/Makefile index 9abf6a64c..c77423846 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -5,7 +5,6 @@ LFAI_RUN_REPEATER_TESTS ?= true set-supabase: $(eval SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d)) - @echo "Supabase URL: $(SUPABASE_URL)" @echo "Supabase Anon Key: $(SUPABASE_ANON_KEY)" define get_jwt_token @@ -19,7 +18,7 @@ define get_jwt_token echo "Extracting token from $$TOKEN_RESPONSE"; \ JWT=$$(echo $$TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d '"' -f 4); \ echo "SUPABASE_USER_JWT=$$JWT" > .env; \ - echo "SUPABASE_URL=$(3)" >> .env; \ + echo "SUPABASE_URL=$(SUPABASE_URL)" >> .env; \ echo "SUPABASE_ANON_KEY=$(SUPABASE_ANON_KEY)" >> .env; \ echo "LEAPFROGAI_MODEL=$(LEAPFROGAI_MODEL)" >> .env echo "DONE - variables exported to .env file" @@ -66,7 +65,3 @@ test-api-unit: set-supabase test-load: locust -f $$(pwd)/tests/load/loadtest.py --web-port 8089 - -debug: set-supabase - @echo $(SUPABASE_URL) - @echo $(SUPABASE_ANON_KEY) diff --git a/tests/integration/api/test_rag_files.py b/tests/integration/api/test_rag_files.py index 4c436c6ab..9ed2ad28c 100644 --- a/tests/integration/api/test_rag_files.py +++ b/tests/integration/api/test_rag_files.py @@ -25,7 +25,7 @@ def make_test_run(client, assistant, thread): @pytest.mark.skipif( - os.environ.get("LFAI_RUN_NIAH_TESTS").lower() != "true", + os.environ.get("LFAI_RUN_NIAH_TESTS") != "true", reason="LFAI_RUN_NIAH_TESTS envvar was not set to true", ) def test_rag_needle_haystack(): From 3152d1863167ae846e24b2f67446b2f9c52b3b88 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 10:51:37 -0400 Subject: [PATCH 18/28] revert comments in on-trigger --- .github/workflows/pytest.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 744c0dc24..08ef28c13 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -3,11 +3,11 @@ name: pytest on: pull_request: types: - - opened # default trigger - - reopened # default trigger - - synchronize # default trigger - - ready_for_review # don't run on draft PRs - - milestoned # allows us to trigger on bot PRs + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs paths: - "**" - "!.github/**" From b41db473086916c775838e40d7aaaa1e1890f147 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 10:53:34 -0400 Subject: [PATCH 19/28] revert set-supabase --- tests/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index c77423846..7744f1013 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -4,8 +4,8 @@ LFAI_RUN_NIAH_TESTS ?= false LFAI_RUN_REPEATER_TESTS ?= true set-supabase: - $(eval SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d)) - @echo "Supabase Anon Key: $(SUPABASE_ANON_KEY)" + SUPABASE_URL := ${SUPABASE_URL} + SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d) define get_jwt_token echo "Getting JWT token from $(3)..."; \ From a5aacce9dde44675fc13ef2c53b700645c1b74fc Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 11:04:19 -0400 Subject: [PATCH 20/28] fix repeater --- packages/repeater/Dockerfile | 2 +- packages/repeater/Makefile | 2 +- tests/Makefile | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/repeater/Dockerfile b/packages/repeater/Dockerfile index 4d58f46a6..e0717d2fd 100644 --- a/packages/repeater/Dockerfile +++ b/packages/repeater/Dockerfile @@ -33,4 +33,4 @@ COPY packages/repeater/repeater.py . EXPOSE 50051:50051 # Run the repeater model -ENTRYPOINT ["python", "-u", "repeater.py"] +ENTRYPOINT ["python", "-m", "repeater"] diff --git a/packages/repeater/Makefile b/packages/repeater/Makefile index 780f8c36a..f5ef8463f 100644 --- a/packages/repeater/Makefile +++ b/packages/repeater/Makefile @@ -4,4 +4,4 @@ install: dev: make install - python -m leapfrogai_sdk.cli --app-dir=. main:Model + python -m repeater diff --git a/tests/Makefile b/tests/Makefile index 7744f1013..c2b6aaee8 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -4,8 +4,8 @@ LFAI_RUN_NIAH_TESTS ?= false LFAI_RUN_REPEATER_TESTS ?= true set-supabase: - SUPABASE_URL := ${SUPABASE_URL} - SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d) + $(eval SUPABASE_URL := $(SUPABASE_URL)) + $(eval SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d)) define get_jwt_token echo "Getting JWT token from $(3)..."; \ From 3ff9059ee04eade03b172eba5fddf7d3330b1ba3 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 11:48:24 -0400 Subject: [PATCH 21/28] all tests pass with repeater! --- packages/repeater/Dockerfile | 6 +- packages/repeater/Makefile | 2 +- packages/repeater/config.yaml | 14 +++++ packages/repeater/main.py | 25 ++++++++ packages/repeater/repeater.py | 113 ---------------------------------- 5 files changed, 43 insertions(+), 117 deletions(-) create mode 100644 packages/repeater/config.yaml create mode 100644 packages/repeater/main.py delete mode 100644 packages/repeater/repeater.py diff --git a/packages/repeater/Dockerfile b/packages/repeater/Dockerfile index e0717d2fd..e60c7ce75 100644 --- a/packages/repeater/Dockerfile +++ b/packages/repeater/Dockerfile @@ -26,11 +26,11 @@ ENV PATH="/leapfrogai/.venv/bin:$PATH" WORKDIR /leapfrogai COPY --from=builder /leapfrogai/.venv/ /leapfrogai/.venv/ - -COPY packages/repeater/repeater.py . +COPY packages/repeater/main.py . +COPY packages/repeater/config.yaml . # Publish port EXPOSE 50051:50051 # Run the repeater model -ENTRYPOINT ["python", "-m", "repeater"] +ENTRYPOINT ["python", "-m", "leapfrogai_sdk.cli", "--app-dir=.", "main:Model"] diff --git a/packages/repeater/Makefile b/packages/repeater/Makefile index f5ef8463f..780f8c36a 100644 --- a/packages/repeater/Makefile +++ b/packages/repeater/Makefile @@ -4,4 +4,4 @@ install: dev: make install - python -m repeater + python -m leapfrogai_sdk.cli --app-dir=. main:Model diff --git a/packages/repeater/config.yaml b/packages/repeater/config.yaml new file mode 100644 index 000000000..b97195c5d --- /dev/null +++ b/packages/repeater/config.yaml @@ -0,0 +1,14 @@ +# for testing purposes, not actually used by Repeater +model: + source: "." +max_context_length: 10000000000000 +stop_tokens: + - "" +prompt_format: + chat: + system: "{}\n" + assistant: "{}\n" + user: "{}\n" +defaults: + top_p: 1.0 + top_k: 0 diff --git a/packages/repeater/main.py b/packages/repeater/main.py new file mode 100644 index 000000000..2b00e267b --- /dev/null +++ b/packages/repeater/main.py @@ -0,0 +1,25 @@ +import logging +import os +from typing import Any, AsyncGenerator + +from leapfrogai_sdk.llm import LLM, GenerationConfig + +logging.basicConfig( + level=os.getenv("LFAI_LOG_LEVEL", logging.INFO), + format="%(name)s: %(asctime)s | %(levelname)s | %(filename)s:%(lineno)s >>> %(message)s", +) +logger = logging.getLogger(__name__) + + +@LLM +class Model: + async def generate( + self, prompt: str, config: GenerationConfig + ) -> AsyncGenerator[str, Any]: + logger.info("Begin generating streamed response") + for char in prompt: + yield char # type: ignore + logger.info("Streamed response complete") + + async def count_tokens(self, raw_text: str) -> int: + return len(raw_text) diff --git a/packages/repeater/repeater.py b/packages/repeater/repeater.py deleted file mode 100644 index 9e5dbdfd4..000000000 --- a/packages/repeater/repeater.py +++ /dev/null @@ -1,113 +0,0 @@ -import logging - -import leapfrogai_sdk -import asyncio - -from leapfrogai_sdk import CompletionUsage -from leapfrogai_sdk.chat.chat_pb2 import Usage - - -class Repeater( - leapfrogai_sdk.CompletionServiceServicer, - leapfrogai_sdk.EmbeddingsServiceServicer, - leapfrogai_sdk.ChatCompletionServiceServicer, - leapfrogai_sdk.ChatCompletionStreamServiceServicer, - leapfrogai_sdk.AudioServicer, -): - async def Complete( - self, - request: leapfrogai_sdk.CompletionRequest, - context: leapfrogai_sdk.GrpcContext, - ) -> leapfrogai_sdk.CompletionResponse: - result = request.prompt # just returns what's provided - print(f"Repeater.Complete: { request }") - completion = leapfrogai_sdk.CompletionChoice( - text=result, index=0, finish_reason="stop" - ) - return leapfrogai_sdk.CompletionResponse( - choices=[completion], - usage=CompletionUsage( - prompt_tokens=len(request.prompt), - completion_tokens=len(request.prompt), - total_tokens=len(request.prompt) * 2, - ), - ) - - async def CompleteStream( - self, - request: leapfrogai_sdk.CompletionRequest, - context: leapfrogai_sdk.GrpcContext, - ) -> leapfrogai_sdk.CompletionResponse: - for _ in range(5): - completion = leapfrogai_sdk.CompletionChoice( - text=request.prompt, index=0, finish_reason="stop" - ) - yield leapfrogai_sdk.CompletionResponse( - choices=[completion], - usage=CompletionUsage( - prompt_tokens=len(request.prompt), - completion_tokens=len(request.prompt), - total_tokens=len(request.prompt) * 2, - ), - ) - - async def CreateEmbedding( - self, - request: leapfrogai_sdk.EmbeddingRequest, - context: leapfrogai_sdk.GrpcContext, - ) -> leapfrogai_sdk.EmbeddingResponse: - return leapfrogai_sdk.EmbeddingResponse( - embeddings=[leapfrogai_sdk.Embedding(embedding=[0.0 for _ in range(10)])] - ) - - async def ChatComplete( - self, - request: leapfrogai_sdk.ChatCompletionRequest, - context: leapfrogai_sdk.GrpcContext, - ) -> leapfrogai_sdk.ChatCompletionResponse: - completion = leapfrogai_sdk.ChatCompletionChoice( - chat_item=request.chat_items[0], finish_reason="stop" - ) - return leapfrogai_sdk.ChatCompletionResponse( - choices=[completion], - usage=Usage( - prompt_tokens=len(request.chat_items[0].content), - completion_tokens=len(request.chat_items[0].content), - total_tokens=len(request.chat_items[0].content) * 2, - ), - ) - - async def ChatCompleteStream( - self, - request: leapfrogai_sdk.ChatCompletionRequest, - context: leapfrogai_sdk.GrpcContext, - ) -> leapfrogai_sdk.ChatCompletionResponse: - for _ in range(5): - completion = leapfrogai_sdk.ChatCompletionChoice( - chat_item=request.chat_items[0], finish_reason="stop" - ) - yield leapfrogai_sdk.ChatCompletionResponse( - choices=[completion], - usage=Usage( - prompt_tokens=len(request.chat_items[0].content), - completion_tokens=len(request.chat_items[0].content), - total_tokens=len(request.chat_items[0].content) * 2, - ), - ) - - async def Transcribe( - self, request: leapfrogai_sdk.AudioRequest, context: leapfrogai_sdk.GrpcContext - ) -> leapfrogai_sdk.AudioResponse: - return leapfrogai_sdk.AudioResponse( - text="The repeater model received a transcribe request", - duration=1, - language="en", - ) - - async def Name(self, request, context): - return leapfrogai_sdk.NameResponse(name="repeater") - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - asyncio.run(leapfrogai_sdk.serve(Repeater())) From 84b5c8c8fceda4500832ecb3e534063a1bdd06d6 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 11:53:56 -0400 Subject: [PATCH 22/28] refactor repeater --- packages/repeater/main.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/repeater/main.py b/packages/repeater/main.py index 2b00e267b..e45c29b03 100644 --- a/packages/repeater/main.py +++ b/packages/repeater/main.py @@ -2,7 +2,16 @@ import os from typing import Any, AsyncGenerator +from leapfrogai_sdk.audio.audio_pb2 import AudioRequest, AudioResponse +from leapfrogai_sdk.embeddings.embeddings_pb2 import ( + Embedding, + EmbeddingRequest, + EmbeddingResponse, + GrpcContext, +) + from leapfrogai_sdk.llm import LLM, GenerationConfig +from leapfrogai_sdk.name.name_pb2 import NameResponse logging.basicConfig( level=os.getenv("LFAI_LOG_LEVEL", logging.INFO), @@ -23,3 +32,24 @@ async def generate( async def count_tokens(self, raw_text: str) -> int: return len(raw_text) + + async def CreateEmbedding( + self, + request: EmbeddingRequest, + context: GrpcContext, + ) -> EmbeddingResponse: + return EmbeddingResponse( + embeddings=[Embedding(embedding=[0.0 for _ in range(10)])] + ) + + async def Transcribe( + self, request: AudioRequest, context: GrpcContext + ) -> AudioResponse: + return AudioResponse( + text="The repeater model received a transcribe request", + duration=1, + language="en", + ) + + async def Name(self, request, context): + return NameResponse(name="repeater") From c54d909eb22ce1fbeb50c56c0910cafbb858d2bd Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 12:09:50 -0400 Subject: [PATCH 23/28] refactor repeater, done! --- packages/repeater/main.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/repeater/main.py b/packages/repeater/main.py index e45c29b03..8bac4d60c 100644 --- a/packages/repeater/main.py +++ b/packages/repeater/main.py @@ -2,16 +2,22 @@ import os from typing import Any, AsyncGenerator -from leapfrogai_sdk.audio.audio_pb2 import AudioRequest, AudioResponse -from leapfrogai_sdk.embeddings.embeddings_pb2 import ( - Embedding, +from leapfrogai_sdk import ( + CompletionServiceServicer, + EmbeddingsServiceServicer, + ChatCompletionServiceServicer, + ChatCompletionStreamServiceServicer, + AudioServicer, + GrpcContext, EmbeddingRequest, EmbeddingResponse, - GrpcContext, + Embedding, + AudioRequest, + AudioResponse, + NameResponse, + serve, ) - from leapfrogai_sdk.llm import LLM, GenerationConfig -from leapfrogai_sdk.name.name_pb2 import NameResponse logging.basicConfig( level=os.getenv("LFAI_LOG_LEVEL", logging.INFO), @@ -21,7 +27,13 @@ @LLM -class Model: +class Model( + CompletionServiceServicer, + EmbeddingsServiceServicer, + ChatCompletionServiceServicer, + ChatCompletionStreamServiceServicer, + AudioServicer, +): async def generate( self, prompt: str, config: GenerationConfig ) -> AsyncGenerator[str, Any]: @@ -53,3 +65,7 @@ async def Transcribe( async def Name(self, request, context): return NameResponse(name="repeater") + + +if __name__ == "__main__": + serve(Model()) From f803c06097889ffb3935bfc0886a245c0f7a0aa7 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 12:15:21 -0400 Subject: [PATCH 24/28] refactor unit tests --- packages/repeater/main.py | 3 +-- tests/pytest/leapfrogai_api/test_api.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/repeater/main.py b/packages/repeater/main.py index 8bac4d60c..eaa3ecf07 100644 --- a/packages/repeater/main.py +++ b/packages/repeater/main.py @@ -38,8 +38,7 @@ async def generate( self, prompt: str, config: GenerationConfig ) -> AsyncGenerator[str, Any]: logger.info("Begin generating streamed response") - for char in prompt: - yield char # type: ignore + yield prompt # type: ignore logger.info("Streamed response complete") async def count_tokens(self, raw_text: str) -> int: diff --git a/tests/pytest/leapfrogai_api/test_api.py b/tests/pytest/leapfrogai_api/test_api.py index a80df6b6c..abe2e4ec6 100644 --- a/tests/pytest/leapfrogai_api/test_api.py +++ b/tests/pytest/leapfrogai_api/test_api.py @@ -4,6 +4,7 @@ import time from typing import Optional +from leapfrogai_sdk.llm import FinishReason import pytest from fastapi.applications import BaseHTTPMiddleware from fastapi.security import HTTPBearer @@ -260,7 +261,7 @@ def test_chat_completion(dummy_auth_middleware): # parse finish reason assert "finish_reason" in response_choices[0] - assert "stop" == response_choices[0].get("finish_reason") + assert FinishReason.STOP == response_choices[0].get("finish_reason") # parse usage data response_usage = response_obj.get("usage") From 8634084556c74f3750ad644f2602d9f63afd3492 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 12:21:28 -0400 Subject: [PATCH 25/28] refactor unit tests, done! --- packages/repeater/config.yaml | 6 +++--- tests/pytest/leapfrogai_api/test_api.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/repeater/config.yaml b/packages/repeater/config.yaml index b97195c5d..7a9ef7600 100644 --- a/packages/repeater/config.yaml +++ b/packages/repeater/config.yaml @@ -6,9 +6,9 @@ stop_tokens: - "" prompt_format: chat: - system: "{}\n" - assistant: "{}\n" - user: "{}\n" + system: "{}" + assistant: "{}" + user: "{}" defaults: top_p: 1.0 top_k: 0 diff --git a/tests/pytest/leapfrogai_api/test_api.py b/tests/pytest/leapfrogai_api/test_api.py index abe2e4ec6..b921d4930 100644 --- a/tests/pytest/leapfrogai_api/test_api.py +++ b/tests/pytest/leapfrogai_api/test_api.py @@ -4,7 +4,6 @@ import time from typing import Optional -from leapfrogai_sdk.llm import FinishReason import pytest from fastapi.applications import BaseHTTPMiddleware from fastapi.security import HTTPBearer @@ -261,7 +260,7 @@ def test_chat_completion(dummy_auth_middleware): # parse finish reason assert "finish_reason" in response_choices[0] - assert FinishReason.STOP == response_choices[0].get("finish_reason") + assert "FinishReason.STOP" == response_choices[0].get("finish_reason") # parse usage data response_usage = response_obj.get("usage") @@ -319,7 +318,7 @@ def test_stream_chat_completion(dummy_auth_middleware): iter_length += 1 # parse finish reason assert "finish_reason" in choices[0] - assert "stop" == choices[0].get("finish_reason") + assert "FinishReason.STOP" == choices[0].get("finish_reason") # parse usage data response_usage = stream_response.get("usage") prompt_tokens = response_usage.get("prompt_tokens") From ec2e3a12d55203887b49c1014e38387b55bd9262 Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 12:50:38 -0400 Subject: [PATCH 26/28] refactor unit tests, done! pt.2 --- .github/workflows/pytest.yaml | 2 +- packages/repeater/main.py | 3 ++- tests/Makefile | 1 + tests/pytest/leapfrogai_api/test_api.py | 30 ++++++++++++++++++------- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 08ef28c13..08ff0645f 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -70,7 +70,7 @@ jobs: docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:$LOCAL_VERSION - name: Run Pytest - run: python -m pytest tests/pytest -v + run: make test-api-unit env: LFAI_RUN_REPEATER_TESTS: true diff --git a/packages/repeater/main.py b/packages/repeater/main.py index eaa3ecf07..8bac4d60c 100644 --- a/packages/repeater/main.py +++ b/packages/repeater/main.py @@ -38,7 +38,8 @@ async def generate( self, prompt: str, config: GenerationConfig ) -> AsyncGenerator[str, Any]: logger.info("Begin generating streamed response") - yield prompt # type: ignore + for char in prompt: + yield char # type: ignore logger.info("Streamed response complete") async def count_tokens(self, raw_text: str) -> int: diff --git a/tests/Makefile b/tests/Makefile index c2b6aaee8..2c234dd1b 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -62,6 +62,7 @@ test-api-integration: test-api-unit: set-supabase LFAI_RUN_REPEATER_TESTS=$(LFAI_RUN_REPEATER_TESTS) PYTHONPATH=$$(pwd) pytest -vv -s tests/unit + LFAI_RUN_REPEATER_TESTS=$(LFAI_RUN_REPEATER_TESTS) PYTHONPATH=$$(pwd) python -m pytest -vv -s tests/pytest test-load: locust -f $$(pwd)/tests/load/loadtest.py --web-port 8089 diff --git a/tests/pytest/leapfrogai_api/test_api.py b/tests/pytest/leapfrogai_api/test_api.py index b921d4930..7ff0fa17c 100644 --- a/tests/pytest/leapfrogai_api/test_api.py +++ b/tests/pytest/leapfrogai_api/test_api.py @@ -283,6 +283,7 @@ def test_stream_chat_completion(dummy_auth_middleware): """Test the stream chat completion endpoint.""" with TestClient(app) as client: input_content = "this is the stream chat completion input." + input_length = len(input_content) chat_completion_request = lfai_types.ChatCompletionRequest( model="repeater", @@ -314,19 +315,32 @@ def test_stream_chat_completion(dummy_auth_middleware): assert len(choices) == 1 assert "delta" in choices[0] assert "content" in choices[0].get("delta") - assert choices[0].get("delta").get("content") == input_content + assert ( + choices[0].get("delta").get("content") + == input_content[iter_length] + ) iter_length += 1 # parse finish reason assert "finish_reason" in choices[0] - assert "FinishReason.STOP" == choices[0].get("finish_reason") + # in streaming responses, the stop reason is not STOP until the last iteration (token) is sent back + if iter_length == input_length: + assert "FinishReason.STOP" == choices[0].get("finish_reason") + else: + assert "FinishReason.NONE" == choices[0].get("finish_reason") # parse usage data response_usage = stream_response.get("usage") prompt_tokens = response_usage.get("prompt_tokens") completion_tokens = response_usage.get("completion_tokens") total_tokens = response_usage.get("total_tokens") - assert prompt_tokens == len(input_content) - assert completion_tokens == len(input_content) - assert total_tokens == len(input_content) * 2 - - # The repeater only response with 5 messages - assert iter_length == 5 + # in streaming responses, the length is not returned until the last iteration (token) is sent back + if iter_length == input_length: + assert prompt_tokens == input_length + assert completion_tokens == input_length + assert total_tokens == input_length * 2 + else: + assert total_tokens == 0 + assert completion_tokens == 0 + assert total_tokens == 0 + + # The repeater only responds with 1 message, the exact one that was prompted + assert iter_length == input_length From 13d5362740e139117ccac54022ed783e082d0e3e Mon Sep 17 00:00:00 2001 From: Justin Law Date: Mon, 9 Sep 2024 13:00:03 -0400 Subject: [PATCH 27/28] install cmd and docs --- tests/Makefile | 3 +++ tests/README.md | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index 2c234dd1b..b2bb66861 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -3,6 +3,9 @@ LEAPFROGAI_MODEL ?= llama-cpp-python LFAI_RUN_NIAH_TESTS ?= false LFAI_RUN_REPEATER_TESTS ?= true +install: + pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" "packages/repeater" + set-supabase: $(eval SUPABASE_URL := $(SUPABASE_URL)) $(eval SUPABASE_ANON_KEY := $(shell uds zarf tools kubectl get secret -n leapfrogai supabase-bootstrap-jwt -o json | uds zarf tools yq '.data.anon-key' | base64 -d)) diff --git a/tests/README.md b/tests/README.md index d2b410fff..9b09bb82f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,7 +15,8 @@ For the unit and integration tests within this directory, the following componen If you are running everything in a [UDS Kubernetes cluster](../k3d-gpu/README.md), you must port-forward your model (e.g., Repeater, vLLM, etc.) using the following command: ```bash -uds zarf connect --name=vllm-model --namespace=leapfrogai --local-port=50051 --remote-port=50051 +# may be named repeater OR repeater-model depending on the rendered Helm manifests +uds zarf connect --name=repeater-model --namespace=leapfrogai --local-port=50051 --remote-port=50051 ``` If running everything via Docker containers or in a local Python environment, then ensure they are accessible based on the test configurations in each testing target's sub-directory. @@ -24,7 +25,7 @@ Please see the [Makefile](./Makefile) for more details on turning tests on/off a ```bash # Install the python dependencies -python -m pip install ".[dev]" +make install # create a test user for the tests # prompts for a password and email From 926ba60259a293664165413a026ccd41b3632d82 Mon Sep 17 00:00:00 2001 From: Justin Law <81255462+justinthelaw@users.noreply.github.com> Date: Mon, 9 Sep 2024 16:18:43 -0400 Subject: [PATCH 28/28] Update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index abde59662..8f3d1b52a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,9 @@ build/ **/*.whl .model/ *.gguf -.env* +.env.password +.env.email +.env .ruff_cache .branches .temp