Skip to content

Commit 10fb93b

Browse files
committed
fix(llmrails): load library files deterministically
1 parent 68db0d2 commit 10fb93b

3 files changed

Lines changed: 23 additions & 1 deletion

File tree

nemoguardrails/llm/prompts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ def _load_prompts() -> List[TaskPrompt]:
4141

4242
for path in prompts_dirs:
4343
for root, dirs, files in os.walk(path):
44+
dirs.sort()
45+
files.sort()
4446
for filename in files:
4547
if filename.endswith(".yml") or filename.endswith(".yaml"):
4648
with open(os.path.join(root, filename), encoding="utf-8") as prompts_file:

nemoguardrails/rails/llm/llmrails.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,13 @@ def __init__(
318318
self.config.flows.extend(default_flows)
319319

320320
# We also need to load the content from the components library.
321+
# Sort entries so the traversal order is filesystem-independent;
322+
# otherwise the order in which library bot_messages are inserted
323+
# (and which definition wins on collisions) varies between platforms.
321324
library_path = os.path.join(os.path.dirname(__file__), "../../library")
322325
for root, dirs, files in os.walk(library_path):
323-
for file in files:
326+
dirs.sort()
327+
for file in sorted(files):
324328
# Extract the full path for the file
325329
full_path = os.path.join(root, file)
326330
if file.endswith(".co"):

tests/test_prompt_override.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import os
1717

1818
from nemoguardrails import RailsConfig
19+
from nemoguardrails.llm import prompts as prompts_module
1920
from nemoguardrails.llm.prompts import get_prompt
2021
from nemoguardrails.llm.types import Task
2122

@@ -28,3 +29,18 @@ def test_custom_llm_registration():
2829
prompt = get_prompt(config, Task.GENERATE_USER_INTENT)
2930

3031
assert prompt.content == "<<This is a placeholder for a custom prompt for generating the user intent>>"
32+
33+
34+
def test_load_prompts_sorts_files_for_deterministic_overrides(tmp_path, monkeypatch):
35+
prompts_dir = tmp_path / "prompts"
36+
prompts_dir.mkdir()
37+
(prompts_dir / "z.yml").write_text("prompts:\n- task: generate_user_intent\n content: z\n", encoding="utf-8")
38+
(prompts_dir / "a.yml").write_text("prompts:\n- task: generate_user_intent\n content: a\n", encoding="utf-8")
39+
40+
def walk(_path):
41+
yield str(prompts_dir), [], ["z.yml", "a.yml"]
42+
43+
monkeypatch.setattr(prompts_module, "CURRENT_DIR", str(tmp_path))
44+
monkeypatch.setattr(prompts_module.os, "walk", walk)
45+
46+
assert [prompt.content for prompt in prompts_module._load_prompts()] == ["a", "z"]

0 commit comments

Comments
 (0)