agent-zero/extensions/python/startup_migration/_05_framework_dependencies.py
Alessandro b91384f4fb Upgrade LiteLLM and OpenAI with framework startup migration
Update LiteLLM to 1.104.0, OpenAI to 2.54.0, and python-dotenv to 1.2.2.
Raise shared security floors for aiohttp, anyio, urllib3, filelock, fsspec,
and multidict while retaining the compatible OpenAI 2.x SDK line.

Use the existing startup_migration hook to apply the same requirements to
v2.x Docker instances updating to v2.14. Read versions from requirements.txt,
install only when requirements are unmet, and reuse restart_process() to
load the upgraded SDKs before serving requests. Skip current and non-Docker
environments, bound installation time, and propagate installation failures.

Add installed-SDK HTTP tests for Chat Completions, Responses, streaming,
tools, state, usage, fallback, embeddings, and Anthropic cache markers.
Cover dependency migration, restart behavior, and installation failures.

Validation:
- 70 focused dependency, SDK, and self-update tests passed.
- Docker startup upgraded the v2.0-v2.13 SDK pins and became healthy in
  24 seconds, with exactly one dependency installation and process restart.
- An already-current startup performed no installation or extra restart.
- pip check passed; live Codex Responses and Moonshot Chat Completions
  tasks and follow-ups validated the upgraded SDK versions.
2026-10-07 14:50:32 +02:00

67 lines
2.3 KiB
Python

import importlib.metadata
from pathlib import Path
import shutil
import subprocess
import sys
from packaging.requirements import Requirement
from helpers import files, process, runtime
from helpers.extension import Extension
from helpers.print_style import PrintStyle
_REQUIREMENTS_FILE = Path(files.get_abs_path("requirements.txt"))
_PACKAGES = {
"litellm", "openai", "python-dotenv", "aiohttp", "anyio", "urllib3",
"filelock", "fsspec", "multidict",
}
class FrameworkDependencies(Extension):
def execute(self, **kwargs):
if not runtime.is_dockerized():
return
requirements = _requirements()
outdated = _outdated(requirements)
if not outdated:
return
uv = shutil.which("uv")
if not uv:
raise RuntimeError("Framework dependency update requires 'uv'.")
PrintStyle.info("Updating framework dependencies:", ", ".join(outdated))
subprocess.check_call(
[uv, "pip", "install", "--python", sys.executable, *map(str, requirements)],
timeout=60,
)
outdated = _outdated(requirements)
if outdated:
raise RuntimeError(f"Framework dependencies still unavailable after installation: {outdated}")
# Models were imported before startup migrations; load the upgraded SDKs.
process.restart_process()
def _requirements() -> list[Requirement]:
requirements = []
for line in _REQUIREMENTS_FILE.read_text(encoding="utf-8").splitlines():
line = line.partition("#")[0].strip()
if line:
requirement = Requirement(line)
if requirement.name in _PACKAGES:
requirements.append(requirement)
missing = _PACKAGES - {requirement.name for requirement in requirements}
if missing:
raise RuntimeError(f"Framework requirements missing from {_REQUIREMENTS_FILE}: {sorted(missing)}")
return requirements
def _outdated(requirements: list[Requirement]) -> list[str]:
outdated = []
for requirement in requirements:
try:
installed = importlib.metadata.version(requirement.name)
except importlib.metadata.PackageNotFoundError:
installed = None
if installed is None or installed not in requirement.specifier:
outdated.append(str(requirement))
return outdated