Skip to content

fix(proxy): regenerate lazy OpenAPI snapshot and guard it in CI - #38410

Merged
mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_regenerate_lazy_openapi_snapshot
Aug 27, 2026
Merged

mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_regenerate_lazy_openapi_snapshot

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • /openapi.json served stale docs for the 31 lazily loaded route groups
  • Two lazy features (a2a_registration, gemini_agents) had no snapshot at all, so only a placeholder GET showed
  • Nothing in CI noticed when a lazy route changed without the snapshot being regenerated

How it solves it:

  • Regenerates _lazy_openapi_snapshot.json (33 fragments) and schema.d.ts from it
  • The schema.d.ts sync job now regenerates the snapshot first and fails on drift
  • make check runs the same regenerate-and-diff locally
  • The generator refuses to write a snapshot when any feature fails to import

User Flow

Before: a platform engineer wiring their API portal to the gateway's spec gets stale or placeholder docs for routes the proxy has not loaded yet

  1. They boot the proxy and, before any request touches those features, send GET https://litellm-domain/openapi.json
  2. /v1/a2a/discover and /v1beta/agents each appear as a single GET whose summary is just a2a_registration or gemini_agents, and /v1beta/agents/{name} plus /v1beta/agents/{name}/versions are missing
  3. The GET /policies/list description still reads "On a name conflict with a DB policy, only the DB policy is returned" and the POST /v1/agents example body is mis-indented
  4. They open https://litellm-domain/ and Swagger shows the same placeholders and old text

After: the same request returns the current routes and docs for every lazily loaded feature

  1. They boot the proxy and, before any request touches those features, send GET https://litellm-domain/openapi.json
  2. /v1/a2a/discover shows POST Discover Agent Card, /v1beta/agents shows GET List Gemini Agents and POST Create Gemini Agent, and /v1beta/agents/{name} plus /v1beta/agents/{name}/versions are listed with their real operations
  3. The GET /policies/list description reads the current "On a name conflict with a production DB policy" text and the POST /v1/agents example is indented like the source
  4. https://litellm-domain/ renders those same routes and descriptions

Relevant issues

Linear ticket

Resolves LIT-6273

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Setup shared by both sides: a DB-backed proxy booted with --num_workers 2 from the commit named in each heading, /openapi.json fetched as the first request after boot so no lazy feature has been loaded by traffic. Config:

model_list:
  - model_name: gpt-5-mini
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY

Inspection script inspect.py, fed the raw spec on stdin:

import json, sys

spec = json.load(sys.stdin)
paths = spec["paths"]
print("paths:", len(paths))
for p in ("/v1/a2a/discover", "/v1beta/agents", "/v1beta/agents/{name}", "/v1beta/agents/{name}/versions"):
    ops = paths.get(p)
    print(p, "ABSENT" if ops is None else {m: op.get("summary") for m, op in ops.items()})
print("GET /policies/list:", paths["/policies/list"]["get"]["description"].splitlines()[3])
print("POST /v1/agents:", repr(paths["/v1/agents"]["post"]["description"].splitlines()[9]))

Before (f57e4b8)

Lazy routes for a2a_registration and gemini_agents

  1. curl -s http://localhost:20230/openapi.json | python3 inspect.py
  2. Observed:
paths: 539
/v1/a2a/discover {'get': 'a2a_registration'}
/v1beta/agents {'get': 'gemini_agents'}
/v1beta/agents/{name} ABSENT
/v1beta/agents/{name}/versions ABSENT

Docstring drift on GET /policies/list and POST /v1/agents

  1. Same command, remaining output lines:
GET /policies/list: as production versions. On a name conflict with a DB policy, only the DB policy is returned.
POST /v1/agents: '            "agent_card_params": {'

