mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
refactor: gitignore generated openapi files, keep only alias types
- Filter schema to management endpoints only (182 paths vs 510), reducing api.generated.ts from 50k to 18.5k lines - Gitignore openapi.json and api.generated.ts (intermediate artifacts) - Only committed files are the script, alias types, and package.json
This commit is contained in:
parent
d7123aef05
commit
e7893c07d7
4 changed files with 133 additions and 22 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -103,6 +103,5 @@ STABILIZATION_TODO.md
|
|||
**/*.storageState.json
|
||||
**/coverage
|
||||
|
||||
# Generated OpenAPI types (regenerate with: cd ui/litellm-dashboard && npm run generate:all)
|
||||
ui/litellm-dashboard/openapi.json
|
||||
# Generated OpenAPI types (regenerate with: cd ui/litellm-dashboard && npm run generate:types)
|
||||
ui/litellm-dashboard/src/types/api.generated.ts
|
||||
|
|
@ -1,25 +1,106 @@
|
|||
"""
|
||||
Export the LiteLLM proxy's OpenAPI schema to a JSON file.
|
||||
Generate TypeScript types for LiteLLM management endpoints.
|
||||
|
||||
Usage:
|
||||
python scripts/export_openapi.py [output_path]
|
||||
python scripts/export_openapi.py
|
||||
|
||||
Defaults to ui/litellm-dashboard/openapi.json if no path is given.
|
||||
Does NOT require a running server — imports the FastAPI app and calls app.openapi().
|
||||
Filters to management endpoints only (no passthrough/LLM API routes).
|
||||
Produces ui/litellm-dashboard/src/types/api.generated.ts (gitignored).
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# Tags for routes the UI dashboard actually uses.
|
||||
# Routes without these tags (passthrough, OpenAI-compatible API, etc.) are excluded.
|
||||
INCLUDED_TAGS = {
|
||||
# Management endpoints (UI dashboard)
|
||||
"access group management",
|
||||
"budget management",
|
||||
"credential management",
|
||||
"email management",
|
||||
"key management",
|
||||
"model management",
|
||||
"organization management",
|
||||
"policy management",
|
||||
"project management",
|
||||
"tag management",
|
||||
"team management",
|
||||
"tool management",
|
||||
"vector store management",
|
||||
"Settings",
|
||||
"SSO Settings",
|
||||
"UI Settings",
|
||||
"UI Theme Settings",
|
||||
"Router Settings",
|
||||
"[beta] MCP",
|
||||
"[beta] Agents",
|
||||
"[beta] A2A Agents",
|
||||
"health",
|
||||
# LLM API endpoints
|
||||
"chat/completions",
|
||||
"completions",
|
||||
"responses",
|
||||
}
|
||||
|
||||
|
||||
def filter_management_routes(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Remove all routes that aren't management endpoints.
|
||||
Also prunes unused schemas from components.
|
||||
"""
|
||||
filtered_paths: dict[str, Any] = {}
|
||||
|
||||
for path, path_obj in schema.get("paths", {}).items():
|
||||
filtered_methods: dict[str, Any] = {}
|
||||
for method, method_obj in path_obj.items():
|
||||
if not isinstance(method_obj, dict):
|
||||
filtered_methods[method] = method_obj
|
||||
continue
|
||||
tags = set(method_obj.get("tags", []))
|
||||
if tags & INCLUDED_TAGS:
|
||||
filtered_methods[method] = method_obj
|
||||
if any(isinstance(v, dict) for v in filtered_methods.values()):
|
||||
filtered_paths[path] = filtered_methods
|
||||
|
||||
schema["paths"] = filtered_paths
|
||||
|
||||
# Prune schemas not referenced by the remaining paths
|
||||
paths_json = json.dumps(filtered_paths)
|
||||
referenced = set(re.findall(r'#/components/schemas/([\w.\-]+)', paths_json))
|
||||
|
||||
# Schemas can reference other schemas, so resolve transitively
|
||||
all_schemas = schema.get("components", {}).get("schemas", {})
|
||||
resolved: set[str] = set()
|
||||
to_resolve = list(referenced)
|
||||
while to_resolve:
|
||||
name = to_resolve.pop()
|
||||
if name in resolved:
|
||||
continue
|
||||
resolved.add(name)
|
||||
if name in all_schemas:
|
||||
nested_json = json.dumps(all_schemas[name])
|
||||
for ref in re.findall(r'#/components/schemas/([\w.\-]+)', nested_json):
|
||||
if ref not in resolved:
|
||||
to_resolve.append(ref)
|
||||
|
||||
schema["components"]["schemas"] = {
|
||||
k: v for k, v in all_schemas.items() if k in resolved
|
||||
}
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def deduplicate_operation_ids(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
FastAPI pass-through proxy routes generate duplicate operationIds when
|
||||
multiple HTTP methods share the same path pattern. This causes
|
||||
openapi-typescript to emit duplicate TS identifiers.
|
||||
|
||||
multiple HTTP methods share the same path pattern.
|
||||
Fix: append _2, _3, etc. to duplicates.
|
||||
"""
|
||||
seen: dict[str, int] = {}
|
||||
|
|
@ -38,24 +119,57 @@ def deduplicate_operation_ids(schema: dict[str, Any]) -> dict[str, Any]:
|
|||
|
||||
|
||||
def main() -> None:
|
||||
# Determine output path
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
default_output = repo_root / "ui" / "litellm-dashboard" / "openapi.json"
|
||||
output_path = Path(sys.argv[1]) if len(sys.argv) > 1 else default_output
|
||||
ui_dir = repo_root / "ui" / "litellm-dashboard"
|
||||
output_ts = ui_dir / "src" / "types" / "api.generated.ts"
|
||||
|
||||
# Import the FastAPI app — this triggers module-level setup including
|
||||
# the assignment of app.openapi = get_openapi_schema
|
||||
# Step 1: Export OpenAPI schema from FastAPI app
|
||||
print("Exporting OpenAPI schema from FastAPI app...")
|
||||
from litellm.proxy.proxy_server import app
|
||||
|
||||
schema = app.openapi()
|
||||
|
||||
full_paths = len(schema.get("paths", {}))
|
||||
full_schemas = len(schema.get("components", {}).get("schemas", {}))
|
||||
|
||||
# Step 2: Filter to management endpoints only
|
||||
schema = filter_management_routes(schema)
|
||||
schema = deduplicate_operation_ids(schema)
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(json.dumps(schema, indent=2) + "\n")
|
||||
mgmt_paths = len(schema.get("paths", {}))
|
||||
mgmt_schemas = len(schema.get("components", {}).get("schemas", {}))
|
||||
|
||||
print(f"OpenAPI schema written to {output_path}")
|
||||
print(f" paths: {len(schema.get('paths', {}))}")
|
||||
print(f" schemas: {len(schema.get('components', {}).get('schemas', {}))}")
|
||||
print(f" Full schema: {full_paths} paths, {full_schemas} schemas")
|
||||
print(f" After filtering: {mgmt_paths} paths, {mgmt_schemas} schemas")
|
||||
|
||||
# Step 3: Run openapi-typescript to generate TS types
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".json", delete=False
|
||||
) as tmp:
|
||||
json.dump(schema, tmp, indent=2)
|
||||
tmp_path = tmp.name
|
||||
|
||||
print(f"Generating TypeScript types -> {output_ts.relative_to(repo_root)}")
|
||||
output_ts.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
result = subprocess.run(
|
||||
["npx", "openapi-typescript", tmp_path, "-o", str(output_ts)],
|
||||
cwd=str(ui_dir),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
Path(tmp_path).unlink(missing_ok=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"openapi-typescript failed:\n{result.stderr}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Report final size
|
||||
lines = output_ts.read_text().count("\n")
|
||||
size_kb = output_ts.stat().st_size / 1024
|
||||
print(f" Output: {lines} lines, {size_kb:.0f} KB")
|
||||
print("Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@
|
|||
"e2e:ui": "playwright test --ui --config e2e_tests/playwright.config.ts",
|
||||
"knip": "knip",
|
||||
"knip:fix": "knip --fix",
|
||||
"generate:schema": "python ../../scripts/export_openapi.py",
|
||||
"generate:types": "openapi-typescript ./openapi.json -o src/types/api.generated.ts",
|
||||
"generate:all": "npm run generate:schema && npm run generate:types"
|
||||
"generate:types": "python ../../scripts/export_openapi.py"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.54.0",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Key management types — derived from the backend OpenAPI schema.
|
||||
*
|
||||
* Depends on api.generated.ts which is gitignored.
|
||||
* Run `npm run generate:all` from ui/litellm-dashboard/ to generate it.
|
||||
* Run `npm run generate:types` from ui/litellm-dashboard/ to generate it.
|
||||
*/
|
||||
import type { components, paths } from "./api.generated";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue