Skip to content

perf: cut TUI startup from ~20s to ~0.15s (lazy-import litellm + IPv4-first) - #15

Merged
Aaryanverma merged 3 commits into
Aaryanverma:mainfrom
gilsonolegario:pr-perf
Aug 9, 2026
Merged

perf: cut TUI startup from ~20s to ~0.15s (lazy-import litellm + IPv4-first)#15
Aaryanverma merged 3 commits into
Aaryanverma:mainfrom
gilsonolegario:pr-perf

Conversation

@gilsonolegario

Copy link
Copy Markdown
Contributor

Summary

graybox.cli startup currently takes ~20s because importing it pulls in litellm, whose own import does a blocking network fetch of its model-cost map (and hangs ~20s on networks without routable IPv6). This PR makes importing the CLI ~0.14s — a ~150x speedup.

Changes

Two commits:

  1. perf: lazy-import LLM modules for fast startup — litellm and its ~900 modules are no longer imported at module level. They load only when a command actually needs the LLM (organize/ask/chat/dupes/merge/edit/delete/refresh).
  2. perf: prefer IPv4 and local cost map in litellm startup — two fixes applied before litellm is imported:
    • Reorder getaddrinfo results so IPv4 is tried first (avoids the ~20s hang on broken-IPv6 networks).
    • Set LITELLM_LOCAL_MODEL_COST_MAP so litellm's bundled local cost map is used instead of a network fetch.

Verification

  • python3 -c "import time; t0=time.time(); import graybox.cli; print(time.time()-t0)"
    • before: 21.5s
    • after: 0.13s
  • 245 tests pass.

Why

The interactive TUI (graybox.cli with no args) is the primary way people run Gray Box. Waiting ~20s for a splash screen that doesn't even use the LLM is a poor first impression and unnecessary work.

Importing graybox.cli dropped from ~1.4s to ~0.14s. litellm and its
~900 modules are now only loaded when a command that uses the LLM runs
(organize/ask/chat/dupes/merge/edit/delete/refresh).

Imports moved from module level into the command functions:
- graybox.ai.AIService
- graybox.retrieval (ask, ConversationTurn)
- graybox.organizer (organize_all)
- graybox.curate (find_possible_duplicates, merge_pages, edit_page, delete_page)
- graybox.summarizer (refresh_all_summaries)
litellm's own import does a blocking network fetch of its model-cost
map, which hangs ~20s on networks without routable IPv6 (getaddrinfo
lists IPv6 first). Two fixes, both applied before litellm is imported:

- Reorder getaddrinfo results so IPv4 is tried first.
- Set LITELLM_LOCAL_MODEL_COST_MAP so the bundled local cost map is
  used instead of a network fetch — faster for everyone.

Tests cover the reordering, the patching, and that both happen before
the litellm import.
@Aaryanverma

Copy link
Copy Markdown
Owner

Hi @gilsonolegario,

Request changes:

  • The lazy-import optimization is good and should be retained. However, graybox.ai.ai_service globally replaces socket.getaddrinfo, which changes DNS resolution behavior for the entire host process, not just LiteLLM. Please avoid the global monkey-patch if possible. First verify whether LITELLM_LOCAL_MODEL_COST_MAP=true eliminates the import-time network dependency by itself; otherwise isolate the IPv4 preference to the LiteLLM/networking layer.

  • Also add a regression test ensuring importing/starting the CLI does not load LiteLLM.

@gilsonolegario

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — the points are valid and both are addressed in the latest commit.

  • I verified empirically that LITELLM_LOCAL_MODEL_COST_MAP=true alone eliminates the import-time network dependency: with it set, import litellm performs zero getaddrinfo/network calls (litellm 1.93.0). The global socket.getaddrinfo monkey-patch has been removed entirely; the env var is still set right before the litellm import, which is all that's needed.
  • Added tests/test_startup.py: a subprocess-based regression test asserting that importing graybox.cli and starting the CLI (main(['--help'])) does not load litellm.

Full suite passes (243 tests on this branch). Happy to adjust if anything else stands out.

@Aaryanverma
Aaryanverma merged commit 75ea594 into Aaryanverma:main Aug 9, 2026
4 checks passed
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.

2 participants