Skip to content

Preload installed locale resources and keep user overrides live - #95

Open
goldyfruit wants to merge 4 commits into
OpenVoiceOS:devfrom
goldyfruit:feat/resource-expansion-cache
Open

Preload installed locale resources and keep user overrides live#95
goldyfruit wants to merge 4 commits into
OpenVoiceOS:devfrom
goldyfruit:feat/resource-expansion-cache

Conversation

@goldyfruit

@goldyfruit goldyfruit commented Aug 9, 2026

Copy link
Copy Markdown

Summary

LocaleResources now follows the installed-resource lifecycle directly:

  • skill and core locale trees are read and indexed once at construction;
  • valid intent, entity, vocabulary, and blacklist resources are pre-expanded;
  • dialogs and prompts remain in memory;
  • symlinked locale aliases share one physical snapshot;
  • returned lists remain defensive copies;
  • only the optional user_locale tree stays live, so create/update/delete is visible without restarting;
  • a live user vocabulary re-expands a static intent that references it;
  • unused malformed resources remain access-local and cannot break process startup.

The earlier opt-in bounded LRU and expanded_cache_size API were removed. Installed resources have no runtime invalidation path because replacing an installed package or skill already requires recreating its owning service/resource loader. There is no arbitrary capacity, eviction policy, subclass, or compatibility fallback.

The snapshot also gives standardized exact locale directories precedence over macro-language fallback. For example, eu-ES remains distinct when both eu/ and eu-ES/ exist. Explicit custom resolvers retain control.

Why

The runtime previously repeated installed-directory discovery, file reads, vocabulary-map assembly, and template expansion. Those inputs are static for the process lifetime. User overrides are the only source that needs runtime change detection, and they are deliberately excluded from the immutable snapshot.

Benchmark

Compared exact PR head 80cd7c8056858549197333b47320394967660f2c with dev baseline 18272335d247e88e639477d7793753b2135a38b5 on Python 3.14.6 and an AMD Ryzen AI 9 HX 370. The harness loaded both implementations in the same process, warmed filesystem pages, and reported medians. Expanded-resource trials used at least 1,600 lookups; the StopService-equivalent trial used 2,000 requests across en-us and fr-fr and performed both stop and global_stop vocabulary matches.

Real resource tree Snapshot construction Retained snapshot Expanded lookup, baseline → PR Dialog lookup, baseline → PR
Thalovant Weather: 86 expanded, 94 dialogs 10.254 ms 604.3 KiB 450.787 → 1.096 µs (411.1×) 96.151 → 0.482 µs (199.4×)
Thalovant Date/Time: 76 expanded, 80 dialogs 5.628 ms 466.0 KiB 303.353 → 0.719 µs (421.7×) 68.667 → 0.462 µs (148.7×)
Thalovant Joke Garden: 72 expanded, 60 dialogs 5.107 ms 379.8 KiB 396.840 → 0.714 µs (556.1×) 57.465 → 0.476 µs (120.6×)
OVOS Core stop locales: 59 valid expanded across 17 locales 6.876 ms 411.7 KiB 338.579 → 0.770 µs (439.9×) n/a

Runtime hot path

Scenario dev PR Change
StopService-equivalent request, two locales 720.327 µs 112.769 µs 6.4× faster, 607.558 µs CPU saved/request

The optional empty live-user layer costs 59–77 µs per lookup because it must check for runtime changes. It remains isolated from the installed static path. This is a resource-loader microbenchmark and does not claim an equivalent end-to-end assistant latency reduction.

Five existing Core stop resources fail strict expansion. The constructor snapshots their source data but deliberately defers their existing validation failures until those individual resources are accessed; unused locales therefore do not become startup failures.

Validation

  • uv run --extra test pytest -q582 passed
  • uv run ruff check ovos_spec_tools/resources.py test/test_resources.py test/test_find_lang_dir.py — passed
  • uv run --with build python -m build — sdist and wheel built
  • 532-resource parity sweep across Weather, Date/Time, Joke Garden, and OVOS Core — 532/532 equal
  • git diff --check — passed

Tests cover immutable skill/core snapshots, defensive copies, live user create/update/delete, static-intent re-expansion from live user vocabularies, live dialogs/prompts, malformed-resource fault isolation, exact regional locale selection, and custom resolver behavior.

No CI workflow was changed.

Summary by CodeRabbit

  • New Features

    • Improved locale resource performance with consistent caching for installed resources.
    • User-provided resource changes now take effect immediately without recreating the resource manager.
    • Added exact regional-language matching before falling back to a base language.
    • Protected returned resource data from unintended modification.
  • Bug Fixes

    • Improved handling of vocabulary, prompts, dialogs, and intent expansions when resources change.
    • Preserved validation for duplicate, empty, and invalid resource data.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

LocaleResources now snapshots installed skill/core resources at construction and keeps user resources live. Static language resolution, file reads, prompts, and expansions use caches. User overrides are re-read and re-expanded on each call.

Changes

Locale resource lifecycle

