Skip to content

Commit f12b6e6

Browse files
authored
fix: lazy load Policies ToolGuard dependencies (#13145)
fix: Lazy loading of policies deps
1 parent 3e99bbc commit f12b6e6

2 files changed

Lines changed: 149 additions & 75 deletions

File tree

src/backend/tests/unit/components/models_and_agents/policies/test_policies_component.py

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,63 @@ def mock_component(mock_tool):
3636
return component
3737

3838

39+
def _make_fake_tg(**overrides):
40+
"""Build a fake toolguard-imports dict for use with `_import_toolguard` patching.
41+
42+
Mirrors the keys returned by `PoliciesComponent._import_toolguard()`; tests
43+
can override individual entries to assert specific call wiring.
44+
"""
45+
fake = {
46+
"PolicySpecOptions": MagicMock(),
47+
"ToolGuardsCodeGenerationResult": MagicMock(),
48+
"generate_guard_specs": MagicMock(),
49+
"generate_guards_code": MagicMock(),
50+
"langchain_tools_to_openapi": MagicMock(),
51+
"load_toolguards": MagicMock(),
52+
"load_toolguards_from_memory": MagicMock(),
53+
"RESULTS_FILENAME": "results.json",
54+
"sync_generated_guard_code_inputs": MagicMock(),
55+
"GuardedTool": MagicMock(),
56+
"LangchainModelWrapper": MagicMock(),
57+
}
58+
fake.update(overrides)
59+
return fake
60+
61+
3962
@pytest.mark.asyncio
4063
async def test_cache_mode_success(mock_component, mock_tool):
4164
"""Test PoliciesComponent in cache mode with valid cached guards."""
4265
code_dir = mock_component.work_dir / STEP2
4366

67+
fake_tg = _make_fake_tg()
68+
mock_tg_result = MagicMock()
69+
mock_tg_runtime = MagicMock()
70+
fake_tg["load_toolguards_from_memory"].return_value = mock_tg_runtime
71+
mock_guarded_instance = MagicMock()
72+
fake_tg["GuardedTool"].return_value = mock_guarded_instance
73+
4474
# Mock the cache directory exists and toolguard loading
4575
with (
4676
patch.object(Path, "exists", return_value=True),
47-
patch("lfx.components.models_and_agents.policies_component.load_toolguards") as mock_load_guards,
77+
patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg),
4878
patch.object(mock_component, "make_toolguard_result") as mock_make_result,
49-
patch("lfx.components.models_and_agents.policies_component.load_toolguards_from_memory") as mock_load_memory,
50-
patch("lfx.components.models_and_agents.policies_component.GuardedTool") as mock_guarded_tool,
5179
):
52-
mock_tg_result = MagicMock()
5380
mock_make_result.return_value = mock_tg_result
54-
mock_tg_runtime = MagicMock()
55-
mock_load_memory.return_value = mock_tg_runtime
56-
mock_guarded_instance = MagicMock()
57-
mock_guarded_tool.return_value = mock_guarded_instance
5881

5982
result = await mock_component.guard_tools()
6083

6184
# Verify load_toolguards was called during validation
62-
mock_load_guards.assert_called_once_with(code_dir)
85+
fake_tg["load_toolguards"].assert_called_once_with(code_dir)
6386

6487
# Verify make_toolguard_result was called
6588
mock_make_result.assert_called_once()
6689

6790
# Verify load_toolguards_from_memory was called with the result
68-
mock_load_memory.assert_called_once_with(mock_tg_result)
91+
fake_tg["load_toolguards_from_memory"].assert_called_once_with(mock_tg_result)
6992

7093
# Verify GuardedTool was created for each tool
71-
assert mock_guarded_tool.call_count == len(mock_component.in_tools)
72-
mock_guarded_tool.assert_called_with(mock_tool, mock_component.in_tools, mock_tg_runtime)
94+
assert fake_tg["GuardedTool"].call_count == len(mock_component.in_tools)
95+
fake_tg["GuardedTool"].assert_called_with(mock_tool, mock_component.in_tools, mock_tg_runtime)
7396

7497
# Verify result contains guarded tools
7598
assert len(result) == 1
@@ -79,9 +102,11 @@ async def test_cache_mode_success(mock_component, mock_tool):
79102
@pytest.mark.asyncio
80103
async def test_cache_mode_directory_not_found(mock_component):
81104
"""Test PoliciesComponent in cache mode when cache directory doesn't exist."""
105+
fake_tg = _make_fake_tg()
82106
# Mock the cache directory does not exist
83107
with (
84108
patch.object(Path, "exists", return_value=False),
109+
patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg),
85110
pytest.raises(ValueError, match="Cache directory not found"),
86111
):
87112
await mock_component.guard_tools()
@@ -90,29 +115,29 @@ async def test_cache_mode_directory_not_found(mock_component):
90115
@pytest.mark.asyncio
91116
async def test_cache_mode_file_not_found(mock_component):
92117
"""Test PoliciesComponent in cache mode when required files are missing."""
118+
fake_tg = _make_fake_tg()
119+
fake_tg["load_toolguards"].side_effect = FileNotFoundError("Guard file not found")
93120
# Mock the cache directory exists but files are missing
94121
with (
95122
patch.object(Path, "exists", return_value=True),
96-
patch("lfx.components.models_and_agents.policies_component.load_toolguards") as mock_load_guards,
123+
patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg),
124+
pytest.raises(ValueError, match="Required guard code files missing"),
97125
):
98-
mock_load_guards.side_effect = FileNotFoundError("Guard file not found")
99-
100-
with pytest.raises(ValueError, match="Required guard code files missing"):
101-
await mock_component.guard_tools()
126+
await mock_component.guard_tools()
102127

