Keep FastAPI instrumentation dependencies compatible (#731)

`llama-agents-appserver` allowed FastAPI's included-router
implementation to resolve with Prometheus instrumentator versions that
still assumed `app.routes` was flat. That combination raises before the
route handler runs, including on `/health`.

Require FastAPI 0.137 or newer and prometheus-fastapi-instrumentator 8.x
in both the appserver and control plane, with major-version upper
bounds. Starlette 1.x returns the request origin for credentialed CORS
responses, so the appserver assertion now covers that behavior.

CI builds the appserver wheel and resolves its published dependency
metadata with uv's `highest` and `lowest-direct` strategies. Each clean
environment sends requests through `/health`, the mounted workflow
route, and `/metrics`. The job has no copied dependency versions, and it
exercises the package a consumer installs instead of rerunning the
source test suite with overrides.
This commit is contained in:
Adrian Lyjak
2026-08-05 15:11:58 +00:00
committed by GitHub
parent c713fb7009
commit ce27389649
7 changed files with 126 additions and 23 deletions
@@ -0,0 +1,6 @@
---
"llama-agents-appserver": patch
"llama-agents-control-plane": patch
---
Require FastAPI and Prometheus instrumentation versions that support included routers.
+27
View File
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 LlamaIndex Inc.
from __future__ import annotations
from fastapi.testclient import TestClient
from llama_agents.appserver.app import app
def main() -> None:
with TestClient(app) as client:
health = client.get("/health")
assert health.status_code == 200, health.text
assert health.json() == {"status": "Healthy"}
workflows = client.get("/deployments/compatibility-test/workflows")
assert workflows.status_code == 200, workflows.text
assert workflows.json() == {"workflows": []}
metrics = client.get("/metrics")
assert metrics.status_code == 200, metrics.text
assert b'apiserver_state{apiserver_state="running"} 1.0' in metrics.content
assert b'handler="/deployments/{deployment_name}/workflows"' in metrics.content
if __name__ == "__main__":
main()
+47 -1
View File
@@ -129,8 +129,54 @@ jobs:
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
test-appserver-package:
name: "test appserver package (${{ matrix.resolution }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
resolution: [highest, lowest-direct]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install uv and set the python version
uses: astral-sh/setup-uv@v6
with:
python-version: "3.14"
enable-cache: true
- name: Build appserver wheel
run: uv build --wheel --package llama-agents-appserver --out-dir dist
- name: Resolve package dependencies
run: >-
uv pip compile
packages/llama-agents-appserver/pyproject.toml
--no-sources
--resolution ${{ matrix.resolution }}
--output-file "${RUNNER_TEMP}/appserver-requirements.txt"
- name: Install appserver wheel
run: |
uv venv "${RUNNER_TEMP}/appserver-venv"
uv pip install \
--python "${RUNNER_TEMP}/appserver-venv/bin/python" \
--requirements "${RUNNER_TEMP}/appserver-requirements.txt" \
dist/llama_agents_appserver-*.whl
- name: Smoke test installed appserver
run: |
mkdir -p "${RUNNER_TEMP}/appserver-smoke"
printf 'name: compatibility-test\nworkflows: {}\n' > "${RUNNER_TEMP}/appserver-smoke/llama_deploy.yaml"
LLAMA_DEPLOY_APISERVER_APP_ROOT="${RUNNER_TEMP}/appserver-smoke" \
"${RUNNER_TEMP}/appserver-venv/bin/python" \
.github/scripts/test_appserver_package.py
coveralls-finish:
needs: [test, test-docker]
needs: [test, test-docker, test-appserver-package]
runs-on: ubuntu-latest
if: github.repository == 'run-llama/llama-agents'
steps:
@@ -29,11 +29,11 @@ dependencies = [
"llama-index-workflows>=2.21.0,<3.0.0",
"llama-agents-server>=0.5.1",
"pydantic-settings>=2.10.1",
"fastapi>=0.100.0",
"fastapi>=0.137.0,<1",
"websockets>=12.0",
"llama-agents-core>=0.5.0",
"httpx>=0.24.0,<1.0.0",
"prometheus-fastapi-instrumentator>=7.1.0",
"prometheus-fastapi-instrumentator>=8.0.1,<9.0.0",
"packaging>=25.0",
"structlog>=25.4.0",
"rich>=14.1.0",
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 LlamaIndex Inc.
from __future__ import annotations
from pathlib import Path
@@ -47,8 +50,10 @@ def test_root_redirect_and_metrics(tmp_path: Path) -> None:
assert b"apiserver_state" in m.content
# CORS header present when Origin is sent
r2 = client.get("/health", headers={"Origin": "http://example.com"})
assert r2.headers.get("access-control-allow-origin") == "*"
origin = "http://example.com"
r2 = client.get("/health", headers={"Origin": origin})
assert r2.headers.get("access-control-allow-origin") == origin
assert r2.headers.get("access-control-allow-credentials") == "true"
def test_static_ui_mount_serves_dist(tmp_path: Path) -> None:
@@ -30,10 +30,10 @@ authors = [
]
requires-python = ">=3.10, <4"
dependencies = [
"fastapi[standard]>=0.115.6,<0.116",
"fastapi[standard]>=0.137.0,<1",
"llama-agents-core[server]>=0.5.0",
"prometheus-client>=0.21.1,<0.22",
"prometheus-fastapi-instrumentator>=7.1.0",
"prometheus-fastapi-instrumentator>=8.0.1,<9.0.0",
"kubernetes>=33.1.0",
"gitpython>=3.1.40,<4",
"pyjwt[crypto]>=2.10.1",
Generated
+35 -16
View File
@@ -1226,31 +1226,36 @@ wheels = [
[[package]]
name = "fastapi"
version = "0.115.14"
version = "0.141.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
{ name = "pydantic" },
{ name = "starlette" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ca/53/8c38a874844a8b0fa10dd8adf3836ac154082cf88d3f22b544e9ceea0a15/fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739", size = 296263, upload-time = "2025-06-26T15:29:08.21Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/53/50/b1222562c6d270fea83e9c9075b8e8600b8479150a18e4516a6138b980d1/fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca", size = 95514, upload-time = "2025-06-26T15:29:06.49Z" },
{ url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" },
]
[package.optional-dependencies]
standard = [
{ name = "email-validator" },
{ name = "fastapi-cli", extra = ["standard"] },
{ name = "fastar" },
{ name = "httpx" },
{ name = "jinja2" },
{ name = "pydantic-extra-types" },
{ name = "pydantic-settings" },
{ name = "python-multipart" },
{ name = "uvicorn", extra = ["standard"] },
]
[[package]]
name = "fastapi-cli"
version = "0.0.24"
version = "0.0.32"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "rich-toolkit" },
@@ -1258,9 +1263,9 @@ dependencies = [
{ name = "typer" },
{ name = "uvicorn", extra = ["standard"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/6e/58/74797ae9e4610cfa0c6b34c8309096d3b20bb29be3b8b5fbf1004d10fa5f/fastapi_cli-0.0.24.tar.gz", hash = "sha256:1afc9c9e21d7ebc8a3ca5e31790cd8d837742be7e4f8b9236e99cb3451f0de00", size = 19043, upload-time = "2026-02-24T10:45:10.476Z" }
sdist = { url = "https://files.pythonhosted.org/packages/33/eb/3b534c6f8e157f9ddbf2a153512307c886cad0b258739c200dd8ff8c4452/fastapi_cli-0.0.32.tar.gz", hash = "sha256:38024d2345275e1b37ce8848727a580d84901b570e96b3256d9d36a9a5039424", size = 26636, upload-time = "2026-07-16T12:16:58.678Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/4b/68f9fe268e535d79c76910519530026a4f994ce07189ac0dded45c6af825/fastapi_cli-0.0.24-py3-none-any.whl", hash = "sha256:4a1f78ed798f106b4fee85ca93b85d8fe33c0a3570f775964d37edb80b8f0edc", size = 12304, upload-time = "2026-02-24T10:45:09.552Z" },
{ url = "https://files.pythonhosted.org/packages/d5/53/56ae5ae17bb0a5d89d1d31e5320eb1865553ebbfbde91cdc4c221245f2a8/fastapi_cli-0.0.32-py3-none-any.whl", hash = "sha256:8dcc286fa32f01bbd3f65dd09cfd5a2540ed5f2230b77db7fd30978d6165f3c4", size = 14670, upload-time = "2026-07-16T12:16:57.297Z" },
]
[package.optional-dependencies]
@@ -2268,13 +2273,13 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "fastapi", specifier = ">=0.100.0" },
{ name = "fastapi", specifier = ">=0.137.0,<1" },
{ name = "httpx", specifier = ">=0.24.0,<1.0.0" },
{ name = "llama-agents-core", editable = "packages/llama-agents-core" },
{ name = "llama-agents-server", editable = "packages/llama-agents-server" },
{ name = "llama-index-workflows", editable = "packages/llama-index-workflows" },
{ name = "packaging", specifier = ">=25.0" },
{ name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" },
{ name = "prometheus-fastapi-instrumentator", specifier = ">=8.0.1,<9.0.0" },
{ name = "pydantic-settings", specifier = ">=2.10.1" },
{ name = "pyyaml", specifier = ">=6.0.2" },
{ name = "rich", specifier = ">=14.1.0" },
@@ -2381,12 +2386,12 @@ requires-dist = [
{ name = "aiocache", specifier = ">=0.12.3" },
{ name = "cryptography", specifier = ">=44.0.0" },
{ name = "dulwich", extras = ["https"], specifier = ">=0.22.0" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6,<0.116" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.137.0,<1" },
{ name = "gitpython", specifier = ">=3.1.40,<4" },
{ name = "kubernetes", specifier = ">=33.1.0" },
{ name = "llama-agents-core", extras = ["server"], editable = "packages/llama-agents-core" },
{ name = "prometheus-client", specifier = ">=0.21.1,<0.22" },
{ name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" },
{ name = "prometheus-fastapi-instrumentator", specifier = ">=8.0.1,<9.0.0" },
{ name = "pydantic-settings", specifier = ">=2.10.1" },
{ name = "pyjwt", extras = ["crypto"], specifier = ">=2.10.1" },
{ name = "python-dotenv", specifier = ">=1.1.1" },
@@ -4051,15 +4056,15 @@ wheels = [
[[package]]
name = "prometheus-fastapi-instrumentator"
version = "7.1.0"
version = "8.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "prometheus-client" },
{ name = "starlette" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/6d/24d53033cf93826aa7857699a4450c1c67e5b9c710e925b1ed2b320c04df/prometheus_fastapi_instrumentator-7.1.0.tar.gz", hash = "sha256:be7cd61eeea4e5912aeccb4261c6631b3f227d8924542d79eaf5af3f439cbe5e", size = 20220, upload-time = "2025-03-19T19:35:05.351Z" }
sdist = { url = "https://files.pythonhosted.org/packages/95/f4/cdcebf7094b03b99fba71ac8f56bd6f227973642662f49d272332d8419b3/prometheus_fastapi_instrumentator-8.1.0.tar.gz", hash = "sha256:b77f3043665e8d28e2bbd21017506195a43d9adf1d402d01bf95b494b7e560e1", size = 20492, upload-time = "2026-07-26T11:12:44.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/72/0824c18f3bc75810f55dacc2dd933f6ec829771180245ae3cc976195dec0/prometheus_fastapi_instrumentator-7.1.0-py3-none-any.whl", hash = "sha256:978130f3c0bb7b8ebcc90d35516a6fe13e02d2eb358c8f83887cdef7020c31e9", size = 19296, upload-time = "2025-03-19T19:35:04.323Z" },
{ url = "https://files.pythonhosted.org/packages/44/b9/91a2246e6cf01b7ccb14479803c8a50f9c258ae5c6a0f16ba3294820632b/prometheus_fastapi_instrumentator-8.1.0-py3-none-any.whl", hash = "sha256:b9f40b2cff3f7891ca0610b3ae4fc6ec723fd326b04bb659819aaeb821a0fc7d", size = 19649, upload-time = "2026-07-26T11:12:45.168Z" },
]
[[package]]
@@ -4453,6 +4458,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" },
]
[[package]]
name = "pydantic-extra-types"
version = "2.11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" },
]
[[package]]
name = "pydantic-settings"
version = "2.13.1"
@@ -5243,14 +5261,15 @@ wheels = [
[[package]]
name = "starlette"
version = "0.46.2"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ac/16/dc49e4b9d348b0f0567a67613a946f4e26b3299408ccf78df318c51a2aec/starlette-1.4.0.tar.gz", hash = "sha256:ecf3a067176d63c6412f98c660dab3355b525584d3725c0c40a4519d33ec2130", size = 2708995, upload-time = "2026-08-05T09:36:43.693Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" },
{ url = "https://files.pythonhosted.org/packages/be/f6/7916cd7717e196bba370c0f17abb30466014ce29094665c3581e51a8c24b/starlette-1.4.0-py3-none-any.whl", hash = "sha256:cacd43738e07b834844e2c8e97b898b40089d74f2678234b0a3e93cd3d515813", size = 74021, upload-time = "2026-08-05T09:36:41.944Z" },
]
[[package]]