llama-stack-mirror/.github/workflows/conformance.yml
Sébastien Han e3cb8ed74a
chore: use Pydantic to generate OpenAPI schema
Removes the need for the strong_typing and pyopenapi packages and purely
use Pydantic for schema generation.

Our generator now purely relies on Pydantic and FastAPI, it is available
at `scripts/fastapi_generator.py`, you can run it like so:

```
uv run ./scripts/run_openapi_generator.sh
```

The generator will:

* Generate the deprecated, experimental, stable and combined specs
* Validate all the spec it generates against OpenAPI standards

A few changes in the schema required for oasdiff some updates so I've
made the following ignore rules. The new Pydantic-based generator is
likely more correct and follows OpenAPI standards better than the old
pyopenapi generator. Instead of trying to make the new generator match
the old one's quirks, we should focus on what's actually correct
according to OpenAPI standards.

These are non-critical changes:

* response-property-became-nullable: Backward compatible:
  existing non-null values still work, now also accepts null
* response-required-property-removed: oasdiff reports a false
  positive because it doesn't resolve $refs inside anyOf; we could use
  tool like 'redocly' to flatten the schema to a single file.
* response-property-type-changed: properties are still object
  types, but oasdiff doesn't resolve $refs, so it flags the missing
  inline type: object even though the referenced schemas define type:
  object
* request-property-one-of-removed: These are false positives
  caused by schema restructuring (wrapping in anyOf for nullability,
  using -Input variants, or simplifying nested oneOf structures)
  that don't change the actual API contract - the same data types are
  still accepted, just represented differently in the schema.
* request-parameter-enum-value-removed: These are false
  positives caused by oasdiff not resolving $refs - the enum values
  (asc, desc, assistants, batch) are still present in the referenced
  schemas (Order and OpenAIFilePurpose), just represented via schema
  references instead of inline enums.
* request-property-enum-value-removed: this is a false positive caused
    by oasdiff not resolving $refs - the enum values (llm, embedding,
    rerank) are still present in the referenced ModelType schema,
    just represented via schema reference instead of inline enums.
* request-property-type-changed: These are schema quality issues
    where type information is missing (due to Any fallback in dynamic
    model creation), but the API contract remains unchanged -
    properties still exist with correct names and defaults, so the same
    requests will work.
* response-body-type-changed: These are false positives caused
  by schema representation changes (from inferred/empty types to
  explicit $ref schemas, or vice versa) - the actual response types
  an API contract remain unchanged, just how they're represented in the
  OpenAPI spec.
* response-media-type-removed: This is a false positive caused
  by FastAPI's OpenAPI generator not documenting union return types with
  AsyncIterator - the streaming functionality with text/event-stream
  media type still works when stream=True is passed, it's just not
  reflected in the generated OpenAPI spec.
* request-body-type-changed: This is a schema correction - the
  old spec incorrectly represented the request body as an object, but
  the function signature shows chunks: list[Chunk], so the new spec
  correctly shows it as an array, matching the actual API
  implementation.

Signed-off-by: Sébastien Han <seb@redhat.com>
2025-11-14 09:56:02 +01:00

163 lines
6.9 KiB
YAML