103128

104129
@pytest.mark.asyncio
105130
async def test_cache_mode_corrupted_cache(mock_component):
106131
"""Test PoliciesComponent in cache mode when cached code is corrupted."""
132+
fake_tg = _make_fake_tg()
133+
fake_tg["load_toolguards"].side_effect = Exception("Invalid Python syntax")
107134
# Mock the cache directory exists but code is corrupted
108135
with (
109136
patch.object(Path, "exists", return_value=True),
110-
patch("lfx.components.models_and_agents.policies_component.load_toolguards") as mock_load_guards,
137+
patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg),
138+
pytest.raises(ValueError, match="Failed to load guard code"),
111139
):
112-
mock_load_guards.side_effect = Exception("Invalid Python syntax")
113-
114-
with pytest.raises(ValueError, match="Failed to load guard code"):
115-
await mock_component.guard_tools()
140+
await mock_component.guard_tools()
116141

117142

118143
# @pytest.mark.asyncio
@@ -163,29 +188,31 @@ async def test_inenabled_returns_original_tools(mock_component, mock_tool):
163188
async def test_generate_mode_validation_errors(mock_component):
164189
"""Test PoliciesComponent in generate mode with validation errors."""
165190
mock_component.mode = MODE_GENERATE
191+
fake_tg = _make_fake_tg()
166192

167-
# Test empty project
168-
mock_component.project = ""
169-
with pytest.raises(ValueError): # noqa: PT011
170-
await mock_component.guard_tools()
193+
with patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg):
194+
# Test empty project
195+
mock_component.project = ""
196+
with pytest.raises(ValueError): # noqa: PT011
197+
await mock_component.guard_tools()
171198

172-
# Test empty policies
173-
mock_component.project = "test_project"
174-
mock_component.policies = []
175-
with pytest.raises(ValueError, match="policies cannot be empty"):
176-
await mock_component.guard_tools()
199+
# Test empty policies
200+
mock_component.project = "test_project"
201+
mock_component.policies = []
202+
with pytest.raises(ValueError, match="policies cannot be empty"):
203+
await mock_component.guard_tools()
177204

178-
# Test empty tools
179-
mock_component.policies = ["Policy 1"]
180-
mock_component.in_tools = []
181-
with pytest.raises(ValueError, match="in_tools cannot be empty"):
182-
await mock_component.guard_tools()
205+
# Test empty tools
206+
mock_component.policies = ["Policy 1"]
207+
mock_component.in_tools = []
208+
with pytest.raises(ValueError, match="in_tools cannot be empty"):
209+
await mock_component.guard_tools()
183210

184-
# Test missing model
185-
mock_component.in_tools = [MagicMock()]
186-
mock_component.model = None
187-
with pytest.raises(ValueError, match="model or api_key cannot be empty"):
188-
await mock_component.guard_tools()
211+
# Test missing model
212+
mock_component.in_tools = [MagicMock()]
213+
mock_component.model = None
214+
with pytest.raises(ValueError, match="model or api_key cannot be empty"):
215+
await mock_component.guard_tools()
189216

190217
# # Test non-recommended model
191218
# mock_component.model = [{"name": "gpt-3.5-turbo", "provider": "OpenAI"}]
@@ -250,32 +277,36 @@ async def test_verify_cached_guards_error_messages(mock_component):
250277
code_dir = mock_component.work_dir / STEP2
251278

252279
# Test directory not found error message
253-
with patch.object(Path, "exists", return_value=False):
280+
fake_tg = _make_fake_tg()
281+
with (
282+
patch.object(Path, "exists", return_value=False),
283+
patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg),
284+
):
254285
with pytest.raises(ValueError, match="Cache directory not found") as exc_info:
255286
mock_component._verify_cached_guards(code_dir)
256287

257288
assert "Generate" in str(exc_info.value)
258289
assert str(code_dir) in str(exc_info.value)
259290

260291
# Test file not found error message
292+
fake_tg = _make_fake_tg()
293+
fake_tg["load_toolguards"].side_effect = FileNotFoundError("Missing file")
261294
with (
262295
patch.object(Path, "exists", return_value=True),
263-
patch("lfx.components.models_and_agents.policies_component.load_toolguards") as mock_load,
296+
patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg),
264297
):
265-
mock_load.side_effect = FileNotFoundError("Missing file")
266-
267298
with pytest.raises(ValueError, match="Required guard code files missing") as exc_info:
268299
mock_component._verify_cached_guards(code_dir)
269300

270301
assert "Generate" in str(exc_info.value)
271302

272303
# Test general error message
304+
fake_tg = _make_fake_tg()
305+
fake_tg["load_toolguards"].side_effect = RuntimeError("Unexpected error")
273306
with (
274307
patch.object(Path, "exists", return_value=True),
275-
patch("lfx.components.models_and_agents.policies_component.load_toolguards") as mock_load,
308+
patch.object(PoliciesComponent, "_import_toolguard", return_value=fake_tg),
276309
):
277-
mock_load.side_effect = RuntimeError("Unexpected error")
278-
279310
with pytest.raises(ValueError, match="Failed to load guard code") as exc_info:
280311
mock_component._verify_cached_guards(code_dir)
281312

0 commit comments

Comments
 (0)