massgen 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of massgen might be problematic. Click here for more details.
- massgen/__init__.py +1 -1
- massgen/backend/base_with_custom_tool_and_mcp.py +453 -23
- massgen/backend/capabilities.py +39 -0
- massgen/backend/chat_completions.py +111 -197
- massgen/backend/claude.py +210 -181
- massgen/backend/gemini.py +1015 -1559
- massgen/backend/grok.py +3 -2
- massgen/backend/response.py +160 -220
- massgen/chat_agent.py +340 -20
- massgen/cli.py +399 -25
- massgen/config_builder.py +20 -54
- massgen/config_validator.py +931 -0
- massgen/configs/README.md +95 -10
- massgen/configs/memory/gpt5mini_gemini_baseline_research_to_implementation.yaml +94 -0
- massgen/configs/memory/gpt5mini_gemini_context_window_management.yaml +187 -0
- massgen/configs/memory/gpt5mini_gemini_research_to_implementation.yaml +127 -0
- massgen/configs/memory/gpt5mini_high_reasoning_gemini.yaml +107 -0
- massgen/configs/memory/single_agent_compression_test.yaml +64 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +1 -1
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +1 -1
- massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +1 -1
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_langgraph_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_openai_assistant_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/agentscope_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/langgraph_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/interop/openai_assistant_lesson_planner_example.yaml +50 -0
- massgen/configs/tools/custom_tools/interop/smolagent_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/two_models_with_tools_example.yaml +44 -0
- massgen/formatter/_gemini_formatter.py +61 -15
- massgen/memory/README.md +277 -0
- massgen/memory/__init__.py +26 -0
- massgen/memory/_base.py +193 -0
- massgen/memory/_compression.py +237 -0
- massgen/memory/_context_monitor.py +211 -0
- massgen/memory/_conversation.py +255 -0
- massgen/memory/_fact_extraction_prompts.py +333 -0
- massgen/memory/_mem0_adapters.py +257 -0
- massgen/memory/_persistent.py +687 -0
- massgen/memory/docker-compose.qdrant.yml +36 -0
- massgen/memory/docs/DESIGN.md +388 -0
- massgen/memory/docs/QUICKSTART.md +409 -0
- massgen/memory/docs/SUMMARY.md +319 -0
- massgen/memory/docs/agent_use_memory.md +408 -0
- massgen/memory/docs/orchestrator_use_memory.md +586 -0
- massgen/memory/examples.py +237 -0
- massgen/orchestrator.py +207 -7
- massgen/tests/memory/test_agent_compression.py +174 -0
- massgen/tests/memory/test_context_window_management.py +286 -0
- massgen/tests/memory/test_force_compression.py +154 -0
- massgen/tests/memory/test_simple_compression.py +147 -0
- massgen/tests/test_ag2_lesson_planner.py +223 -0
- massgen/tests/test_agent_memory.py +534 -0
- massgen/tests/test_config_validator.py +1156 -0
- massgen/tests/test_conversation_memory.py +382 -0
- massgen/tests/test_langgraph_lesson_planner.py +223 -0
- massgen/tests/test_orchestrator_memory.py +620 -0
- massgen/tests/test_persistent_memory.py +435 -0
- massgen/token_manager/token_manager.py +6 -0
- massgen/tool/__init__.py +2 -9
- massgen/tool/_decorators.py +52 -0
- massgen/tool/_extraframework_agents/ag2_lesson_planner_tool.py +251 -0
- massgen/tool/_extraframework_agents/agentscope_lesson_planner_tool.py +303 -0
- massgen/tool/_extraframework_agents/langgraph_lesson_planner_tool.py +275 -0
- massgen/tool/_extraframework_agents/openai_assistant_lesson_planner_tool.py +247 -0
- massgen/tool/_extraframework_agents/smolagent_lesson_planner_tool.py +180 -0
- massgen/tool/_manager.py +102 -16
- massgen/tool/_registered_tool.py +3 -0
- massgen/tool/_result.py +3 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/METADATA +138 -77
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/RECORD +82 -37
- massgen/backend/gemini_mcp_manager.py +0 -545
- massgen/backend/gemini_trackers.py +0 -344
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Tests for configuration validator.
|
|
4
|
+
|
|
5
|
+
Tests cover:
|
|
6
|
+
- Valid configs (should pass)
|
|
7
|
+
- Missing required fields
|
|
8
|
+
- Invalid types and values
|
|
9
|
+
- Backend-specific validation
|
|
10
|
+
- V1 config rejection
|
|
11
|
+
- Warning generation
|
|
12
|
+
- Error reporting format
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import tempfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from massgen.config_validator import ConfigValidator, ValidationResult
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestConfigValidator:
|
|
25
|
+
"""Test suite for ConfigValidator."""
|
|
26
|
+
|
|
27
|
+
def test_valid_single_agent_config(self):
|
|
28
|
+
"""Test validation of a valid single agent config."""
|
|
29
|
+
config = {
|
|
30
|
+
"agent": {
|
|
31
|
+
"id": "test-agent",
|
|
32
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
validator = ConfigValidator()
|
|
37
|
+
result = validator.validate_config(config)
|
|
38
|
+
|
|
39
|
+
assert result.is_valid()
|
|
40
|
+
assert not result.has_errors()
|
|
41
|
+
|
|
42
|
+
def test_valid_multi_agent_config(self):
|
|
43
|
+
"""Test validation of a valid multi-agent config."""
|
|
44
|
+
config = {
|
|
45
|
+
"agents": [
|
|
46
|
+
{
|
|
47
|
+
"id": "agent-1",
|
|
48
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "agent-2",
|
|
52
|
+
"backend": {"type": "claude", "model": "claude-sonnet-4-5-20250929"},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
validator = ConfigValidator()
|
|
58
|
+
result = validator.validate_config(config)
|
|
59
|
+
|
|
60
|
+
assert result.is_valid()
|
|
61
|
+
assert not result.has_errors()
|
|
62
|
+
|
|
63
|
+
def test_valid_config_with_orchestrator(self):
|
|
64
|
+
"""Test validation of config with orchestrator settings."""
|
|
65
|
+
config = {
|
|
66
|
+
"agents": [
|
|
67
|
+
{
|
|
68
|
+
"id": "agent-1",
|
|
69
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
"orchestrator": {
|
|
73
|
+
"voting_sensitivity": "balanced",
|
|
74
|
+
"answer_novelty_requirement": "strict",
|
|
75
|
+
"coordination": {
|
|
76
|
+
"enable_planning_mode": True,
|
|
77
|
+
"max_orchestration_restarts": 2,
|
|
78
|
+
},
|
|
79
|
+
"timeout": {
|
|
80
|
+
"orchestrator_timeout_seconds": 1800,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
validator = ConfigValidator()
|
|
86
|
+
result = validator.validate_config(config)
|
|
87
|
+
|
|
88
|
+
assert result.is_valid()
|
|
89
|
+
assert not result.has_errors()
|
|
90
|
+
|
|
91
|
+
def test_valid_config_with_ui(self):
|
|
92
|
+
"""Test validation of config with UI settings."""
|
|
93
|
+
config = {
|
|
94
|
+
"agent": {
|
|
95
|
+
"id": "test-agent",
|
|
96
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
97
|
+
},
|
|
98
|
+
"ui": {
|
|
99
|
+
"display_type": "rich_terminal",
|
|
100
|
+
"logging_enabled": True,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
validator = ConfigValidator()
|
|
105
|
+
result = validator.validate_config(config)
|
|
106
|
+
|
|
107
|
+
assert result.is_valid()
|
|
108
|
+
assert not result.has_errors()
|
|
109
|
+
|
|
110
|
+
def test_v1_config_rejected(self):
|
|
111
|
+
"""Test that V1 configs are rejected with helpful error."""
|
|
112
|
+
config = {
|
|
113
|
+
"models": ["gpt-4o", "claude-3-opus"],
|
|
114
|
+
"num_agents": 2,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
validator = ConfigValidator()
|
|
118
|
+
result = validator.validate_config(config)
|
|
119
|
+
|
|
120
|
+
assert not result.is_valid()
|
|
121
|
+
assert result.has_errors()
|
|
122
|
+
assert any("V1 config format detected" in error.message for error in result.errors)
|
|
123
|
+
assert any("migrate" in error.suggestion.lower() for error in result.errors if error.suggestion)
|
|
124
|
+
|
|
125
|
+
def test_missing_agents_field(self):
|
|
126
|
+
"""Test error when neither 'agents' nor 'agent' is present."""
|
|
127
|
+
config = {
|
|
128
|
+
"orchestrator": {},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
validator = ConfigValidator()
|
|
132
|
+
result = validator.validate_config(config)
|
|
133
|
+
|
|
134
|
+
assert not result.is_valid()
|
|
135
|
+
assert any("must have either 'agents'" in error.message for error in result.errors)
|
|
136
|
+
|
|
137
|
+
def test_both_agents_and_agent(self):
|
|
138
|
+
"""Test error when both 'agents' and 'agent' are present."""
|
|
139
|
+
config = {
|
|
140
|
+
"agents": [{"id": "a1", "backend": {"type": "openai", "model": "gpt-4o"}}],
|
|
141
|
+
"agent": {"id": "a2", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
validator = ConfigValidator()
|
|
145
|
+
result = validator.validate_config(config)
|
|
146
|
+
|
|
147
|
+
assert not result.is_valid()
|
|
148
|
+
assert any("cannot have both 'agents' and 'agent'" in error.message for error in result.errors)
|
|
149
|
+
|
|
150
|
+
def test_missing_agent_id(self):
|
|
151
|
+
"""Test error when agent is missing required 'id' field."""
|
|
152
|
+
config = {
|
|
153
|
+
"agent": {
|
|
154
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
validator = ConfigValidator()
|
|
159
|
+
result = validator.validate_config(config)
|
|
160
|
+
|
|
161
|
+
assert not result.is_valid()
|
|
162
|
+
assert any("missing required field 'id'" in error.message for error in result.errors)
|
|
163
|
+
|
|
164
|
+
def test_missing_backend(self):
|
|
165
|
+
"""Test error when agent is missing required 'backend' field."""
|
|
166
|
+
config = {
|
|
167
|
+
"agent": {
|
|
168
|
+
"id": "test-agent",
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
validator = ConfigValidator()
|
|
173
|
+
result = validator.validate_config(config)
|
|
174
|
+
|
|
175
|
+
assert not result.is_valid()
|
|
176
|
+
assert any("missing required field 'backend'" in error.message for error in result.errors)
|
|
177
|
+
|
|
178
|
+
def test_duplicate_agent_ids(self):
|
|
179
|
+
"""Test error when multiple agents have the same ID."""
|
|
180
|
+
config = {
|
|
181
|
+
"agents": [
|
|
182
|
+
{"id": "agent-1", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
183
|
+
{"id": "agent-1", "backend": {"type": "claude", "model": "claude-sonnet-4-5-20250929"}},
|
|
184
|
+
],
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
validator = ConfigValidator()
|
|
188
|
+
result = validator.validate_config(config)
|
|
189
|
+
|
|
190
|
+
assert not result.is_valid()
|
|
191
|
+
assert any("Duplicate agent ID" in error.message for error in result.errors)
|
|
192
|
+
|
|
193
|
+
def test_missing_backend_type(self):
|
|
194
|
+
"""Test error when backend is missing required 'type' field."""
|
|
195
|
+
config = {
|
|
196
|
+
"agent": {
|
|
197
|
+
"id": "test-agent",
|
|
198
|
+
"backend": {"model": "gpt-4o"},
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
validator = ConfigValidator()
|
|
203
|
+
result = validator.validate_config(config)
|
|
204
|
+
|
|
205
|
+
assert not result.is_valid()
|
|
206
|
+
assert any("missing required field 'type'" in error.message for error in result.errors)
|
|
207
|
+
|
|
208
|
+
def test_missing_backend_model(self):
|
|
209
|
+
"""Test error when backend is missing required 'model' field."""
|
|
210
|
+
config = {
|
|
211
|
+
"agent": {
|
|
212
|
+
"id": "test-agent",
|
|
213
|
+
"backend": {"type": "chatcompletion"}, # Uses default_model="custom", requires explicit model
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
validator = ConfigValidator()
|
|
218
|
+
result = validator.validate_config(config)
|
|
219
|
+
|
|
220
|
+
assert not result.is_valid()
|
|
221
|
+
assert any("missing required field 'model'" in error.message for error in result.errors)
|
|
222
|
+
|
|
223
|
+
def test_unknown_backend_type(self):
|
|
224
|
+
"""Test error when backend type is not recognized."""
|
|
225
|
+
config = {
|
|
226
|
+
"agent": {
|
|
227
|
+
"id": "test-agent",
|
|
228
|
+
"backend": {"type": "unknown_backend", "model": "some-model"},
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
validator = ConfigValidator()
|
|
233
|
+
result = validator.validate_config(config)
|
|
234
|
+
|
|
235
|
+
assert not result.is_valid()
|
|
236
|
+
assert any("Unknown backend type" in error.message for error in result.errors)
|
|
237
|
+
|
|
238
|
+
def test_invalid_permission_mode(self):
|
|
239
|
+
"""Test error when permission_mode has invalid value."""
|
|
240
|
+
config = {
|
|
241
|
+
"agent": {
|
|
242
|
+
"id": "test-agent",
|
|
243
|
+
"backend": {
|
|
244
|
+
"type": "claude_code",
|
|
245
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
246
|
+
"permission_mode": "invalid_mode",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
validator = ConfigValidator()
|
|
252
|
+
result = validator.validate_config(config)
|
|
253
|
+
|
|
254
|
+
assert not result.is_valid()
|
|
255
|
+
assert any("Invalid permission_mode" in error.message for error in result.errors)
|
|
256
|
+
|
|
257
|
+
def test_backend_capability_validation(self):
|
|
258
|
+
"""Test that backend capabilities are validated."""
|
|
259
|
+
config = {
|
|
260
|
+
"agent": {
|
|
261
|
+
"id": "test-agent",
|
|
262
|
+
"backend": {
|
|
263
|
+
"type": "lmstudio", # lmstudio doesn't support web_search
|
|
264
|
+
"model": "custom",
|
|
265
|
+
"enable_web_search": True,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
validator = ConfigValidator()
|
|
271
|
+
result = validator.validate_config(config)
|
|
272
|
+
|
|
273
|
+
assert not result.is_valid()
|
|
274
|
+
assert any("does not support" in error.message for error in result.errors)
|
|
275
|
+
|
|
276
|
+
def test_invalid_display_type(self):
|
|
277
|
+
"""Test error when UI display_type is invalid."""
|
|
278
|
+
config = {
|
|
279
|
+
"agent": {
|
|
280
|
+
"id": "test-agent",
|
|
281
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
282
|
+
},
|
|
283
|
+
"ui": {
|
|
284
|
+
"display_type": "invalid_type",
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
validator = ConfigValidator()
|
|
289
|
+
result = validator.validate_config(config)
|
|
290
|
+
|
|
291
|
+
assert not result.is_valid()
|
|
292
|
+
assert any("Invalid display_type" in error.message for error in result.errors)
|
|
293
|
+
|
|
294
|
+
def test_invalid_voting_sensitivity(self):
|
|
295
|
+
"""Test error when voting_sensitivity is invalid."""
|
|
296
|
+
config = {
|
|
297
|
+
"agents": [
|
|
298
|
+
{"id": "agent-1", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
299
|
+
],
|
|
300
|
+
"orchestrator": {
|
|
301
|
+
"voting_sensitivity": "invalid_value",
|
|
302
|
+
},
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
validator = ConfigValidator()
|
|
306
|
+
result = validator.validate_config(config)
|
|
307
|
+
|
|
308
|
+
assert not result.is_valid()
|
|
309
|
+
assert any("Invalid voting_sensitivity" in error.message for error in result.errors)
|
|
310
|
+
|
|
311
|
+
def test_invalid_context_path_permission(self):
|
|
312
|
+
"""Test error when context_paths permission is invalid."""
|
|
313
|
+
config = {
|
|
314
|
+
"agents": [
|
|
315
|
+
{"id": "agent-1", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
316
|
+
],
|
|
317
|
+
"orchestrator": {
|
|
318
|
+
"context_paths": [
|
|
319
|
+
{"path": "/some/path", "permission": "invalid_permission"},
|
|
320
|
+
],
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
validator = ConfigValidator()
|
|
325
|
+
result = validator.validate_config(config)
|
|
326
|
+
|
|
327
|
+
assert not result.is_valid()
|
|
328
|
+
assert any("Invalid permission" in error.message for error in result.errors)
|
|
329
|
+
|
|
330
|
+
def test_warning_both_allowed_and_exclude_tools(self):
|
|
331
|
+
"""Test warning when both allowed_tools and exclude_tools are used."""
|
|
332
|
+
config = {
|
|
333
|
+
"agent": {
|
|
334
|
+
"id": "test-agent",
|
|
335
|
+
"backend": {
|
|
336
|
+
"type": "claude_code",
|
|
337
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
338
|
+
"allowed_tools": ["Read", "Write"],
|
|
339
|
+
"exclude_tools": ["Bash"],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
validator = ConfigValidator()
|
|
345
|
+
result = validator.validate_config(config)
|
|
346
|
+
|
|
347
|
+
assert result.is_valid() # No errors
|
|
348
|
+
assert result.has_warnings()
|
|
349
|
+
assert any("both 'allowed_tools' and 'exclude_tools'" in warning.message for warning in result.warnings)
|
|
350
|
+
|
|
351
|
+
def test_no_warning_missing_system_message(self):
|
|
352
|
+
"""Test that missing system_message doesn't generate a warning."""
|
|
353
|
+
config = {
|
|
354
|
+
"agent": {
|
|
355
|
+
"id": "test-agent",
|
|
356
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
validator = ConfigValidator()
|
|
361
|
+
result = validator.validate_config(config)
|
|
362
|
+
|
|
363
|
+
assert result.is_valid()
|
|
364
|
+
# Should not have warnings about missing system_message
|
|
365
|
+
assert not any("system_message" in warning.message.lower() for warning in result.warnings)
|
|
366
|
+
|
|
367
|
+
def test_no_warning_multi_agent_no_orchestrator(self):
|
|
368
|
+
"""Test that multi-agent setup without orchestrator doesn't generate a warning."""
|
|
369
|
+
config = {
|
|
370
|
+
"agents": [
|
|
371
|
+
{"id": "agent-1", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
372
|
+
{"id": "agent-2", "backend": {"type": "claude", "model": "claude-sonnet-4-5-20250929"}},
|
|
373
|
+
],
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
validator = ConfigValidator()
|
|
377
|
+
result = validator.validate_config(config)
|
|
378
|
+
|
|
379
|
+
assert result.is_valid()
|
|
380
|
+
# Should not have warnings about missing orchestrator
|
|
381
|
+
assert not any("orchestrator" in warning.message.lower() for warning in result.warnings)
|
|
382
|
+
|
|
383
|
+
def test_invalid_type_field_types(self):
|
|
384
|
+
"""Test errors for wrong field types."""
|
|
385
|
+
config = {
|
|
386
|
+
"agent": {
|
|
387
|
+
"id": 123, # Should be string
|
|
388
|
+
"backend": {
|
|
389
|
+
"type": "openai",
|
|
390
|
+
"model": "gpt-4o",
|
|
391
|
+
"enable_web_search": "yes", # Should be boolean
|
|
392
|
+
},
|
|
393
|
+
"system_message": ["not", "a", "string"], # Should be string
|
|
394
|
+
},
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
validator = ConfigValidator()
|
|
398
|
+
result = validator.validate_config(config)
|
|
399
|
+
|
|
400
|
+
assert not result.is_valid()
|
|
401
|
+
assert len(result.errors) >= 2 # Multiple type errors
|
|
402
|
+
|
|
403
|
+
def test_validate_file_not_found(self):
|
|
404
|
+
"""Test validation of non-existent file."""
|
|
405
|
+
validator = ConfigValidator()
|
|
406
|
+
result = validator.validate_config_file("/nonexistent/config.yaml")
|
|
407
|
+
|
|
408
|
+
assert not result.is_valid()
|
|
409
|
+
assert any("Config file not found" in error.message for error in result.errors)
|
|
410
|
+
|
|
411
|
+
def test_validate_yaml_file(self):
|
|
412
|
+
"""Test validation of a YAML file."""
|
|
413
|
+
config = {
|
|
414
|
+
"agent": {
|
|
415
|
+
"id": "test-agent",
|
|
416
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
# Create temporary YAML file
|
|
421
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
|
422
|
+
yaml.dump(config, f)
|
|
423
|
+
temp_path = f.name
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
validator = ConfigValidator()
|
|
427
|
+
result = validator.validate_config_file(temp_path)
|
|
428
|
+
|
|
429
|
+
assert result.is_valid()
|
|
430
|
+
assert not result.has_errors()
|
|
431
|
+
finally:
|
|
432
|
+
Path(temp_path).unlink()
|
|
433
|
+
|
|
434
|
+
def test_validate_json_file(self):
|
|
435
|
+
"""Test validation of a JSON file."""
|
|
436
|
+
config = {
|
|
437
|
+
"agent": {
|
|
438
|
+
"id": "test-agent",
|
|
439
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
440
|
+
},
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# Create temporary JSON file
|
|
444
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
445
|
+
json.dump(config, f)
|
|
446
|
+
temp_path = f.name
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
validator = ConfigValidator()
|
|
450
|
+
result = validator.validate_config_file(temp_path)
|
|
451
|
+
|
|
452
|
+
assert result.is_valid()
|
|
453
|
+
assert not result.has_errors()
|
|
454
|
+
finally:
|
|
455
|
+
Path(temp_path).unlink()
|
|
456
|
+
|
|
457
|
+
def test_validation_result_to_dict(self):
|
|
458
|
+
"""Test conversion of ValidationResult to dict."""
|
|
459
|
+
result = ValidationResult()
|
|
460
|
+
result.add_error("Test error", "test.location", "Test suggestion")
|
|
461
|
+
result.add_warning("Test warning", "test.location", "Test suggestion")
|
|
462
|
+
|
|
463
|
+
result_dict = result.to_dict()
|
|
464
|
+
|
|
465
|
+
assert result_dict["valid"] is False
|
|
466
|
+
assert result_dict["error_count"] == 1
|
|
467
|
+
assert result_dict["warning_count"] == 1
|
|
468
|
+
assert len(result_dict["errors"]) == 1
|
|
469
|
+
assert len(result_dict["warnings"]) == 1
|
|
470
|
+
assert result_dict["errors"][0]["message"] == "Test error"
|
|
471
|
+
assert result_dict["warnings"][0]["message"] == "Test warning"
|
|
472
|
+
|
|
473
|
+
def test_validation_result_format_errors(self):
|
|
474
|
+
"""Test error formatting."""
|
|
475
|
+
result = ValidationResult()
|
|
476
|
+
result.add_error("Test error message", "config.agent.backend", "Use correct type")
|
|
477
|
+
|
|
478
|
+
formatted = result.format_errors()
|
|
479
|
+
|
|
480
|
+
assert "Configuration Errors Found" in formatted
|
|
481
|
+
assert "Test error message" in formatted
|
|
482
|
+
assert "config.agent.backend" in formatted
|
|
483
|
+
assert "Use correct type" in formatted
|
|
484
|
+
|
|
485
|
+
def test_validation_result_format_warnings(self):
|
|
486
|
+
"""Test warning formatting."""
|
|
487
|
+
result = ValidationResult()
|
|
488
|
+
result.add_warning("Test warning message", "config.agent", "Add system_message")
|
|
489
|
+
|
|
490
|
+
formatted = result.format_warnings()
|
|
491
|
+
|
|
492
|
+
assert "Configuration Warnings" in formatted
|
|
493
|
+
assert "Test warning message" in formatted
|
|
494
|
+
assert "config.agent" in formatted
|
|
495
|
+
assert "Add system_message" in formatted
|
|
496
|
+
|
|
497
|
+
def test_tool_filtering_validation(self):
|
|
498
|
+
"""Test validation of tool filtering lists."""
|
|
499
|
+
config = {
|
|
500
|
+
"agent": {
|
|
501
|
+
"id": "test-agent",
|
|
502
|
+
"backend": {
|
|
503
|
+
"type": "claude_code",
|
|
504
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
505
|
+
"allowed_tools": "not-a-list", # Should be list
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
validator = ConfigValidator()
|
|
511
|
+
result = validator.validate_config(config)
|
|
512
|
+
|
|
513
|
+
assert not result.is_valid()
|
|
514
|
+
assert any("'allowed_tools' must be a list" in error.message for error in result.errors)
|
|
515
|
+
|
|
516
|
+
def test_mcp_servers_validation(self):
|
|
517
|
+
"""Test that MCP server configs are validated."""
|
|
518
|
+
config = {
|
|
519
|
+
"agent": {
|
|
520
|
+
"id": "test-agent",
|
|
521
|
+
"backend": {
|
|
522
|
+
"type": "claude",
|
|
523
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
524
|
+
"mcp_servers": "invalid-format", # Should trigger MCP validator
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
validator = ConfigValidator()
|
|
530
|
+
result = validator.validate_config(config)
|
|
531
|
+
|
|
532
|
+
# Should have error from MCP validation
|
|
533
|
+
assert not result.is_valid()
|
|
534
|
+
|
|
535
|
+
def test_complex_valid_config(self):
|
|
536
|
+
"""Test a complex but valid configuration."""
|
|
537
|
+
config = {
|
|
538
|
+
"agents": [
|
|
539
|
+
{
|
|
540
|
+
"id": "researcher",
|
|
541
|
+
"backend": {
|
|
542
|
+
"type": "openai",
|
|
543
|
+
"model": "gpt-4o",
|
|
544
|
+
"enable_web_search": True,
|
|
545
|
+
},
|
|
546
|
+
"system_message": "You are a research assistant.",
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
"id": "analyst",
|
|
550
|
+
"backend": {
|
|
551
|
+
"type": "claude",
|
|
552
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
553
|
+
"enable_code_execution": True,
|
|
554
|
+
},
|
|
555
|
+
"system_message": "You are a data analyst.",
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
"orchestrator": {
|
|
559
|
+
"voting_sensitivity": "balanced",
|
|
560
|
+
"answer_novelty_requirement": "lenient",
|
|
561
|
+
"coordination": {
|
|
562
|
+
"enable_planning_mode": False,
|
|
563
|
+
"max_orchestration_restarts": 1,
|
|
564
|
+
},
|
|
565
|
+
"context_paths": [
|
|
566
|
+
{"path": "/data", "permission": "read"},
|
|
567
|
+
{"path": "/output", "permission": "write"},
|
|
568
|
+
],
|
|
569
|
+
"timeout": {
|
|
570
|
+
"orchestrator_timeout_seconds": 3600,
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
"ui": {
|
|
574
|
+
"display_type": "rich_terminal",
|
|
575
|
+
"logging_enabled": True,
|
|
576
|
+
},
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
validator = ConfigValidator()
|
|
580
|
+
result = validator.validate_config(config)
|
|
581
|
+
|
|
582
|
+
assert result.is_valid()
|
|
583
|
+
# May have warnings but should have no errors
|
|
584
|
+
assert not result.has_errors()
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
class TestCommonBadConfigs:
|
|
588
|
+
"""Test suite for common configuration mistakes users might make."""
|
|
589
|
+
|
|
590
|
+
def test_v1_config_with_models_list(self):
|
|
591
|
+
"""Test V1 config with models list is rejected."""
|
|
592
|
+
config = {
|
|
593
|
+
"models": ["gpt-4o", "claude-3-opus"],
|
|
594
|
+
"num_agents": 2,
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
validator = ConfigValidator()
|
|
598
|
+
result = validator.validate_config(config)
|
|
599
|
+
|
|
600
|
+
assert not result.is_valid()
|
|
601
|
+
assert any("V1 config format detected" in error.message for error in result.errors)
|
|
602
|
+
assert any("migrate" in error.suggestion.lower() for error in result.errors if error.suggestion)
|
|
603
|
+
|
|
604
|
+
def test_v1_config_with_model_configs(self):
|
|
605
|
+
"""Test V1 config with model_configs is rejected."""
|
|
606
|
+
config = {
|
|
607
|
+
"model_configs": {
|
|
608
|
+
"gpt-4o": {"temperature": 0.7},
|
|
609
|
+
"claude-3-opus": {"temperature": 0.5},
|
|
610
|
+
},
|
|
611
|
+
"agents": [{"id": "test"}], # Even with agents present
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
validator = ConfigValidator()
|
|
615
|
+
result = validator.validate_config(config)
|
|
616
|
+
|
|
617
|
+
assert not result.is_valid()
|
|
618
|
+
assert any("V1 config" in error.message for error in result.errors)
|
|
619
|
+
|
|
620
|
+
def test_missing_both_agents_and_agent(self):
|
|
621
|
+
"""Test config without agents or agent field."""
|
|
622
|
+
config = {
|
|
623
|
+
"orchestrator": {},
|
|
624
|
+
"ui": {"display_type": "simple"},
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
validator = ConfigValidator()
|
|
628
|
+
result = validator.validate_config(config)
|
|
629
|
+
|
|
630
|
+
assert not result.is_valid()
|
|
631
|
+
assert any("must have either 'agents'" in error.message for error in result.errors)
|
|
632
|
+
|
|
633
|
+
def test_typo_in_backend_type(self):
|
|
634
|
+
"""Test common typo in backend type."""
|
|
635
|
+
config = {
|
|
636
|
+
"agent": {
|
|
637
|
+
"id": "test-agent",
|
|
638
|
+
"backend": {
|
|
639
|
+
"type": "openi", # Common typo
|
|
640
|
+
"model": "gpt-4o",
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
validator = ConfigValidator()
|
|
646
|
+
result = validator.validate_config(config)
|
|
647
|
+
|
|
648
|
+
assert not result.is_valid()
|
|
649
|
+
assert any("Unknown backend type: 'openi'" in error.message for error in result.errors)
|
|
650
|
+
assert any("openai" in error.suggestion for error in result.errors if error.suggestion)
|
|
651
|
+
|
|
652
|
+
def test_wrong_case_backend_type(self):
|
|
653
|
+
"""Test wrong case in backend type."""
|
|
654
|
+
config = {
|
|
655
|
+
"agent": {
|
|
656
|
+
"id": "test-agent",
|
|
657
|
+
"backend": {
|
|
658
|
+
"type": "OpenAI", # Should be lowercase
|
|
659
|
+
"model": "gpt-4o",
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
validator = ConfigValidator()
|
|
665
|
+
result = validator.validate_config(config)
|
|
666
|
+
|
|
667
|
+
assert not result.is_valid()
|
|
668
|
+
assert any("Unknown backend type" in error.message for error in result.errors)
|
|
669
|
+
|
|
670
|
+
def test_unsupported_feature_for_backend(self):
|
|
671
|
+
"""Test requesting unsupported feature from backend."""
|
|
672
|
+
config = {
|
|
673
|
+
"agent": {
|
|
674
|
+
"id": "test-agent",
|
|
675
|
+
"backend": {
|
|
676
|
+
"type": "lmstudio",
|
|
677
|
+
"model": "custom",
|
|
678
|
+
"enable_web_search": True, # lmstudio doesn't support this
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
validator = ConfigValidator()
|
|
684
|
+
result = validator.validate_config(config)
|
|
685
|
+
|
|
686
|
+
assert not result.is_valid()
|
|
687
|
+
assert any("does not support web_search" in error.message for error in result.errors)
|
|
688
|
+
|
|
689
|
+
def test_boolean_as_string(self):
|
|
690
|
+
"""Test using string instead of boolean."""
|
|
691
|
+
config = {
|
|
692
|
+
"agent": {
|
|
693
|
+
"id": "test-agent",
|
|
694
|
+
"backend": {
|
|
695
|
+
"type": "openai",
|
|
696
|
+
"model": "gpt-4o",
|
|
697
|
+
"enable_web_search": "true", # Should be boolean true
|
|
698
|
+
},
|
|
699
|
+
},
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
validator = ConfigValidator()
|
|
703
|
+
result = validator.validate_config(config)
|
|
704
|
+
|
|
705
|
+
assert not result.is_valid()
|
|
706
|
+
assert any("must be a boolean" in error.message for error in result.errors)
|
|
707
|
+
|
|
708
|
+
def test_number_as_string(self):
|
|
709
|
+
"""Test using string for numeric field."""
|
|
710
|
+
config = {
|
|
711
|
+
"agents": [
|
|
712
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
713
|
+
],
|
|
714
|
+
"orchestrator": {
|
|
715
|
+
"timeout": {
|
|
716
|
+
"orchestrator_timeout_seconds": "1800", # Should be number
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
validator = ConfigValidator()
|
|
722
|
+
result = validator.validate_config(config)
|
|
723
|
+
|
|
724
|
+
assert not result.is_valid()
|
|
725
|
+
assert any("must be a positive number" in error.message for error in result.errors)
|
|
726
|
+
|
|
727
|
+
def test_invalid_display_type_typo(self):
|
|
728
|
+
"""Test typo in display_type."""
|
|
729
|
+
config = {
|
|
730
|
+
"agent": {
|
|
731
|
+
"id": "test-agent",
|
|
732
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
733
|
+
},
|
|
734
|
+
"ui": {
|
|
735
|
+
"display_type": "detailed", # Not a valid type
|
|
736
|
+
},
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
validator = ConfigValidator()
|
|
740
|
+
result = validator.validate_config(config)
|
|
741
|
+
|
|
742
|
+
assert not result.is_valid()
|
|
743
|
+
assert any("Invalid display_type" in error.message for error in result.errors)
|
|
744
|
+
assert any("rich_terminal" in error.suggestion for error in result.errors if error.suggestion)
|
|
745
|
+
|
|
746
|
+
def test_invalid_permission_mode(self):
|
|
747
|
+
"""Test invalid permission_mode value."""
|
|
748
|
+
config = {
|
|
749
|
+
"agent": {
|
|
750
|
+
"id": "test-agent",
|
|
751
|
+
"backend": {
|
|
752
|
+
"type": "claude_code",
|
|
753
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
754
|
+
"permission_mode": "auto", # Not valid
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
validator = ConfigValidator()
|
|
760
|
+
result = validator.validate_config(config)
|
|
761
|
+
|
|
762
|
+
assert not result.is_valid()
|
|
763
|
+
assert any("Invalid permission_mode" in error.message for error in result.errors)
|
|
764
|
+
|
|
765
|
+
def test_context_path_wrong_permission(self):
|
|
766
|
+
"""Test wrong permission value in context_paths."""
|
|
767
|
+
config = {
|
|
768
|
+
"agents": [
|
|
769
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
770
|
+
],
|
|
771
|
+
"orchestrator": {
|
|
772
|
+
"context_paths": [
|
|
773
|
+
{"path": "/data", "permission": "readonly"}, # Should be "read"
|
|
774
|
+
],
|
|
775
|
+
},
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
validator = ConfigValidator()
|
|
779
|
+
result = validator.validate_config(config)
|
|
780
|
+
|
|
781
|
+
assert not result.is_valid()
|
|
782
|
+
assert any("Invalid permission" in error.message for error in result.errors)
|
|
783
|
+
assert any("'read' or 'write'" in error.suggestion for error in result.errors if error.suggestion)
|
|
784
|
+
|
|
785
|
+
def test_negative_timeout(self):
|
|
786
|
+
"""Test negative timeout value."""
|
|
787
|
+
config = {
|
|
788
|
+
"agents": [
|
|
789
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
790
|
+
],
|
|
791
|
+
"orchestrator": {
|
|
792
|
+
"timeout": {
|
|
793
|
+
"orchestrator_timeout_seconds": -100, # Negative
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
validator = ConfigValidator()
|
|
799
|
+
result = validator.validate_config(config)
|
|
800
|
+
|
|
801
|
+
assert not result.is_valid()
|
|
802
|
+
assert any("must be a positive number" in error.message for error in result.errors)
|
|
803
|
+
|
|
804
|
+
def test_negative_max_restarts(self):
|
|
805
|
+
"""Test negative max_orchestration_restarts."""
|
|
806
|
+
config = {
|
|
807
|
+
"agents": [
|
|
808
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
809
|
+
],
|
|
810
|
+
"orchestrator": {
|
|
811
|
+
"coordination": {
|
|
812
|
+
"max_orchestration_restarts": -1, # Negative
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
validator = ConfigValidator()
|
|
818
|
+
result = validator.validate_config(config)
|
|
819
|
+
|
|
820
|
+
assert not result.is_valid()
|
|
821
|
+
assert any("must be a non-negative integer" in error.message for error in result.errors)
|
|
822
|
+
|
|
823
|
+
def test_agent_without_id(self):
|
|
824
|
+
"""Test agent missing id field (common mistake)."""
|
|
825
|
+
config = {
|
|
826
|
+
"agents": [
|
|
827
|
+
{
|
|
828
|
+
# Missing id
|
|
829
|
+
"backend": {"type": "openai", "model": "gpt-4o"},
|
|
830
|
+
},
|
|
831
|
+
],
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
validator = ConfigValidator()
|
|
835
|
+
result = validator.validate_config(config)
|
|
836
|
+
|
|
837
|
+
assert not result.is_valid()
|
|
838
|
+
assert any("missing required field 'id'" in error.message for error in result.errors)
|
|
839
|
+
|
|
840
|
+
def test_agent_without_backend(self):
|
|
841
|
+
"""Test agent missing backend field (common mistake)."""
|
|
842
|
+
config = {
|
|
843
|
+
"agents": [
|
|
844
|
+
{
|
|
845
|
+
"id": "test-agent",
|
|
846
|
+
# Missing backend
|
|
847
|
+
},
|
|
848
|
+
],
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
validator = ConfigValidator()
|
|
852
|
+
result = validator.validate_config(config)
|
|
853
|
+
|
|
854
|
+
assert not result.is_valid()
|
|
855
|
+
assert any("missing required field 'backend'" in error.message for error in result.errors)
|
|
856
|
+
|
|
857
|
+
def test_tools_list_with_non_strings(self):
|
|
858
|
+
"""Test tool filtering with non-string values."""
|
|
859
|
+
config = {
|
|
860
|
+
"agent": {
|
|
861
|
+
"id": "test-agent",
|
|
862
|
+
"backend": {
|
|
863
|
+
"type": "claude_code",
|
|
864
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
865
|
+
"allowed_tools": ["Read", 123, "Write"], # 123 is not a string
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
validator = ConfigValidator()
|
|
871
|
+
result = validator.validate_config(config)
|
|
872
|
+
|
|
873
|
+
assert not result.is_valid()
|
|
874
|
+
assert any("must be a string" in error.message for error in result.errors)
|
|
875
|
+
|
|
876
|
+
def test_mcp_servers_wrong_type(self):
|
|
877
|
+
"""Test mcp_servers as wrong type."""
|
|
878
|
+
config = {
|
|
879
|
+
"agent": {
|
|
880
|
+
"id": "test-agent",
|
|
881
|
+
"backend": {
|
|
882
|
+
"type": "claude",
|
|
883
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
884
|
+
"mcp_servers": "filesystem", # Should be list or dict
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
validator = ConfigValidator()
|
|
890
|
+
result = validator.validate_config(config)
|
|
891
|
+
|
|
892
|
+
assert not result.is_valid()
|
|
893
|
+
assert any("MCP configuration error" in error.message for error in result.errors)
|
|
894
|
+
|
|
895
|
+
def test_invalid_voting_sensitivity(self):
|
|
896
|
+
"""Test invalid voting_sensitivity value."""
|
|
897
|
+
config = {
|
|
898
|
+
"agents": [
|
|
899
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
900
|
+
],
|
|
901
|
+
"orchestrator": {
|
|
902
|
+
"voting_sensitivity": "medium", # Should be lenient/balanced/strict
|
|
903
|
+
},
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
validator = ConfigValidator()
|
|
907
|
+
result = validator.validate_config(config)
|
|
908
|
+
|
|
909
|
+
assert not result.is_valid()
|
|
910
|
+
assert any("Invalid voting_sensitivity" in error.message for error in result.errors)
|
|
911
|
+
|
|
912
|
+
def test_invalid_answer_novelty(self):
|
|
913
|
+
"""Test invalid answer_novelty_requirement value."""
|
|
914
|
+
config = {
|
|
915
|
+
"agents": [
|
|
916
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
917
|
+
],
|
|
918
|
+
"orchestrator": {
|
|
919
|
+
"answer_novelty_requirement": "high", # Should be lenient/balanced/strict
|
|
920
|
+
},
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
validator = ConfigValidator()
|
|
924
|
+
result = validator.validate_config(config)
|
|
925
|
+
|
|
926
|
+
assert not result.is_valid()
|
|
927
|
+
assert any("Invalid answer_novelty_requirement" in error.message for error in result.errors)
|
|
928
|
+
|
|
929
|
+
def test_v1_max_rounds(self):
|
|
930
|
+
"""Test V1 max_rounds parameter is rejected."""
|
|
931
|
+
config = {
|
|
932
|
+
"agents": [
|
|
933
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
934
|
+
],
|
|
935
|
+
"max_rounds": 5, # V1 parameter
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
validator = ConfigValidator()
|
|
939
|
+
result = validator.validate_config(config)
|
|
940
|
+
|
|
941
|
+
assert not result.is_valid()
|
|
942
|
+
assert any("V1 config format detected" in error.message for error in result.errors)
|
|
943
|
+
assert any("max_rounds" in error.message for error in result.errors)
|
|
944
|
+
|
|
945
|
+
def test_v1_consensus_threshold(self):
|
|
946
|
+
"""Test V1 consensus_threshold parameter is rejected."""
|
|
947
|
+
config = {
|
|
948
|
+
"agents": [
|
|
949
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
950
|
+
],
|
|
951
|
+
"consensus_threshold": 0.6, # V1 parameter
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
validator = ConfigValidator()
|
|
955
|
+
result = validator.validate_config(config)
|
|
956
|
+
|
|
957
|
+
assert not result.is_valid()
|
|
958
|
+
assert any("V1 config format detected" in error.message for error in result.errors)
|
|
959
|
+
assert any("consensus_threshold" in error.message for error in result.errors)
|
|
960
|
+
|
|
961
|
+
def test_v1_voting_enabled(self):
|
|
962
|
+
"""Test V1 voting_enabled parameter is rejected."""
|
|
963
|
+
config = {
|
|
964
|
+
"agents": [
|
|
965
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
966
|
+
],
|
|
967
|
+
"voting_enabled": True, # V1 parameter
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
validator = ConfigValidator()
|
|
971
|
+
result = validator.validate_config(config)
|
|
972
|
+
|
|
973
|
+
assert not result.is_valid()
|
|
974
|
+
assert any("V1 config format detected" in error.message for error in result.errors)
|
|
975
|
+
assert any("voting_enabled" in error.message for error in result.errors)
|
|
976
|
+
|
|
977
|
+
def test_v1_multiple_keywords(self):
|
|
978
|
+
"""Test config with multiple V1 keywords."""
|
|
979
|
+
config = {
|
|
980
|
+
"agents": [
|
|
981
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
982
|
+
],
|
|
983
|
+
"max_rounds": 5,
|
|
984
|
+
"voting_enabled": True,
|
|
985
|
+
"consensus_threshold": 0.6,
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
validator = ConfigValidator()
|
|
989
|
+
result = validator.validate_config(config)
|
|
990
|
+
|
|
991
|
+
assert not result.is_valid()
|
|
992
|
+
assert any("V1 config format detected" in error.message for error in result.errors)
|
|
993
|
+
# Should mention all found V1 keywords
|
|
994
|
+
error_messages = " ".join([e.message for e in result.errors])
|
|
995
|
+
assert "max_rounds" in error_messages
|
|
996
|
+
assert "voting_enabled" in error_messages
|
|
997
|
+
assert "consensus_threshold" in error_messages
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
class TestMemoryValidation:
|
|
1001
|
+
"""Test suite for memory configuration validation."""
|
|
1002
|
+
|
|
1003
|
+
def test_valid_memory_config(self):
|
|
1004
|
+
"""Test valid memory configuration."""
|
|
1005
|
+
config = {
|
|
1006
|
+
"agents": [
|
|
1007
|
+
{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}},
|
|
1008
|
+
],
|
|
1009
|
+
"memory": {
|
|
1010
|
+
"enabled": True,
|
|
1011
|
+
"conversation_memory": {
|
|
1012
|
+
"enabled": True,
|
|
1013
|
+
},
|
|
1014
|
+
"persistent_memory": {
|
|
1015
|
+
"enabled": True,
|
|
1016
|
+
"on_disk": True,
|
|
1017
|
+
"vector_store": "qdrant",
|
|
1018
|
+
"llm": {
|
|
1019
|
+
"provider": "openai",
|
|
1020
|
+
"model": "gpt-4.1-nano-2025-04-14",
|
|
1021
|
+
},
|
|
1022
|
+
"embedding": {
|
|
1023
|
+
"provider": "openai",
|
|
1024
|
+
"model": "text-embedding-3-small",
|
|
1025
|
+
},
|
|
1026
|
+
"qdrant": {
|
|
1027
|
+
"mode": "server",
|
|
1028
|
+
"host": "localhost",
|
|
1029
|
+
"port": 6333,
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
"compression": {
|
|
1033
|
+
"trigger_threshold": 0.75,
|
|
1034
|
+
"target_ratio": 0.40,
|
|
1035
|
+
},
|
|
1036
|
+
"retrieval": {
|
|
1037
|
+
"limit": 10,
|
|
1038
|
+
"exclude_recent": True,
|
|
1039
|
+
},
|
|
1040
|
+
},
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
validator = ConfigValidator()
|
|
1044
|
+
result = validator.validate_config(config)
|
|
1045
|
+
|
|
1046
|
+
assert result.is_valid()
|
|
1047
|
+
assert not result.has_errors()
|
|
1048
|
+
|
|
1049
|
+
def test_memory_enabled_wrong_type(self):
|
|
1050
|
+
"""Test memory enabled with wrong type."""
|
|
1051
|
+
config = {
|
|
1052
|
+
"agents": [{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}}],
|
|
1053
|
+
"memory": {
|
|
1054
|
+
"enabled": "yes", # Should be boolean
|
|
1055
|
+
},
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
validator = ConfigValidator()
|
|
1059
|
+
result = validator.validate_config(config)
|
|
1060
|
+
|
|
1061
|
+
assert not result.is_valid()
|
|
1062
|
+
assert any("'enabled' must be a boolean" in error.message for error in result.errors)
|
|
1063
|
+
|
|
1064
|
+
def test_memory_qdrant_invalid_mode(self):
|
|
1065
|
+
"""Test invalid qdrant mode."""
|
|
1066
|
+
config = {
|
|
1067
|
+
"agents": [{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}}],
|
|
1068
|
+
"memory": {
|
|
1069
|
+
"persistent_memory": {
|
|
1070
|
+
"qdrant": {
|
|
1071
|
+
"mode": "distributed", # Should be 'server' or 'local'
|
|
1072
|
+
},
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
validator = ConfigValidator()
|
|
1078
|
+
result = validator.validate_config(config)
|
|
1079
|
+
|
|
1080
|
+
assert not result.is_valid()
|
|
1081
|
+
assert any("Invalid qdrant mode" in error.message for error in result.errors)
|
|
1082
|
+
assert any("'server' or 'local'" in error.suggestion for error in result.errors if error.suggestion)
|
|
1083
|
+
|
|
1084
|
+
def test_memory_compression_out_of_range(self):
|
|
1085
|
+
"""Test compression threshold out of valid range."""
|
|
1086
|
+
config = {
|
|
1087
|
+
"agents": [{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}}],
|
|
1088
|
+
"memory": {
|
|
1089
|
+
"compression": {
|
|
1090
|
+
"trigger_threshold": 1.5, # Should be 0-1
|
|
1091
|
+
},
|
|
1092
|
+
},
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
validator = ConfigValidator()
|
|
1096
|
+
result = validator.validate_config(config)
|
|
1097
|
+
|
|
1098
|
+
assert not result.is_valid()
|
|
1099
|
+
assert any("must be between 0 and 1" in error.message for error in result.errors)
|
|
1100
|
+
|
|
1101
|
+
def test_memory_retrieval_negative_limit(self):
|
|
1102
|
+
"""Test negative retrieval limit."""
|
|
1103
|
+
config = {
|
|
1104
|
+
"agents": [{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}}],
|
|
1105
|
+
"memory": {
|
|
1106
|
+
"retrieval": {
|
|
1107
|
+
"limit": -5, # Should be positive
|
|
1108
|
+
},
|
|
1109
|
+
},
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
validator = ConfigValidator()
|
|
1113
|
+
result = validator.validate_config(config)
|
|
1114
|
+
|
|
1115
|
+
assert not result.is_valid()
|
|
1116
|
+
assert any("must be a positive integer" in error.message for error in result.errors)
|
|
1117
|
+
|
|
1118
|
+
def test_memory_qdrant_invalid_port(self):
|
|
1119
|
+
"""Test invalid qdrant port."""
|
|
1120
|
+
config = {
|
|
1121
|
+
"agents": [{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}}],
|
|
1122
|
+
"memory": {
|
|
1123
|
+
"persistent_memory": {
|
|
1124
|
+
"qdrant": {
|
|
1125
|
+
"mode": "server",
|
|
1126
|
+
"port": 99999, # Out of valid range
|
|
1127
|
+
},
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
validator = ConfigValidator()
|
|
1133
|
+
result = validator.validate_config(config)
|
|
1134
|
+
|
|
1135
|
+
assert not result.is_valid()
|
|
1136
|
+
assert any("must be a valid port number" in error.message for error in result.errors)
|
|
1137
|
+
|
|
1138
|
+
def test_memory_llm_provider_wrong_type(self):
|
|
1139
|
+
"""Test llm provider with wrong type."""
|
|
1140
|
+
config = {
|
|
1141
|
+
"agents": [{"id": "test", "backend": {"type": "openai", "model": "gpt-4o"}}],
|
|
1142
|
+
"memory": {
|
|
1143
|
+
"persistent_memory": {
|
|
1144
|
+
"llm": {
|
|
1145
|
+
"provider": 123, # Should be string
|
|
1146
|
+
"model": "gpt-4o",
|
|
1147
|
+
},
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
validator = ConfigValidator()
|
|
1153
|
+
result = validator.validate_config(config)
|
|
1154
|
+
|
|
1155
|
+
assert not result.is_valid()
|
|
1156
|
+
assert any("must be a string" in error.message for error in result.errors)
|