Skip to content

cache PlanExeConfig / PlanExeDotEnv / PlanExeLLMConfig loaders#673

Merged
neoneye merged 2 commits intomainfrom
perf/cache-config-loaders
May 5, 2026
Merged

cache PlanExeConfig / PlanExeDotEnv / PlanExeLLMConfig loaders#673
neoneye merged 2 commits intomainfrom
perf/cache-config-loaders

Conversation

@neoneye
Copy link
Copy Markdown
Member

@neoneye neoneye commented May 5, 2026

Summary

Follow-up to #671. That PR cached _load_llm_config in llm_factory, but the underlying loaders still re-parsed on every direct caller — most visibly run_plan_pipeline.resolve_luigi_workers calling PlanExeLLMConfig.load(...) directly. Each PlanExeLLMConfig.load also fans out into two PlanExeConfig.load calls (one direct, one via PlanExeDotEnv.load), so the production logs kept emitting:

INFO  planexe_config        - Optional file '.env' not found …
INFO  planexe_config        - Optional configuration file '.env' not found; relying on environment variables only.
WARN  planexe_llmconfig     - Environment variable 'OPENAI_API_KEY' not found.
WARN  planexe_llmconfig     - Environment variable 'DASHSCOPE_API_KEY' not found.
WARN  planexe_llmconfig     - Environment variable 'DEEPSEEK_API_KEY' not found.
INFO  planexe_llmconfig     - Applied PLANEXE_LLM_CONFIG_WHITELISTED_CLASSES=…

…on essentially every Luigi task.

Changes

Move the body of each loader's load(...) classmethod into a module-level @functools.cache-decorated _load_cached(...) helper.

  • PlanExeConfig.load(...)
  • PlanExeDotEnv.load() (singleton — no args)
  • PlanExeLLMConfig.load(...)

The classmethod normalizes inputs to hashable form (stripping ModelProfileEnum to its str value) and delegates. Cache keys are plain tuples of normalized inputs. cache_clear() is available on each _load_cached if a test ever needs to reset.

Reorder PlanExeLLMConfig.load: filter by whitelisted classes BEFORE substituting env vars. With PLANEXE_LLM_CONFIG_WHITELISTED_CLASSES=OpenRouter in production, entries that need OPENAI_API_KEY / DASHSCOPE_API_KEY / DEEPSEEK_API_KEY are dropped before substitution looks for those vars — those warnings stop firing entirely, even on first cache miss.

Each Luigi worker subprocess still parses once on startup. That's a hard limit of in-process caching across subprocess boundaries — expect one batch of warnings per fresh process, not zero.

Test plan

  • Run a full plan in production. Verify Optional file '.env' not found / Environment variable 'X' not found lines appear at most once per worker process (not once per task).
  • With PLANEXE_LLM_CONFIG_WHITELISTED_CLASSES=OpenRouter, verify OPENAI_API_KEY / DASHSCOPE_API_KEY / DEEPSEEK_API_KEY warnings no longer appear at all.
  • Confirm get_llm("openrouter-…") still resolves to a working model.

🤖 Generated with Claude Code

neoneye added 2 commits May 5, 2026 22:32
#671 cached `_load_llm_config` in llm_factory, but the underlying
loaders still re-parsed on every direct caller — most notably
`run_plan_pipeline.resolve_luigi_workers` calling
`PlanExeLLMConfig.load(...)` directly. Each call also fans out
into two `PlanExeConfig.load` invocations (one direct, one via
`PlanExeDotEnv.load`), so a single LLMConfig load logs the
".env not found" / "Optional configuration file '.env' not
found" / per-API-key warnings every time.

Move the work in each loader's `load(...)` classmethod into a
module-level `@functools.cache`-decorated `_load_cached(...)`
helper. The classmethod normalizes inputs to hashable form
(stripping `ModelProfileEnum` to its `str` value) and delegates.
Each cache key is a plain tuple of inputs; `cache_clear()` is
available on each `_load_cached` if tests need to reset.

Also reorder `PlanExeLLMConfig.load`: filter by whitelisted
classes BEFORE substituting env vars. With
`PLANEXE_LLM_CONFIG_WHITELISTED_CLASSES=OpenRouter` set in
production, the entries needing `OPENAI_API_KEY` /
`DASHSCOPE_API_KEY` / `DEEPSEEK_API_KEY` are dropped before
substitution looks for those vars, so those warnings stop
firing entirely — even on the first cache miss.

Each Luigi worker subprocess still parses once on startup.
That's a hard limit of in-process caching — one batch of
warnings per fresh process, not zero.
Refactor in 6300ca7 ("cache PlanExeConfig / PlanExeDotEnv /
PlanExeLLMConfig loaders") accidentally moved instance methods
out of two classes:

- `PlanExeDotEnv.update_os_environ`, `get`, `get_absolute_path_to_file`,
  `get_absolute_path_to_dir`, `__repr__`
- `PlanExeLLMConfig.load_llm_config`, `substitute_env_vars`,
  `filter_by_whitelisted_classes`, `__repr__`

The methods ended up indented at module level after a `return`
statement inside `_load_cached`, so they parsed as nested
functions inside `_load_cached`'s body and were unreachable —
not class methods.

Production blew up at app.py:17 with
`AttributeError: 'PlanExeDotEnv' object has no attribute 'update_os_environ'`
on container start.

Move the methods back inside their respective class bodies and
keep the cached `_load_cached` after the class definition.
@neoneye neoneye merged commit f5c5f7f into main May 5, 2026
3 checks passed
@neoneye neoneye deleted the perf/cache-config-loaders branch May 5, 2026 20:45
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.

1 participant