perf(imports): lazy-load heavy SDKs off the cold-start waterfall

Four deferrals following the established truthy-skip / PEP 562
lazy-load patterns (PRs #22681/#22859 lineage). Rebased over #74194,
which independently landed the browser_tool half of this work — that
file is dropped here; the remaining four modules are untouched by it:

- tools/vision_tools.py: defer agent.auxiliary_client
  (credential_pool -> hermes_cli.auth -> httpx -> rich, ~50 ms) to
  first vision handler call. async_call_llm /
  extract_content_or_reasoning stay patchable module attributes;
  injected test mocks win over the loader.
- agent/model_metadata.py: defer 'requests' (+urllib3, ~27 ms of the
  'import cli' waterfall) to the fetch functions. PEP 562 __getattr__
  keeps patch('agent.model_metadata.requests.get') working.
- tools/browser_supervisor.py: websockets (~22 ms) imports on first
  CDP connect; ClientConnection type under TYPE_CHECKING.
- cron/jobs.py: croniter (~15 ms) resolves on first cron-expression
  use; HAS_CRONITER stays monkeypatchable (None = unprobed sentinel).

A/B vs current main incl. #74194 (median of 7, cold subprocess):
  import cli          147 -> 132 ms  (-10%)
  import model_tools  244 -> 224 ms  (-8%)
  import run_agent    264 -> 244 ms  (-8%)

Lazy-verify: importing the four modules no longer pulls requests /
croniter / websockets into sys.modules. 369 targeted tests green
post-rebase.
This commit is contained in:
teknium1 2026-07-29 09:24:17 -07:00 committed by Teknium
parent 2006cd5895
commit bc747001ee
4 changed files with 82 additions and 14 deletions

View file

@ -26,10 +26,14 @@ import logging
import threading
import time
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
import websockets
from websockets.asyncio.client import ClientConnection
# ``websockets`` costs ~22 ms at import and is only needed when a supervisor
# actually connects to a CDP endpoint (``_connect_ws``). With
# ``from __future__ import annotations`` in force the ``ClientConnection``
# annotation is string-only, so the type import stays under TYPE_CHECKING.
if TYPE_CHECKING:
from websockets.asyncio.client import ClientConnection
logger = logging.getLogger(__name__)
@ -649,6 +653,7 @@ class CDPSupervisor:
attempt = 0
last_success_at = 0.0
backoff = 0.5
import websockets # deferred: only supervisors that connect pay the import
while not self._stop_requested:
try:
self._ws = await asyncio.wait_for(

View file

@ -40,7 +40,28 @@ from pathlib import Path
from typing import Any, Awaitable, Dict, Optional
from urllib.parse import urlparse
import httpx
from agent.auxiliary_client import async_call_llm, extract_content_or_reasoning
# ``agent.auxiliary_client`` pulls credential_pool → hermes_cli.auth → httpx
# → rich (~50 ms cold); only vision handlers need it. Loaded lazily; both
# names stay module attributes so tests can keep patching
# ``tools.vision_tools.async_call_llm``. Truthy-skip: injected mocks win.
async_call_llm: Any = None
extract_content_or_reasoning: Any = None
def _load_auxiliary_client() -> None:
global async_call_llm, extract_content_or_reasoning
if async_call_llm is None or extract_content_or_reasoning is None:
from agent.auxiliary_client import (
async_call_llm as _acl,
extract_content_or_reasoning as _ecr,
)
if async_call_llm is None:
async_call_llm = _acl
if extract_content_or_reasoning is None:
extract_content_or_reasoning = _ecr
from hermes_constants import get_hermes_dir
from tools.debug_helpers import DebugSession
from tools.website_policy import check_website_access
@ -1251,6 +1272,7 @@ async def vision_analyze_tool(
}
if model:
call_kwargs["model"] = model
_load_auxiliary_client()
# Try full-size image first; on size-related rejection, downscale and retry.
try:
response = await async_call_llm(**call_kwargs)
@ -1758,6 +1780,7 @@ async def video_analyze_tool(
if model:
call_kwargs["model"] = model
_load_auxiliary_client()
response = await async_call_llm(**call_kwargs)
analysis = extract_content_or_reasoning(response)