Layer / File(s) Summary
Exact language resolution
ovos_spec_tools/resources.py, test/test_find_lang_dir.py, test/test_resources.py, docs/locale-resources.md
Exact standardized language directories take precedence over fallback resolution. Static resolution is cached per requested language, while user-language resolution remains live.
Resource snapshot and source plumbing
ovos_spec_tools/resources.py
Installed skill/core resources are indexed and cached during construction. User resources are checked on each call. Resource lookup, vocabulary, entities, keywords, dialogs, and prompts use shared source helpers.
Expansion behavior and validation
ovos_spec_tools/resources.py, test/test_resources.py, docs/api-reference.md, docs/locale-resources.md
Static expansions use cached tuples and return fresh lists. User vocabulary changes trigger intent re-expansion. Tests cover live overrides, mutation isolation, exact precedence, and deferred malformed-resource errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: jarbasal

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant LocaleResources
  participant UserResources
  participant StaticResourceIndex
  Caller->>LocaleResources: request resource or expansion
  LocaleResources->>UserResources: read current user files
  UserResources-->>LocaleResources: return override or no match
  LocaleResources->>StaticResourceIndex: read indexed static resource
  StaticResourceIndex-->>LocaleResources: return cached static value
  LocaleResources-->>Caller: return fresh result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preloading installed locale resources while keeping user overrides live.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@goldyfruit goldyfruit changed the title Add opt-in expanded resource caching Preload installed locale resources and keep user overrides live Aug 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/test_resources.py (1)

130-152: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Compare resolved paths in counted_reader, and do not shadow the module name.

_snapshot_static_sources reads through lang_target.resolve(), so path is a resolved path. The test compares it to the unresolved resource. If the temporary root is a symlink, the comparison fails, reads stays 0, and the assertion fails for an unrelated reason. Also, rebinding resources to the LocaleResources instance hides the module that was just patched.

♻️ Proposed test hardening
-    def counted_reader(path):
-        nonlocal reads
-        if path == resource:
-            reads += 1
-        return original_reader(path)
-
-    monkeypatch.setattr(resources, "read_resource_file", counted_reader)
-    resources = LocaleResources(str(locale))
-
-    first = resources.load_vocabulary("stop", "en-US")
-    first.append("mutated by caller")
-    assert resources.load_vocabulary("stop", "en-US") == ["stop"]
-    assert reads == 1
+    target = resource.resolve()
+
+    def counted_reader(path):
+        nonlocal reads
+        if Path(path).resolve() == target:
+            reads += 1
+        return original_reader(path)
+
+    monkeypatch.setattr(resources, "read_resource_file", counted_reader)
+    loader = LocaleResources(str(locale))
+
+    first = loader.load_vocabulary("stop", "en-US")
+    first.append("mutated by caller")
+    assert loader.load_vocabulary("stop", "en-US") == ["stop"]
+    assert reads == 1

The diff assumes Path is imported in this module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test_resources.py` around lines 130 - 152, Update
test_static_resources_are_read_once_and_return_defensive_copies so
counted_reader compares path against resource.resolve(), and use a distinct
variable name for the LocaleResources instance instead of rebinding the patched
resources module. Keep the existing assertions and read-count behavior
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ovos_spec_tools/resources.py`:
- Around line 427-432: The documentation must clarify that expanded results are
cached and pre-expanded only when no user_locale is configured, while the
resource index, file contents, dialogs, and prompts remain cached in both
configurations. Update ovos_spec_tools/resources.py lines 427-432 and
docs/locale-resources.md lines 86-96 to state this condition consistently.
- Around line 505-532: Update the static-language initialization around the
source-directory traversal so construction indexes resource paths and groups
them by language without calling read_resource_file or read_prompt_file for
every language. Defer those reads until the corresponding language is first
requested, then cache the loaded contents for reuse while preserving
language_index and _static_lines/_static_prompts behavior.
- Around line 461-491: The constructor currently lacks the documented opt-in
expanded cache API and always snapshots static resources, preventing live locale
reloads. Add the public expanded_cache_size option to the resource class
constructor, default it to disabled, use it to control _snapshot_static_sources
and expanded-resource caching, and provide an explicit refresh/invalidation
method that clears relevant caches and rebuilds snapshots; preserve existing
behavior when caching is enabled.

---

Nitpick comments:
In `@test/test_resources.py`:
- Around line 130-152: Update
test_static_resources_are_read_once_and_return_defensive_copies so
counted_reader compares path against resource.resolve(), and use a distinct
variable name for the LocaleResources instance instead of rebinding the patched
resources module. Keep the existing assertions and read-count behavior
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 466febac-edfd-43e8-a65f-cab2d828c921

📥 Commits

Reviewing files that changed from the base of the PR and between 4d5efed and d62e770.

📒 Files selected for processing (5)
  • docs/api-reference.md
  • docs/locale-resources.md
  • ovos_spec_tools/resources.py
  • test/test_find_lang_dir.py
  • test/test_resources.py

Comment thread ovos_spec_tools/resources.py Outdated
Comment thread ovos_spec_tools/resources.py
Comment thread ovos_spec_tools/resources.py
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