Staging merge (cd9dcb5) delta

  1. Same command after merging current litellm_internal_staging (which includes fix(prompts): reject keyed prompt_data with prompt_id and populate prompt version #38404) into the branch
  2. Observed: still 553 paths with identical inspect output; the only path whose spec changed vs the pre-merge tip is POST /prompts, which now serves the fixed create_prompt example that fix(prompts): reject keyed prompt_data with prompt_id and populate prompt version #38404 shipped

Snapshot drift check

  1. uv run python -m litellm.proxy._lazy_openapi_snapshot && git diff --stat -- litellm/proxy/_lazy_openapi_snapshot.json
  2. Observed: wrote 33 feature fragments, then 1 file changed, 7514 insertions(+), 956 deletions(-) with no CI job or local check flagging it

After (cd9dcb5)

Lazy routes for a2a_registration and gemini_agents

  1. curl -s http://localhost:56140/openapi.json | python3 inspect.py
  2. Observed:
paths: 553
/v1/a2a/discover {'post': 'Discover Agent Card'}
/v1beta/agents {'get': 'List Gemini Agents', 'post': 'Create Gemini Agent'}
/v1beta/agents/{name} {'delete': 'Delete Gemini Agent', 'get': 'Get Gemini Agent'}
/v1beta/agents/{name}/versions {'get': 'List Gemini Agent Versions'}

Docstring drift on GET /policies/list and POST /v1/agents

  1. Same command, remaining output lines:
GET /policies/list: as production versions. On a name conflict with a production DB policy, only the DB policy
POST /v1/agents: '        "agent_card_params": {'

Snapshot drift check

  1. uv run python -m litellm.proxy._lazy_openapi_snapshot && git diff --stat -- litellm/proxy/_lazy_openapi_snapshot.json
  2. Observed: wrote 33 feature fragments and an empty diff; three local runs plus one from a CI-equivalent uv sync --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router env on Python 3.12 produced byte-identical files
  3. Guard check (captured at afe5a24; scripts/pre_commit_lint.sh and the workflow are unchanged since): delete the gemini_agents fragment from the JSON, git add it, run make check
  4. Observed:
check: checking the lazy OpenAPI snapshot and dashboard API types are in sync (npm run gen:api)
✗ The lazy OpenAPI snapshot is stale; regenerated litellm/proxy/_lazy_openapi_snapshot.json. Stage it and commit; re-run make check only if other checks failed too.
check: FAIL

Type

🐛 Bug Fix
🚄 Infrastructure

Caveats (if any)

Medium

  • mcp_management, cloudzero, vantage, config_overrides fragments still missing from /openapi.json on a DB-backed proxy

    • Their modules get imported at boot, which switches the snapshot injection off; the routers are never mounted
    • Pre-existing on the merge base, this PR leaves it alone; tracked in LIT-6275
  • Open PRs that change a lazily loaded route now fail the schema.d.ts sync job until they regenerate the snapshot

Low

  • The schema.d.ts sync job now also imports every lazy feature, adding roughly a minute to that check

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

  • cd9dcb5 passes /live-pr-risk


Note

Low Risk
Infrastructure and OpenAPI snapshot sync only; no changes to request handling, auth, or data paths in the reviewed workflow step.

Overview
Keeps litellm/proxy/_lazy_openapi_snapshot.json aligned with lazily loaded proxy routes so /openapi.json and the dashboard types are not stale before those features are imported at runtime.

The Check UI API Types workflow now regenerates the snapshot (python -m litellm.proxy._lazy_openapi_snapshot) after Prisma generate and fails if the committed JSON drifts, with instructions to rerun the module and npm run gen:api. The same regenerate-and-diff flow is mirrored in make check (scripts/pre_commit_lint.sh), and the snapshot writer refuses to commit a file when any lazy feature fails to import.

Reviewed by Cursor Bugbot for commit cd9dcb5. Bugbot is set up for automated code reviews on this repo. Configure here.

The committed snapshot behind /openapi.json for unloaded lazy features had drifted on 30 of 31 fragments and never had one for a2a_registration or gemini_agents, so those routes showed as placeholder GET stubs or old docstrings until traffic loaded them. Regenerate the snapshot and schema.d.ts, make the check-ui-api-types job and make check regenerate the snapshot and fail on drift, and make the generator refuse to write a snapshot when any feature fails to import so a broken import cannot silently drop fragments.
@mateo-berri
mateo-berri requested a review from a team August 26, 2026 21:32
@greptile-apps

greptile-apps Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR regenerates the lazy-route OpenAPI snapshot and generated dashboard API types, then adds local and CI drift checks.

  • Adds complete snapshots for all 33 lazy feature fragments.
  • Aborts snapshot generation if any lazy feature fails to import or register.
  • Regenerates and validates the snapshot before checking dashboard API types.
  • Adds coverage for deterministic generation, import failures, and snapshot output.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/proxy/_lazy_openapi_snapshot.py Generates typed, deterministic lazy-route OpenAPI fragments and refuses partial output when feature imports fail.
tests/test_litellm/proxy/test_lazy_openapi_snapshot.py Covers snapshot generation, deterministic serialization, missing features, and import or registration failures.
.github/workflows/check-ui-api-types.yml Regenerates the lazy OpenAPI snapshot and rejects drift before regenerating dashboard types.
scripts/pre_commit_lint.sh Adds the same snapshot regeneration and drift validation to the local check workflow.
litellm/proxy/_lazy_openapi_snapshot.json Refreshes lazy feature fragments and adds previously absent a2a_registration and gemini_agents specifications.
ui/litellm-dashboard/src/lib/http/schema.d.ts Regenerates dashboard API types from the updated proxy OpenAPI specification.

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/lit..." | Re-trigger Greptile

Comment thread litellm/proxy/_lazy_openapi_snapshot.py Outdated
@codecov

codecov Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.14815% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/_lazy_openapi_snapshot.py 98.14% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit e9f3963. Configure here.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit cd9dcb5. Configure here.

@mateo-berri
mateo-berri enabled auto-merge August 26, 2026 22:12
@codspeed

codspeed Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_regenerate_lazy_openapi_snapshot (cd9dcb5) with litellm_internal_staging (f57e4b8)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (632a007) during the generation of this report, so f57e4b8 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@mateo-berri
mateo-berri merged commit 62341e9 into litellm_internal_staging Aug 27, 2026
83 of 84 checks passed
@mateo-berri
mateo-berri deleted the litellm_regenerate_lazy_openapi_snapshot branch August 27, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants