chore: import upstream snapshot with attribution
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:44 +08:00
commit 5a558eb09e
11579 changed files with 1795921 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
name: "Backend Tests"
run-name: "Backend Tests on ${{ github.ref_name }} by @${{ github.actor }}"
on:
pull_request:
paths:
- "apps/opik-backend/**"
- ".github/workflows/backend_tests.yml"
- ".github/scripts/discover-backend-tests.sh"
push:
branches:
- 'main'
paths:
- "apps/opik-backend/**"
- ".github/workflows/backend_tests.yml"
- ".github/scripts/discover-backend-tests.sh"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
checks: write
pull-requests: write
jobs:
# Automatically classify tests as unit/integration and split integration
# tests into balanced groups. No manual maintenance when adding new tests.
discover-tests:
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
matrix: ${{ steps.split.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 1
sparse-checkout: |
apps/opik-backend/src/test/java
.github/scripts
- name: Classify and split tests
id: split
working-directory: apps/opik-backend
run: ../../.github/scripts/discover-backend-tests.sh
run-backend-tests:
needs: discover-tests
name: ${{ matrix.name }}
runs-on: ubuntu-latest
timeout-minutes: ${{ matrix.timeout }}
defaults:
run:
working-directory: apps/opik-backend/
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.discover-tests.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'corretto'
cache: maven
- name: Run ${{ matrix.name }}
env:
TESTCONTAINERS_REUSE_ENABLE: true
run: >-
mvn clean test
-Dtest="${{ matrix.tests }}"
-Dmaven.test.failure.ignore=true
-Dsurefire.rerunFailingTestsCount=3
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: "Backend Tests - ${{ matrix.name }}"
files: '**/target/surefire-reports/TEST-*.xml'
@@ -0,0 +1,81 @@
name: "Build and Publish SDK"
run-name: "Build SDK ${{ github.ref_name }} by @${{ github.actor }}"
on:
workflow_dispatch:
inputs:
version:
type: string
required: true
description: Version
default: ""
is_release:
type: boolean
required: false
default: false
workflow_call:
inputs:
version:
type: string
required: true
description: Version
is_release:
type: boolean
required: false
default: false
push:
branches:
- 'main'
paths:
- "sdks/python/**"
jobs:
build-and-publish:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Setup
id: setup
run: |
if [[ "${{inputs.version}}" == "" ]]; then
echo "build_from=${{github.ref_name}}" | tee -a "$GITHUB_OUTPUT"
else
echo "build_from=${{inputs.version}}" | tee -a "$GITHUB_OUTPUT"
fi
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{steps.setup.outputs.build_from}}
fetch-tags: true
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: PyPI token preflight
if: inputs.is_release
uses: ./.github/actions/pypi-token-preflight
with:
pypi_token: ${{ secrets.PYPI_API_TOKEN }}
package_name: opik
- name: Build pip package
run: |
cd sdks/python
pip3 install -U pip build
if [[ "${{inputs.version}}" != "" ]]; then export VERSION=${{inputs.version}};fi
python3 -m build --sdist --wheel --outdir dist/ .
- name: Publish package distributions to PyPI
if: inputs.is_release
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: sdks/python/dist
# Symmetric to OPIK-6624's npm publish 403-skip: replays / races where
# the version is already on PyPI should not fail the release. PyPI returns
# `400 Bad Request` (filename already exists) — `skip-existing: true`
# makes twine treat it as a no-op success.
skip-existing: true
@@ -0,0 +1,310 @@
name: "Build Docker Images Multi-Arch"
run-name: "Build ${{ inputs.image }}${{ inputs.build_comet_image && '-comet' || '' }} - ${{ inputs.version }}"
on:
workflow_call:
inputs:
image:
type: string
required: true
description: Docker Image
version:
type: string
required: true
description: Version
build_from:
type: string
required: true
description: Original version to build from
build_comet_image:
type: boolean
required: true
description: If to build a Comet integration image
comet_build_args:
type: string
required: false
default: ""
description: Arguments for cloud docker build
is_release:
type: boolean
required: true
description: If this is a release build (will build multi-arch and push latest tag)
is_adhoc:
type: boolean
required: false
description: If this is an adhoc build (will skip comet variant)
default: false
build_arm64:
type: boolean
required: false
description: Build multiarch images
default: true
ai_spend_plugin_ref:
type: string
required: false
default: "main"
description: ai-spend plugin ref
secrets:
OPIK_FE_SENTRY_DSN:
required: false
OPIK_PLUGIN_AI_SPEND_TOKEN:
required: false
env:
DOCKER_REGISTRY: "ghcr.io/comet-ml/opik"
DOCKER_BUILD_SUMMARY: "false"
jobs:
generate-matrix:
runs-on: ubuntu-latest
outputs:
build_matrix: ${{ steps.set-matrix.outputs.build_matrix }}
merge_matrix: ${{ steps.set-matrix.outputs.merge_matrix }}
steps:
- name: Generate build and merge matrices
id: set-matrix
run: |
# Build 2D matrix: image_type x platform
BUILD_MATRIX='{"image_type":["regular","comet"],"platform":["amd64","arm64"],"exclude":[]}'
MERGE_MATRIX='{"image_type":["regular","comet"],"exclude":[]}'
# Exclude comet if build_comet_image is false
if [[ "${{ inputs.build_comet_image }}" != "true" ]]; then
BUILD_MATRIX=$(echo "$BUILD_MATRIX" | jq '.exclude += [{"image_type":"comet"}]')
MERGE_MATRIX=$(echo "$MERGE_MATRIX" | jq '.exclude += [{"image_type":"comet"}]')
fi
# Exclude arm64 when the caller opts out, or for guardrails backend
if [[ "${{ inputs.build_arm64 }}" != "true" ]] || [[ "${{ inputs.image }}" == "opik-guardrails-backend" ]]; then
BUILD_MATRIX=$(echo "$BUILD_MATRIX" | jq '.exclude += [{"platform":"arm64"}]')
fi
{
echo "build_matrix<<EOF"
echo "$BUILD_MATRIX"
echo "EOF"
echo "merge_matrix<<EOF"
echo "$MERGE_MATRIX"
echo "EOF"
} >> "$GITHUB_OUTPUT"
build:
needs: generate-matrix
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.build_matrix) }}
runs-on: ${{ inputs.image == 'opik-guardrails-backend' && matrix.platform == 'amd64' && 'ubuntu-latest-m' || (matrix.platform == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm') }}
name: ${{ matrix.platform }} ${{ inputs.image }}${{ matrix.image_type == 'comet' && '-comet' || '' }}
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.build.outputs.digest }}
platform: ${{ matrix.platform }}
image_name: ${{ steps.set_vars.outputs.image_name }}
timeout-minutes: 60
steps:
- name: Set image variables
id: set_vars
run: |
if [[ "${{ matrix.image_type }}" == "comet" ]]; then
IMAGE_NAME="${{ inputs.image }}-comet"
else
IMAGE_NAME="${{ inputs.image }}"
fi
echo "image_name=$IMAGE_NAME" >> "$GITHUB_OUTPUT"
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.build_from }}
- name: Save opik-sandbox-executor-python
if: inputs.image == 'opik-python-backend'
run: |
# Use latest tag if adhoc build, otherwise use the specific version
if [[ "${{ inputs.is_adhoc }}" == "true" ]]; then
TAG="latest"
else
TAG="${{inputs.version}}"
fi
docker pull "${{env.DOCKER_REGISTRY}}/opik-sandbox-executor-python:$TAG"
docker save "${{env.DOCKER_REGISTRY}}/opik-sandbox-executor-python:$TAG" | gzip > apps/opik-python-backend/opik-sandbox-executor-python.tar.gz
- name: Checkout ai-spend plugin (private)
if: inputs.image == 'opik-frontend' && matrix.image_type == 'comet'
uses: actions/checkout@v6
with:
repository: comet-ml/opik-plugin-ai-spend
ref: ${{ inputs.ai_spend_plugin_ref }}
token: ${{ secrets.OPIK_PLUGIN_AI_SPEND_TOKEN }}
path: .ai-spend-plugin
- name: Stage ai-spend plugin into frontend src
if: inputs.image == 'opik-frontend' && matrix.image_type == 'comet'
run: |
mkdir -p apps/opik-frontend/src/plugins/ai-spend
cp -R .ai-spend-plugin/src/. apps/opik-frontend/src/plugins/ai-spend/
rm -rf .ai-spend-plugin
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ steps.set_vars.outputs.image_name }}
tags: |
type=raw,value=${{ inputs.version }}
type=raw,value=latest,enable=${{ inputs.is_release == true }}
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and Push Docker Image
id: build
uses: docker/build-push-action@v6
with:
context: apps/${{ inputs.image }}/
platforms: linux/${{ matrix.platform }}
cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ steps.set_vars.outputs.image_name }}:main
provenance: false
load: false
outputs: type=image,name=${{ env.DOCKER_REGISTRY }}/${{ steps.set_vars.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
labels: ${{ steps.meta.outputs.labels }}
build-args: |
OPIK_VERSION=${{ inputs.version }}
${{ matrix.image_type == 'comet' && inputs.comet_build_args }}
${{ inputs.image == 'opik-frontend' && matrix.image_type == 'comet' && 'BUILD_PLUGINS=comet,ai-spend' }}
${{ matrix.image_type == 'comet' && 'SENTRY_ENABLED=true' }}
${{ matrix.image_type == 'comet' && format('SENTRY_DSN={0}', secrets.OPIK_FE_SENTRY_DSN) }}
secrets: |
${{ matrix.image_type == 'comet' && secrets.OPIK_FE_SENTRY_DSN != '' && format('OPIK_FE_SENTRY_DSN={0}', secrets.OPIK_FE_SENTRY_DSN) || '' }}
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
- name: Export digest
run: |
mkdir -p /tmp/digests/${{ steps.set_vars.outputs.image_name }}
digest="${{ steps.build.outputs.digest }}"
# Strip sha256: prefix
HASH="${digest#sha256:}"
echo "$HASH" > "/tmp/digests/${{ steps.set_vars.outputs.image_name }}/${{ matrix.platform }}.digest"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: ${{ github.run_id }}-${{ steps.set_vars.outputs.image_name }}-digest-${{ matrix.platform }}
path: /tmp/digests/${{ steps.set_vars.outputs.image_name }}/${{ matrix.platform }}.digest
if-no-files-found: error
retention-days: 1
merge:
needs: [generate-matrix, build]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix: ${{ fromJson(needs.generate-matrix.outputs.merge_matrix) }}
steps:
- name: Set image variables
id: set_vars
run: |
if [[ "${{ matrix.image_type }}" == "comet" ]]; then
IMAGE_NAME="${{ inputs.image }}-comet"
else
IMAGE_NAME="${{ inputs.image }}"
fi
echo "image_name=$IMAGE_NAME" >> "$GITHUB_OUTPUT"
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests/${{ steps.set_vars.outputs.image_name }}
pattern: ${{ github.run_id }}-${{ steps.set_vars.outputs.image_name }}-digest-*
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ steps.set_vars.outputs.image_name }}
tags: |
type=raw,value=${{ inputs.version }}
type=raw,value=latest,enable=${{ inputs.is_release == true }}
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
- name: Create manifest list and push tags
run: |
cd /tmp/digests/${{ steps.set_vars.outputs.image_name }}
DIGESTS=""
for artifact_dir in ${{ github.run_id }}-${{ steps.set_vars.outputs.image_name }}-digest-*; do
if [ -d "$artifact_dir" ]; then
PLATFORM="${artifact_dir##*-}"
DIGEST_FILE="$artifact_dir/$PLATFORM.digest"
if [ -f "$DIGEST_FILE" ]; then
DIGEST_VALUE=$(tr -d '\n\r ' < "$DIGEST_FILE")
DIGEST_REF="${{ env.DOCKER_REGISTRY }}/${{ steps.set_vars.outputs.image_name }}@sha256:$DIGEST_VALUE"
DIGESTS="$DIGESTS $DIGEST_REF"
fi
fi
done
if [ -z "$DIGESTS" ]; then
echo "ERROR: No digests found for ${{ steps.set_vars.outputs.image_name }}"
exit 1
fi
# Create and push manifest using metadata tags
# Metadata outputs tags as newline-separated string, convert to -t flags
TAG_ARGS=""
while IFS= read -r tag; do
if [ -n "$tag" ]; then
TAG_ARGS="$TAG_ARGS -t $tag"
fi
done <<< "${{ steps.meta.outputs.tags }}"
if [ -z "$TAG_ARGS" ]; then
echo "ERROR: No tags generated from metadata!"
exit 1
fi
# $TAG_ARGS and $DIGESTS are intentionally unquoted: each holds a
# space-separated argument list (`-t tag1 -t tag2 ...` and a list of
# digest refs) that must word-split into individual argv entries.
# Quoting would collapse each into a single argument and break the
# docker invocation.
# shellcheck disable=SC2086
docker buildx imagetools create $TAG_ARGS $DIGESTS
- name: Write Build Summary
run: |
echo "### Docker images pushed: ${{ steps.set_vars.outputs.image_name }}" >> "$GITHUB_STEP_SUMMARY"
echo "${{ steps.meta.outputs.tags }}" >> "$GITHUB_STEP_SUMMARY"
if [[ "${{ inputs.image }}" == "opik-guardrails-backend" ]] || [[ "${{ inputs.build_arm64 }}" != "true" ]]; then
echo "Built for platforms: linux/amd64" >> "$GITHUB_STEP_SUMMARY"
else
echo "Built for platforms: linux/amd64, linux/arm64" >> "$GITHUB_STEP_SUMMARY"
fi
echo "- [View on GitHub Container Registry](https://github.com/${{ github.repository }}/pkgs/container/opik%2F${{ steps.set_vars.outputs.image_name }})" >> "$GITHUB_STEP_SUMMARY"
+238
View File
@@ -0,0 +1,238 @@
name: "Build Opik Docker Images"
run-name: "Build ${{ github.ref_name }} by @${{ github.actor }}"
on:
push:
branches:
- 'main'
paths-ignore:
- "deployment/**"
- 'sdks/**'
workflow_dispatch:
inputs:
is_release:
type: boolean
required: true
description: Is release build
default: false
is_adhoc:
type: boolean
required: false
description: Is adhoc build
default: false
build_guardrails:
type: boolean
required: true
description: Build guardrails backend
default: true
build_arm64:
type: boolean
required: false
description: Build multiarch images
default: true
ai_spend_plugin_ref:
type: string
required: false
description: ai-spend plugin ref
default: "main"
workflow_call:
inputs:
version:
type: string
required: true
description: Version
is_release:
type: boolean
required: true
description: Is release build
is_adhoc:
type: boolean
required: false
description: Is adhoc build
default: false
ref:
type: string
required: false
description: Git ref to build from (branch, tag, or commit SHA)
default: ""
build_guardrails:
type: boolean
required: false
description: Build guardrails backend
default: true
build_arm64:
type: boolean
required: false
description: Build multiarch images
default: true
ai_spend_plugin_ref:
type: string
required: false
description: ai-spend plugin ref
default: "main"
outputs:
version:
description: "The version that was built"
value: ${{ jobs.set-version.outputs.version }}
jobs:
set-version:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
build_from: ${{ steps.version.outputs.build_from }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
- name: Set version
id: version
run: |
if [[ "${{inputs.version}}" != "" ]]; then
VERSION=${{inputs.version}}
else
BASE_VERSION=$(cat version.txt)
BRANCH_NAME="${{ github.ref_name }}"
if [[ "${BRANCH_NAME}" == "main" ]]; then
VERSION=${BASE_VERSION}-${{ github.run_number }}
elif [[ "${BRANCH_NAME}" =~ ^hotfix/.* ]]; then
# Special handling for hotfix branches
VERSION="${BASE_VERSION}-hotfix.${{ github.run_number }}"
else
# Normalize branch name
fixedBranchName=$(echo "${BRANCH_NAME}" | sed 's/origin\///; s/\//-/g; s/_/-/g; s/.*/\L&/')
if [ ${#fixedBranchName} -gt 21 ]; then
fixedBranchName="${fixedBranchName:0:20}"
# remove dash at the end
while [ "${fixedBranchName: -1}" = "-" ]; do
fixedBranchName="${fixedBranchName%?}"
done
fi
VERSION="${BASE_VERSION}-${fixedBranchName}-${{ github.run_number }}"
fi
fi
echo "version=${VERSION}" | tee -a "$GITHUB_OUTPUT"
echo "Version is ${VERSION}" >> "$GITHUB_STEP_SUMMARY"
if [ "${{inputs.ref}}" != "" ]; then
echo "build_from=${{inputs.ref}}" | tee -a "$GITHUB_OUTPUT"
elif [[ "${{inputs.version}}" == "" ]]; then
echo "build_from=${{github.ref_name}}" | tee -a "$GITHUB_OUTPUT"
else
echo "build_from=${{inputs.version}}" | tee -a "$GITHUB_OUTPUT"
fi
build-backend:
needs:
- set-version
secrets: inherit
uses: ./.github/workflows/build_and_push_docker.yaml
with:
image: "opik-backend"
version: ${{needs.set-version.outputs.version}}
build_from: ${{needs.set-version.outputs.build_from}}
build_comet_image: false
comet_build_args: ""
is_release: ${{ inputs.is_release || false }}
is_adhoc: ${{ inputs.is_adhoc || false }}
# push events have no `inputs` context, so force multi-arch there; otherwise honor the input (workflow_dispatch / workflow_call use the declared default: true)
build_arm64: ${{ github.event_name == 'push' || inputs.build_arm64 }}
build-sandbox-executor-python:
if: ${{ !inputs.is_adhoc }}
needs:
- set-version
secrets: inherit
uses: ./.github/workflows/build_and_push_docker.yaml
with:
image: "opik-sandbox-executor-python"
version: ${{needs.set-version.outputs.version}}
build_from: ${{needs.set-version.outputs.build_from}}
build_comet_image: false
comet_build_args: ""
is_release: ${{ inputs.is_release || false }}
is_adhoc: ${{ inputs.is_adhoc || false }}
build_arm64: ${{ github.event_name == 'push' || inputs.build_arm64 }}
build-python-backend:
if: always() && needs.set-version.result == 'success'
needs:
- set-version
- build-sandbox-executor-python
secrets: inherit
uses: ./.github/workflows/build_and_push_docker.yaml
with:
image: "opik-python-backend"
version: ${{needs.set-version.outputs.version}}
build_from: ${{needs.set-version.outputs.build_from}}
build_comet_image: false
comet_build_args: ""
is_release: ${{ inputs.is_release || false }}
is_adhoc: ${{ inputs.is_adhoc || false }}
build_arm64: ${{ github.event_name == 'push' || inputs.build_arm64 }}
# build-guardrails-backend:
# if: inputs.build_guardrails != false
# needs:
# - set-version
# secrets: inherit
# uses: ./.github/workflows/build_and_push_docker.yaml
# with:
# image: "opik-guardrails-backend"
# version: ${{needs.set-version.outputs.version}}
# build_from: ${{needs.set-version.outputs.build_from}}
# build_comet_image: false
# comet_build_args: ""
# is_release: ${{ inputs.is_release || false }}
build-frontend:
needs:
- set-version
secrets: inherit
uses: ./.github/workflows/build_and_push_docker.yaml
with:
image: "opik-frontend"
version: ${{needs.set-version.outputs.version}}
build_from: ${{needs.set-version.outputs.build_from}}
build_comet_image: ${{ !inputs.is_adhoc }}
comet_build_args: ${{ !inputs.is_adhoc && 'BUILD_MODE=comet' || '' }}
is_release: ${{ inputs.is_release || false }}
is_adhoc: ${{ inputs.is_adhoc || false }}
build_arm64: ${{ github.event_name == 'push' || inputs.build_arm64 }}
ai_spend_plugin_ref: ${{ inputs.ai_spend_plugin_ref || 'main' }}
create-git-tag:
if: |
always() &&
!inputs.is_release &&
needs.set-version.result == 'success' &&
needs.build-backend.result == 'success' &&
needs.build-frontend.result == 'success' &&
needs.build-python-backend.result == 'success'
needs:
- set-version
- build-backend
- build-frontend
- build-python-backend
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Create git tag
run: |
set -x
git config --local user.email "github-actions@comet.com"
git config --local user.name "github-actions"
git tag ${{needs.set-version.outputs.version}}
git push --no-verify origin refs/tags/${{needs.set-version.outputs.version}}
+92
View File
@@ -0,0 +1,92 @@
name: Build E2E Docker Images
on:
workflow_call:
inputs:
include_guardrails:
description: "Build and include guardrails-backend image"
required: false
default: false
type: boolean
outputs:
image_tag:
description: "The GHCR image tag used for this build"
value: ${{ jobs.build.outputs.image_tag }}
env:
DOCKER_REGISTRY: ghcr.io/comet-ml/opik
jobs:
build:
name: Build E2E Docker Images
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: write
outputs:
image_tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Set image tag
id: tag
run: |
SHA="${{ github.sha }}"
SHORT_SHA="${SHA:0:7}"
SUFFIX="${{ inputs.include_guardrails == true && '-guardrails' || '' }}"
if [ -n "${{ github.event.pull_request.number }}" ]; then
TAG="ci-e2e-pr${{ github.event.pull_request.number }}-${SHORT_SHA}${SUFFIX}"
else
TAG="ci-e2e-${SHORT_SHA}${SUFFIX}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build E2E Docker images
env:
COMPOSE_BAKE: false
run: |
SERVICES="backend python-backend"
if [ "${{ inputs.include_guardrails }}" = "true" ]; then
SERVICES="$SERVICES guardrails-backend"
fi
# shellcheck disable=SC2086
docker compose -f deployment/docker-compose/docker-compose.yaml \
build $SERVICES
- name: Push images to GHCR
run: |
TAG="${{ steps.tag.outputs.tag }}"
IMAGES="opik-backend opik-python-backend"
if [ "${{ inputs.include_guardrails }}" = "true" ]; then
IMAGES="${IMAGES} opik-guardrails-backend"
fi
# Tag all images first (cheap, sequential)
for IMAGE in ${IMAGES}; do
docker tag "${{ env.DOCKER_REGISTRY }}/${IMAGE}:latest" \
"${{ env.DOCKER_REGISTRY }}/${IMAGE}:${TAG}"
done
# Push all images in parallel
PIDS=()
for IMAGE in ${IMAGES}; do
docker push "${{ env.DOCKER_REGISTRY }}/${IMAGE}:${TAG}" &
PIDS+=($!)
done
# Wait for all pushes, fail if any fails
FAILED=0
for PID in "${PIDS[@]}"; do
wait "${PID}" || FAILED=1
done
exit "${FAILED}"
+112
View File
@@ -0,0 +1,112 @@
name: Cleanup E2E Docker Images
on:
pull_request:
types: [closed]
schedule:
- cron: "0 3 * * 6" # Every Saturday at 03:00 UTC
workflow_dispatch:
inputs:
max_age_days:
description: "Delete CI tags older than this many days (0 = delete all)"
required: false
default: 7
type: number
permissions:
contents: read
packages: write
env:
IMAGES: >-
opik-backend
opik-python-backend
opik-guardrails-backend
jobs:
cleanup:
name: Cleanup CI images
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Delete CI images
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
MAX_AGE_DAYS: ${{ github.event.inputs.max_age_days || '7' }}
run: |
DELETED=0
SKIPPED=0
FAILED=0
# Build the jq filter and age cutoff based on trigger type
if [ "$EVENT_NAME" = "pull_request" ]; then
echo "Mode: PR close — deleting all ci-e2e-pr${PR_NUMBER}-* images"
JQ_FILTER=".[] | select(.metadata.container.tags | any(startswith(\"ci-e2e-pr${PR_NUMBER}-\"))) | {id: .id, tags: .metadata.container.tags, created_at: .created_at}"
CUTOFF=""
else
echo "Mode: stale sweep — deleting ci-e2e-* images older than ${MAX_AGE_DAYS} days"
JQ_FILTER='.[] | select(.metadata.container.tags | any(startswith("ci-e2e-"))) | {id: .id, tags: .metadata.container.tags, created_at: .created_at}'
CUTOFF=$(date -u -d "${MAX_AGE_DAYS} days ago" +%s 2>/dev/null \
|| date -u -v-"${MAX_AGE_DAYS}"d +%s)
fi
for IMAGE in ${{ env.IMAGES }}; do
echo "=== Scanning ${IMAGE} ==="
if ! VERSIONS=$(gh api \
"/orgs/comet-ml/packages/container/opik%2F${IMAGE}/versions" \
--paginate \
--jq "$JQ_FILTER" 2>&1); then
echo " Failed to list versions: ${VERSIONS}"
continue
fi
if [ -z "$VERSIONS" ]; then
echo " No matching versions found"
continue
fi
COUNT=$(echo "$VERSIONS" | wc -l | tr -d ' ')
echo " Found ${COUNT} matching versions"
while read -r VERSION; do
[ -z "$VERSION" ] && continue
VERSION_ID=$(echo "$VERSION" | jq -r '.id')
CI_TAG=$(echo "$VERSION" | jq -r '.tags[] | select(startswith("ci-e2e-"))' | head -1)
# For stale sweep, check age; for PR close, delete unconditionally
if [ -n "$CUTOFF" ]; then
CREATED_AT=$(echo "$VERSION" | jq -r '.created_at')
CREATED_TS=$(date -u -d "$CREATED_AT" +%s 2>/dev/null \
|| date -u -j -f "%Y-%m-%dT%H:%M:%S" "${CREATED_AT%%.*}" +%s 2>/dev/null \
|| echo "")
if [ -z "$CREATED_TS" ]; then
echo " Skipping ${IMAGE}:${CI_TAG} (could not parse created_at: ${CREATED_AT})"
SKIPPED=$((SKIPPED + 1))
continue
fi
if [ "$CREATED_TS" -ge "$CUTOFF" ]; then
SKIPPED=$((SKIPPED + 1))
continue
fi
echo " Deleting ${IMAGE}:${CI_TAG} (older than ${MAX_AGE_DAYS} days)"
else
echo " Deleting ${IMAGE}:${CI_TAG}"
fi
if gh api --method DELETE \
"/orgs/comet-ml/packages/container/opik%2F${IMAGE}/versions/${VERSION_ID}"; then
DELETED=$((DELETED + 1))
else
FAILED=$((FAILED + 1))
fi
done <<< "$VERSIONS"
done
echo ""
echo "=== Summary ==="
echo "Deleted: ${DELETED}"
echo "Skipped: ${SKIPPED}"
echo "Failed: ${FAILED}"
@@ -0,0 +1,134 @@
name: "ClickHouse Migration Cluster Check"
run-name: "ClickHouse Migration Cluster Check on ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
on:
pull_request:
paths:
- "apps/opik-backend/src/main/resources/liquibase/db-app-analytics/migrations/**/*.sql"
push:
branches:
- "main"
paths:
- "apps/opik-backend/src/main/resources/liquibase/db-app-analytics/migrations/**/*.sql"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
validate-clickhouse-migrations:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Get changed ClickHouse migration files
id: changed-files
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "🔍 Detecting ClickHouse migration files that were added or modified..."
if [ "${{ github.event_name }}" = "pull_request" ]; then
# For PRs, use GitHub's built-in files API
gh api repos/${{ github.repository }}/pulls/${{ github.event.number }}/files \
--jq '.[] | select(.status == "added" or .status == "modified") | select(.filename | test("^apps/opik-backend/src/main/resources/liquibase/db-app-analytics/migrations/.*\\.sql$")) | .filename' > changed_files.txt
CHANGED_FILES=$(cat changed_files.txt || echo "")
rm -f changed_files.txt
else
# For pushes to main, use GitHub API to get commit files
# This works reliably regardless of fetch-depth
gh api repos/${{ github.repository }}/commits/${{ github.sha }} \
--jq '.files[] | select(.status == "added" or .status == "modified") | select(.filename | test("^apps/opik-backend/src/main/resources/liquibase/db-app-analytics/migrations/.*\\.sql$")) | .filename' > changed_files.txt
CHANGED_FILES=$(cat changed_files.txt || echo "")
rm -f changed_files.txt
fi
if [ -n "$CHANGED_FILES" ]; then
echo "📄 Found ClickHouse migration files to validate:"
# shellcheck disable=SC2086 # intentional word-split on newlines in CHANGED_FILES
printf '%s\n' $CHANGED_FILES | while read -r file; do
[ -n "$file" ] && echo " - $file"
done
# Save the files to environment variable for next step
echo "MIGRATION_FILES<<EOF" >> "$GITHUB_ENV"
# shellcheck disable=SC2086 # intentional word-split on newlines in CHANGED_FILES
printf '%s\n' $CHANGED_FILES | while read -r file; do
[ -n "$file" ] && echo "$file" >> "$GITHUB_ENV"
done
echo "EOF" >> "$GITHUB_ENV"
echo "HAS_MIGRATION_FILES=true" >> "$GITHUB_ENV"
else
echo "📋 No ClickHouse migration files were added or modified."
echo "✅ No validation needed."
echo "HAS_MIGRATION_FILES=false" >> "$GITHUB_ENV"
fi
- name: Validate ClickHouse migrations have ON CLUSTER clause
if: env.HAS_MIGRATION_FILES == 'true'
run: |
echo "🔍 Validating ClickHouse migrations for proper ON CLUSTER clause usage..."
echo "📖 Reference: https://clickhouse.com/docs/sql-reference/distributed-ddl"
echo
# Run the validation script with the migration files
# Use xargs with -0 flag and tr to handle newline-separated list safely
# This approach correctly handles filenames with spaces across different platforms
if echo "$MIGRATION_FILES" | tr '\n' '\0' | xargs -0 ./scripts/check_clickhouse_migrations_cluster.sh; then
echo "✅ All modified ClickHouse migrations are properly configured!"
echo "🎉 DDL operations will be executed distributively across the cluster."
else
echo "❌ ClickHouse migration validation failed!"
echo
echo "💡 What this means:"
echo " - DDL operations without ON CLUSTER clause will only execute on a single node"
echo " - This can cause inconsistencies in distributed ClickHouse deployments"
echo " - All CREATE, DROP, ALTER, RENAME, and EXCHANGE statements must include ON CLUSTER '{cluster}'"
echo
echo "🔧 How to fix:"
echo " 1. Add 'ON CLUSTER '\"'\"'{cluster}'\"'\"'' to all DDL statements in your migration files"
echo " 2. Example: CREATE TABLE my_table ON CLUSTER '{cluster}' (...)"
echo " 3. Reference: https://clickhouse.com/docs/sql-reference/distributed-ddl"
echo
exit 1
fi
- name: Summary for no migration files
if: env.HAS_MIGRATION_FILES == 'false'
run: |
echo "=========================================="
echo "✅ No ClickHouse Migration Files Changed"
echo "=========================================="
echo "📋 No ClickHouse migration files were added or modified in this change."
echo "✅ No validation required."
echo
echo "💡 Note: This check only validates NEW or MODIFIED migration files."
echo " Existing migration files are not checked as they cannot be changed."
- name: Summary for validated files
if: env.HAS_MIGRATION_FILES == 'true' && success()
run: |
echo "=========================================="
echo "✅ ClickHouse Migration Validation PASSED"
echo "=========================================="
echo "✅ All modified DDL operations include proper ON CLUSTER clause"
echo "✅ Migrations are ready for distributed ClickHouse deployment"
echo "✅ No cluster inconsistencies will occur"
echo
echo "🎯 This check ensures that:"
echo " • CREATE statements will create tables/indexes on all cluster nodes"
echo " • DROP statements will remove objects from all cluster nodes"
echo " • ALTER statements will modify schema on all cluster nodes"
echo " • RENAME statements will rename objects on all cluster nodes"
echo
echo "📚 For more information about distributed DDL:"
echo " https://clickhouse.com/docs/sql-reference/distributed-ddl"
+337
View File
@@ -0,0 +1,337 @@
name: '🐙 Code Quality'
run-name: "🐙 Code Quality ${{ github.ref_name }} by @${{ github.actor }}"
# Lint + format + typecheck across all languages, driven by the root
# .pre-commit-config.yaml (the source of truth for which checks run on which
# paths). Changed-files-only, mirroring local commits.
#
# Shape: one CI job PER LINTER that actually has work.
# detect — replays pre-commit's path matching over the diff and emits one
# matrix leg per hook with ≥1 matching file (no skip-spam legs).
# lint — fan-out matrix, one job per linter, fail-fast: false, so each
# linter is reported, retried, and made visible independently.
# summary — aggregates every leg's timing into one sticky PR comment and
# gates the workflow red if any leg failed.
#
# PRs lint the whole PR diff (base..head); pushes to main lint the push range.
# A full-repo audit is available on demand via workflow_dispatch.
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
inputs:
all_files:
description: "Run all hooks against the whole repo (full audit)"
type: boolean
default: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
# Needed by the summary job to post/update the sticky timing comment on PRs.
# On fork PRs the token is read-only regardless, so that step is guarded.
pull-requests: write
jobs:
# ---------------------------------------------------------------------------
# Resolve the diff range and emit one matrix leg per hook that has matching
# changed files. The matrix is built from CHANGED hooks only, so a linter that
# matches nothing never spawns a (skipped) job.
# ---------------------------------------------------------------------------
detect:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
legs: ${{ steps.detect.outputs.legs }}
has_legs: ${{ steps.detect.outputs.has_legs }}
legs_count: ${{ steps.detect.outputs.legs_count }}
skipped: ${{ steps.detect.outputs.skipped }}
from: ${{ steps.range.outputs.from }}
to: ${{ steps.range.outputs.to }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Diff refs (base/head, push before/after) must be reachable.
fetch-depth: 0
- name: Resolve diff range
id: range
env:
EVENT: ${{ github.event_name }}
PR_BASE: ${{ github.event.pull_request.base.sha }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.sha }}
run: |
case "$EVENT" in
pull_request) FROM="$PR_BASE"; TO="$PR_HEAD" ;;
push)
FROM="$PUSH_BEFORE"; TO="$PUSH_AFTER"
if [ "$FROM" = "0000000000000000000000000000000000000000" ]; then FROM="${TO}^"; fi ;;
*) FROM=""; TO="" ;; # workflow_dispatch: full-repo audit
esac
echo "from=$FROM" >> "$GITHUB_OUTPUT"
echo "to=$TO" >> "$GITHUB_OUTPUT"
- name: Detect hooks with work
id: detect
env:
FROM: ${{ steps.range.outputs.from }}
TO: ${{ steps.range.outputs.to }}
run: |
set -o pipefail
if [ -n "$FROM" ]; then
# ACMR: added/copied/modified/renamed only. Exclude deletions —
# pre-commit lints files present in the worktree, so a deleted path
# would spawn a leg that finds "no files to check" and Skips.
mapfile -t changed < <(git diff --name-only --diff-filter=ACMR "$FROM..$TO")
else
# workflow_dispatch full audit: every tracked file is "changed".
mapfile -t changed < <(git ls-files)
fi
out="$(printf '%s\n' "${changed[@]}" \
| python3 scripts/precommit-detect-hooks.py .pre-commit-config.yaml)"
# Full {legs, skipped} JSON drives the matrix (.legs) and the summary
# (.skipped). legs_count lets the summary reconcile rendered rows
# against the jobs that should have produced timing.
echo "legs=$out" >> "$GITHUB_OUTPUT"
echo "skipped=$(echo "$out" | python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin)["skipped"]))')" >> "$GITHUB_OUTPUT"
count="$(echo "$out" | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["legs"]))')"
echo "legs_count=$count" >> "$GITHUB_OUTPUT"
if [ "$count" -gt 0 ]; then echo "has_legs=true" >> "$GITHUB_OUTPUT"; else echo "has_legs=false" >> "$GITHUB_OUTPUT"; fi
echo "Detected $count hook(s) with work."
# ---------------------------------------------------------------------------
# One job per linter. Each leg provisions only the toolchain it needs, runs
# its single hook over its matched files, and uploads a timing fragment for
# the summary. fail-fast: false so one red linter doesn't cancel the others
# (independent reporting + single-leg re-run).
# ---------------------------------------------------------------------------
lint:
needs: detect
if: needs.detect.outputs.has_legs == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
leg: ${{ fromJSON(needs.detect.outputs.legs).legs }}
name: "lint (${{ matrix.leg.name }})"
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Shallow: the leg lints explicit files (paths come from detect's
# matrix value, not a git diff here), so it needs the worktree content
# at HEAD, not history. Avoids a full clone on every one of N legs.
fetch-depth: 1
- name: Set up JDK 25
if: matrix.leg.toolchain == 'java'
uses: actions/setup-java@v4
with:
java-version: "25"
distribution: "corretto"
cache: maven
- name: Set up Node.js
if: matrix.leg.toolchain == 'node-fe' || matrix.leg.toolchain == 'node-ts'
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install frontend deps
if: matrix.leg.toolchain == 'node-fe'
run: npm ci
working-directory: apps/opik-frontend
- name: Install TypeScript SDK deps
if: matrix.leg.toolchain == 'node-ts'
run: npm ci
working-directory: sdks/typescript
- name: Set up uv
uses: astral-sh/setup-uv@v5
- name: Cache pre-commit environments
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
# Run exactly this leg's hook over its matched files. Hook ids aren't
# unique across scopes, so `--files` narrows a shared id (e.g. ruff) to
# the one scope that matched; sibling hooks report "no files to check".
# Capture the verbose log and exit code, then filter the log to this
# hook's own result lines so the summary table shows one row per linter.
- name: Run hook
id: hook
env:
HOOK_ID: ${{ matrix.leg.id }}
HOOK_FILES: ${{ matrix.leg.files }}
run: |
set -o pipefail
set +e
# shellcheck disable=SC2086 # HOOK_FILES is an intentional word-split list
uvx pre-commit run "$HOOK_ID" --files $HOOK_FILES --show-diff-on-failure --verbose 2>&1 \
| tee /tmp/leg-raw.log
echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
set -e
./scripts/precommit-filter-leg-log.sh "$HOOK_ID" /tmp/leg-raw.log > /tmp/leg.log
cat /tmp/leg.log
- name: Sanitize artifact name
id: artifact
env:
HOOK_NAME: ${{ matrix.leg.name }}
run: |
# Artifact names can't contain emojis/spaces/slashes; derive a safe,
# collision-free one. The summary downloads with merge-multiple (all
# fragments flattened into one dir), so the FILE inside must be unique
# too — name it the same as the artifact, not a shared "leg.log".
safe="$(echo "$HOOK_NAME" | tr -cd '[:alnum:]_-' | head -c 80)"
[ -n "$safe" ] || safe="hook"
frag="timing-${safe}-${{ strategy.job-index }}"
echo "name=$frag" >> "$GITHUB_OUTPUT"
cp /tmp/leg.log "/tmp/${frag}.log"
- name: Upload timing fragment
if: always()
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact.outputs.name }}
path: /tmp/${{ steps.artifact.outputs.name }}.log
if-no-files-found: ignore
retention-days: 1
- name: Fail leg if hook failed
if: steps.hook.outputs.exit_code != '0'
run: |
echo "::error::${{ matrix.leg.name }} failed (exit ${{ steps.hook.outputs.exit_code }})."
exit 1
# ---------------------------------------------------------------------------
# Aggregate every leg's timing fragment into ONE slowest-first table, post it
# as a sticky PR comment (overwritten each run), and gate the workflow red if
# any leg failed. Runs even when legs fail (if: always) so the comment + the
# remediation always land. Also runs when detect found no work (posts a clean
# "nothing to lint" comment and passes).
# ---------------------------------------------------------------------------
summary:
needs: [detect, lint]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Only needs the render scripts + shared description TSV, not history.
fetch-depth: 1
- name: Download timing fragments
if: needs.detect.outputs.has_legs == 'true'
uses: actions/download-artifact@v4
with:
path: /tmp/fragments
pattern: timing-*
merge-multiple: true
- name: Render timing table
env:
HAS_LEGS: ${{ needs.detect.outputs.has_legs }}
LEGS_COUNT: ${{ needs.detect.outputs.legs_count }}
SKIPPED_JSON: ${{ needs.detect.outputs.skipped }}
LINT_RESULT: ${{ needs.lint.result }}
run: |
# Build the running-hooks table first so we can reconcile its row
# count against the number of jobs detect spawned: every running leg
# must contribute exactly one row. A shortfall means a leg ran but its
# fragment was empty (e.g. detect over-emitted a leg that Skipped at
# runtime) — surface it instead of silently under-reporting.
ran_rows=0
if [ "$HAS_LEGS" = "true" ]; then
cat /tmp/fragments/*.log > /tmp/precommit-output.log 2>/dev/null || true
if [ -s /tmp/precommit-output.log ]; then
./scripts/precommit-timing-table.sh /tmp/precommit-output.log > /tmp/timing-rows.md
# Data rows = "| "-prefixed lines minus the header and Total rows.
# (The "|---" separator starts with "|-", so it isn't counted.)
ran_rows="$(grep -c '^| ' /tmp/timing-rows.md || true)"
ran_rows=$(( ran_rows - 2 ))
[ "$ran_rows" -lt 0 ] && ran_rows=0
fi
fi
{
# Hidden marker lets find-comment locate this PR's comment so it's
# updated in place each run (no per-commit spam).
echo "<!-- precommit-timing -->"
echo "### ⏱️ pre-commit per-hook timing"
echo ""
if [ "$HAS_LEGS" != "true" ]; then
echo "_No linted files changed — nothing to run._"
elif [ -s /tmp/timing-rows.md ]; then
cat /tmp/timing-rows.md
else
echo "_No pre-commit output captured._"
fi
# Reconcile: rendered rows must equal the jobs detect spawned.
if [ "$HAS_LEGS" = "true" ] && [ "${ran_rows}" -ne "${LEGS_COUNT:-0}" ]; then
echo ""
echo "> [!WARNING]"
echo "> Timing shows **${ran_rows}** of **${LEGS_COUNT}** lint jobs — $(( LEGS_COUNT - ran_rows )) produced no timing (ran but matched no files at runtime). detect over-emitted a leg; see \`scripts/precommit-detect-hooks.py\` (\`TYPED_IDS\`)."
fi
# Skipped checks — hooks with no matching files (no job ran).
./scripts/precommit-skipped-table.sh "$SKIPPED_JSON"
# Remediation — only when a leg failed, so green runs stay clean.
if [ "$LINT_RESULT" = "failure" ]; then
echo ""
echo "> [!WARNING]"
echo "> **Lint failed.** Fix locally, then push again:"
echo "> \`\`\`bash"
echo "> pip install pre-commit # or: brew install pre-commit"
echo "> make hooks # install the git hook (once per clone)"
echo "> make precommit # run the same checks on your changed files"
echo "> \`\`\`"
echo "> Formatters fix files in place — review the changes, \`git add\`, and commit."
echo "> Or apply the diff shown in the failed CI leg directly."
fi
} > /tmp/precommit-table.md
cat /tmp/precommit-table.md >> "$GITHUB_STEP_SUMMARY"
- name: Find prior timing comment
id: find_comment
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
uses: peter-evans/find-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: github-actions[bot]
body-includes: "<!-- precommit-timing -->"
- name: Post or update timing comment
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find_comment.outputs.comment-id }}
edit-mode: replace
body-path: /tmp/precommit-table.md
# Gate: the workflow is red iff any lint leg failed. (detect/summary infra
# failures surface on their own.) Fork PRs get no sticky comment, so the
# remediation also lives in this step log — the one place every contributor
# can see.
- name: Gate on lint result
if: needs.lint.result == 'failure'
run: |
echo "::error::One or more linters failed. Fix locally:"
echo " pip install pre-commit # or: brew install pre-commit"
echo " make hooks # install the git hook (once per clone)"
echo " make precommit # run the same checks on your changed files"
echo "Formatters fix files in place — review, git add, and commit. Or apply the diff shown in the failed leg."
exit 1
@@ -0,0 +1,51 @@
name: Deploy Opik Python SDK docs
run-name: Deploy Python SDK docs ${{ github.ref_name }} to S3 ${{ inputs.environment }} by @${{ github.actor }}
on:
workflow_dispatch:
inputs:
environment:
type: choice
description: Choose environment
options:
- staging
- production
- development
jobs:
upload:
runs-on: ubuntu-latest
timeout-minutes: 15
environment: ${{ inputs.environment }}
permissions:
id-token: write # required for OIDC
contents: read
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ vars.AWS_REGION }}
steps:
- name: Verify AWS credentials
run: aws sts get-caller-identity
- name: checkout
uses: actions/checkout@v6
with:
show-progress: false
- name: Setup uv
uses: astral-sh/setup-uv@v8.2.0
- name: Build Python SDK docs
run: |
echo "Build sdk documentation"
cd apps/opik-documentation/python-sdk-docs
uv venv .venv
uv pip install -r requirements.txt
uv pip install ../../../sdks/python
uv run make build
- name: Upload sdk docs to site.comet.com (${{ inputs.environment }})
run: |
aws s3 sync apps/opik-documentation/python-sdk-docs/build/html \
s3://site.comet.com/${{ inputs.environment }}/docs-opik/python-sdk-reference \
--follow-symlinks --no-progress --delete --exclude '.git*'
@@ -0,0 +1,193 @@
name: Docker Vulnerability Scan
on:
schedule:
# Run Monday at midnight UTC
- cron: '0 0 * * 1'
workflow_dispatch:
inputs:
image-tag:
description: 'Image tag to scan'
required: true
default: main
type: string
permissions:
contents: read
env:
DOCKER_REGISTRY: "ghcr.io/comet-ml/opik"
jobs:
scan-docker-images:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
image:
- opik-backend
- opik-frontend
- opik-frontend-comet
- opik-python-backend
- opik-sandbox-executor-python
name: Scan ${{ matrix.image }}
steps:
- name: Pull Docker image
id: pull
run: |
IMAGE_URL="${{ env.DOCKER_REGISTRY }}/${{ matrix.image }}:${{ inputs.image-tag || 'main' }}"
echo "Pulling Docker image: ${IMAGE_URL}"
docker pull ${IMAGE_URL}
BASENAME=$(basename ${IMAGE_URL} | cut -d: -f1)
REPORT_NAME="trivy_${BASENAME}.txt"
echo "image-url=${IMAGE_URL}" >> "$GITHUB_OUTPUT"
echo "report-name=${REPORT_NAME}" >> "$GITHUB_OUTPUT"
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@v0.35.0
env:
TRIVY_DISABLE_VEX_NOTICE: true
with:
image-ref: '${{ steps.pull.outputs.image-url }}'
scan-type: 'image'
format: 'table'
ignore-unfixed: 'false'
output: '${{ steps.pull.outputs.report-name }}'
scanners: vuln
severity: 'CRITICAL,HIGH'
hide-progress: true
timeout: 10m0s
- name: Set outputs
id: set-outputs
run: |
REPORT_NAME="${{ steps.pull.outputs.report-name }}"
echo "report-file=${REPORT_NAME}" >> "$GITHUB_OUTPUT"
if [ -f "${REPORT_NAME}" ]; then
echo "Report generated: ${REPORT_NAME}"
ls -lh "${REPORT_NAME}"
else
echo "Warning: Report file not found at ${REPORT_NAME}"
fi
- name: Write to job summary
run: |
{
echo "## Trivy Vulnerability Scan: \`${{ steps.pull.outputs.image-url }}\`"
echo ""
echo "<details><summary>Click to expand report</summary>"
echo ""
echo '```'
cat "${{ steps.pull.outputs.report-name }}" 2>/dev/null || echo "Report not found"
echo '```'
echo ""
echo "</details>"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload scan results as artifact
uses: actions/upload-artifact@v7
with:
name: trivy-scan-${{ matrix.image }}
path: ${{ steps.pull.outputs.report-name }}
retention-days: 30
if-no-files-found: warn
- name: Checkout scripts
uses: actions/checkout@v6
with:
sparse-checkout: scripts/analyze_trivy_report.sh
sparse-checkout-cone-mode: false
path: repo
- name: Analyze vulnerabilities
id: analyze
run: |
./repo/scripts/analyze_trivy_report.sh \
"${{ steps.pull.outputs.report-name }}" \
"${{ steps.pull.outputs.image-url }}" \
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--artifact-name "trivy-scan-${{ matrix.image }}"
- name: Format base image Slack message
if: steps.analyze.outputs.has-base-image-vulns == 'true'
id: format-base
env:
MESSAGE: ${{ steps.analyze.outputs.base-image-message }}
MENTION: ${{ secrets.SLACK_MENTION_USERS }}
run: |
python3 << 'PYTHON_SCRIPT'
import re, os
input_message = os.environ.get('MESSAGE', '').strip()
mention = os.environ.get('MENTION', '').strip()
result = re.sub(r"'(.*?)'", r"`\1`", input_message)
if mention:
# Ensure each user ID is wrapped in <@...> for Slack mention syntax
parts = mention.split()
wrapped = ' '.join(
uid if uid.startswith('<@') else f'<@{uid}>'
for uid in parts
)
result = wrapped + '\n\n' + result
with open(os.environ.get('GITHUB_OUTPUT', '/dev/null'), 'a') as f:
f.write('message<<EOF\n')
f.write(result)
f.write('\nEOF\n')
PYTHON_SCRIPT
- name: Format app Slack message
if: steps.analyze.outputs.has-app-vulns == 'true'
id: format-app
env:
MESSAGE: ${{ steps.analyze.outputs.app-message }}
MENTION: ${{ secrets.SLACK_MENTION_USERS }}
run: |
python3 << 'PYTHON_SCRIPT'
import re, os
input_message = os.environ.get('MESSAGE', '').strip()
mention = os.environ.get('MENTION', '').strip()
result = re.sub(r"'(.*?)'", r"`\1`", input_message)
if mention:
# Ensure each user ID is wrapped in <@...> for Slack mention syntax
parts = mention.split()
wrapped = ' '.join(
uid if uid.startswith('<@') else f'<@{uid}>'
for uid in parts
)
result = wrapped + '\n\n' + result
with open(os.environ.get('GITHUB_OUTPUT', '/dev/null'), 'a') as f:
f.write('message<<EOF\n')
f.write(result)
f.write('\nEOF\n')
PYTHON_SCRIPT
- name: Notify Slack - base image vulnerabilities
if: steps.analyze.outputs.has-base-image-vulns == 'true'
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_VULNERABILITY_WEBHOOK_URL }}
SLACK_COLOR: warning
SLACK_USERNAME: 'GitHub Actions'
SLACK_ICON: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
SLACK_LINK_NAMES: true
MSG_MINIMAL: false
ENABLE_ESCAPES: true
SLACK_TITLE: ${{ steps.analyze.outputs.base-image-title }}
SLACK_MESSAGE: |
*Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}>
${{ steps.format-base.outputs.message }}
- name: Notify Slack - application vulnerabilities
if: steps.analyze.outputs.has-app-vulns == 'true'
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_VULNERABILITY_WEBHOOK_URL }}
SLACK_COLOR: danger
SLACK_USERNAME: 'GitHub Actions'
SLACK_ICON: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
SLACK_LINK_NAMES: true
MSG_MINIMAL: false
ENABLE_ESCAPES: true
SLACK_TITLE: ${{ steps.analyze.outputs.app-title }}
SLACK_MESSAGE: |
*Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}>
${{ steps.format-app.outputs.message }}
@@ -0,0 +1,74 @@
name: Docs - Test cookbooks
on:
workflow_dispatch:
inputs:
install_opik:
description: 'Enable opik installation from source files'
required: false
default: 'false'
type: choice
options:
- 'false'
- 'true'
jobs:
test-notebooks:
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest]
python-version: ["3.12"]
notebooks:
- apps/opik-documentation/documentation/docs/cookbook/quickstart_notebook.ipynb
- apps/opik-documentation/documentation/docs/cookbook/evaluate_hallucination_metric.ipynb
- apps/opik-documentation/documentation/docs/cookbook/evaluate_moderation_metric.ipynb
- apps/opik-documentation/documentation/docs/cookbook/langchain.ipynb
- apps/opik-documentation/documentation/docs/cookbook/langgraph.ipynb
- apps/opik-documentation/documentation/docs/cookbook/llama-index.ipynb
- apps/opik-documentation/documentation/docs/cookbook/openai.ipynb
- apps/opik-documentation/documentation/docs/cookbook/litellm.ipynb
- apps/opik-documentation/documentation/docs/cookbook/ragas.ipynb
- apps/opik-documentation/documentation/docs/cookbook/dspy.ipynb
- apps/opik-documentation/documentation/docs/cookbook/crewai.ipynb
- apps/opik-documentation/documentation/docs/cookbook/instructor.ipynb
env:
NOTEBOOK_TO_TEST: ${{ matrix.notebooks }}
OPIK_SENTRY_ENABLE: False
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "dir=$(pip cache dir)" >> "$GITHUB_OUTPUT"
- name: pip cache
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -U ipython nbconvert
- name: Install opik from source files (optional)
if: ${{ github.event_name == 'workflow_dispatch' && inputs.install_opik == 'true' }}
run: pip install sdks/python
- name: Prepare env variables
run: |
directory=$(dirname -- "${NOTEBOOK_TO_TEST}")
notebook=$(basename -- "${NOTEBOOK_TO_TEST}")
echo "TEST_DIRECTORY=${directory}" >> "$GITHUB_ENV"
echo "TEST_NOTEBOOK=${notebook}" >> "$GITHUB_ENV"
- name: Test notebook
run: |
cd "$TEST_DIRECTORY" || exit
python -X faulthandler "$(which ipython)" "$TEST_NOTEBOOK"
env:
OPENAI_API_KEY: ${{ secrets.DOCS_OPENAI_API_KEY }}
OPIK_API_KEY: ${{ secrets.COMET_API_KEY }}
OPIK_WORKSPACE: ${{ secrets.COMET_WORKSPACE }}
@@ -0,0 +1,33 @@
name: Docs - Publish
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'apps/opik-documentation/documentation/**'
jobs:
run:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Fetch all history for git diff
- name: Install Fern
working-directory: apps/opik-documentation/documentation
run: npm install
- name: Publish Docs
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY }}
GTM_CONTAINER_ID: ${{ secrets.GTM_CONTAINER_ID }}
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
POSTHOG_API_HOST: ${{ secrets.POSTHOG_API_HOST }}
run: |
cd apps/opik-documentation/documentation
npx fern generate --docs
@@ -0,0 +1,37 @@
name: Docs - Compress Images
on:
workflow_dispatch:
pull_request:
# Run Image Actions when JPG, JPEG, PNG or WebP files are added or changed.
# See https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths for reference.
paths:
[
"apps/opik-documentation/documentation/fern/img/**.jpg",
"apps/opik-documentation/documentation/fern/img/**.jpeg",
"apps/opik-documentation/documentation/fern/img/**.png",
"apps/opik-documentation/documentation/fern/img/**.webp",
]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
# Only run on non-draft PRs within the same repository.
if:
github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.draft
== false
name: calibreapp/image-actions
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout Repo
uses: actions/checkout@v6
- name: Compress Images
uses: calibreapp/image-actions@1.1.0
with:
# The `GITHUB_TOKEN` is automatically generated by GitHub and scoped only to the repository that is currently running the action. By default, the action cant update Pull Requests initiated from forked repositories.
# See https://docs.github.com/en/actions/reference/authentication-in-a-workflow and https://help.github.com/en/articles/virtual-environments-for-github-actions#token-permissions
githubToken: ${{ secrets.GITHUB_TOKEN }}
ignorePaths: "apps/opik-frontend/**,apps/opik-backend/**,apps/opik-python-backend/**"
@@ -0,0 +1,147 @@
name: Docs - Preview link
on:
pull_request:
paths:
- 'apps/opik-documentation/documentation/**'
- '.github/workflows/documentation_preview_link.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
run:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions: write-all
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Fern
working-directory: apps/opik-documentation/documentation
run: npm install
- name: Generate preview URL
id: generate-docs
working-directory: apps/opik-documentation/documentation
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
run: |
OUTPUT=$(npx fern generate --docs --preview 2>&1) || true
echo "$OUTPUT"
URL=$(echo "$OUTPUT" | grep -oP 'Published docs to \K.*(?= \()')
echo "Preview URL: $URL"
echo "URL=$URL" >> "$GITHUB_OUTPUT"
echo "🌿 Preview your docs: $URL" > preview_url.txt
- name: Check for broken links
id: check-broken-links
working-directory: apps/opik-documentation/documentation
run: |
echo -e "\n\n" >> preview_url.txt
# --concurrency 500: the preview CDN serves bursts without rate-limiting;
# the recurse otherwise brushes the job's 15-minute timeout.
npx linkinator ${{ steps.generate-docs.outputs.URL }} \
--recurse \
--concurrency 500 \
--skip "search\/v2\/key" \
--skip "https://ai.google.dev/gemini-api" \
--skip "https://ai.google.dev/aistudio" \
--skip "chat.comet.com" \
--skip "http://localhost" \
--skip "http://localhost:5173" \
--skip "googletagmanager.com" \
--skip "insights/script.js" \
--format json > linkinator_results.json || true
PREVIEW_ORIGIN=$(echo "${{ steps.generate-docs.outputs.URL }}" | grep -oE '^https?://[^/]+')
# linkinator exits non-zero both when it finds broken links and when it
# crashes mid-crawl (e.g. unhandled ECONNRESET), so trust the output, not
# the exit code: invalid/missing JSON means the crawl didn't complete.
if ! jq -e 'type == "object" and (.links | type == "array")' linkinator_results.json > /dev/null 2>&1; then
{
echo "⚠️ The link check did not complete (likely a crawl crash or timeout). Please re-run this check."
echo ""
echo "---"
echo "📌 Results for commit ${{ github.sha }}"
} >> preview_url.txt
echo "true" > check_failed.flag
exit 0
fi
# `↳ on page` strips the ephemeral preview host, leaving a stable,
# greppable doc path. Append one report entry for a status-0 url whose
# recheck label is computed by the caller.
report_timeout() {
local url=$1 label=$2 parent
parent=$(jq -r --arg u "$url" --arg origin "$PREVIEW_ORIGIN" \
'(first(.links[] | select(.url == $u)).parent // "") | sub("^" + $origin; "")' linkinator_results.json)
BROKEN_LINKS=$(printf '%s\n❌ Broken link: %s (%s)\n ↳ on page: %s' "$BROKEN_LINKS" "$url" "$label" "$parent")
}
# Real HTTP errors (excluding 403s), deduped by url — one broken sidebar
# link otherwise multiplies into hundreds of lines and overflows GitHub's
# 65,536-char comment limit.
BROKEN_LINKS=$(jq -r --arg origin "$PREVIEW_ORIGIN" '
[.links[] | select(.state != "OK" and .status != 403 and .status != 0)]
| group_by(.url) | map(.[0]) | .[]
| "❌ Broken link: \(.url) (\(.status))\n ↳ on page: \((.parent // "") | sub("^" + $origin; ""))"
' linkinator_results.json)
# Status 0 is "crawl gave up", not an HTTP error — a false positive for
# healthy-but-slow pages. Re-check each (only on the preview origin, so a
# PR-authored link can't point curl at an arbitrary host) and keep it only
# if genuinely unreachable.
while IFS= read -r url; do
[ -z "$url" ] && continue
case "$url" in
"$PREVIEW_ORIGIN"/*|"$PREVIEW_ORIGIN")
code=$(curl -s -L -o /dev/null -w "%{http_code}" --max-time 20 "$url" 2>/dev/null)
{ [ -n "$code" ] && [ "$code" -ge 200 ] && [ "$code" -lt 400 ]; } && continue
report_timeout "$url" "timeout, recheck ${code:-000}"
;;
*)
report_timeout "$url" "timeout"
;;
esac
done <<< "$(jq -r '[.links[] | select(.status == 0) | .url] | unique | .[]' linkinator_results.json)"
if [ -n "$BROKEN_LINKS" ]; then
{
echo "**The following broken links were found:**"
echo ""
echo "$BROKEN_LINKS"
} >> preview_url.txt
# Fail the job AFTER the comment is posted (see the Fail step below).
echo "true" > check_failed.flag
else
echo "No broken links found" >> preview_url.txt
fi
rm -f linkinator_results.json
{
echo ""
echo "---"
echo "📌 Results for commit ${{ github.sha }}"
} >> preview_url.txt
- name: Comment URL in PR
if: always()
uses: thollander/actions-comment-pull-request@v3
with:
file-path: apps/opik-documentation/documentation/preview_url.txt
comment-tag: docs-preview
- name: Fail if the link check failed
if: always()
working-directory: apps/opik-documentation/documentation
run: |
if [ -f check_failed.flag ]; then
echo "Link check failed — broken links were found, or the crawl did not complete. See the PR comment."
exit 1
fi
@@ -0,0 +1,425 @@
name: E2E Tests - Post Merge (v2)
run-name: "E2E Tests v2 (${{ github.event.inputs.tier || 't1' }}) - ${{ github.event_name == 'push' && format('Post-merge {0}', github.sha) || format('Manual run by @{0}', github.actor) }}"
on:
push:
branches:
- 'main'
# Skip workflow if only documentation or unrelated files changed
paths-ignore:
- '**.md'
- 'apps/opik-documentation/**'
pull_request:
paths:
- '.github/workflows/e2e_tests_post_merge_v2.yml'
workflow_dispatch:
inputs:
tier:
type: choice
description: 'Which tag tier to run (t1=smoke, t2=cuj, t3=nightly)'
required: true
default: 't1'
options:
- t1
- t2
- t3
notify_on_success:
type: boolean
description: 'Send Slack notification even on success'
required: false
default: false
ALLURE_JOB_RUN_ID:
description: 'ALLURE_JOB_RUN_ID service parameter. Leave blank.'
required: false
env:
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
ALLURE_ENDPOINT: https://comet.testops.cloud/
ALLURE_PROJECT_ID: 1
ALLURE_RESULTS: allure-results
ALLURE_JOB_RUN_ID: ${{ github.event.inputs.ALLURE_JOB_RUN_ID }}
OPIK_SENTRY_ENABLE: false
permissions:
contents: read
actions: read
jobs:
e2e-tests:
name: "🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }})"
runs-on: ubuntu-latest
timeout-minutes: 45
outputs:
test_result: ${{ steps.run-tests.outcome }}
testops_url: ${{ steps.set-outputs.outputs.testops_url }}
total_tests: ${{ steps.parse-results.outputs.total }}
passed_tests: ${{ steps.parse-results.outputs.passed }}
failed_tests: ${{ steps.parse-results.outputs.failed }}
skipped_tests: ${{ steps.parse-results.outputs.skipped }}
pr_author: ${{ steps.get-pr-info.outputs.author }}
steps:
# ========================================
# PHASE 1: Checkout & Environment Setup
# ========================================
- name: "📥 Checkout repository"
uses: actions/checkout@v6
- name: "🔍 Get PR/commit information"
id: get-pr-info
run: |
if [ "${{ github.event_name }}" == "push" ]; then
# For push events, get the author from the commit
AUTHOR="${{ github.event.head_commit.author.username }}"
if [ -z "$AUTHOR" ]; then
AUTHOR="${{ github.actor }}"
fi
else
AUTHOR="${{ github.actor }}"
fi
echo "author=$AUTHOR" >> "$GITHUB_OUTPUT"
echo "📝 Author: $AUTHOR"
- name: "📦 Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: tests_end_to_end/e2e/package-lock.json
- name: "🐍 Setup Python"
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: "⚡ Setup uv"
uses: astral-sh/setup-uv@v5
- name: "📚 Install E2E test dependencies"
working-directory: tests_end_to_end/e2e
run: |
npm ci
npx playwright install --with-deps chromium
- name: "📚 Sync bridge dependencies"
working-directory: tests_end_to_end/e2e/services/opik-sdk-driver
run: uv sync
- name: "📊 Install allurectl"
uses: allure-framework/setup-allurectl@v1
# ========================================
# PHASE 2: Build & Start Opik
# ========================================
- name: "🔨 Build and start Opik server"
env:
OPIK_USAGE_REPORT_ENABLED: false
COMPOSE_BAKE: false
run: |
echo "::group::Building Opik from branch: ${{ github.ref_name }}"
cd ${{ github.workspace }}
TOGGLE_WELCOME_WIZARD_ENABLED="false" TOGGLE_FORCE_WORKSPACE_VERSION="version_2" ./opik.sh --build
echo "::endgroup::"
echo "✅ Opik build complete"
- name: "🔍 Verify Docker pods are running"
run: |
echo "::group::Checking Docker pods"
chmod +x ./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "::endgroup::"
echo "✅ All Docker pods are running"
- name: "🏥 Verify backend health"
run: |
echo "::group::Checking backend health"
chmod +x ./tests_end_to_end/installer_utils/check_backend.sh
./tests_end_to_end/installer_utils/check_backend.sh
echo "::endgroup::"
echo "✅ Backend is healthy"
# ========================================
# PHASE 3: Run E2E Tests
# ========================================
- name: "🧪 Run E2E test suite"
id: run-tests
working-directory: tests_end_to_end/e2e
env:
OPIK_DEPLOYMENT: oss
OPIK_BASE_URL: http://localhost:5173
OPIK_WORKSPACE: default
WORKERS: 2
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ALLURE_LAUNCH_NAME: "Opik v2 Post-Merge ${{ github.event.inputs.tier || 't1' }} - ${{ github.run_id }}"
run: |
TIER="${{ github.event.inputs.tier || 't1' }}"
echo "========================================"
echo "🧪 Running v2 tier: $TIER"
echo "========================================"
echo ""
# Point both the allure-playwright reporter and allurectl at the same
# absolute results dir, then stream results to TestOps via allurectl watch.
export ALLURE_RESULTS="${{ github.workspace }}/tests_end_to_end/e2e/${{ env.ALLURE_RESULTS }}"
# Create .allure directory to prevent testplan.json error
mkdir -p .allure
# Run tests with allurectl watch for TestOps integration
# Capture output to extract the direct report URL
set +e
allurectl watch -- npm run test:${TIER} 2>&1 | tee allure_output.log
TEST_EXIT_CODE=${PIPESTATUS[0]}
set -e
# Extract the direct job run URL from allurectl output
if grep -q "Report link:" allure_output.log; then
REPORT_URL=$(grep "Report link:" allure_output.log | head -1 | sed 's/.*Report link:[[:space:]]*//' | awk '{print $1}')
echo "allure_report_url=${REPORT_URL}" >> "$GITHUB_OUTPUT"
echo "📊 Captured Allure TestOps report URL: ${REPORT_URL}"
else
echo "allure_report_url=" >> "$GITHUB_OUTPUT"
echo "⚠️ Could not capture direct report URL from allurectl output"
fi
echo ""
echo "========================================"
echo "Test run completed with exit code: $TEST_EXIT_CODE"
echo "========================================"
exit "$TEST_EXIT_CODE"
# ========================================
# PHASE 4: Process Results
# ========================================
- name: "📊 Parse test results"
id: parse-results
if: always()
working-directory: tests_end_to_end/e2e
run: |
echo "::group::Parsing test results"
if [ -f "test-results/results.json" ]; then
echo "Found results.json, parsing..."
TOTAL=$(jq '.stats.expected + .stats.unexpected + .stats.flaky + .stats.skipped' test-results/results.json 2>/dev/null || echo "0")
PASSED=$(jq '.stats.expected' test-results/results.json 2>/dev/null || echo "0")
FAILED=$(jq '.stats.unexpected' test-results/results.json 2>/dev/null || echo "0")
SKIPPED=$(jq '.stats.skipped' test-results/results.json 2>/dev/null || echo "0")
FLAKY=$(jq '.stats.flaky' test-results/results.json 2>/dev/null || echo "0")
echo "Results: Total=$TOTAL, Passed=$PASSED, Failed=$FAILED, Skipped=$SKIPPED, Flaky=$FLAKY"
else
echo "⚠️ results.json not found, using defaults"
TOTAL="0"
PASSED="0"
FAILED="0"
SKIPPED="0"
FLAKY="0"
fi
{
echo "total=$TOTAL"
echo "passed=$PASSED"
echo "failed=$FAILED"
echo "skipped=$SKIPPED"
echo "flaky=$FLAKY"
} >> "$GITHUB_OUTPUT"
echo "::endgroup::"
- name: "🔗 Set output URLs"
id: set-outputs
if: always()
run: |
# Use direct report URL if captured, otherwise fall back to launches page
DIRECT_URL="${{ steps.run-tests.outputs.allure_report_url }}"
if [ -n "$DIRECT_URL" ]; then
TESTOPS_URL="$DIRECT_URL"
echo "📊 Using direct TestOps report URL"
else
TESTOPS_URL="${{ env.ALLURE_ENDPOINT }}project/${{ env.ALLURE_PROJECT_ID }}/launches"
echo "📊 Using fallback launches page URL"
fi
echo "testops_url=$TESTOPS_URL" >> "$GITHUB_OUTPUT"
# ========================================
# PHASE 5: Generate Summary
# ========================================
- name: "📋 Generate job summary"
if: always()
run: |
TIER="${{ github.event.inputs.tier || 't1' }}"
TESTOPS_URL="${{ steps.set-outputs.outputs.testops_url }}"
TOTAL="${{ steps.parse-results.outputs.total }}"
PASSED="${{ steps.parse-results.outputs.passed }}"
FAILED="${{ steps.parse-results.outputs.failed }}"
SKIPPED="${{ steps.parse-results.outputs.skipped }}"
# Redirect stdout to the summary file for the remainder of this step
exec >> "$GITHUB_STEP_SUMMARY"
# Header
echo "# 🧪 E2E v2 Test Results"
echo ""
# Status banner
if [ "${{ steps.run-tests.outcome }}" == "success" ]; then
echo "> ✅ **All tests passed!**"
elif [ "${{ steps.run-tests.outcome }}" == "failure" ]; then
echo "> ❌ **$FAILED test(s) failed** - Please review the results below"
else
echo "> ⚠️ **Test run status: ${{ steps.run-tests.outcome }}**"
fi
echo ""
# Configuration table
echo "## 📝 Configuration"
echo ""
echo "| Property | Value |"
echo "|----------|-------|"
echo "| **Tier** | \`$TIER\` |"
echo "| **Branch** | \`${{ github.ref_name }}\` |"
echo "| **Commit** | [\`${GITHUB_SHA:0:7}\`](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) |"
echo "| **Trigger** | ${{ github.event_name == 'push' && 'Post-merge (push to main)' || 'Manual dispatch' }} |"
echo "| **Author** | @${{ steps.get-pr-info.outputs.author }} |"
echo ""
# Results table
echo "## 📊 Results"
echo ""
echo "| Status | Count |"
echo "|--------|------:|"
echo "| ✅ Passed | $PASSED |"
echo "| ❌ Failed | $FAILED |"
echo "| ⏭️ Skipped | $SKIPPED |"
echo "| **Total** | **$TOTAL** |"
echo ""
# Links section
echo "## 🔗 Quick Links"
echo ""
echo "| Resource | Link |"
echo "|----------|------|"
echo "| 📊 **TestOps Dashboard** | [View detailed results, traces & history]($TESTOPS_URL) |"
echo "| 📄 **Playwright Report** | [Download from Artifacts below](#artifacts) |"
echo ""
# Debugging section (only on failure)
if [ "${{ steps.run-tests.outcome }}" == "failure" ]; then
echo "## 🔧 Debugging Guide"
echo ""
echo "### Quick steps to investigate failures:"
echo ""
echo "1. **Open TestOps** → Click the link above to see detailed failure traces, screenshots, and video recordings"
echo "2. **Download Playwright Report** → The HTML report includes step-by-step traces for each test"
echo "3. **Check test logs** → Expand the 'Run E2E test suite' step above for console output"
echo ""
echo "### Common failure causes:"
echo "- UI element selectors changed (missing data-testid)"
echo "- API response format changed"
echo "- Timing/race conditions"
echo "- New feature requiring test updates"
fi
# ========================================
# PHASE 6: Upload Artifacts
# ========================================
- name: "📤 Upload Allure results"
if: always()
uses: actions/upload-artifact@v7
with:
name: allure-results-v2
path: tests_end_to_end/e2e/allure-results
retention-days: 14
- name: "📤 Upload Playwright report"
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-report-v2
path: tests_end_to_end/e2e/playwright-report
retention-days: 14
- name: "📤 Upload test results JSON"
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results-json-v2
path: tests_end_to_end/e2e/test-results/results.json
retention-days: 14
if-no-files-found: ignore
- name: "🧹 Stop Opik server"
if: always()
run: |
echo "Stopping Opik server..."
./opik.sh --stop || true
echo "✅ Cleanup complete"
notify-slack:
name: "📢 Slack Notification"
runs-on: ubuntu-latest
needs: e2e-tests
if: |
always() &&
needs.e2e-tests.result != 'cancelled' &&
(needs.e2e-tests.result == 'failure' || github.event.inputs.notify_on_success == 'true')
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Resolve Slack mentions
id: resolve-slack-mentions
env:
SLACK_USER_MAPPING: ${{ secrets.SLACK_USER_MAPPING }}
SLACK_QA_LEAD_ID: ${{ secrets.SLACK_QA_LEAD_ID }}
run: |
AUTHOR="${{ needs.e2e-tests.outputs.pr_author }}"
IS_FAILURE="${{ needs.e2e-tests.result == 'failure' }}"
if [ "$IS_FAILURE" == "true" ]; then
if [ -n "$SLACK_QA_LEAD_ID" ]; then
QA_MENTION="<@$SLACK_QA_LEAD_ID>"
else
QA_MENTION=""
fi
AUTHOR_MENTION=""
if [ -n "$SLACK_USER_MAPPING" ]; then
AUTHOR_SLACK_ID=$(echo "$SLACK_USER_MAPPING" | jq -r --arg user "$AUTHOR" '.[$user] // empty' 2>/dev/null || echo "")
if [ -n "$AUTHOR_SLACK_ID" ] && [ "$AUTHOR_SLACK_ID" != "$SLACK_QA_LEAD_ID" ]; then
AUTHOR_MENTION="<@$AUTHOR_SLACK_ID>"
fi
fi
MENTIONS="${QA_MENTION:+$QA_MENTION }${AUTHOR_MENTION}"
echo "mentions=${MENTIONS% }" >> "$GITHUB_OUTPUT"
else
echo "mentions=" >> "$GITHUB_OUTPUT"
fi
echo "author_display=$AUTHOR" >> "$GITHUB_OUTPUT"
- name: Send Slack notification
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
TEST_RESULT: ${{ needs.e2e-tests.result }}
SUITE_NAME: "v2 ${{ github.event.inputs.tier || 't1' }}"
PASSED_TESTS: ${{ needs.e2e-tests.outputs.passed_tests }}
FAILED_TESTS: ${{ needs.e2e-tests.outputs.failed_tests }}
TESTOPS_URL: ${{ needs.e2e-tests.outputs.testops_url }}
AUTHOR_DISPLAY: ${{ steps.resolve-slack-mentions.outputs.author_display }}
MENTIONS: ${{ steps.resolve-slack-mentions.outputs.mentions }}
run: .github/scripts/notify-slack-e2e-post-merge.sh
+147
View File
@@ -0,0 +1,147 @@
name: Application E2E Tests (v2)
env:
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
ALLURE_JOB_RUN_ID: ${{ github.event.inputs.ALLURE_JOB_RUN_ID }}
ALLURE_ENDPOINT: https://comet.testops.cloud/
ALLURE_PROJECT_ID: 1
ALLURE_RESULTS: allure-results
on:
workflow_dispatch:
inputs:
tier:
type: choice
description: 'Which tag tier to run'
required: true
default: 't1'
options:
- t1
- t2
- t3
ALLURE_JOB_RUN_ID:
description: ALLURE_JOB_RUN_ID service parameter. Leave blank.
ALLURE_USERNAME:
description: ALLURE_USERNAME service parameter. Leave blank.
pull_request:
paths:
- 'sdks/code_generation/fern/openapi/openapi.yaml'
- 'tests_end_to_end/e2e/**'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
run-name: Application E2E Tests (v2) - ${{ github.event.inputs.tier || 't1' }}
permissions:
contents: read
jobs:
run_suite:
name: "Run v2 suite: ${{ github.event.inputs.tier || 't1' }}"
runs-on: ubuntu-latest
timeout-minutes: 30
env:
OPIK_DEPLOYMENT: oss
OPIK_BASE_URL: http://localhost:5173
OPIK_WORKSPACE: default
WORKERS: 2
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: tests_end_to_end/e2e/package-lock.json
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Install E2E test dependencies
working-directory: ${{ github.workspace }}/tests_end_to_end/e2e
run: |
npm ci
npx playwright install --with-deps chromium
- name: Sync bridge dependencies
working-directory: ${{ github.workspace }}/tests_end_to_end/e2e/services/opik-sdk-driver
run: uv sync
- name: Install allurectl
uses: allure-framework/setup-allurectl@v1
- name: Install Opik (Local)
env:
OPIK_USAGE_REPORT_ENABLED: false
# Avoid Buildx Bake concurrent image export collisions in CI.
COMPOSE_BAKE: false
run: |
cd ${{ github.workspace }}
TOGGLE_WELCOME_WIZARD_ENABLED="false" TOGGLE_FORCE_WORKSPACE_VERSION="version_2" ./opik.sh --build
- name: Check Docker pods are up (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
shell: bash
- name: Check backend health (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_backend.sh
./tests_end_to_end/installer_utils/check_backend.sh
shell: bash
- name: Run v2 E2E suite
working-directory: ${{ github.workspace }}/tests_end_to_end/e2e
env:
ALLURE_LAUNCH_NAME: "Opik v2 E2E ${{ github.event.inputs.tier || 't1' }} - ${{ github.run_id }}"
run: |
# Point both the allure-playwright reporter and allurectl at the same
# absolute results dir, then stream results to TestOps via allurectl watch.
export ALLURE_RESULTS="${{ github.workspace }}/tests_end_to_end/e2e/allure-results"
allurectl watch -- npm run test:${{ github.event.inputs.tier || 't1' }}
- name: Upload Allure Results
if: always()
uses: actions/upload-artifact@v7
with:
name: allure-results-v2
path: tests_end_to_end/e2e/allure-results
retention-days: 7
- name: Upload Playwright Report
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-report-v2
path: tests_end_to_end/e2e/playwright-report
retention-days: 7
- name: Upload Test Results (traces, videos)
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results-v2
path: tests_end_to_end/e2e/test-results
retention-days: 7
- name: Stop Opik server (Local)
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop
continue-on-error: ${{ github.event_name == 'pull_request' }}
+46
View File
@@ -0,0 +1,46 @@
name: Frontend Unit Tests
run-name: "Frontend Unit Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
on:
pull_request:
paths:
- "apps/opik-frontend/**"
push:
branches:
- 'main'
paths:
- "apps/opik-frontend/**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
tests:
name: Test on Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
timeout-minutes: 5
defaults:
run:
working-directory: apps/opik-frontend
strategy:
fail-fast: false
matrix:
node-version: [ 20 ]
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Unit tests
run: npm run test
+157
View File
@@ -0,0 +1,157 @@
name: Guardrails E2E Tests
run-name: "Guardrails E2E Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
on:
workflow_dispatch:
pull_request:
paths:
- 'apps/opik-guardrails-backend/**'
- 'sdks/python/src/opik/guardrails/**'
- 'sdks/python/tests/e2e/test_guardrails.py'
- '.github/workflows/guardrails_e2e_tests.yml'
push:
branches:
- 'main'
paths:
- 'apps/opik-guardrails-backend/**'
- 'apps/opik-backend/**'
- 'sdks/python/src/opik/**'
- 'sdks/python/tests/e2e/test_guardrails.py'
- '.github/workflows/guardrails_e2e_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_URL_OVERRIDE: http://localhost:8080
OPIK_GUARDRAILS_URL_OVERRIDE: http://localhost:5000
OPIK_CONSOLE_LOGGING_LEVEL: DEBUG
jobs:
select-matrix:
name: Select Python version matrix
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
python_versions: ${{ steps.pick.outputs.versions }}
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: .github/actions/select-e2e-matrix
sparse-checkout-cone-mode: false
- uses: ./.github/actions/select-e2e-matrix
id: pick
with:
all-versions: ${{ vars.PYTHON_VERSIONS }}
match-regex: '^apps/opik-guardrails-backend/|^sdks/python/src/opik/guardrails/|^sdks/python/tests/e2e/test_guardrails\.py$|^\.github/workflows/guardrails_e2e_tests\.yml$'
github-token: ${{ secrets.GITHUB_TOKEN }}
e2e-tests:
needs: select-matrix
name: Guardrails E2E Tests ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python_version: ${{ fromJSON(needs.select-matrix.outputs.python_versions) }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Run latest Opik server with guardrails
env:
OPIK_USAGE_REPORT_ENABLED: false
COMPOSE_BAKE: false
TOGGLE_RUNNERS_ENABLED: "true"
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping --guardrails --build
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
echo "Check Docker pods are up"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "Check backend health"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Install opik SDK
run: |
cd ${{ github.workspace }}/sdks/python
pip install .
- name: Install test requirements
run: |
cd ${{ github.workspace }}/sdks/python
pip install -r tests/test_requirements.txt
pip list
- name: Run guardrails E2E tests
run: |
cd ${{ github.workspace }}/sdks/python
pytest tests/e2e/test_guardrails.py -vv --durations=20 --junitxml=${{ github.workspace }}/test_results_guardrails_${{matrix.python_version}}.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Guardrails E2E Tests Results (Python ${{matrix.python_version}})
files: ${{ github.workspace }}/test_results_guardrails_${{matrix.python_version}}.xml
- name: Keep BE log in case of failure
if: failure()
run: |
docker logs opik-backend-1 > ${{ github.workspace }}/opik-backend_guardrails_p${{matrix.python_version}}.log || true
docker logs opik-guardrails-backend-1 > ${{ github.workspace }}/opik-guardrails-backend_p${{matrix.python_version}}.log || true
- name: Attach BE log
if: failure()
uses: actions/upload-artifact@v7
with:
name: opik-guardrails-logs-p${{matrix.python_version}}
path: |
${{ github.workspace }}/opik-backend_guardrails_p${{matrix.python_version}}.log
${{ github.workspace }}/opik-guardrails-backend_p${{matrix.python_version}}.log
- name: Stop opik server
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop --guardrails
notify-slack:
name: "📢 Slack Notification"
runs-on: ubuntu-latest
needs: e2e-tests
if: always() && needs.e2e-tests.result == 'failure' && github.event_name != 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Send Slack failure notification
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: .github/scripts/notify-slack-test-failure.sh "Guardrails E2E Tests"
@@ -0,0 +1,78 @@
name: "Guardrails Backend Unit Tests"
run-name: "Guardrails Backend Unit Tests on ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
on:
workflow_dispatch:
pull_request:
paths:
- 'apps/opik-guardrails-backend/**'
- '.github/workflows/guardrails_unit_tests.yml'
push:
branches:
- 'main'
paths:
- 'apps/opik-guardrails-backend/**'
- '.github/workflows/guardrails_unit_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
unit-tests:
name: Guardrails Backend Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: apps/opik-guardrails-backend/
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r tests/test_requirements.txt
- name: Run unit tests
run: |
cd tests && pytest unit -v --junitxml=../test-results.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Guardrails Backend Unit Tests Results
files: apps/opik-guardrails-backend/test-results.xml
notify-slack:
name: "📢 Slack Notification"
runs-on: ubuntu-latest
needs: unit-tests
if: always() && needs.unit-tests.result == 'failure' && github.event_name != 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Send Slack failure notification
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: .github/scripts/notify-slack-test-failure.sh "Guardrails Backend Unit Tests"
+267
View File
@@ -0,0 +1,267 @@
name: Installation Tests
on:
schedule:
- cron: '0 12 * * *' # runs every day at noon UTC
workflow_dispatch:
jobs:
test-opiksh:
name: opik.sh (main README)
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install Python dependencies
run: |
pip install -r ${{ github.workspace }}/tests_end_to_end/test_requirements.txt
pip install opik
playwright install chromium
- name: Install using opik.sh
env:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}
./opik.sh
- name: Run trace test
run: |
cd ${{ github.workspace }}/tests_end_to_end
export PYTHONPATH='.'
pytest -xvs tests/Installation/test_trace_logging.py --browser chromium
- name: Stop Opik
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop
test-docker-compose-up:
name: docker compose up --detach (Comet docs)
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Python dependencies
run: |
pip install -r ${{ github.workspace }}/tests_end_to_end/test_requirements.txt
pip install opik
playwright install chromium
- name: Install using docker compose up --detach
env:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose --profile opik up --detach
- name: Check Docker pods are up (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
shell: bash
- name: Check backend health (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_backend.sh
./tests_end_to_end/installer_utils/check_backend.sh
shell: bash
- name: Run trace test
run: |
cd ${{ github.workspace }}/tests_end_to_end
export PYTHONPATH='.'
pytest -xvs tests/Installation/test_trace_logging.py --browser chromium
- name: Stop Opik
if: always()
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose --profile opik down
test-docker-compose-build:
name: docker compose up --build (Comet docs)
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Python dependencies
run: |
pip install -r ${{ github.workspace }}/tests_end_to_end/test_requirements.txt
pip install opik
playwright install chromium
- name: Install with --build flag
env:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose --profile opik up -d --build
- name: Check Docker pods are up (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
shell: bash
- name: Check backend health (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_backend.sh
./tests_end_to_end/installer_utils/check_backend.sh
shell: bash
- name: Run trace test
run: |
cd ${{ github.workspace }}/tests_end_to_end
export PYTHONPATH='.'
pytest -xvs tests/Installation/test_trace_logging.py --browser chromium
- name: Stop Opik
if: always()
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose --profile opik down
test-docker-compose-with-version:
name: Set version - docker compose (deployment README)
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Python dependencies
run: |
pip install -r ${{ github.workspace }}/tests_end_to_end/test_requirements.txt
pip install opik
playwright install chromium
- name: Install with docker compose with specific version set
env:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}/deployment/docker-compose
export OPIK_VERSION=1.6.5
docker compose -f docker-compose.yaml --profile opik up -d
- name: Check Docker pods are up (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
shell: bash
- name: Check backend health (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_backend.sh
./tests_end_to_end/installer_utils/check_backend.sh
shell: bash
- name: Run trace test
run: |
cd ${{ github.workspace }}/tests_end_to_end
export PYTHONPATH='.'
pytest -xvs tests/Installation/test_trace_logging.py --browser chromium
- name: Stop Opik
if: always()
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose --profile opik down
test-docker-compose-with-pull:
name: Docker compose pull + up (Deployment README)
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Python dependencies
run: |
pip install -r ${{ github.workspace }}/tests_end_to_end/test_requirements.txt
pip install opik
playwright install chromium
- name: Install with docker compose after pulling latest version
env:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose --profile opik pull
docker compose -f docker-compose.yaml --profile opik up -d
- name: Check Docker pods are up (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
./tests_end_to_end/installer_utils/check_docker_compose_pods.sh
shell: bash
- name: Check backend health (Local)
run: |
chmod +x ./tests_end_to_end/installer_utils/check_backend.sh
./tests_end_to_end/installer_utils/check_backend.sh
shell: bash
- name: Run trace test
run: |
cd ${{ github.workspace }}/tests_end_to_end
export PYTHONPATH='.'
pytest -xvs tests/Installation/test_trace_logging.py --browser chromium
- name: Stop Opik
if: always()
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose --profile opik down
+29
View File
@@ -0,0 +1,29 @@
name: 🗂️ Auto Label PR
on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
labeler:
name: PR Labeler - Apply and Sync Labels
runs-on: ubuntu-latest
timeout-minutes: 1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: read
pull-requests: write
steps:
- name: Apply and Sync Labels
uses: actions/labeler@v5
with:
sync-labels: true # Remove labels if the file paths no longer match
configuration-path: .github/labeler.yml
@@ -0,0 +1,69 @@
# Workflow to run ADK tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib ADK (Google) Tests
run-name: "SDK Lib ADK Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
GOOGLE_GENAI_USE_VERTEXAI: TRUE
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: ADK Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
# Throttle concurrent matrix jobs so we keep full Python coverage
# without re-multiplying live Vertex AI calls into 429 RESOURCE_EXHAUSTED
# against the shared opik-sdk-tests GCP project.
max-parallel: 2
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/adk/requirements_legacy_adk_1_3_0.txt
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
- name: Run tests
run: |
cd ./tests/library_integration/adk/
python -m pytest -vv .
+72
View File
@@ -0,0 +1,72 @@
# Workflow to run ADK tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib ADK (Google) Tests
run-name: "SDK Lib ADK Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
GOOGLE_GENAI_USE_VERTEXAI: TRUE
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: ADK Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
# This suite makes live Vertex AI calls against the shared
# opik-sdk-tests GCP project (shared with the GenAI suite). Running the
# full 3.10-3.14 matrix re-multiplied those calls into 429
# RESOURCE_EXHAUSTED, so we run only the oldest and newest supported
# Python versions — enough to catch version-specific breakage at a
# fraction of the request volume — and keep the concurrency cap.
max-parallel: 2
matrix:
python_version: ["3.10", "3.14"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/adk/requirements.txt
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
- name: Run tests
run: |
cd ./tests/library_integration/adk/
python -m pytest -vv .
+59
View File
@@ -0,0 +1,59 @@
# Workflow to run AISuite tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib AISuite Tests
run-name: "SDK Lib AISuite Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: AISuite Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
# 3.14 is not supported by aisuite's docstring_parser dependency
# https://github.com/andrewyng/aisuite/issues/265
python_version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/aisuite/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/aisuite/
python -m pytest -vv .
+55
View File
@@ -0,0 +1,55 @@
# Workflow to run Anthropic tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Anthropic Tests
run-name: "SDK Lib Anthropic Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Anthropic Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/anthropic/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/anthropic/
python -m pytest -vv .
+67
View File
@@ -0,0 +1,67 @@
# Workflow to run Bedrock tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Bedrock Tests
run-name: "SDK Lib Bedrock Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Bedrock Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/bedrock/requirements.txt
- name: change aws role
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ vars.BEDROCK_ROLE }}
aws-region: us-east-1
role-chaining: true
role-skip-session-tagging: true
- name: Run tests
run: |
cd ./tests/library_integration/bedrock/
python -m pytest -vv .
+75
View File
@@ -0,0 +1,75 @@
# Workflow to run CrewAI v0.x tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib CrewAI v0 Tests
run-name: "SDK Lib CrewAI v0 Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: CrewAI v0 Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
# 3.14+ is not supported by crewai v0.x
python_version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/crewai/requirements_v0.txt
- name: Configure AWS credentials for Bedrock
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ vars.BEDROCK_ROLE }}
aws-region: us-east-1
role-chaining: true
role-skip-session-tagging: true
- name: Run tests
run: |
cd ./tests/library_integration/crewai/
python -m pytest -vv .
+75
View File
@@ -0,0 +1,75 @@
# Workflow to run CrewAI v1.0.0+ tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib CrewAI v1 Tests
run-name: "SDK Lib CrewAI v1 Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: CrewAI v1 Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
# 3.14+ is not supported by crewai v1.x
python_version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/crewai/requirements_v1.txt
- name: Configure AWS credentials for Bedrock
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ vars.BEDROCK_ROLE }}
aws-region: us-east-1
role-chaining: true
role-skip-session-tagging: true
- name: Run tests
run: |
cd ./tests/library_integration/crewai/
python -m pytest -vv .
+57
View File
@@ -0,0 +1,57 @@
# Workflow to run DSPy tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib DSPy Tests
run-name: "SDK Lib DSPy Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: DSPy Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
# 3.14 is supported in 3.1.0b1 and up. Once 3.1.0 is released, we can add 3.14 to the matrix.
python_version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/dspy/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/dspy/
python -m pytest -vv .
+71
View File
@@ -0,0 +1,71 @@
# Workflow to run GenAI tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib GenAI (Google) Tests
run-name: "SDK Lib GenAI Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPIK_TEST_EXPENSIVE: ${{ inputs.run_expensive_tests }}
on:
workflow_call:
inputs:
run_expensive_tests:
description: "Run expensive tests (e.g., video generation)"
required: false
type: boolean
default: false
jobs:
tests:
name: GenAI Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
# This suite makes live Vertex AI calls against the shared
# opik-sdk-tests GCP project (shared with the ADK suite). Running the
# full 3.10-3.14 matrix re-multiplied those calls into 429
# RESOURCE_EXHAUSTED, so we run only the oldest and newest supported
# Python versions — enough to catch version-specific breakage at a
# fraction of the request volume — and keep the concurrency cap.
max-parallel: 2
matrix:
python_version: ["3.10", "3.14"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/genai/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/genai/
python -m pytest -vv .
+55
View File
@@ -0,0 +1,55 @@
# Workflow to run Groq tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Groq Tests
run-name: "SDK Lib Groq Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Groq Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/groq/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/groq/
python -m pytest -vv .
@@ -0,0 +1,62 @@
# Workflow to run Guar tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Guardrails Tests
run-name: "SDK Lib Guardrails Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
GUARDRAILS_API_KEY: ${{ secrets.GUARDRAILS_API_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Guardrails Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/guardrails/requirements.txt
- name: Install checks from guardrails hub
run: |
guardrails configure --token "$GUARDRAILS_API_KEY" --disable-metrics --enable-remote-inferencing;
guardrails hub install hub://guardrails/politeness_check
- name: Run tests
run: |
cd ./tests/library_integration/guardrails/
python -m pytest -vv .
+54
View File
@@ -0,0 +1,54 @@
# Workflow to run Harbor tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Harbor Tests
run-name: "SDK Lib Harbor Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Harbor Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ["3.12", "3.13"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/harbor/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/harbor/
python -m pytest -vv .
+56
View File
@@ -0,0 +1,56 @@
# Workflow to run Haystack tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Haystack Tests
run-name: "SDK Lib Haystack Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Haystack Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/haystack/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/haystack/
python -m pytest -vv .
@@ -0,0 +1,303 @@
# Runner for the suite of library integration tests
#
# echo "result=${{ secrets.OPENAI_API_KEY != '' }}" >> $GITHUB_OUTPUT
name: SDK Library Integration Tests Runner
run-name: "SDK Library Integration Tests Runner ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
on:
workflow_dispatch:
inputs:
libs:
description: "Choose specific library to test against or all"
required: true
type: choice
options:
- all
- openai
- langchain
- langchain_legacy
- llama_index
- anthropic
- mistral
- groq
- aisuite
- haystack
# - guardrails # disabled: guardrails-ai is on PyPI quarantine
- dspy
- crewai_v0
- crewai_v1
- genai
- adk
- adk_legacy_1_3_0
- metrics
- bedrock
- litellm
- harbor
run_expensive_tests:
description: "Run expensive tests (e.g., video generation). Enabled by default on weekly scheduled runs."
required: false
type: boolean
default: false
schedule:
# Daily run at midnight UTC Monday-Saturday (without expensive tests)
- cron: "0 0 * * 1-6"
# Weekly run on Sunday at midnight UTC (with expensive tests)
- cron: "0 0 * * 0"
pull_request:
paths:
- 'sdks/python/**'
push:
branches:
- 'main'
paths:
- 'sdks/python/**'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
LIBS: ${{ github.event.inputs.libs != '' && github.event.inputs.libs || 'all' }}
OPIK_SENTRY_ENABLE: False
jobs:
has_needed_secrets:
name: Check Secrets
runs-on: ubuntu-latest
outputs:
has_secrets: ${{ steps.init.outputs.has_secrets }}
steps:
- name: Print has secrets into output
id: init
run: |
echo "has_secrets=${{ secrets.OPENAI_API_KEY != '' }}" >> "$GITHUB_OUTPUT"
missed_api_key_warning:
name: Missed OpenAI API Key Warning
needs: [has_needed_secrets]
runs-on: ubuntu-latest
if: ${{ needs.has_needed_secrets.outputs.has_secrets == 'false' }}
steps:
- name: Print disabled message
run: |
echo "::warning::SDK Library Integration Tests are disabled because OPENAI_API_KEY is not set"
init_environment:
name: Build
needs: [has_needed_secrets]
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ needs.has_needed_secrets.outputs.has_secrets == 'true' }}
outputs:
LIBS: ${{ steps.init.outputs.LIBS }}
steps:
- name: Make LIBS variable global (workaround for cron)
id: init
run: |
echo "LIBS=${{ env.LIBS }}" >> "$GITHUB_OUTPUT"
openai_tests:
needs: [init_environment]
if: contains(fromJSON('["openai", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-openai-tests.yml
with:
# Run expensive tests on weekly schedule (Sunday) or when manually requested
run_expensive_tests: ${{ (github.event_name == 'schedule' && github.event.schedule == '0 0 * * 0') || github.event.inputs.run_expensive_tests == 'true' }}
secrets: inherit
langchain_tests:
needs: [init_environment]
if: contains(fromJSON('["langchain", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-langchain-tests.yml
secrets: inherit
langchain_legacy_tests:
needs: [init_environment]
if: contains(fromJSON('["langchain_legacy", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-langchain-legacy-tests.yml
secrets: inherit
llama_index_tests:
needs: [init_environment]
if: contains(fromJSON('["llama_index", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-llama-index-tests.yml
secrets: inherit
anthropic_tests:
needs: [init_environment]
if: contains(fromJSON('["anthropic", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-anthropic-tests.yml
secrets: inherit
mistral_tests:
needs: [init_environment]
if: contains(fromJSON('["mistral", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-mistral-tests.yml
secrets: inherit
groq_tests:
needs: [init_environment]
if: contains(fromJSON('["groq", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-groq-tests.yml
secrets: inherit
aisuite_tests:
needs: [init_environment]
if: contains(fromJSON('["aisuite", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-aisuite-tests.yml
secrets: inherit
haystack_tests:
needs: [init_environment]
if: contains(fromJSON('["haystack", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-haystack-tests.yml
secrets: inherit
# guardrails-ai is on PyPI quarantine, so the install step fails before
# pytest runs. Re-enable once the package is back on PyPI.
# guardrails_tests:
# needs: [init_environment]
# if: contains(fromJSON('["guardrails", "all"]'), needs.init_environment.outputs.LIBS)
# uses: ./.github/workflows/lib-guardrails-tests.yml
# secrets: inherit
dspy_tests:
needs: [init_environment]
if: contains(fromJSON('["dspy", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-dspy-tests.yml
secrets: inherit
crewai_v0_tests:
needs: [init_environment]
if: contains(fromJSON('["crewai_v0", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-crewai-v0-tests.yml
secrets: inherit
crewai_v1_tests:
needs: [init_environment]
if: contains(fromJSON('["crewai_v1", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-crewai-v1-tests.yml
secrets: inherit
genai_tests:
needs: [init_environment]
if: contains(fromJSON('["genai", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-genai-tests.yml
with:
# Run expensive tests on weekly schedule (Sunday) or when manually requested
run_expensive_tests: ${{ (github.event_name == 'schedule' && github.event.schedule == '0 0 * * 0') || github.event.inputs.run_expensive_tests == 'true' }}
secrets: inherit
adk_tests:
needs: [init_environment]
if: contains(fromJSON('["adk", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-adk-tests.yml
secrets: inherit
adk_legacy_1_3_0_tests:
needs: [init_environment]
if: contains(fromJSON('["adk_legacy_1_3_0", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-adk-legacy-1-3-0-tests.yml
secrets: inherit
evaluation_metrics_tests:
needs: [init_environment]
if: contains(fromJSON('["metrics", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-metrics-with-llm-judge-tests.yml
secrets: inherit
bedrock_tests:
needs: [init_environment]
if: contains(fromJSON('["bedrock", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-bedrock-tests.yml
secrets: inherit
litellm_tests:
needs: [init_environment]
if: contains(fromJSON('["litellm", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-litellm-tests.yml
secrets: inherit
harbor_tests:
needs: [init_environment]
if: contains(fromJSON('["harbor", "all"]'), needs.init_environment.outputs.LIBS)
uses: ./.github/workflows/lib-harbor-tests.yml
secrets: inherit
# ========================================
# Slack Notification (manual and scheduled runs only)
# ========================================
notify-slack:
name: "Slack Notification"
runs-on: ubuntu-latest
needs:
- openai_tests
- langchain_tests
- langchain_legacy_tests
- llama_index_tests
- anthropic_tests
- mistral_tests
- groq_tests
- aisuite_tests
- haystack_tests
# - guardrails_tests # disabled: guardrails-ai is on PyPI quarantine
- dspy_tests
- crewai_v0_tests
- crewai_v1_tests
- genai_tests
- adk_tests
- adk_legacy_1_3_0_tests
- evaluation_metrics_tests
- bedrock_tests
- litellm_tests
- harbor_tests
if: always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Determine trigger type
id: trigger
run: |
if [ "${{ github.event_name }}" == "schedule" ]; then
TYPE=$([[ "${{ github.event.schedule }}" == "0 0 * * 0" ]] && echo "Weekly Schedule" || echo "Daily Schedule")
else
TYPE="Manual Dispatch"
fi
echo "type=$TYPE" >> "$GITHUB_OUTPUT"
- name: Send Slack notification
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USER_ID: ${{ secrets.SLACK_SDK_TESTS_NOTIFY_USER_ID }}
TRIGGER_TYPE: ${{ steps.trigger.outputs.type }}
SUITE_RESULTS: >-
{
"OpenAI": "${{ needs.openai_tests.result }}",
"LangChain": "${{ needs.langchain_tests.result }}",
"LangChain Legacy": "${{ needs.langchain_legacy_tests.result }}",
"LlamaIndex": "${{ needs.llama_index_tests.result }}",
"Anthropic": "${{ needs.anthropic_tests.result }}",
"Mistral": "${{ needs.mistral_tests.result }}",
"Groq": "${{ needs.groq_tests.result }}",
"AISuite": "${{ needs.aisuite_tests.result }}",
"Haystack": "${{ needs.haystack_tests.result }}",
"DSPy": "${{ needs.dspy_tests.result }}",
"CrewAI v0": "${{ needs.crewai_v0_tests.result }}",
"CrewAI v1": "${{ needs.crewai_v1_tests.result }}",
"GenAI": "${{ needs.genai_tests.result }}",
"ADK": "${{ needs.adk_tests.result }}",
"ADK Legacy 1.3.0": "${{ needs.adk_legacy_1_3_0_tests.result }}",
"Evaluation Metrics": "${{ needs.evaluation_metrics_tests.result }}",
"Bedrock": "${{ needs.bedrock_tests.result }}",
"LiteLLM": "${{ needs.litellm_tests.result }}",
"Harbor": "${{ needs.harbor_tests.result }}"
}
run: .github/scripts/notify-slack-lib-integration.sh
@@ -0,0 +1,76 @@
# Workflow to run Langchain tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Langchain Tests for langchain < 1.0.0
run-name: "SDK Lib Langchain Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
on:
workflow_call:
jobs:
tests:
name: Langchain Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
# 3.14+ is not supported by the legacy langchain (langchain<1.0.0)
python_version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/langchain/legacy_requirements.txt
- name: change aws role
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ vars.BEDROCK_ROLE }}
aws-region: us-east-1
role-chaining: true
role-skip-session-tagging: true
- name: Run tests
run: |
cd ./tests/library_integration/langchain/
python -m pytest -vv .
+75
View File
@@ -0,0 +1,75 @@
# Workflow to run Langchain tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Langchain Tests
run-name: "SDK Lib Langchain Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
on:
workflow_call:
jobs:
tests:
name: Langchain Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/langchain/requirements.txt
- name: change aws role
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ vars.BEDROCK_ROLE }}
aws-region: us-east-1
role-chaining: true
role-skip-session-tagging: true
- name: Run tests
run: |
cd ./tests/library_integration/langchain/
python -m pytest -vv .
+61
View File
@@ -0,0 +1,61 @@
# Workflow to run LiteLLM integration tests
# This workflow is used to test the LiteLLM integration in opik.integrations.litellm, however
# there is another, external, integration with LiteLLM that lives in litellm repository, it's being tested
# in a separate workflow: .github/workflows/sdk-e2e-library-tests.yaml
#
# Please read inputs to provide correct values.
#
name: SDK Lib LiteLLM Tests
run-name: "SDK Lib LiteLLM Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: LiteLLM Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/litellm/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/litellm/
python -m pytest -vv .
@@ -0,0 +1,56 @@
# Workflow to run Langchain tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib LLama Index Tests
run-name: "SDK Lib LLama Index Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: LLamaIndex Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/llama_index/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/llama_index/
python -m pytest -vv .
@@ -0,0 +1,62 @@
# Workflow to run tests for evaluation metrics that use llm to judge scores
#
# Please read inputs to provide correct values.
#
name: SDK Lib OpenAI Tests
run-name: "SDK Lib Metrics Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Evaluation Metrics Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/metrics_with_llm_judge/requirements.txt
pip list
- name: Run tests
run: |
cd ./tests/library_integration/metrics_with_llm_judge/
python -m pytest -vv --durations=20 .
+55
View File
@@ -0,0 +1,55 @@
# Workflow to run Mistral AI tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib Mistral Tests
run-name: "SDK Lib Mistral Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
on:
workflow_call:
jobs:
tests:
name: Mistral Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ["3.12"]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/mistral/requirements.txt
- name: Run tests
run: |
cd ./tests/library_integration/mistral/
python -m pytest -vv .
+73
View File
@@ -0,0 +1,73 @@
# Workflow to run OpenAI tests
#
# Please read inputs to provide correct values.
#
name: SDK Lib OpenAI Tests
run-name: "SDK Lib OpenAI Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
GOOGLE_CLOUD_LOCATION: us-east1
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
GOOGLE_GENAI_USE_VERTEXAI: TRUE
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPIK_TEST_EXPENSIVE: ${{ inputs.run_expensive_tests }}
on:
workflow_call:
inputs:
run_expensive_tests:
description: "Run expensive tests (e.g., video generation)"
required: false
type: boolean
default: false
jobs:
tests:
name: OpenAI Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: sdks/python
strategy:
fail-fast: true
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Install opik
run: pip install .
- name: Install test tools
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Install lib
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r library_integration/openai/requirements.txt
pip list
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
- name: Run tests
run: |
cd ./tests/library_integration/openai/
python -m pytest -vv .
+161
View File
@@ -0,0 +1,161 @@
name: Lint Opik Helm Chart
run-name: "Lint Opik Helm Chart ${{ github.ref_name }} by @${{ github.actor }}"
on:
workflow_dispatch:
pull_request:
paths:
- "deployment/helm_chart/opik/**"
push:
branches:
- 'main'
paths:
- "deployment/helm_chart/opik/**"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# This workflow only reads the repo (lint + render); no write access needed.
permissions:
contents: read
jobs:
lint-helm-chart:
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
# Prove dual compatibility: the chart must keep working on Helm 3.x
# while also rendering cleanly under Helm 4.x (DND-537).
helm-version:
- v3.21.0
- v4.2.0
name: lint-helm-chart (Helm ${{ matrix.helm-version }})
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Helm ${{ matrix.helm-version }}
uses: azure/setup-helm@v4.3.1
with:
version: ${{ matrix.helm-version }}
- name: Run lint on Helm chart
run: |
set -e
cd deployment/helm_chart/opik
helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add clickhouse-operator https://docs.altinity.com/clickhouse-operator
helm dependency build
helm lint --values values.yaml .
cd -
# Assert the rendered content of specific resources (e.g. the `opik.probe`
# helper) via helm-unittest. Tests live in deployment/helm_chart/opik/tests/.
unittest-helm-chart:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Helm
uses: azure/setup-helm@v4.3.1
with:
version: v3.21.0
- name: Install helm-unittest plugin
run: helm plugin install https://github.com/helm-unittest/helm-unittest
- name: Run helm unittest
run: |
set -e
cd deployment/helm_chart/opik
helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add clickhouse-operator https://docs.altinity.com/clickhouse-operator
helm dependency build
helm unittest .
cd -
# Prove the chart renders equivalently under Helm 3.x and Helm 4.x so we can ship
# Helm 4 support without changing behavior for Helm 3 users (DND-537). Each version
# resolves its own dependencies (`helm dependency build`) and renders from scratch.
# Helm 4 only differs from Helm 3 in trailing whitespace it emits between manifests,
# which YAML ignores and does not change any rendered Kubernetes object — so we
# compare after stripping trailing whitespace and blank lines.
render-equality:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Render with Helm 3.x
uses: azure/setup-helm@v4.3.1
with:
version: v3.21.0
- name: helm template (Helm 3.x)
run: |
set -e
cd deployment/helm_chart/opik
rm -rf charts Chart.lock
helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add clickhouse-operator https://docs.altinity.com/clickhouse-operator
helm dependency build
helm template opik . --values values.yaml > "${RUNNER_TEMP}/render-helm3.yaml"
cd -
- name: Render with Helm 4.x
uses: azure/setup-helm@v4.3.1
with:
version: v4.2.0
- name: helm template (Helm 4.x)
run: |
set -e
cd deployment/helm_chart/opik
# Wipe Helm 3's resolved deps so Helm 4 runs its own dependency resolution.
rm -rf charts Chart.lock
helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add clickhouse-operator https://docs.altinity.com/clickhouse-operator
helm dependency build
helm template opik . --values values.yaml > "${RUNNER_TEMP}/render-helm4.yaml"
cd -
- name: Diff rendered output (semantically; must be identical)
run: |
set -e
# Compare by parsing every rendered YAML document and re-serializing it
# canonically (sorted keys), instead of a line-based diff. This ignores the
# ONLY thing Helm 4 changes vs Helm 3 — cosmetic inter-document spacing and
# trailing whitespace — while preserving real content, including blank lines
# *inside* block scalars (e.g. embedded config in ConfigMaps). A line-based
# blank-line strip would hide those; a YAML round-trip does not.
canonicalize() {
python3 - "$1" <<'PY'
import sys, yaml
docs = [d for d in yaml.safe_load_all(open(sys.argv[1])) if d is not None]
sys.stdout.write(yaml.safe_dump_all(docs, sort_keys=True, default_flow_style=False))
PY
}
canonicalize "${RUNNER_TEMP}/render-helm3.yaml" > "${RUNNER_TEMP}/render-helm3.norm.yaml"
canonicalize "${RUNNER_TEMP}/render-helm4.yaml" > "${RUNNER_TEMP}/render-helm4.norm.yaml"
if ! diff -u "${RUNNER_TEMP}/render-helm3.norm.yaml" "${RUNNER_TEMP}/render-helm4.norm.yaml"; then
echo "::error::Rendered manifests differ between Helm 3.x and Helm 4.x. The chart must render equivalently on both."
exit 1
fi
echo "Helm 3.x and Helm 4.x produce equivalent rendered manifests."
+239
View File
@@ -0,0 +1,239 @@
name: Load Tests (SDK ingestion)
run-name: "Load Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
# checks: write so EnricoMi/publish-unit-test-result-action can post the
# "Load Test Results" check; without it the step gets 403 Forbidden and
# fails the whole workflow even when all scenarios passed.
checks: write
on:
schedule:
- cron: '0 4 * * 0' # Weekly, Sunday 04:00 UTC
workflow_dispatch:
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPIK_URL_OVERRIDE: http://localhost:8080
OPIK_CONSOLE_LOGGING_LEVEL: WARNING
jobs:
run-load-tests:
# Larger GitHub-hosted runner (~4 vCPU / 16 GB) instead of the default
# ubuntu-latest (2 vCPU / 7 GB). The heaviest ingestion scenarios
# (e.g. test_many_spans_per_trace) intermittently OOM-killed an xdist
# worker on 7 GB; the extra memory headroom is what stops that.
runs-on: ubuntu-latest-m
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run latest Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
COMPOSE_BAKE: false
TOGGLE_RUNNERS_ENABLED: "true"
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping --build
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Install Opik SDK
run: |
cd ${{ github.workspace }}/sdks/python
pip install .
- name: Install Python SDK load-test requirements
run: |
cd ${{ github.workspace }}/tests_load/suite/python_sdk
pip install -r requirements.txt
- name: Run Python SDK load tests
# -n 2 + --dist=worksteal runs two scenarios in parallel at a time.
# History: on the old 7 GB ubuntu-latest, `-n auto` (= 4 workers)
# reliably OOM-killed the heaviest scenarios
# (test_many_traces_one_span_each, test_many_spans_per_trace) when
# co-scheduled against the same docker-compose Opik stack, and even
# -n 2 still crashed a worker occasionally. The job now runs on the
# larger ubuntu-latest-m (~16 GB, see runs-on above), so -n 2 has
# comfortable memory headroom; it's kept conservative for now and can
# be raised toward -n auto on the larger runner if more parallelism is
# wanted. Each test uses a unique project so isolation holds. Worksteal
# balances the very uneven per-test durations (spread is window-locked
# at 600 s, others run in 1-6 min).
run: |
cd ${{ github.workspace }}/tests_load
pytest suite/python_sdk -n 2 --dist=worksteal --junitxml=${{ github.workspace }}/load_test_results.xml
- name: Render metrics into job summary
if: always()
run: |
python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
import json
import pathlib
metrics_dir = pathlib.Path("tests_load/.last_run")
files = sorted(metrics_dir.glob("*.json")) if metrics_dir.exists() else []
if not files:
print("## Load test metrics\n\n_No metrics produced (suite did not run)._")
raise SystemExit
def fmt_seconds(value):
if not isinstance(value, (int, float)):
return "—"
return f"{value / 60:.1f} m" if value >= 60 else f"{value:.1f} s"
def fmt_count(value):
return f"{value:,}" if isinstance(value, (int, float)) else "—"
def submit_seconds(metrics):
# The submit phase is timed as "logging" for trace-based tests and
# as "insert" for the dataset-versions test.
for key in ("logging_seconds", "insert_seconds"):
if isinstance(metrics.get(key), (int, float)):
return metrics[key]
return None
def submitted_volume(metrics):
# Returns (count, label). Picks the most representative unit per
# test type so the table shows what was actually submitted.
if "expected_total_items" in metrics:
return metrics["expected_total_items"], (
f"{metrics['expected_total_items']:,} items"
)
if "total_traces" in metrics:
return metrics["total_traces"], (
f"{metrics['total_traces']:,} traces"
)
trace_count = metrics.get("trace_count")
spans_per_trace = metrics.get("spans_per_trace")
if isinstance(trace_count, int) and isinstance(spans_per_trace, int):
return trace_count, (
f"{trace_count:,} traces × {spans_per_trace} spans"
)
if isinstance(trace_count, int):
return trace_count, f"{trace_count:,} traces"
return None, "—"
def submit_rate(count, seconds):
if count is None or not isinstance(seconds, (int, float)) or seconds <= 0:
return "—"
return f"{count / seconds:,.0f}/s"
def total_seconds(metrics):
parts = [
metrics.get(key)
for key in (
"logging_seconds",
"insert_seconds",
"flush_seconds",
"verify_seconds",
)
]
parts = [p for p in parts if isinstance(p, (int, float))]
return sum(parts) if parts else None
def delivered_count(metrics):
for key in ("delivered_trace_count", "delivered_item_count"):
if key in metrics:
return metrics[key]
return None
print("## Load test metrics\n")
print(
"_Submit = time spent calling decorated functions / context managers."
" Submit rate = volume ÷ submit time."
" Total = submit + flush + verify._\n"
)
print(
"| Test | Volume | Submit | Submit rate"
" | Flush | Verify | Total | Delivered |"
)
print(
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |"
)
payloads = []
for path in files:
metrics = json.loads(path.read_text())
payloads.append(metrics)
name = metrics.get("test_name", path.stem)
count, volume_label = submitted_volume(metrics)
submit_s = submit_seconds(metrics)
print(
f"| `{name}`"
f" | {volume_label}"
f" | {fmt_seconds(submit_s)}"
f" | {submit_rate(count, submit_s)}"
f" | {fmt_seconds(metrics.get('flush_seconds'))}"
f" | {fmt_seconds(metrics.get('verify_seconds'))}"
f" | {fmt_seconds(total_seconds(metrics))}"
f" | {fmt_count(delivered_count(metrics))} |"
)
print("\n<details>\n<summary>Per-test metrics (raw JSON)</summary>\n")
for metrics in payloads:
print(f"\n**{metrics.get('test_name', '?')}**")
print("```json")
print(json.dumps(metrics, indent=2))
print("```")
print("\n</details>")
PY
- name: Upload metrics report
if: always()
uses: actions/upload-artifact@v7
with:
name: load-test-metrics
path: ${{ github.workspace }}/tests_load/.last_run/
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Load Test Results
files: ${{ github.workspace }}/load_test_results.xml
- name: Keep BE log in case of failure
if: failure()
run: |
docker logs opik-backend-1 > ${{ github.workspace }}/opik-backend.log
- name: Attach BE log
if: failure()
uses: actions/upload-artifact@v7
with:
name: opik-backend-log
path: ${{ github.workspace }}/opik-backend.log
- name: Stop opik server
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop
@@ -0,0 +1,31 @@
name: "Database Migration Prefix Check"
run-name: "Backend Database Migration Prefix Check on ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
on:
pull_request:
paths:
- "apps/opik-backend/src/main/resources/liquibase/db-app-state/migrations/**"
- "apps/opik-backend/src/main/resources/liquibase/db-app-analytics/migrations/**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check-migration-prefix-conflicts:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout PR branch
uses: actions/checkout@v6
with:
fetch-depth: 0 # Fetch full history to compare with main
- name: Check for migration prefix conflicts
shell: bash
run: ./scripts/check_backend_migration_conflicts.sh
@@ -0,0 +1,195 @@
name: "Opik Optimizer - E2E Tests"
run-name: "Opik Optimizer E2E Tests ${{ github.ref_name }} by @${{ github.actor }}"
concurrency:
group: opik-optimizer-e2e-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
pull_request:
paths:
- 'sdks/opik_optimizer/**'
- '.github/workflows/opik-optimizer-e2e-tests.yaml'
push:
branches:
- 'main'
paths:
- 'sdks/opik_optimizer/**'
- '.github/workflows/opik-optimizer-e2e-tests.yaml'
permissions:
contents: read
packages: write
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
HF_HUB_ENABLE_PROGRESS_BARS: "0"
HF_HUB_DISABLE_TELEMETRY: "1"
OPIK_URL_OVERRIDE: http://localhost:8080
OPIK_CONSOLE_LOGGING_LEVEL: DEBUG
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
jobs:
build-opik:
uses: ./.github/workflows/build_e2e_docker.yaml
with:
include_guardrails: true
run-e2e-tests:
name: Opik Optimizer E2E Tests Python ${{matrix.python_version}}
needs: build-opik
runs-on: ubuntu-latest
timeout-minutes: 40
defaults:
run:
working-directory: sdks/opik_optimizer/
strategy:
fail-fast: false
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker images from GHCR
env:
TAG: ${{ needs.build-opik.outputs.image_tag }}
run: |
docker pull "ghcr.io/comet-ml/opik/opik-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-python-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-guardrails-backend:$TAG"
docker tag "ghcr.io/comet-ml/opik/opik-backend:$TAG" ghcr.io/comet-ml/opik/opik-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-python-backend:$TAG" ghcr.io/comet-ml/opik/opik-python-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-guardrails-backend:$TAG" ghcr.io/comet-ml/opik/opik-guardrails-backend:latest
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
cache: 'pip'
cache-dependency-path: |
sdks/opik_optimizer/pyproject.toml
sdks/opik_optimizer/setup.py
sdks/opik_optimizer/tests/test_requirements.txt
- name: Run latest Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
# Avoid Buildx Bake concurrent image export collisions in CI.
COMPOSE_BAKE: false
TOGGLE_RUNNERS_ENABLED: "true"
OPIK_BACKEND_PULL_POLICY: never
PYTHON_BACKEND_PULL_POLICY: never
OPIK_GUARDRAILS_BACKEND_PULL_POLICY: never
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping --guardrails
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
echo "Check Docker pods are up"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "Check backend health"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Cache HuggingFace datasets
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: hf-datasets-${{ hashFiles('**/datasets/*.py') }}
restore-keys: |
hf-datasets-
- name: Cache LiteLLM cache
uses: actions/cache@v4
with:
path: ~/.litellm_cache
key: litellm-cache-${{ hashFiles('**/requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
litellm-cache-
- name: Install opik_optimizer
run: pip install .[dev]
- name: Install test requirements
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Run E2E tests
run: pytest tests/e2e -vv --junitxml=${{ github.workspace }}/test_results_${{matrix.python_version}}.xml
- name: Upload test artifacts on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: opik-optimizer-e2e-logs-p${{matrix.python_version}}
path: |
*.log
tests/**/*.log
retention-days: 7
- name: Stop opik server
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop --guardrails
dataset-source-check:
name: Opik Optimizer Integration Smoke Tests
runs-on: ubuntu-latest
timeout-minutes: 30
needs: run-e2e-tests
env:
OPIK_USE_HF_STREAMING: "true"
defaults:
run:
working-directory: sdks/opik_optimizer/
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: 'pip'
cache-dependency-path: |
sdks/opik_optimizer/pyproject.toml
sdks/opik_optimizer/setup.py
sdks/opik_optimizer/tests/test_requirements.txt
- name: Cache HuggingFace datasets
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: hf-datasets-${{ hashFiles('**/datasets/*.py') }}
restore-keys: |
hf-datasets-
- name: Install opik_optimizer
run: pip install .
- name: Install test requirements
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Run dataset integration check
run: pytest -n auto --dist loadfile tests/integration/datasets/test_dataset_sources.py -vv
@@ -0,0 +1,46 @@
name: "Opik Optimizer - Publish SDK"
run-name: "Opik Optimizer Publish SDK ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
packages: write
on:
workflow_dispatch:
jobs:
build-and-publish:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
HF_HUB_ENABLE_PROGRESS_BARS: "0"
HF_HUB_DISABLE_TELEMETRY: "1"
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: PyPI token preflight
uses: ./.github/actions/pypi-token-preflight
with:
pypi_token: ${{ secrets.PYPI_API_TOKEN }}
package_name: opik-optimizer
- name: Build pip package
run: |
cd sdks/opik_optimizer
pip3 install -U pip build
python3 -m build --sdist --wheel --outdir dist/ .
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: sdks/opik_optimizer/dist
# Symmetric to OPIK-6624's npm publish 403-skip: replays / races where
# the version is already on PyPI should not fail the release.
skip-existing: true
@@ -0,0 +1,82 @@
name: "Opik Optimizer - Unit Tests"
run-name: "Opik Optimizer Unit Tests ${{ github.ref_name }} by @${{ github.actor }}"
on:
workflow_dispatch:
pull_request:
paths:
- 'sdks/opik_optimizer/**'
push:
branches:
- 'main'
paths:
- 'sdks/opik_optimizer/**'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
HF_HUB_OFFLINE: true
HF_DATASETS_OFFLINE: true
HF_HUB_ENABLE_PROGRESS_BARS: "0"
HF_HUB_DISABLE_TELEMETRY: "1"
permissions:
contents: read
jobs:
run-unit-tests:
name: Opik Optimizer Unit Tests Python ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: sdks/opik_optimizer/
strategy:
fail-fast: false
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS || '["3.10","3.11","3.12","3.13","3.14"]') }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
cache: 'pip'
cache-dependency-path: |
sdks/opik_optimizer/pyproject.toml
sdks/opik_optimizer/setup.py
sdks/opik_optimizer/tests/test_requirements.txt
- name: Cache HuggingFace datasets
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: hf-datasets-${{ hashFiles('**/datasets/*.py') }}
restore-keys: |
hf-datasets-
- name: Cache LiteLLM cache
uses: actions/cache@v4
with:
path: ~/.litellm_cache
key: litellm-cache-${{ hashFiles('**/requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
litellm-cache-
- name: Install opik_optimizer
run: pip install .
- name: Install test requirements
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt
- name: Run unit tests
run: pytest -n auto --dist loadfile tests/unit -vv --junitxml=${{ github.workspace }}/test_results_${{matrix.python_version}}.xml
+163
View File
@@ -0,0 +1,163 @@
name: Opik TypeScript Configure Publish
run-name: "Opik TypeScript Configure Publish from ${{github.ref_name}} by @${{ github.actor }}"
permissions:
contents: write
packages: write
on:
workflow_dispatch:
inputs:
version:
type: string
required: true
description: Version
default: ""
is_release:
type: boolean
required: false
default: false
workflow_call:
inputs:
version:
type: string
required: true
description: Version
is_release:
type: boolean
required: false
default: false
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
VERSION: ${{ github.event.inputs.version || inputs.version }}
IS_RELEASE: ${{ github.event.inputs.is_release || inputs.is_release }}
defaults:
run:
working-directory: sdks/typescript/src/opik/configure
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
fetch-depth: 0
token: ${{ secrets.GH_PAT_TO_ACCESS_GITHUB_API }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Resolve published package name
if: ${{ env.IS_RELEASE }}
id: resolve_pkg_name
shell: bash
working-directory: sdks/typescript/src/opik/configure
run: |
# Derive from package.json so the preflight always probes the actual publish
# target, even if the package is renamed (e.g., opik-configure -> opik-ts).
PKG=$(node -p "require('./package.json').name")
echo "name=${PKG}" >> "$GITHUB_OUTPUT"
- name: NPM token preflight
if: ${{ env.IS_RELEASE }}
uses: ./.github/actions/npm-token-preflight
with:
npm_token: ${{ secrets.NPM_TOKEN }}
package_name: ${{ steps.resolve_pkg_name.outputs.name }}
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9.15.5
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Validate version format
if: ${{ env.IS_RELEASE }}
run: |
if ! [[ "${{ env.VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "Error: Invalid version format '${{ env.VERSION }}'. Expected format: X.Y.Z or X.Y.Z-suffix"
exit 1
fi
- name: Update version
if: ${{ env.IS_RELEASE }}
run: pnpm version ${{ env.VERSION }} --no-git-tag-version
- name: Lint and format check
run: |
pnpm run fmt:check || echo "Warning: Format check failed"
pnpm run lint || echo "Warning: Lint check failed"
- name: Build package
run: pnpm build
- name: Verify build
run: |
if [ ! -d "dist" ]; then
echo "Error: Build failed - dist directory not found"
exit 1
fi
- name: Check if version already exists on NPM
if: ${{ env.IS_RELEASE }}
run: |
PACKAGE_NAME=$(node -p "require('./package.json').name")
if npm view "$PACKAGE_NAME@${{ env.VERSION }}" version 2>/dev/null; then
echo "Error: Version ${{ env.VERSION }} already exists on NPM"
exit 1
fi
echo "Version check passed - ${{ env.VERSION }} is available"
- name: Commit version changes
if: ${{ env.IS_RELEASE }}
run: |
git config --local user.email "github-actions@comet.com"
git config --local user.name "github-actions"
git add package.json pnpm-lock.yaml
git diff --staged --quiet || git commit -m "chore: update Opik Configure version to ${{ env.VERSION }}"
- name: Create git tag
if: ${{ env.IS_RELEASE }}
run: |
git tag -a "configure-v${{ env.VERSION }}" -m "Release Opik Configure v${{ env.VERSION }}"
- name: Publish to NPM
if: ${{ env.IS_RELEASE }}
run: |
echo "Publishing opik-configure version ${{ env.VERSION }} to NPM..."
pnpm publish --no-git-checks --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Push changes and tags
if: ${{ env.IS_RELEASE }}
run: |
git push origin ${{ github.ref }}
git push origin "configure-v${{ env.VERSION }}"
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT_TO_ACCESS_GITHUB_API }}
- name: Workflow summary
if: always()
run: |
exec >> "$GITHUB_STEP_SUMMARY"
echo "## Workflow Summary"
echo ""
echo "- **Version**: ${{ env.VERSION }}"
echo "- **Is Release**: ${{ env.IS_RELEASE }}"
echo "- **Branch**: ${{ github.ref_name }}"
echo "- **Triggered by**: @${{ github.actor }}"
echo ""
if [ "${{ env.IS_RELEASE }}" == "true" ]; then
echo "✅ Package published to NPM: [opik-configure@${{ env.VERSION }}](https://www.npmjs.com/package/opik-configure/v/${{ env.VERSION }})"
echo "✅ Git tag created: configure-v${{ env.VERSION }}"
else
echo "️ Dry run completed - no changes published"
fi
+77
View File
@@ -0,0 +1,77 @@
name: 🏷️ Auto Assign PR
on:
workflow_call:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
assign_pr_creator_or_pusher:
name: Assign PR Creator or Pusher
runs-on: ubuntu-latest
timeout-minutes: 1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
pull-requests: write
steps:
- name: Determine user to assign
id: get_assignee_user
run: |
ASSIGN_USER=""
EVENT_ACTOR=""
if [[ "${{ github.event.action }}" == "synchronize" ]]; then
EVENT_ACTOR="${{ github.event.sender.login }}"
else
EVENT_ACTOR="${{ github.event.pull_request.user.login }}"
fi
KNOWN_BOTS=("dependabot[bot]" "github-actions[bot]" "github-copilot[bot]" "copilot-bot[bot]")
IS_KNOWN_BOT=false
for BOT in "${KNOWN_BOTS[@]}"; do
if [[ "$EVENT_ACTOR" == "$BOT" ]]; then
IS_KNOWN_BOT=true
break
fi
done
if $IS_KNOWN_BOT || [[ "$EVENT_ACTOR" =~ \[bot\]$ ]]; then
echo "Skipping bot user: $EVENT_ACTOR"
ASSIGN_USER=""
else
ASSIGN_USER="$EVENT_ACTOR"
echo "Determined user: $ASSIGN_USER"
fi
echo "assigned_user=$ASSIGN_USER" >> "$GITHUB_OUTPUT"
- name: Assign PR to determined user
if: steps.get_assignee_user.outputs.assigned_user != ''
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
ASSIGN_USER_LOGIN="${{ steps.get_assignee_user.outputs.assigned_user }}"
echo "Attempting to assign PR #$PR_NUMBER to $ASSIGN_USER_LOGIN"
# Notes:
# - If the user is already assigned, `gh pr edit` succeeds without error.
# - External-collaborator PRs may fail outright (token lacks write on that user) — we
# log and continue rather than failing the workflow.
# - GitHub can also silently ignore assignees without push access while still exiting 0,
# so we re-read the PR's assignees after the edit and confirm the user actually landed
# before logging success.
if gh pr edit "$PR_NUMBER" --add-assignee "$ASSIGN_USER_LOGIN" --repo "${{ github.repository }}"; then
CURRENT_ASSIGNEES="$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json assignees --jq '.assignees[].login' 2>/dev/null || true)"
if grep -Fxq "$ASSIGN_USER_LOGIN" <<<"$CURRENT_ASSIGNEES"; then
echo "Successfully assigned PR #$PR_NUMBER to $ASSIGN_USER_LOGIN"
else
echo "⚠ gh pr edit returned success but $ASSIGN_USER_LOGIN is not in the assignees list (likely silently ignored by GitHub — no push access)"
fi
else
echo "❌ Failed to assign PR #$PR_NUMBER to $ASSIGN_USER_LOGIN, likely an external collaborator"
fi
+115
View File
@@ -0,0 +1,115 @@
permissions:
contents: read
pull-requests: write
name: PR Linter
# This workflow runs on pull requests that are opened, edited, or have new commits pushed.
on:
pull_request:
types: [opened, edited, synchronize]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
lint-pr:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Validate PR Title, Description, and Commits
uses: actions/github-script@v7
with:
script: |
// Get the PR context
const pr = context.payload.pull_request;
if (!pr) {
core.setFailed("Could not get PR from context.");
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
let errors = [];
// 1. Validate PR Title
const title = pr.title;
const titleRegex = /^\[(OPIK-\d+|DND-\d+|DEV-\d+|CUST-\d+|issue-\d+|NA)\](\s*\[(BE|FE|DOCS|SDK|GHA|CI|HELM)\])*\s*.+$/;
if (!titleRegex.test(title)) {
errors.push(
"❌ **Invalid Title Format.** Your PR title must include a ticket/issue number and may optionally include component tags (`[FE]`, `[BE]`, etc.).\n\n" +
" - **Internal contributors: Open a JIRA ticket and link to it:** `[OPIK-xxxx]` or `[CUST-xxxx]` or `[DND-xxxx]` or `[DEV-xxxx] [COMPONENT] Your change`\n" +
" - **External contributors: Open a Github Issue and link to it via its number:** `[issue-xxxx] [COMPONENT] Your change`\n" +
" - **No ticket:** Use `[NA] [COMPONENT] Your change` (Issues section not required)\n\n" +
" *Example: `[issue-3108] [BE] [FE] Fix authentication bug` or `[OPIK-1234] Fix bug` or `[NA] Update README`*"
);
}
// 2. Validate PR Description
const body = pr.body || "";
const requiredHeadings = ["## Details", "## Change checklist", "## Issues", "## Testing", "## Documentation"];
if (body.length === 0) {
errors.push("❌ **Missing Description.** The PR description cannot be empty.");
} else {
for (const heading of requiredHeadings) {
if (!body.includes(heading)) {
errors.push(`❌ **Missing Section.** The description is missing the \`${heading}\` section.`);
}
}
// Helper function to get content of a markdown section
const getSectionContent = (sectionTitle) => {
const startIndex = body.indexOf(sectionTitle);
if (startIndex === -1) return null;
const nextHeaderIndex = body.indexOf("## ", startIndex + sectionTitle.length);
const endIndex = nextHeaderIndex !== -1 ? nextHeaderIndex : body.length;
return body.substring(startIndex + sectionTitle.length, endIndex).trim();
};
const detailsContent = getSectionContent("## Details");
if (detailsContent !== null && detailsContent.length === 0) {
errors.push("❌ **Incomplete Details Section.** The `## Details` section cannot be empty.");
}
const issuesContent = getSectionContent("## Issues");
const issuesRegex = /(#\d+|OPIK-\d+|DND-\d+|DEV-\d+|CUST-\d+)/;
const isNATicket = title.startsWith("[NA]");
if (issuesContent !== null && !isNATicket && !issuesRegex.test(issuesContent)) {
errors.push(
"❌ **Incomplete Issues Section.** You must reference at least one GitHub issue (`#xxxx`), Jira ticket (`OPIK-xxxx`), CUST ticket (`CUST-xxxx`), DEV ticket (`DEV-xxxx`), or DND ticket (`DND-xxxx`) under the `## Issues` section."
);
}
}
// 3. Find all existing linter comments across all pages
const COMMENT_MARKER = "<!-- pr-linter-comment -->";
const allComments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr.number });
const markerComments = allComments.filter(c => c.body && c.body.includes(COMMENT_MARKER));
// 4. Final step: upsert/delete comment and/or fail the job
if (errors.length > 0) {
const commentBody = COMMENT_MARKER + "\n### 📋 PR Linter Failed\n\n" + errors.join("\n\n---\n\n");
const logMessage = "PR Linter Failed:\n\n" + errors.join("\n\n")
.replace(/❌ \*\*/g, '❌ ')
.replace(/\*\*`/g, '`')
.replace(/`\*\*/g, '`')
.replace(/###\s/g, '')
.replace(/---\n\n/g, '');
if (markerComments.length > 0) {
// Keep the newest, delete any duplicates
const [keep, ...duplicates] = [...markerComments].sort((a, b) => b.id - a.id);
await github.rest.issues.updateComment({ owner, repo, comment_id: keep.id, body: commentBody });
await Promise.all(duplicates.map(c => github.rest.issues.deleteComment({ owner, repo, comment_id: c.id })));
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: commentBody });
}
core.setFailed(logMessage);
} else {
await Promise.all(markerComments.map(c => github.rest.issues.deleteComment({ owner, repo, comment_id: c.id })));
console.log("✅ PR linting passed!");
}
+141
View File
@@ -0,0 +1,141 @@
name: 📏 Auto Label PR Size
on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
size-labeler:
name: Label PR by size
runs-on: ubuntu-latest
timeout-minutes: 2
permissions:
contents: read
pull-requests: write
issues: write
steps:
- name: Compute size and apply label
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
const prNumber = pr.number;
// Uniform charcoal chip for every size label so the size metadata reads as one
// recessive family and doesn't compete with semantic labels (bug, Frontend, …) on
// a busy PR list. The cool -> hot ramp lives entirely in the per-bucket emoji
// (🔵 smallest -> 🔴 largest), which keeps its crisp shape on a neutral background.
// maxSize is the inclusive upper bound of changed LOC (additions + deletions) for
// the bucket; the last entry (null) catches everything above it.
const CHIP_COLOR = "2d3139";
const BUCKETS = [
{ name: "🔵 size/XS", color: CHIP_COLOR, maxSize: 19 },
{ name: "🟢 size/S", color: CHIP_COLOR, maxSize: 100 },
{ name: "🟡 size/M", color: CHIP_COLOR, maxSize: 300 },
{ name: "🟠 size/L", color: CHIP_COLOR, maxSize: 600 },
{ name: "🔴 size/XL", color: CHIP_COLOR, maxSize: null },
];
const MANAGED_LABELS = BUCKETS.map((b) => b.name);
// Lockfiles and generated clients dwarf the human-reviewed diff, so exclude them
// from the count — otherwise a lockfile bump mislabels a tiny PR as XL.
const IGNORE_GLOBS = [
"**/package-lock.json",
"**/yarn.lock",
"**/pnpm-lock.yaml",
"**/poetry.lock",
"**/uv.lock",
"sdks/python/src/opik/rest_api/**",
"sdks/typescript/src/opik/rest_api/**",
"**/*.snap",
"**/*.svg",
"**/*.png",
];
const globToRegExp = (glob) => {
let re = "";
for (let i = 0; i < glob.length; i++) {
if (glob.startsWith("**/", i)) { re += "(?:[^/]+/)*"; i += 2; continue; }
if (glob.startsWith("**", i)) { re += ".*"; i += 1; continue; }
const c = glob[i];
if (c === "*") { re += "[^/]*"; continue; }
if (".+^${}()|[]\\".includes(c)) { re += "\\" + c; continue; }
re += c;
}
return new RegExp("^" + re + "$");
};
const ignoreRes = IGNORE_GLOBS.map(globToRegExp);
const isIgnored = (path) => ignoreRes.some((re) => re.test(path));
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
const total = files
.filter((f) => !isIgnored(f.filename))
.reduce((sum, f) => sum + f.additions + f.deletions, 0);
const bucket =
BUCKETS.find((b) => b.maxSize !== null && total <= b.maxSize) ||
BUCKETS[BUCKETS.length - 1];
core.info(`PR #${prNumber}: ${total} changed LOC (excluding ignored files) -> ${bucket.name}`);
// Keep the label's color/emoji authoritative regardless of how it was first created.
try {
const { data: existing } = await github.rest.issues.getLabel({
owner,
repo,
name: bucket.name,
});
if (existing.color !== bucket.color) {
await github.rest.issues.updateLabel({
owner,
repo,
name: bucket.name,
color: bucket.color,
});
}
} catch (err) {
if (err.status !== 404) throw err;
await github.rest.issues.createLabel({
owner,
repo,
name: bucket.name,
color: bucket.color,
});
}
const current = pr.labels.map((l) => l.name);
for (const stale of current.filter(
(name) => MANAGED_LABELS.includes(name) && name !== bucket.name
)) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: stale,
});
} catch (err) {
if (err.status !== 404) throw err;
}
}
if (!current.includes(bucket.name)) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [bucket.name],
});
}
@@ -0,0 +1,42 @@
name: Publish Cursor Extension
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
defaults:
run:
working-directory: extensions/cursor
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Package Extension
run: npm run build
- name: Get Extension Filename
id: get_filename
run: echo "filename=$(ls ./*.vsix)" >> "$GITHUB_OUTPUT"
# TODO(OPIK-6617): Add an OVSX_PAT preflight before this step so a bad / expired
# Open VSX token fails fast with an actionable message instead of erroring out
# inside `ovsx publish`. See .github/actions/npm-token-preflight for the pattern.
- name: Publish Extension to Open VSX
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
run: |
npx ovsx publish ./${{ steps.get_filename.outputs.filename }} -p ${{ secrets.OVSX_PAT }}
+161
View File
@@ -0,0 +1,161 @@
name: Publish Opik Helm Chart
run-name: "Publish Opik Helm Chart ${{ github.ref_name }} by @${{ github.actor }}"
on:
workflow_dispatch:
inputs:
version:
type: string
required: true
description: Version
default: ""
workflow_call:
inputs:
version:
type: string
required: true
description: Version
jobs:
publish-helm-chart:
# TODO(OPIK-6617): Add a preflight check that verifies the runner can push to the
# `gh-pages` branch (e.g. `git ls-remote --exit-code` with the same credentials)
# so a missing/expired GH token fails fast instead of after `helm package`.
# See .github/actions/npm-token-preflight for the pattern.
runs-on: ubuntu-latest
timeout-minutes: 15
env:
VERSION: ${{inputs.version}}
steps:
- name: Install Helm
# azure/setup-helm v4.3.1 pinned to commit SHA 1a275c3b69536ee54be43f2070a358922e12c8d4
# Source: https://github.com/Azure/setup-helm/releases/tag/v4.3.1
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4
with:
version: v3.19.4
- name: Validate version input
shell: bash
run: |
set -e
# Validate version input to prevent command injection
if ! echo "$VERSION" | grep -qE '^v?[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?(\+[A-Za-z0-9.-]+)?$'; then
echo "ERROR: Invalid version format: $VERSION"
echo "Expected SemVer format (e.g., v1.2.3, 1.2.3-alpha, 1.2.3+build)"
exit 1
fi
echo "Version validated: $VERSION"
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{inputs.version}}
fetch-tags: true
path: 'src'
fetch-depth: 0
- name: Checkout GH Pages branch
uses: actions/checkout@v6
with:
path: 'dest'
ref: 'gh-pages'
fetch-depth: 0
- name: Run lint on Helm chart
shell: bash
working-directory: src
run: |
set -e
cd deployment/helm_chart/opik
helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm repo add clickhouse-operator https://docs.altinity.com/clickhouse-operator
helm dependency build
helm lint --values values.yaml .
cd -
- name: Package helm chart
shell: bash
working-directory: src
run: |
set -e
cd deployment/helm_chart
helm package --version "$VERSION" --app-version "$VERSION" opik/ -u -d ../../../dest
cd -
echo "Copy updated README"
cp deployment/helm_chart/opik/README.md ../dest/.
- name: Restore file timestamps from git history
shell: bash
working-directory: dest
run: |
set -e
# Set file modification times based on git commit history
# This is important because helm repo index uses file mtime to set the 'created' field
# 1. Set timestamp for the newly packaged chart from source repo tag
NEW_CHART_FILE="opik-${VERSION}.tgz"
if [ -f "$NEW_CHART_FILE" ]; then
NEW_CHART_TIME=$(cd ../src && git log -1 --format="%cI" "$VERSION") || NEW_CHART_TIME=""
if [ -n "$NEW_CHART_TIME" ]; then
touch -d "$NEW_CHART_TIME" "$NEW_CHART_FILE"
echo "Set $NEW_CHART_FILE mtime to $NEW_CHART_TIME (from source tag)"
fi
fi
# 2. Restore timestamps for existing files from gh-pages git history
echo "Restoring file timestamps from git history..."
git ls-files -z | while read -r -d $'\0' file; do
if [ -f "$file" ]; then
TIME=$(git log -1 --format="%cI" -- "$file")
if [ -n "$TIME" ]; then
touch -d "$TIME" "$file"
echo "Set $file mtime to $TIME"
fi
fi
done
echo "File timestamps restored."
- name: Push New Files
shell: bash
working-directory: dest
env:
INDEX_FILE: index.yaml
run: |
set -e
# Run helm repo index which will overwrite created timestamps
helm repo index . --url https://raw.githubusercontent.com/comet-ml/opik/gh-pages/ --merge "${INDEX_FILE}"
# Fix timestamps again - helm repo index overwrites them based on file mtime
echo "=== Fixing timestamps based on file modification times ==="
CHARTS=$(yq eval '.entries[] | .[] | .urls[0]' "${INDEX_FILE}")
for CHART_URL in $CHARTS; do
FILENAME=$(basename "$CHART_URL")
if [ -f "$FILENAME" ]; then
MTIME=$(date -u -r "$FILENAME" +"%Y-%m-%dT%H:%M:%SZ")
echo "Setting $FILENAME timestamp to $MTIME"
yq eval "(.entries[][] | select(.urls[0] == \"$CHART_URL\") | .created) = \"$MTIME\"" -i --indent 2 "${INDEX_FILE}"
fi
done
git config user.name "Helm Updater"
git config user.email "actions@users.noreply.github.com"
git ls-files -o --exclude-standard -z | xargs -0 -r git add
git add "${INDEX_FILE}" README.md
if git diff --staged --quiet; then
echo "::warning title=helm publish::No changes to commit; chart ${VERSION} is already up to date in gh-pages."
else
git commit -m "Release Opik helm chart $VERSION"
git push
fi
- name: Summary
run: |
echo "Helm chart is published, version is : $VERSION" >> "$GITHUB_STEP_SUMMARY"
@@ -0,0 +1,116 @@
name: Python BE E2E Tests
run-name: "Python BE E2E Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
on:
workflow_dispatch:
pull_request:
paths:
- 'apps/opik-python-backend/**'
- '.github/workflows/python_backend_e2e_tests.yml'
push:
branches:
- 'main'
paths:
- 'apps/opik-python-backend/**'
- '.github/workflows/python_backend_e2e_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPIK_URL_OVERRIDE: http://localhost:8080
# The studio job processor streams subprocess logs to the stack's real Redis
# (password-protected, port-mapped to localhost by `opik.sh --port-mapping`).
# Not required for pass/fail (log flushing is best-effort), but without it the
# collector hits localhost:6379 unauthenticated and the log path goes dead.
REDIS_URL: redis://:opik@localhost:6379/0
# The Anthropic key is stored in the workspace by the tests (model-storage
# principle); the optimizer reaches the provider only through the backend
# gateway, never via this directly.
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# Keep the optimization short — we only need it to start, iterate, and finish.
OPTIMIZER_MAX_TRIALS: "1"
OPTSTUDIO_DATASET_SAMPLES: "4"
jobs:
run-python-be-e2e:
name: Python BE E2E
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Run Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
COMPOSE_BAKE: false
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping --build
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Install opik-python-backend
run: |
cd ${{ github.workspace }}/apps/opik-python-backend
pip install -r requirements.txt -r tests/test_requirements.txt
pip install .
- name: Run Python BE E2E tests
run: |
cd ${{ github.workspace }}/apps/opik-python-backend
# Retry transient LLM/network failures so a flaky provider call
# doesn't fail the run; the assertions themselves are deterministic.
# --log-cli-level streams logs live (incl. the subprocess error +
# traceback surfaced by process_optimizer_job) so failures are fully
# visible even though the optimization runs in a child process.
# --durations=0 logs how long each test took.
pytest tests/e2e -vv \
--reruns 2 --reruns-delay 5 \
--log-cli-level=INFO \
--durations=0 \
--junitxml=${{ github.workspace }}/python_be_e2e_results.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Python BE E2E Tests Results
files: ${{ github.workspace }}/python_be_e2e_results.xml
- name: Keep BE logs on failure
if: failure()
run: |
cd ${{ github.workspace }}/deployment/docker-compose
docker compose logs backend > ${{ github.workspace }}/backend.log 2>&1 || true
docker compose logs python-backend > ${{ github.workspace }}/python-backend.log 2>&1 || true
- name: Upload BE logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: python-be-e2e-be-logs
path: |
${{ github.workspace }}/backend.log
${{ github.workspace }}/python-backend.log
if-no-files-found: ignore
@@ -0,0 +1,64 @@
name: "Python Backend Tests"
run-name: "Python Backend Tests on ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
on:
pull_request:
paths:
- "apps/opik-python-backend/**"
- ".github/workflows/python_backend_tests.yml"
push:
branches:
- 'main'
paths:
- "apps/opik-python-backend/**"
- ".github/workflows/python_backend_tests.yml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
run-python-backend-tests:
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: apps/opik-python-backend/
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r tests/test_requirements.txt
pip install .
- name: Run unit tests
env:
PYTHON_CODE_EXECUTOR_STRATEGY: process
PYTHON_CODE_EXECUTOR_EXEC_TIMEOUT_IN_SECS: 10
run: |
cd tests && pytest unit -v --durations=0 --junitxml=../test-results.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Python Backend Tests Results
files: apps/opik-python-backend/test-results.xml
@@ -0,0 +1,159 @@
name: Python SDK Compatibility V1.x E2E Tests
run-name: "Python SDK Compatibility V1 E2E Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
packages: write
on:
workflow_dispatch:
pull_request:
paths:
- 'sdks/python/**'
- 'apps/opik-backend/**'
- '.github/workflows/python_sdk_compatibility_v1_e2e_tests.yml'
push:
branches:
- 'main'
paths:
- 'sdks/python/**'
- 'apps/opik-backend/**'
- '.github/workflows/python_sdk_compatibility_v1_e2e_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_URL_OVERRIDE: http://localhost:8080
OPIK_CONSOLE_LOGGING_LEVEL: DEBUG
jobs:
select-matrix:
name: Select Python version matrix
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
python_versions: ${{ steps.pick.outputs.versions }}
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: .github/actions/select-e2e-matrix
sparse-checkout-cone-mode: false
- uses: ./.github/actions/select-e2e-matrix
id: pick
with:
all-versions: ${{ vars.PYTHON_VERSIONS }}
match-regex: '^sdks/python/|^\.github/workflows/python_sdk_compatibility_v1_e2e_tests\.yml$'
github-token: ${{ secrets.GITHUB_TOKEN }}
build-opik:
uses: ./.github/workflows/build_e2e_docker.yaml
with:
include_guardrails: true
run-compatibility-v1-e2e:
needs: [select-matrix, build-opik]
name: Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python_version: ${{ fromJSON(needs.select-matrix.outputs.python_versions) }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker images from GHCR
env:
TAG: ${{ needs.build-opik.outputs.image_tag }}
run: |
docker pull "ghcr.io/comet-ml/opik/opik-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-python-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-guardrails-backend:$TAG"
docker tag "ghcr.io/comet-ml/opik/opik-backend:$TAG" ghcr.io/comet-ml/opik/opik-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-python-backend:$TAG" ghcr.io/comet-ml/opik/opik-python-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-guardrails-backend:$TAG" ghcr.io/comet-ml/opik/opik-guardrails-backend:latest
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Run latest Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
# Avoid Buildx Bake concurrent image export collisions in CI.
COMPOSE_BAKE: false
TOGGLE_RUNNERS_ENABLED: "true"
TOGGLE_AGENT_CONFIGURATION_ENABLED: "true"
OPIK_BACKEND_PULL_POLICY: never
PYTHON_BACKEND_PULL_POLICY: never
OPIK_GUARDRAILS_BACKEND_PULL_POLICY: never
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping --guardrails
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
echo "Check Docker pods are up"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "Check backend health"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Install opik SDK
run: |
cd ${{ github.workspace }}/sdks/python
pip install .
- name: Install test requirements
run: |
cd ${{ github.workspace }}/sdks/python
pip install -r tests/test_requirements.txt
pip list
- name: Run compatibility v1 tests
run: |
cd ${{ github.workspace }}/sdks/python
pytest tests/e2e/compatibility_v1 -vv --durations=20 --junitxml=${{ github.workspace }}/test_results_compatibility_v1_${{matrix.python_version}}.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Python SDK Compatibility V1 E2E Tests Results (Python ${{matrix.python_version}})
files: ${{ github.workspace }}/test_results_compatibility_v1_${{matrix.python_version}}.xml
- name: Keep BE log in case of failure
if: failure()
run: |
docker logs opik-backend-1 > ${{ github.workspace }}/opik-backend_compat_v1_p${{matrix.python_version}}.log
- name: Attach BE log
if: failure()
uses: actions/upload-artifact@v7
with:
name: opik-backend-log-compat-v1-p${{matrix.python_version}}
path: ${{ github.workspace }}/opik-backend_compat_v1_p${{matrix.python_version}}.log
- name: Stop opik server
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop --guardrails
+172
View File
@@ -0,0 +1,172 @@
name: Python SDK E2E Tests
run-name: "Python SDK E2E Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
packages: write
on:
workflow_dispatch:
pull_request:
paths:
- 'sdks/python/**'
- 'apps/opik-backend/**'
- '.github/workflows/python_sdk_e2e_tests.yml'
push:
branches:
- 'main'
paths:
- 'sdks/python/**'
- 'apps/opik-backend/**'
- '.github/workflows/python_sdk_e2e_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
OPIK_URL_OVERRIDE: http://localhost:8080
OPIK_CONSOLE_LOGGING_LEVEL: INFO
jobs:
select-matrix:
name: Select Python version matrix
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
python_versions: ${{ steps.pick.outputs.versions }}
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: .github/actions/select-e2e-matrix
sparse-checkout-cone-mode: false
- uses: ./.github/actions/select-e2e-matrix
id: pick
with:
all-versions: ${{ vars.PYTHON_VERSIONS }}
match-regex: '^sdks/python/|^\.github/workflows/python_sdk_e2e_tests\.yml$'
github-token: ${{ secrets.GITHUB_TOKEN }}
build-opik:
uses: ./.github/workflows/build_e2e_docker.yaml
with:
include_guardrails: true
run-e2e:
needs: [select-matrix, build-opik]
name: Python SDK E2E Tests ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python_version: ${{ fromJSON(needs.select-matrix.outputs.python_versions) }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker images from GHCR
env:
TAG: ${{ needs.build-opik.outputs.image_tag }}
run: |
docker pull "ghcr.io/comet-ml/opik/opik-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-python-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-guardrails-backend:$TAG"
docker tag "ghcr.io/comet-ml/opik/opik-backend:$TAG" ghcr.io/comet-ml/opik/opik-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-python-backend:$TAG" ghcr.io/comet-ml/opik/opik-python-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-guardrails-backend:$TAG" ghcr.io/comet-ml/opik/opik-guardrails-backend:latest
- name: Setup Python ${{matrix.python_version}}
uses: actions/setup-python@v5
with:
python-version: ${{matrix.python_version}}
- name: Run latest Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
# Avoid Buildx Bake concurrent image export collisions in CI.
COMPOSE_BAKE: false
TOGGLE_RUNNERS_ENABLED: "true"
OPIK_BACKEND_PULL_POLICY: never
PYTHON_BACKEND_PULL_POLICY: never
OPIK_GUARDRAILS_BACKEND_PULL_POLICY: never
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping --guardrails
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
echo "Check Docker pods are up"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "Check backend health"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Install opik SDK
run: |
cd ${{ github.workspace }}/sdks/python
pip install .
- name: Run smoke tests
run: |
cd ${{ github.workspace }}/sdks/python/tests/e2e_smoke
./smoke_tests_runner.sh
- name: Install test requirements
run: |
cd ${{ github.workspace }}/sdks/python
pip install -r tests/test_requirements.txt
pip list
- name: Run tests
# -n 3 + --dist=loadfile distributes whole test files across three
# worker processes (one file per worker). Past ~3 the gains
# flatten — we become tail-bound on the slowest single file and
# the docker-compose backend on the same runner starts to thrash.
# E2E isolation hinges on --dist=loadfile: the per-module project
# name (generate_project_name("e2e", __name__)) is only collision-
# free if a single file is not split across workers.
# -p no:benchmark silences pytest-benchmark's auto-disable warning
# under xdist.
run: |
cd ${{ github.workspace }}/sdks/python
pytest tests/e2e --ignore=tests/e2e/test_guardrails.py --ignore=tests/e2e/compatibility_v1 -vv -n 3 --dist=loadfile -p no:benchmark --durations=20 --junitxml=${{ github.workspace }}/test_results_${{matrix.python_version}}.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Python SDK E2E Tests Results (Python ${{matrix.python_version}})
files: ${{ github.workspace }}/test_results_${{matrix.python_version}}.xml
- name: Keep BE log in case of failure
if: failure()
run: |
docker logs opik-backend-1 > ${{ github.workspace }}/opik-backend_p${{matrix.python_version}}.log
- name: Attach BE log
if: failure()
uses: actions/upload-artifact@v7
with:
name: opik-backend-log-p${{matrix.python_version}}
path: ${{ github.workspace }}/opik-backend_p${{matrix.python_version}}.log
- name: Stop opik server
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop --guardrails
@@ -0,0 +1,81 @@
name: Python SDK Unit Tests
run-name: "Python SDK Unit Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
on:
pull_request:
paths:
- 'sdks/python/**'
- '.github/workflows/python_sdk_unit_tests.yml'
push:
branches:
- 'main'
paths:
- 'sdks/python/**'
- '.github/workflows/python_sdk_unit_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
jobs:
UnitTests:
name: Python SDK Unit Tests ${{matrix.python_version}}
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: sdks/python/
strategy:
fail-fast: false
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS || '["3.10","3.11","3.12","3.13","3.14"]') }}
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Print environment variables
run: env
- name: Print event object
run: cat "$GITHUB_EVENT_PATH"
- name: Print the PR title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "$PR_TITLE"
- name: Setup Python ${{ matrix.python_version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
- name: Install opik
run: pip install .
- name: Install test requirements
run: |
cd ./tests
pip install --no-cache-dir --disable-pip-version-check -r test_requirements.txt -r ./unit/test_requirements.txt
pip list
- name: Running SDK Unit Tests
# Per-test timeout (thread method dumps all thread stacks via faulthandler)
# so a hung test fails fast with a traceback instead of a silent 20-min cancel.
run: python -m pytest -vv tests/unit/ --timeout=120 --timeout-method=thread --junitxml=${{ github.workspace }}/test_results.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: Python SDK Unit Tests Results (Python ${{matrix.python_version}})
files: ${{ github.workspace }}/test_results.xml
@@ -0,0 +1,77 @@
name: Quickstart Guide Snippets Tests
env:
OPIK_SENTRY_ENABLE: False
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
ALLURE_JOB_RUN_ID: ${{ github.event.inputs.ALLURE_JOB_RUN_ID }}
ALLURE_ENDPOINT: https://comet.testops.cloud/
ALLURE_PROJECT_ID: 1
ALLURE_RESULTS: allure-results
on:
schedule:
- cron: '0 12 * * *' # runs every day at noon UTC
workflow_dispatch:
inputs:
ALLURE_JOB_RUN_ID:
type: string
required: false
description: Allure job run ID for grouping test reports
default: ''
run-name: Quickstart Guide Snippets Tests
jobs:
run_tests:
name: "Quickstart Guide Snippets Tests"
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repo
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install Opik
run: pip install opik
- name: Install Test Dependencies
run: |
pip install -r ${{ github.workspace }}/tests_end_to_end/test_requirements.txt
pip install -r ${{ github.workspace }}/tests_end_to_end/integrations_requirements.txt
pip install allure-pytest
playwright install
- name: Install allurectl
uses: allure-framework/setup-allurectl@v1
- name: Install Opik (Local)
env:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}
./opik.sh
- name: Run suite
env:
OPIK_BASE_URL: http://localhost:5173
OPIK_TEST_WORKSPACE: default
OPIK_TEST_PROJECT_NAME: Quickstart
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
cd ${{ github.workspace }}/tests_end_to_end
export PYTHONPATH='.'
allurectl watch -- pytest -s tests/QuickstartGuide --alluredir="${ALLURE_RESULTS}" --browser chromium
- name: Stop Opik server (Local)
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop
+21
View File
@@ -0,0 +1,21 @@
name: Release Drafter
on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- main
jobs:
update_release_draft:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/release-drafter@v7
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
# with:
# config-name: my-config.yml
# disable-autolabeler: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,82 @@
name: "Release with Release-n-Deploy-Agent"
run-name: "Release from ${{ github.ref_name }}"
on:
workflow_dispatch:
inputs:
resumeUrl:
description: 'URL to call to resume release n deploy'
required: true
trackingId:
description: 'UUID for tracking this release step'
required: true
type: string
reuse_existing_tag:
description: 'Reuse an existing git tag for this version instead of creating + pushing a new one. Use to replay a release whose tag was already created (e.g. by a prior failed run).'
required: false
type: boolean
default: false
jobs:
trigger-release:
uses: ./.github/workflows/release.yaml # Call the actual release workflow
secrets: inherit
with:
reuse_existing_tag: ${{ inputs.reuse_existing_tag }}
notify-release-n-deploy-agent:
runs-on: ubuntu-latest
timeout-minutes: 90
needs: trigger-release
if: ${{ always() }}
steps:
- name: Determine release status
id: status
run: |
if [ "${{ needs.trigger-release.result }}" == "success" ]; then
echo "status=completed" >> "$GITHUB_OUTPUT"
echo "conclusion=success" >> "$GITHUB_OUTPUT"
else
echo "status=completed" >> "$GITHUB_OUTPUT"
echo "conclusion=failure" >> "$GITHUB_OUTPUT"
fi
- name: Notify Release-n-Deploy-Agent
if: ${{ inputs.resumeUrl != '' }}
run: |
echo "All inputs: ${{ toJSON(inputs) }}"
echo "resumeUrl input: '${{ inputs.resumeUrl }}'"
# Get actual release outputs
VERSION="${{ needs.trigger-release.outputs.version }}"
STATUS="${{ steps.status.outputs.status }}"
CONCLUSION="${{ steps.status.outputs.conclusion }}"
echo "📦 Release completed with version: $VERSION"
echo "Status: $STATUS, Conclusion: $CONCLUSION"
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"release": {
"id": "${{ github.run_id }}",
"releaseTag": "'$VERSION'"
},
"trackingId": "${{ inputs.trackingId }}",
"workflow_run": {
"id": "${{ github.event.workflow_run.id }}",
"name": "${{ github.event.workflow_run.name }}",
"status": "'$STATUS'",
"conclusion": "'$CONCLUSION'",
"run_started_at": "${{ github.event.workflow_run.run_started_at }}",
"updated_at": "${{ github.event.workflow_run.updated_at }}",
"html_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
},
"repository": {
"full_name": "${{ github.repository }}"
},
"sender": {
"login": "${{ github.actor }}"
}
}' \
${{ inputs.resumeUrl }}
+259
View File
@@ -0,0 +1,259 @@
# Internal release
name: "Release"
run-name: "Release from ${{github.ref_name}} by @${{ github.actor }}"
on:
workflow_dispatch:
inputs:
reuse_existing_tag:
type: boolean
required: false
default: false
description: "Reuse an existing git tag for this version instead of creating + pushing a new one. Use to replay a release whose tag was already created (e.g. by a prior failed run)."
workflow_call: # in order to make this action been called from outside
inputs:
reuse_existing_tag:
type: boolean
required: false
default: false
description: "Reuse an existing git tag for this version instead of creating + pushing a new one."
outputs:
version:
description: 'The release version'
value: ${{ jobs.set-version.outputs.version }}
jobs:
set-version:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set version
id: version
run: |
echo "version=$(cat version.txt)" | tee -a "$GITHUB_OUTPUT"
create-git-tag:
needs:
- set-version
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Create git tag
env:
RELEASE_VERSION: ${{ needs.set-version.outputs.version }}
REUSE_EXISTING_TAG: ${{ inputs.reuse_existing_tag }}
run: |
set -x
git config --local user.email "github-actions@comet.com"
git config --local user.name "github-actions"
if [ "${REUSE_EXISTING_TAG}" = "true" ]; then
# Resolve the SHA the remote tag points to and surface it in the
# warning. `reuse_existing_tag=true` is an operator trust statement
# ("I trust this tag"). Printing the SHA lets the operator sanity-
# check from the Actions log that the tag points to the commit they
# expect — and cancel the run if it doesn't — without us having to
# take a stance on what 'correct' means here (e.g., an ancestor
# check would falsely reject legitimate replays after a main
# rebase). See PR #7124 review thread for the threat-model
# discussion.
TAG_SHA=$(git ls-remote --tags origin "${RELEASE_VERSION}" | awk '{print $1}')
if [ -n "${TAG_SHA}" ]; then
echo "::warning title=create-git-tag::reuse_existing_tag=true and origin already has tag ${RELEASE_VERSION} (commit ${TAG_SHA}); skipping create+push. Build will pin to this commit — verify it's the source you intend to release."
exit 0
fi
echo "::error title=create-git-tag::reuse_existing_tag=true but origin does not have tag ${RELEASE_VERSION}. Refusing to silently create a new one."
exit 1
fi
git tag "${RELEASE_VERSION}"
git push --no-verify origin "${RELEASE_VERSION}"
release-apps:
needs:
- set-version
- create-git-tag
uses: ./.github/workflows/build_apps.yml
secrets: inherit
with:
version: ${{needs.set-version.outputs.version}}
is_release: true
# Caller-passes ref: build_apps.yml already exposes a ref input (defaults to inputs.ref || github.ref).
# TS SDK workflows in OPIK-6627 used callee-derives because they lacked one. See OPIK-6633.
ref: refs/tags/${{needs.set-version.outputs.version}}
release-sdk:
needs:
- set-version
- create-git-tag
uses: ./.github/workflows/build_and_publish_sdk.yaml
secrets: inherit
with:
version: ${{needs.set-version.outputs.version}}
is_release: true
release-typescript-sdk:
needs:
- set-version
- create-git-tag
uses: ./.github/workflows/typescript_sdk_publish.yml
secrets: inherit
with:
version: ${{needs.set-version.outputs.version}}
is_release: true
skip_commit_push: true
release-typescript-sdk-integrations:
needs:
- set-version
- create-git-tag
uses: ./.github/workflows/typescript_sdk_integration_publish.yml
secrets: inherit
with:
version: ${{needs.set-version.outputs.version}}
is_release: true
skip_commit_push: true
publish-helm-chart:
needs:
- set-version
- create-git-tag
uses: ./.github/workflows/publish_helm_chart.yaml
secrets: inherit
with:
version: ${{needs.set-version.outputs.version}}
create-github-release:
needs:
- set-version
- create-git-tag
- release-apps
- release-sdk
- release-typescript-sdk
- release-typescript-sdk-integrations
- publish-helm-chart
runs-on: ubuntu-latest
steps:
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{needs.set-version.outputs.version}}
generate_release_notes: true
- name: Delete Release Draft
uses: hugo19941994/delete-draft-releases@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
commit-all-version-changes:
needs:
- set-version
- release-typescript-sdk
- release-typescript-sdk-integrations
- publish-helm-chart
- create-github-release
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GH_PAT_TO_ACCESS_GITHUB_API }}
- name: Setup Node.js for TypeScript version update
uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Update TypeScript SDK version
shell: bash
working-directory: sdks/typescript
run: |
npm version ${{ needs.set-version.outputs.version }} --no-git-tag-version
- name: Update TypeScript SDK Integrations versions
shell: bash
run: |
for integration in opik-openai opik-gemini opik-langchain opik-vercel opik-otel; do
if [ -d "sdks/typescript/src/opik/integrations/$integration" ]; then
echo "Updating version for $integration"
cd "sdks/typescript/src/opik/integrations/$integration"
npm version ${{ needs.set-version.outputs.version }} --no-git-tag-version
cd - > /dev/null
fi
done
- name: Update Chart.yaml with release version
shell: bash
run: |
set -x
cd deployment/helm_chart/opik
sed -i "s/^version:.*/version: ${{ needs.set-version.outputs.version }}/" Chart.yaml
sed -i "s/^appVersion:.*/appVersion: ${{ needs.set-version.outputs.version }}/" Chart.yaml
echo "Updated Chart.yaml version and appVersion to: ${{ needs.set-version.outputs.version }}"
grep -E "^version:|^appVersion:" Chart.yaml
cd -
# The chart README badges are derived from Chart.yaml by helm-docs, so
# regenerate here (git-push: false) to fold the update into the version
# commit below. Without this the README lags one release and the next
# chart-touching PR trips the helm-docs / update_helm_readme.yaml gate.
# Same action + inputs as update_helm_readme.yaml, keeping CI and the
# local pre-commit hook in agreement.
- name: Regenerate Helm chart README
uses: losisin/helm-docs-github-action@v1
with:
chart-search-root: deployment/helm_chart/opik
git-push: false
- name: Bump version.txt for next release
id: update_version
shell: bash
run: |
set -x
BASE_VERSION=$(tr -d '\r' < version.txt | xargs)
IFS='.' read -r -a version_parts <<< "$BASE_VERSION"
# Ensure all version parts are set
for i in {0..2}; do
version_parts[i]=${version_parts[i]:-0}
done
# Convert to decimal before incrementing to avoid octal interpretation issues
version_parts[2]=$((10#${version_parts[2]} + 1))
new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}"
# Update version file with the new version
echo "$new_version" > version.txt
echo "new_version=${new_version}" >> "$GITHUB_OUTPUT"
- name: Commit all version changes
run: |
git config --local user.email "github-actions@comet.com"
git config --local user.name "github-actions"
# Add all modified version files
git add sdks/typescript/package.json sdks/typescript/package-lock.json
git add sdks/typescript/src/opik/integrations/*/package.json sdks/typescript/src/opik/integrations/*/package-lock.json
git add deployment/helm_chart/opik/Chart.yaml
git add deployment/helm_chart/opik/README.md
git add version.txt
# Check if there are any changes to commit
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "Update versions to ${{ needs.set-version.outputs.version }} and bump base version to ${{ steps.update_version.outputs.new_version }}"
git push origin main
fi
echo "Version updated to ${{ steps.update_version.outputs.new_version }} on main" >> "$GITHUB_STEP_SUMMARY"
+37
View File
@@ -0,0 +1,37 @@
name: Scout Feedback Sync
on:
# GitHub emits no event for comment reactions, so poll on a schedule.
schedule:
- cron: '0 * * * *' # hourly
# Allow manual runs (and an optional wider scan window for backfills).
workflow_dispatch:
inputs:
since_days:
description: How many days back to scan issues for reactions
required: false
default: '7'
type: string
# Don't let overlapping syncs double up.
concurrency:
group: scout-feedback-sync
cancel-in-progress: false
jobs:
sync-feedback:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
issues: read
contents: read
steps:
- name: Sync reactions to Opik
uses: comet-ml/scout-repo-agent/actions/feedback@main
with:
github_token: ${{ github.token }}
since_days: ${{ github.event.inputs.since_days || '7' }}
env:
OPIK_API_KEY: ${{ secrets.SCOUT_OPIK_API_KEY }}
OPIK_WORKSPACE: ${{ vars.SCOUT_OPIK_WORKSPACE }}
+38
View File
@@ -0,0 +1,38 @@
name: Scout Issue Triage
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: Issue number to triage
required: true
type: number
# One Scout run per issue at a time
concurrency:
group: scout-issue-${{ github.event.issue.number || github.event.inputs.issue_number }}
cancel-in-progress: false
jobs:
triage:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
issues: write
contents: read
steps:
- name: Run Scout
uses: comet-ml/scout-repo-agent@main
with:
anthropic_api_key: ${{ secrets.SCOUT_ANTHROPIC_API_KEY }}
github_token: ${{ github.token }}
env:
SCOUT_ESCALATION_TAG: ${{ vars.SCOUT_ESCALATION_TAG }}
SCOUT_GITHUB_REPO_OWNER: ${{ vars.SCOUT_GITHUB_REPO_OWNER }}
SCOUT_GITHUB_REPO_NAME: ${{ vars.SCOUT_GITHUB_REPO_NAME }}
OPIK_API_KEY: ${{ secrets.SCOUT_OPIK_API_KEY }}
OPIK_WORKSPACE: ${{ vars.SCOUT_OPIK_WORKSPACE }}
ISSUE_NUMBER: ${{ github.event.issue.number || github.event.inputs.issue_number }}
@@ -0,0 +1,183 @@
# The workflow to run tests for third-party libraries integrations that require OPIK server running
#
name: SDK E2E Libraries Integration Tests
permissions:
contents: read
packages: write
run-name: "SDK E2E Libraries Integration Tests ${{ github.ref_name }} by @${{ github.actor }}"
on:
workflow_dispatch:
inputs:
libs:
description: "Choose specific library to test against or all"
required: true
type: choice
options:
- all
- adk
- litellm
- opentelemetry
- pydantic_ai
schedule:
- cron: "0 0 */2 * *"
pull_request:
paths:
- 'sdks/python/**'
- '.github/workflows/sdk-e2e-library-tests.yaml'
push:
branches:
- 'main'
paths:
- 'sdks/python/**'
- '.github/workflows/sdk-e2e-library-tests.yaml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: True # unlike other workflows, here we need it for tests
OPIK_SENTRY_ENABLE: False
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
LIBS: ${{ github.event.inputs.libs != '' && github.event.inputs.libs || 'all' }}
OPIK_URL_OVERRIDE: http://localhost:8080
jobs:
has_needed_secrets:
name: Check Secrets
runs-on: ubuntu-latest
outputs:
has_secrets: ${{ steps.init.outputs.has_secrets }}
steps:
- name: Print has secrets into output
id: init
run: |
echo "has_secrets=${{ secrets.OPENAI_API_KEY != '' }}" >> "$GITHUB_OUTPUT"
missed_api_key_warning:
name: Missed OpenAI API Key Warning
needs: [has_needed_secrets]
runs-on: ubuntu-latest
if: ${{ needs.has_needed_secrets.outputs.has_secrets == 'false' }}
steps:
- name: Print disabled message
run: |
echo "::warning::SDK E2E Libraries Integration Tests workflow is disabled because OPENAI_API_KEY is not set"
build-opik:
needs: [has_needed_secrets]
if: ${{ needs.has_needed_secrets.outputs.has_secrets == 'true' }}
uses: ./.github/workflows/build_e2e_docker.yaml
run_e2e_lib_integration:
name: E2E Lib Integration Python ${{matrix.python_version}}
needs: [has_needed_secrets, build-opik]
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ needs.has_needed_secrets.outputs.has_secrets == 'true' }}
strategy:
fail-fast: false
matrix:
python_version: ${{ fromJSON(vars.PYTHON_VERSIONS) }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker images from GHCR
env:
TAG: ${{ needs.build-opik.outputs.image_tag }}
run: |
docker pull "ghcr.io/comet-ml/opik/opik-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-python-backend:$TAG"
docker tag "ghcr.io/comet-ml/opik/opik-backend:$TAG" ghcr.io/comet-ml/opik/opik-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-python-backend:$TAG" ghcr.io/comet-ml/opik/opik-python-backend:latest
- name: Install uv and set the Python version ${{ matrix.python_version }}
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python_version }}
- name: Make LIBS variable global (workaround for cron)
id: init
run: |
echo "LIBS=${{ env.LIBS }}" >> "$GITHUB_OUTPUT"
- name: Run latest Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
# Avoid Buildx Bake concurrent image export collisions in CI.
COMPOSE_BAKE: false
OPIK_BACKEND_PULL_POLICY: never
PYTHON_BACKEND_PULL_POLICY: never
shell: bash
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
echo "Check Docker pods are up"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "Check backend health"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS_JSON }}'
- name: ADK Tests Python ${{ matrix.python_version }}
if: contains(fromJSON('["adk", "all"]'), env.LIBS)
env:
GOOGLE_CLOUD_LOCATION: global
GOOGLE_CLOUD_PROJECT: opik-sdk-tests
GOOGLE_GENAI_USE_VERTEXAI: TRUE
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
uses: ./.github/actions/install_opik_and_run_e2e_lib_integration_tests
with:
python_version: ${{ matrix.python_version }}
library_name: adk
# This workflow is used to test the external LiteLLM integration that lives in litellm repository.
# However, there is another, internal, integration with LiteLLM that lives in opik.integrations.litellm, it's being tested
# in a separate workflow: .github/workflows/lib-litellm-tests.yml
- name: LiteLLM Tests Python ${{ matrix.python_version }}
if: contains(fromJSON('["litellm", "all"]'), env.LIBS)
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
uses: ./.github/actions/install_opik_and_run_e2e_lib_integration_tests
with:
python_version: ${{ matrix.python_version }}
library_name: litellm
- name: OpenTelemetry Tests Python ${{ matrix.python_version }}
if: contains(fromJSON('["opentelemetry", "all"]'), env.LIBS)
uses: ./.github/actions/install_opik_and_run_e2e_lib_integration_tests
with:
python_version: ${{ matrix.python_version }}
library_name: opentelemetry
- name: PydanticAI Tests Python ${{ matrix.python_version }}
if: contains(fromJSON('["pydantic_ai", "all"]'), env.LIBS)
uses: ./.github/actions/install_opik_and_run_e2e_lib_integration_tests
with:
python_version: ${{ matrix.python_version }}
library_name: pydantic_ai
- name: Stop opik server
run: |
cd ${{ github.workspace }}
./opik.sh --stop
@@ -0,0 +1,109 @@
name: SDKs Generate OpenAPI spec and Fern code
on:
schedule:
- cron: '0 0 * * 1-5' # Runs at midnight UTC, Monday to Friday
workflow_dispatch: # Allows manual trigger
concurrency:
group: fern-auto-update
cancel-in-progress: true
jobs:
update-open-api-spec-and-fern-code:
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 15
steps:
- name: Checkout Repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'corretto'
cache: maven
- name: Install Fern
run: npm install -g fern-api@4.71.5
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install Opik Optimizer SDK (for Fern docs)
run: |
python -m pip install --upgrade pip
pip install -e sdks/opik_optimizer
- name: Set branch name
run: |
echo "BRANCH_NAME=github-actions/NA-automatically-update-openapi-spec-and-fern-code" >> "$GITHUB_ENV"
- name: Run Generate OpenAPI spec and Fern code
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
run: |
set -e
cd apps/opik-backend
mvn clean
cd -
./scripts/generate_openapi.sh
- name: Generate Opik Optimizer Fern docs
run: |
python sdks/opik_optimizer/scripts/generate_fern_docs.py --write
- name: Check for changes
id: check_changes
run: |
if git diff --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Commit files
if: steps.check_changes.outputs.has_changes == 'true'
run: |
set -ex
git config --local user.email "github-actions@comet.com"
git config --local user.name "Github Actions (${{ github.actor }})"
git add sdks/python/src/opik/rest_api/
git add sdks/typescript/src/opik/rest_api/
git add sdks/code_generation/fern/openapi/openapi.yaml
git add apps/opik-documentation/documentation/fern/openapi/opik.yaml
git add apps/opik-documentation/documentation/fern/docs/agent_optimization/opik_optimizer/reference.mdx
git commit -m "[NA] [SDK] [DOCS] Update automatically OpenAPI spec and Fern code"
- name: Create Pull Request
if: steps.check_changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.COMET_ACTION_CREATE_PR }}
branch: ${{ env.BRANCH_NAME }}
title: "[NA] [SDK] [DOCS] Update automatically OpenAPI spec and Fern code"
body: |
## Details
Automated daily update of OpenAPI spec and Fern code after running `./scripts/generate_openapi.sh` and `./sdks/opik_optimizer/scripts/generate_fern_docs.py`.
## Change checklist
- [ ] User facing changes
- [X] Documentation updated
## Issues
N/A
## Testing
- Passed CI
## Documentation
- https://buildwithfern.com/learn/cli-api-reference/cli-reference/commands#fern-generate
base: main
@@ -0,0 +1,71 @@
name: Update Span Cost Daily
on:
schedule:
- cron: '0 0 * * 1-5' # Runs at midnight UTC, Monday to Friday
workflow_dispatch: # Allows manual trigger
jobs:
update-span-cost:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout Repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set branch name and update the file
run: |
echo "BRANCH_NAME=github-actions/NA-automatically-update-model-prices-file-$(date +%Y-%m-%d-%H-%M-%S)" >> "$GITHUB_ENV"
curl -o apps/opik-backend/src/main/resources/model_prices_and_context_window.json https://raw.githubusercontent.com/BerriAI/litellm/refs/heads/main/model_prices_and_context_window.json
cp apps/opik-backend/src/main/resources/model_prices_and_context_window.json apps/opik-frontend/src/data/model_prices_and_context_window.json
- name: Check for changes
id: check_changes
run: |
if git diff --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Commit files
if: steps.check_changes.outputs.has_changes == 'true'
run: |
set -ex
git config --local user.email "github-actions@comet.com"
git config --local user.name "Github Actions (${{ github.actor }})"
git add apps/opik-backend/src/main/resources/model_prices_and_context_window.json
git add apps/opik-frontend/src/data/model_prices_and_context_window.json
git commit -m "[NA] [BE] Update model prices file"
- name: Create Pull Request
if: steps.check_changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.COMET_ACTION_CREATE_PR }}
branch: ${{ env.BRANCH_NAME }}
title: "[NA] [BE] Update model prices file"
body: |
## Details
Automated update of `model_prices_and_context_window.json` file and regeneration of supported models documentation.
**Files updated:**
- `apps/opik-backend/src/main/resources/model_prices_and_context_window.json` - Latest model pricing data from LiteLLM
- `apps/opik-frontend/src/data/model_prices_and_context_window.json` - Frontend copy kept in sync with backend
## Change checklist
- [X] User facing change
- [X] Documentation update
## Issues
N/A
## Testing
- Passed CI
## Documentation
- https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json
base: main
+174
View File
@@ -0,0 +1,174 @@
name: Sync Provider Models Daily
on:
schedule:
- cron: '30 0 * * 1-5' # 30 min after model prices sync, Monday to Friday
workflow_dispatch:
inputs:
force_regen:
description: 'Regenerate and re-upload YAML even if no new models were found'
type: boolean
required: false
default: false
permissions:
contents: write
pull-requests: write
jobs:
sync-models:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install requests
- name: Set branch name
run: |
echo "BRANCH_NAME=github-actions/NA-sync-provider-models-$(date +%Y-%m-%d-%H-%M-%S)" >> "$GITHUB_ENV"
- name: Run sync script
id: sync
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
FORCE_REGEN_FLAG: ${{ inputs.force_regen == true && '--force-regen' || '' }}
run: |
set +e -o pipefail
# $FORCE_REGEN_FLAG is intentionally unquoted: it holds either
# `--force-regen` (a single flag) or the empty string. Quoting it
# would pass a literal empty argument to python, which is harmless
# here but conceptually wrong — the variable is acting as an
# optional argv slot, not a single value.
# shellcheck disable=SC2086
python scripts/sync_provider_models.py $FORCE_REGEN_FLAG 2>sync_stderr.txt | tee sync_summary.txt
EXIT_CODE=${PIPESTATUS[0]}
set -e
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
# Capture summary for PR body (multiline output)
{
echo "summary<<EOF"
cat sync_summary.txt
echo "EOF"
} >> "$GITHUB_OUTPUT"
if [ "$EXIT_CODE" -eq 1 ]; then
echo "No new models found, skipping PR creation."
fi
rm -f sync_summary.txt sync_stderr.txt
- name: Print summary
if: always() && steps.sync.outputs.summary != ''
run: |
{
echo "## Sync Summary"
echo '```'
echo "${{ steps.sync.outputs.summary }}"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Check for changes
if: steps.sync.outputs.exit_code == '0'
id: check_changes
run: |
if git diff --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Commit files
if: steps.check_changes.outputs.has_changes == 'true'
run: |
set -ex
git config --local user.email "github-actions@comet.com"
git config --local user.name "Github Actions (${{ github.actor }})"
git add apps/opik-backend/src/main/java/com/comet/opik/infrastructure/llm/
git add apps/opik-frontend/src/types/providers.ts
git add apps/opik-frontend/src/constants/providerModels.ts
git add apps/opik-backend/src/main/resources/llm-models-default.yaml
git commit -m "[NA] [BE][FE] chore: sync provider model definitions"
- name: Configure AWS credentials
if: steps.sync.outputs.exit_code == '0'
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_OPIK_CDN_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_OPIK_CDN_SECRET }}
aws-region: us-east-1
role-to-assume: ${{ vars.AWS_OPIK_CDN_ROLE }}
role-session-name: sync-provider-models
- name: Upload YAML to S3
if: steps.sync.outputs.exit_code == '0'
run: |
aws s3 cp \
apps/opik-backend/src/main/resources/llm-models-default.yaml \
${{ vars.AWS_OPIK_CDN_BUCKET }}/llm-models-default.yaml \
--cache-control "max-age=300"
- name: Invalidate CloudFront distribution
if: steps.sync.outputs.exit_code == '0'
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.AWS_OPIK_CDN_DISTRIBUTION }} \
--paths "/opik/llm-models-default.yaml"
- name: Create Pull Request
if: steps.check_changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.COMET_ACTION_CREATE_PR }}
branch: ${{ env.BRANCH_NAME }}
title: "[NA] [BE][FE] chore: sync provider model definitions"
body: |
## Details
Automated sync of LLM provider model definitions from source APIs and prices JSON.
**Sync summary:**
```
${{ steps.sync.outputs.summary }}
```
## Change checklist
- [x] User facing
- [ ] Documentation update
## Issues
- NA
## AI-WATERMARK
AI-WATERMARK: yes
- If yes:
- Tools: GitHub Actions (`sync_provider_models.yml`)
- Model(s): N/A (scripted automation)
- Scope: Automated model list sync
- Human verification: Requires manual review before merge
## Testing
- Script dry-run passed in CI
- Changes are add-only; stale/deprecated models flagged for manual review
## Documentation
N/A
base: main
+62
View File
@@ -0,0 +1,62 @@
name: Check Documentation Links
env:
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
ALLURE_JOB_RUN_ID: ${{ github.event.inputs.ALLURE_JOB_RUN_ID }}
ALLURE_ENDPOINT: https://comet.testops.cloud/
ALLURE_PROJECT_ID: 1
ALLURE_RESULTS: allure-results
on:
schedule:
- cron: '0 12 * * *' # Run daily at noon UTC
workflow_dispatch:
inputs:
ALLURE_JOB_RUN_ID:
description: Allure run ID to attach test results to
required: false
type: string
jobs:
check-links:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install pytest and allure
run: |
pip install pytest allure-pytest
- name: Install allurectl
uses: allure-framework/setup-allurectl@v1
- name: Install Test Dependencies
run: |
pip install -r ${{ github.workspace }}/tests_end_to_end/test_requirements.txt
pip install opik
playwright install chromium
- name: Install linkinator
run: |
npm install linkinator
- name: Run linkinator
run: |
npx linkinator https://www.comet.com/docs/opik/ --recurse --format csv > ${{ github.workspace }}/tests_end_to_end/tests/Documentation/output.csv
- name: Check for broken Comet links
run: |
cd ${{ github.workspace }}/tests_end_to_end
export PYTHONPATH='.'
allurectl watch -- pytest -s tests/Documentation/test_links.py --browser chromium --alluredir="${ALLURE_RESULTS}"
@@ -0,0 +1,188 @@
name: Trigger Test Environment Deployment
run-name: ${{ github.event.label.name == 'test-environment' && format('Trigger Test Environment Deployment for PR {0} by {1}', github.event.number, github.actor) || 'Test Environment Not Triggered' }}
on:
pull_request:
types: [labeled]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
packages: write
jobs:
other-labels:
if: github.event_name == 'pull_request' && github.event.label.name != 'test-environment'
runs-on: ubuntu-latest
steps:
- name: Skip
run: |
echo "Skipping deployment of test environment for this PR"
start:
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'test-environment'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get-tag.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref }}
- name: Get latest tag from main at merge-base
id: get-tag
run: |
# Fetch main branch
git fetch origin main
# Find the merge-base (where this branch diverged from main)
MERGE_BASE=$(git merge-base HEAD origin/main)
echo "Merge base: $MERGE_BASE"
# Get the tag at or before the merge-base
BASE_TAG=$(git describe --tags --abbrev=0 "$MERGE_BASE" 2>/dev/null || echo "")
if [ -z "$BASE_TAG" ]; then
echo "❌ No tag found at merge-base"
exit 1
fi
echo "The tag at merge-base: $BASE_TAG"
echo "version=${BASE_TAG}" | tee -a "$GITHUB_OUTPUT"
echo "**Base Version**: \`${BASE_TAG}\`" >> "$GITHUB_STEP_SUMMARY"
- name: Comment on PR - Deployment Started
uses: actions/github-script@v7
# HEAD_REF must be passed via env: — interpolating `${{ github.event.pull_request.head.ref }}`
# directly into the script body would let a crafted branch name break out of the string
# context. See: https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
BASE_VERSION: ${{ steps.get-tag.outputs.version }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: Number('${{ github.event.number }}'),
owner: context.repo.owner,
repo: context.repo.repo,
body: `🔄 **Test environment deployment process has started**
**Phase 1**: Deploying base version \`${process.env.BASE_VERSION}\` (from main branch) if environment doesn't exist
**Phase 2**: Building new images from PR branch \`${process.env.HEAD_REF}\`
**Phase 3**: Will deploy newly built version after build completes
You can monitor the progress [here](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).`
});
build-apps:
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'test-environment'
needs:
- start
uses: ./.github/workflows/build_apps.yml
secrets: inherit
with:
version: ""
is_release: false
is_adhoc: true
build_guardrails: false
ref: ${{ github.event.pull_request.head.ref }}
trigger-deploy-base-version:
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'test-environment'
needs:
- start
runs-on: ubuntu-latest
steps:
- name: Trigger deployment with base version
uses: actions/github-script@v7
env:
VERSION_NAME: ${{ needs.start.outputs.version }}
with:
github-token: ${{ secrets.DEPLOYMENT_TRIGGER_TOKEN }}
script: |
const response = await github.rest.repos.createDispatchEvent({
owner: 'comet-ml',
repo: 'comet-deployment',
event_type: 'deploy-opik-test-env',
client_payload: {
pr_number: '${{ github.event.number }}',
version_name: process.env.VERSION_NAME,
is_base_version: true
}
});
console.log('✅ Repository dispatch sent successfully:', response.status);
console.log('📦 Deploying base version:', process.env.VERSION_NAME);
console.log('⚠️ Will skip if namespace already exists');
console.log('📊 Monitor deployment progress: https://github.com/comet-ml/comet-deployment/actions/workflows/deploy_opik_adhoc_env.yaml');
console.log('🔗 Deployment will verify the version');
- name: Handle trigger failure
if: failure()
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: Number('${{ github.event.number }}'),
owner: context.repo.owner,
repo: context.repo.repo,
body: `❌ **Failed to trigger base version deployment**
Please check the [workflow logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details.`
});
trigger-deploy-new-version:
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'test-environment'
needs:
- build-apps
- trigger-deploy-base-version
runs-on: ubuntu-latest
steps:
- name: Trigger deployment with new version
uses: actions/github-script@v7
env:
VERSION_NAME: ${{ needs.build-apps.outputs.version }}
with:
github-token: ${{ secrets.DEPLOYMENT_TRIGGER_TOKEN }}
script: |
const response = await github.rest.repos.createDispatchEvent({
owner: 'comet-ml',
repo: 'comet-deployment',
event_type: 'deploy-opik-test-env',
client_payload: {
pr_number: '${{ github.event.number }}',
version_name: process.env.VERSION_NAME,
is_base_version: false
}
});
console.log('✅ Repository dispatch sent successfully:', response.status);
console.log('📦 Deploying new version:', process.env.VERSION_NAME);
console.log('🔄 This will update the existing environment');
console.log('📊 Monitor deployment progress: https://github.com/comet-ml/comet-deployment/actions/workflows/deploy_opik_adhoc_env.yaml');
console.log('🔗 Deployment will verify the version and post a comment on this PR when complete');
- name: Handle trigger failure
if: failure()
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: Number('${{ github.event.number }}'),
owner: context.repo.owner,
repo: context.repo.repo,
body: `❌ **Failed to trigger deployment with new version**
Please check the [workflow logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details.`
});
@@ -0,0 +1,148 @@
name: TypeScript SDK Compatibility V1.x E2E Tests
run-name: "TypeScript SDK Compatibility V1 E2E Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
packages: write
on:
workflow_dispatch:
pull_request:
paths:
- 'sdks/typescript/**'
- 'apps/opik-backend/**'
- '.github/workflows/typescript_sdk_compatibility_v1_e2e_tests.yml'
push:
branches:
- 'main'
paths:
- 'sdks/typescript/**'
- 'apps/opik-backend/**'
- '.github/workflows/typescript_sdk_compatibility_v1_e2e_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPIK_URL_OVERRIDE: http://localhost:8080
OPIK_CONSOLE_LOGGING_LEVEL: DEBUG
jobs:
select-matrix:
name: Select Node version matrix
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
node_versions: ${{ steps.pick.outputs.versions }}
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: .github/actions/select-e2e-matrix
sparse-checkout-cone-mode: false
- uses: ./.github/actions/select-e2e-matrix
id: pick
with:
all-versions: ${{ vars.NODE_VERSIONS }}
match-regex: '^sdks/typescript/|^\.github/workflows/typescript_sdk_compatibility_v1_e2e_tests\.yml$'
github-token: ${{ secrets.GITHUB_TOKEN }}
build-opik:
uses: ./.github/workflows/build_e2e_docker.yaml
run-compatibility-v1-e2e:
needs: [select-matrix, build-opik]
name: TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
node_version: ${{ fromJSON(needs.select-matrix.outputs.node_versions) }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker images from GHCR
env:
TAG: ${{ needs.build-opik.outputs.image_tag }}
run: |
docker pull "ghcr.io/comet-ml/opik/opik-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-python-backend:$TAG"
docker tag "ghcr.io/comet-ml/opik/opik-backend:$TAG" ghcr.io/comet-ml/opik/opik-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-python-backend:$TAG" ghcr.io/comet-ml/opik/opik-python-backend:latest
- name: Set up Node.js ${{matrix.node_version}}
uses: actions/setup-node@v4.2.0
with:
node-version: ${{matrix.node_version}}
- name: Run latest Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
COMPOSE_BAKE: false
OPIK_BACKEND_PULL_POLICY: never
PYTHON_BACKEND_PULL_POLICY: never
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
echo "Check Docker pods are up"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "Check backend health"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Install TypeScript SDK dependencies
working-directory: sdks/typescript
run: npm ci
- name: Build TypeScript SDK
working-directory: sdks/typescript
run: npm run build
- name: Run compatibility V1 tests
working-directory: sdks/typescript
run: |
npm run test:compatibility -- --reporter=default --reporter=junit --outputFile.junit=${{ github.workspace }}/compat_v1_results_node${{matrix.node_version}}.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: "TS SDK Compat V1 E2E Tests - Node ${{matrix.node_version}}"
files: ${{ github.workspace }}/compat_v1_results_node${{matrix.node_version}}.xml
- name: Keep BE log in case of failure
if: failure()
run: |
PROJECT_NAME=$(docker compose ls --format json 2>/dev/null | jq -r '.[].Name' | grep '^opik' | head -1)
CONTAINER="${PROJECT_NAME:-opik}-backend-1"
docker logs "$CONTAINER" > ${{ github.workspace }}/opik-backend_compat_v1_node${{matrix.node_version}}.log 2>&1 || echo "Could not capture logs from $CONTAINER"
- name: Attach BE log
if: failure()
uses: actions/upload-artifact@v7
with:
name: opik-backend-compat-v1-log-node${{matrix.node_version}}
path: ${{ github.workspace }}/opik-backend_compat_v1_node${{matrix.node_version}}.log
- name: Stop opik server
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop
@@ -0,0 +1,161 @@
name: TypeScript SDK E2E Tests
run-name: "TypeScript SDK E2E Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
checks: write
pull-requests: write
packages: write
on:
workflow_dispatch:
pull_request:
paths:
- 'sdks/typescript/**'
- 'apps/opik-backend/**'
- '.github/workflows/typescript_sdk_e2e_tests.yml'
push:
branches:
- 'main'
paths:
- 'sdks/typescript/**'
- 'apps/opik-backend/**'
- '.github/workflows/typescript_sdk_e2e_tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
OPIK_SENTRY_ENABLE: False
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPIK_URL_OVERRIDE: http://localhost:8080
OPIK_CONSOLE_LOGGING_LEVEL: DEBUG
jobs:
select-matrix:
name: Select Node version matrix
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
node_versions: ${{ steps.pick.outputs.versions }}
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: .github/actions/select-e2e-matrix
sparse-checkout-cone-mode: false
- uses: ./.github/actions/select-e2e-matrix
id: pick
with:
all-versions: ${{ vars.NODE_VERSIONS }}
match-regex: '^sdks/typescript/|^\.github/workflows/typescript_sdk_e2e_tests\.yml$'
github-token: ${{ secrets.GITHUB_TOKEN }}
build-opik:
uses: ./.github/workflows/build_e2e_docker.yaml
run-e2e:
needs: [select-matrix, build-opik]
name: TypeScript SDK E2E Tests Node ${{matrix.node_version}}
permissions:
contents: read
checks: write
pull-requests: write
packages: read
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
node_version: ${{ fromJSON(needs.select-matrix.outputs.node_versions) }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker images from GHCR
env:
TAG: ${{ needs.build-opik.outputs.image_tag }}
run: |
docker pull "ghcr.io/comet-ml/opik/opik-backend:$TAG"
docker pull "ghcr.io/comet-ml/opik/opik-python-backend:$TAG"
docker tag "ghcr.io/comet-ml/opik/opik-backend:$TAG" ghcr.io/comet-ml/opik/opik-backend:latest
docker tag "ghcr.io/comet-ml/opik/opik-python-backend:$TAG" ghcr.io/comet-ml/opik/opik-python-backend:latest
- name: Set up Node.js ${{matrix.node_version}}
uses: actions/setup-node@v4.2.0
with:
node-version: ${{matrix.node_version}}
- name: Run latest Opik server
env:
OPIK_USAGE_REPORT_ENABLED: false
COMPOSE_BAKE: false
TOGGLE_RUNNERS_ENABLED: "true"
OPIK_BACKEND_PULL_POLICY: never
PYTHON_BACKEND_PULL_POLICY: never
run: |
cd ${{ github.workspace }}
./opik.sh --backend --port-mapping
- name: Check Opik server availability
shell: bash
run: |
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
cd ${{ github.workspace }}/deployment/docker-compose
echo "Check Docker pods are up"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
echo "Check backend health"
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
- name: Install TypeScript SDK dependencies
working-directory: sdks/typescript
run: npm ci
- name: Build TypeScript SDK
working-directory: sdks/typescript
run: npm run build
- name: Run integration tests
working-directory: sdks/typescript
run: |
# once OPIK-3910 is fixed we can revert to npm run test:integration
npm run test:integration -- --reporter=default --reporter=junit --outputFile.junit=${{ github.workspace }}/test_results_node${{matrix.node_version}}.xml
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action/linux@v2
if: always()
with:
action_fail: true
comment_mode: failures
check_name: "TS SDK E2E Tests - Node ${{matrix.node_version}}"
files: ${{ github.workspace }}/test_results_node${{matrix.node_version}}.xml
- name: Keep BE log in case of failure
if: failure()
run: |
PROJECT_NAME=$(docker compose ls --format json 2>/dev/null | jq -r '.[].Name' | grep '^opik' | head -1)
CONTAINER="${PROJECT_NAME:-opik}-backend-1"
docker logs "$CONTAINER" > ${{ github.workspace }}/opik-backend_node${{matrix.node_version}}.log 2>&1 || echo "Could not capture logs from $CONTAINER"
- name: Attach BE log
if: failure()
uses: actions/upload-artifact@v7
with:
name: opik-backend-log-node${{matrix.node_version}}
path: ${{ github.workspace }}/opik-backend_node${{matrix.node_version}}.log
- name: Stop opik server
if: always()
run: |
cd ${{ github.workspace }}
./opik.sh --stop
@@ -0,0 +1,132 @@
name: TypeScript SDK Integration Build & Publish
run-name: "TypeScript SDK Integration Build & Publish ${{ github.event.inputs.version || inputs.version || 'auto' }} from ${{github.ref_name}} by @${{ github.actor }}"
on:
push:
paths:
- "sdks/typescript/src/opik/integrations/**"
- ".github/workflows/typescript_sdk_integration_publish.yml"
pull_request:
paths:
- "sdks/typescript/src/opik/integrations/**"
- ".github/workflows/typescript_sdk_integration_publish.yml"
workflow_dispatch:
inputs:
version:
type: string
required: true
description: Version
default: ""
is_release:
type: boolean
required: false
description: Whether this is a release (publishes to NPM)
default: false
skip_commit_push:
type: boolean
required: false
description: Skip commit and push
default: false
workflow_call:
inputs:
version:
type: string
required: true
description: Version
is_release:
type: boolean
required: false
default: false
skip_commit_push:
type: boolean
required: false
default: false
concurrency:
group: ts-sdk-integration-publish-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build-and-publish:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
integration:
- opik-openai
- opik-gemini
- opik-langchain
- opik-vercel
- opik-otel
env:
IS_RELEASE: ${{ inputs.is_release }}
INTEGRATION: ${{ matrix.integration }}
WORKING_DIR: sdks/typescript/src/opik/integrations/${{ matrix.integration }}
defaults:
run:
working-directory: ${{ env.WORKING_DIR }}
steps:
- name: Checkout
# For releases, check out the tag for the version we're publishing — not
# `github.ref` (which is the dispatch ref and may have moved past the tag).
# Convention: tag name == version string. Locks the build to the commit
# the tag points to, so the published tarball is reproducible from the
# version label. Non-release runs (build-only PR / push triggers) keep
# the dispatch ref because there's no tag to pin to.
#
# NOTE: This is duplicated with typescript_sdk_publish.yml. We tried
# extracting into a local composite action but a local composite cannot
# be a workflow's first step — the repo isn't checked out yet, so the
# composite's action.yml is not on the runner's filesystem. Keep the
# checkout inline.
uses: actions/checkout@v6
with:
ref: ${{ env.IS_RELEASE == 'true' && format('refs/tags/{0}', inputs.version) || github.ref }}
fetch-depth: 0
token: ${{ secrets.GH_PAT_TO_ACCESS_GITHUB_API || github.token }}
- name: Verify integration exists
run: |
if [ ! -d "${{ github.workspace }}/${{ env.WORKING_DIR }}" ]; then
echo "Error: Integration '${{ env.INTEGRATION }}' not found at path: ${{ env.WORKING_DIR }}"
exit 1
fi
echo "Integration '${{ env.INTEGRATION }}' found"
- name: Build and Publish TypeScript SDK Integration
id: build_publish
uses: ./.github/actions/typescript-sdk-build-and-publish
with:
working_directory: ${{ env.WORKING_DIR }}
version: ${{ inputs.version || '' }}
is_release: ${{ env.IS_RELEASE }}
package_name: ${{ env.INTEGRATION }}
npm_access: "public"
enable_cache: "true"
cache_dependency_path: ${{ env.WORKING_DIR }}/package-lock.json
test_package_installation: "true"
npm_token: ${{ secrets.NPM_TOKEN }}
github_token: ${{ secrets.GH_PAT_TO_ACCESS_GITHUB_API || github.token }}
skip_commit_push: ${{ inputs.skip_commit_push }}
- name: Summary
run: |
exec >> "$GITHUB_STEP_SUMMARY"
echo "## ${{ env.INTEGRATION }} Build"
echo ""
echo "**Version:** ${{ steps.build_publish.outputs.version }}"
echo "**Event:** ${{ github.event_name }}"
echo "**Is Release:** ${{ env.IS_RELEASE }}"
if [ "${{ env.IS_RELEASE }}" = "true" ]; then
echo "**Published:** ✅ Published to NPM"
echo "**Package:** 📦 ${{ steps.build_publish.outputs.package_name }}@${{ steps.build_publish.outputs.version }}"
echo "**NPM:** 🔗 https://www.npmjs.com/package/${{ steps.build_publish.outputs.package_name }}/v/${{ steps.build_publish.outputs.version }}"
echo ""
echo "**Install with:**"
echo "\`\`\`bash"
echo "npm install ${{ steps.build_publish.outputs.package_name }}@${{ steps.build_publish.outputs.version }}"
echo "\`\`\`"
else
echo "**Status:** ⏭️ Build and tests only"
fi
@@ -0,0 +1,69 @@
# Real library integration tests for the TypeScript SDK.
#
# Mirrors the Python `lib-integration-tests-runner.yml`: runs the integration
# packages' test suites on TypeScript SDK PRs, with provider API keys supplied
# from secrets so the gated live-LLM tests execute. Deterministic tests in the
# same packages run regardless; the live tests skip themselves when their key
# is absent (e.g. fork PRs), so the job stays green either way.
name: TypeScript SDK Library Integration Tests
run-name: "TypeScript SDK Library Integration Tests ${{ github.ref_name }} by @${{ github.actor }}"
permissions:
contents: read
on:
pull_request:
paths:
- "sdks/typescript/**"
push:
branches:
- "main"
paths:
- "sdks/typescript/**"
workflow_dispatch:
schedule:
# Daily at midnight UTC, Monday-Saturday.
- cron: "0 0 * * 1-6"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
jobs:
check_secrets:
name: Check Secrets
runs-on: ubuntu-latest
outputs:
has_anthropic_key: ${{ steps.check.outputs.has_anthropic_key }}
steps:
- name: Detect available provider keys
id: check
run: echo "has_anthropic_key=${{ secrets.ANTHROPIC_API_KEY != '' }}" >> "$GITHUB_OUTPUT"
vercel:
name: opik-vercel (Vercel AI SDK / eve)
needs: [check_secrets]
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: sdks/typescript/src/opik/integrations/opik-vercel
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v4.2.0
with:
node-version: "22"
- name: Warn when ANTHROPIC_API_KEY is absent
if: ${{ needs.check_secrets.outputs.has_anthropic_key == 'false' }}
run: echo "::warning::ANTHROPIC_API_KEY is not set - live LLM tests will be skipped (deterministic tests still run)."
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
@@ -0,0 +1,105 @@
name: TypeScript SDK Build & Publish
run-name: "TypeScript SDK Build & Publish ${{ github.event.inputs.version || inputs.version || 'auto' }} from ${{github.ref_name}} by @${{ github.actor }}"
on:
push:
branches:
- main
paths:
- "sdks/typescript/**"
- ".github/workflows/typescript_sdk_publish.yml"
pull_request:
paths:
- "sdks/typescript/**"
- ".github/workflows/typescript_sdk_publish.yml"
workflow_dispatch:
inputs:
version:
type: string
required: true
description: Version
default: ""
is_release:
type: boolean
required: false
description: Whether this is a release (publishes to NPM)
default: false
skip_commit_push:
type: boolean
required: false
description: Skip commit and push
default: false
workflow_call:
inputs:
version:
type: string
required: true
description: Version
is_release:
type: boolean
required: false
default: false
skip_commit_push:
type: boolean
required: false
default: false
concurrency:
group: ts-sdk-publish-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build-and-publish:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
IS_RELEASE: ${{ inputs.is_release }}
defaults:
run:
working-directory: sdks/typescript
steps:
- name: Checkout
# For releases, check out the tag for the version we're publishing — not
# `github.ref` (which is the dispatch ref and may have moved past the tag).
# Convention: tag name == version string. Locks the build to the commit
# the tag points to, so the published tarball is reproducible from the
# version label. Non-release runs (build-only PR / push triggers) keep
# the dispatch ref because there's no tag to pin to.
#
# NOTE: We extract this into a local composite action originally, but a
# local composite cannot be a workflow's first step — the repo isn't
# checked out yet, so the composite's action.yml is not on the runner's
# filesystem. Keep the checkout inline. See the closed comment thread on
# PR #7124 for the failure mode.
uses: actions/checkout@v6
with:
ref: ${{ env.IS_RELEASE == 'true' && format('refs/tags/{0}', inputs.version) || github.ref }}
fetch-depth: 0
token: ${{ secrets.GH_PAT_TO_ACCESS_GITHUB_API || github.token }}
- name: Build and Publish TypeScript SDK
id: build_publish
uses: ./.github/actions/typescript-sdk-build-and-publish
with:
working_directory: sdks/typescript
version: ${{ inputs.version || '' }}
is_release: ${{ env.IS_RELEASE }}
package_name: "TypeScript SDK"
npm_token: ${{ secrets.NPM_TOKEN }}
github_token: ${{ secrets.GH_PAT_TO_ACCESS_GITHUB_API || github.token }}
skip_commit_push: ${{ inputs.skip_commit_push }}
- name: Summary
run: |
exec >> "$GITHUB_STEP_SUMMARY"
echo "## TypeScript SDK Build"
echo ""
echo "**Version:** ${{ steps.build_publish.outputs.version }}"
echo "**Event:** ${{ github.event_name }}"
echo "**Is Release:** ${{ env.IS_RELEASE }}"
if [ "${{ env.IS_RELEASE }}" = "true" ]; then
echo "**Published:** ✅ Published to NPM"
else
echo " ⏭️ Build only "
fi
@@ -0,0 +1,52 @@
name: TypeScript SDK Unit Tests
run-name: "TypeScript SDK Unit Tests ${{ github.ref_name }} by @${{ github.actor }}"
on:
pull_request:
paths:
- "sdks/typescript/**"
push:
branches:
- "main"
paths:
- "sdks/typescript/**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
test:
name: Test on Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: sdks/typescript
strategy:
fail-fast: false
matrix:
node-version: ${{ fromJSON(vars.NODE_VERSIONS || '[18,20,22]') }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version || '20' }}
- name: Install dependencies (npm)
run: npm ci
- name: Run tests (npm)
run: npm run test
- name: Install 'configure' tool dependencies
working-directory: sdks/typescript/src/opik/configure
run: npm install
- name: Run 'configure' tool tests
working-directory: sdks/typescript/src/opik/configure
run: npm run test
+50
View File
@@ -0,0 +1,50 @@
name: Generate helm docs
run-name: Generate helm docs ${{ github.ref_name }} by @${{ github.actor }}"
on:
pull_request:
paths:
- "deployment/helm_chart/opik/**"
types: [opened, synchronize, reopened, labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
update-readme:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.ref }}
# If the PR has the "update readme" label, generate and commit
- name: Generate and commit helm docs
if: contains(github.event.pull_request.labels.*.name, 'update readme')
uses: losisin/helm-docs-github-action@v1
with:
chart-search-root: deployment/helm_chart/opik
git-push: true
git-push-user-name: "CometActions"
git-push-user-email: "github-actions@comet.com"
git-commit-message: "Update Helm documentation"
# If the PR does NOT have the label, just validate that docs are up-to-date
- name: Validate helm docs are up-to-date
if: "!contains(github.event.pull_request.labels.*.name, 'update readme')"
uses: losisin/helm-docs-github-action@v1
with:
chart-search-root: deployment/helm_chart/opik
git-push: false
- name: Check for uncommitted helm docs changes
if: "!contains(github.event.pull_request.labels.*.name, 'update readme')"
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::Helm docs are out of date. Please run 'pre-commit run helm-docs --all-files' locally and commit the changes, or add the 'update readme' label to this PR and don't forget to update your local branch afterwards."
git status --short
exit 1
fi