# API Conformance Tests
# This workflow ensures that API changes maintain backward compatibility and don't break existing integrations
# It runs schema validation and OpenAPI diff checks to catch breaking changes early
#
# The workflow handles both monolithic and split API specifications:
# - If split specs exist (stable/experimental/deprecated), they are stitched together for comparison
# - If only monolithic spec exists, it is used directly
# This allows for clean API organization while maintaining robust conformance testing
name: API Conformance Tests
run-name: Run the API Conformance test suite on the changes.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
types: [opened, synchronize, reopened, edited]
paths:
- 'docs/static/llama-stack-spec.yaml' # Legacy monolithic spec
- 'docs/static/stable-llama-stack-spec.yaml' # Stable APIs spec
- 'docs/static/experimental-llama-stack-spec.yaml' # Experimental APIs spec
- 'docs/static/deprecated-llama-stack-spec.yaml' # Deprecated APIs spec
- '.github/workflows/conformance.yml' # This workflow itself
concurrency:
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.ref }}
# Cancel in-progress runs when new commits are pushed to avoid wasting CI resources
cancel-in-progress: true
jobs:
# Job to check if API schema changes maintain backward compatibility
check-schema-compatibility:
runs-on: ubuntu-latest
steps:
- name: Checkout PR Code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
# Check if we should skip conformance testing due to breaking changes
- name: Check if conformance test should be skipped
id: skip-check
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
# Skip if title contains "!:" indicating breaking change (like "feat!:")
if [[ "$PR_TITLE" == *"!:"* ]]; then
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi
# Get all commits in this PR and check for BREAKING CHANGE footer
git log --format="%B" ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | \
grep -q "BREAKING CHANGE:" && echo "skip=true" >> $GITHUB_OUTPUT || echo "skip=false" >> $GITHUB_OUTPUT
shell: bash
# Checkout the base branch to compare against (usually main)
# This allows us to diff the current changes against the previous state
- name: Checkout Base Branch
if: steps.skip-check.outputs.skip != 'true'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.pull_request.base.ref }}
path: 'base'
# Cache oasdiff to avoid checksum failures and speed up builds
- name: Cache oasdiff
if: steps.skip-check.outputs.skip != 'true'
id: cache-oasdiff
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ~/oasdiff
key: oasdiff-${{ runner.os }}
# Install oasdiff: https://github.com/oasdiff/oasdiff, a tool for detecting breaking changes in OpenAPI specs.
- name: Install oasdiff
if: steps.skip-check.outputs.skip != 'true' && steps.cache-oasdiff.outputs.cache-hit != 'true'
run: |
curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh
cp /usr/local/bin/oasdiff ~/oasdiff
# Setup cached oasdiff
- name: Setup cached oasdiff
if: steps.skip-check.outputs.skip != 'true' && steps.cache-oasdiff.outputs.cache-hit == 'true'
run: |
sudo cp ~/oasdiff /usr/local/bin/oasdiff
sudo chmod +x /usr/local/bin/oasdiff
# Install yq for YAML processing
- name: Install yq
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
# Verify API specs exist for conformance testing
- name: Check API Specs
if: steps.skip-check.outputs.skip != 'true'
run: |
echo "Checking for API specification files..."
# Check current branch
if [ -f "docs/static/stable-llama-stack-spec.yaml" ]; then
echo "✓ Found stable API spec in current branch"
CURRENT_SPEC="docs/static/stable-llama-stack-spec.yaml"
elif [ -f "docs/static/llama-stack-spec.yaml" ]; then
echo "✓ Found monolithic API spec in current branch"
CURRENT_SPEC="docs/static/llama-stack-spec.yaml"
else
echo "❌ No API specs found in current branch"
exit 1
fi
# Check base branch
if [ -f "base/docs/static/stable-llama-stack-spec.yaml" ]; then
echo "✓ Found stable API spec in base branch"
BASE_SPEC="base/docs/static/stable-llama-stack-spec.yaml"
elif [ -f "base/docs/static/llama-stack-spec.yaml" ]; then
echo "✓ Found monolithic API spec in base branch"
BASE_SPEC="base/docs/static/llama-stack-spec.yaml"
else
echo "❌ No API specs found in base branch"
exit 1
fi
# Export for next step
echo "BASE_SPEC=${BASE_SPEC}" >> $GITHUB_ENV
echo "CURRENT_SPEC=${CURRENT_SPEC}" >> $GITHUB_ENV
echo "Will compare: ${BASE_SPEC} -> ${CURRENT_SPEC}"
- name: Write ignore file
run: |
cat <<EOF > ignore-oasdiff
response-property-became-nullable none
response-property-list-of-types-widened none
request-parameter-default-value-added none
request-property-min-items-increased none
response-property-became-optional none
response-required-property-removed none
response-property-one-of-added none
response-property-type-changed none
request-property-one-of-removed none
request-parameter-enum-value-removed none
request-property-enum-value-removed none
request-property-type-changed none
response-body-type-changed none
response-media-type-removed none
request-body-type-changed none
EOF
# Run oasdiff to detect breaking changes in the API specification
# This step will fail if incompatible changes are detected, preventing breaking changes from being merged
- name: Run OpenAPI Breaking Change Diff
if: steps.skip-check.outputs.skip != 'true'
run: |
oasdiff breaking --fail-on ERR --severity-levels ignore-oasdiff $BASE_SPEC $CURRENT_SPEC --match-path '^/v1/'
# Report when test is skipped
- name: Report skip reason
if: steps.skip-check.outputs.skip == 'true'
run: |
echo "Conformance test skipped due to breaking change indicator"