pygeai-orchestration 0.1.0b2__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.
- pygeai_orchestration/__init__.py +99 -0
- pygeai_orchestration/cli/__init__.py +7 -0
- pygeai_orchestration/cli/__main__.py +11 -0
- pygeai_orchestration/cli/commands/__init__.py +13 -0
- pygeai_orchestration/cli/commands/base.py +192 -0
- pygeai_orchestration/cli/error_handler.py +123 -0
- pygeai_orchestration/cli/formatters.py +419 -0
- pygeai_orchestration/cli/geai_orch.py +270 -0
- pygeai_orchestration/cli/interactive.py +265 -0
- pygeai_orchestration/cli/texts/help.py +169 -0
- pygeai_orchestration/core/__init__.py +130 -0
- pygeai_orchestration/core/base/__init__.py +23 -0
- pygeai_orchestration/core/base/agent.py +121 -0
- pygeai_orchestration/core/base/geai_agent.py +144 -0
- pygeai_orchestration/core/base/geai_orchestrator.py +77 -0
- pygeai_orchestration/core/base/orchestrator.py +142 -0
- pygeai_orchestration/core/base/pattern.py +161 -0
- pygeai_orchestration/core/base/tool.py +149 -0
- pygeai_orchestration/core/common/__init__.py +18 -0
- pygeai_orchestration/core/common/context.py +140 -0
- pygeai_orchestration/core/common/memory.py +176 -0
- pygeai_orchestration/core/common/message.py +50 -0
- pygeai_orchestration/core/common/state.py +181 -0
- pygeai_orchestration/core/composition.py +190 -0
- pygeai_orchestration/core/config.py +356 -0
- pygeai_orchestration/core/exceptions.py +400 -0
- pygeai_orchestration/core/handlers.py +380 -0
- pygeai_orchestration/core/utils/__init__.py +37 -0
- pygeai_orchestration/core/utils/cache.py +138 -0
- pygeai_orchestration/core/utils/config.py +94 -0
- pygeai_orchestration/core/utils/logging.py +57 -0
- pygeai_orchestration/core/utils/metrics.py +184 -0
- pygeai_orchestration/core/utils/validators.py +140 -0
- pygeai_orchestration/dev/__init__.py +15 -0
- pygeai_orchestration/dev/debug.py +288 -0
- pygeai_orchestration/dev/templates.py +321 -0
- pygeai_orchestration/dev/testing.py +301 -0
- pygeai_orchestration/patterns/__init__.py +15 -0
- pygeai_orchestration/patterns/multi_agent.py +237 -0
- pygeai_orchestration/patterns/planning.py +219 -0
- pygeai_orchestration/patterns/react.py +221 -0
- pygeai_orchestration/patterns/reflection.py +134 -0
- pygeai_orchestration/patterns/tool_use.py +170 -0
- pygeai_orchestration/tests/__init__.py +1 -0
- pygeai_orchestration/tests/test_base_classes.py +187 -0
- pygeai_orchestration/tests/test_cache.py +184 -0
- pygeai_orchestration/tests/test_cli_formatters.py +232 -0
- pygeai_orchestration/tests/test_common.py +214 -0
- pygeai_orchestration/tests/test_composition.py +265 -0
- pygeai_orchestration/tests/test_config.py +301 -0
- pygeai_orchestration/tests/test_dev_utils.py +337 -0
- pygeai_orchestration/tests/test_exceptions.py +327 -0
- pygeai_orchestration/tests/test_handlers.py +307 -0
- pygeai_orchestration/tests/test_metrics.py +171 -0
- pygeai_orchestration/tests/test_patterns.py +165 -0
- pygeai_orchestration-0.1.0b2.dist-info/METADATA +290 -0
- pygeai_orchestration-0.1.0b2.dist-info/RECORD +61 -0
- pygeai_orchestration-0.1.0b2.dist-info/WHEEL +5 -0
- pygeai_orchestration-0.1.0b2.dist-info/entry_points.txt +2 -0
- pygeai_orchestration-0.1.0b2.dist-info/licenses/LICENSE +8 -0
- pygeai_orchestration-0.1.0b2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Tests for configuration system."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from pygeai_orchestration.core.config import (
|
|
10
|
+
CacheConfig,
|
|
11
|
+
ConfigManager,
|
|
12
|
+
ConfigProfile,
|
|
13
|
+
ExecutionConfig,
|
|
14
|
+
LogLevel,
|
|
15
|
+
MetricsConfig,
|
|
16
|
+
OrchestrationConfig,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestCacheConfig(unittest.TestCase):
|
|
21
|
+
def test_default_config(self):
|
|
22
|
+
config = CacheConfig()
|
|
23
|
+
self.assertTrue(config.enabled)
|
|
24
|
+
self.assertEqual(config.max_size, 1000)
|
|
25
|
+
self.assertEqual(config.ttl, 3600)
|
|
26
|
+
self.assertEqual(config.backend, "memory")
|
|
27
|
+
|
|
28
|
+
def test_custom_config(self):
|
|
29
|
+
config = CacheConfig(
|
|
30
|
+
enabled=False,
|
|
31
|
+
max_size=500,
|
|
32
|
+
ttl=1800,
|
|
33
|
+
backend="redis"
|
|
34
|
+
)
|
|
35
|
+
self.assertFalse(config.enabled)
|
|
36
|
+
self.assertEqual(config.max_size, 500)
|
|
37
|
+
self.assertEqual(config.ttl, 1800)
|
|
38
|
+
self.assertEqual(config.backend, "redis")
|
|
39
|
+
|
|
40
|
+
def test_invalid_max_size(self):
|
|
41
|
+
with self.assertRaises(ValueError):
|
|
42
|
+
CacheConfig(max_size=0)
|
|
43
|
+
|
|
44
|
+
with self.assertRaises(ValueError):
|
|
45
|
+
CacheConfig(max_size=-1)
|
|
46
|
+
|
|
47
|
+
def test_invalid_ttl(self):
|
|
48
|
+
with self.assertRaises(ValueError):
|
|
49
|
+
CacheConfig(ttl=-1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestMetricsConfig(unittest.TestCase):
|
|
53
|
+
def test_default_config(self):
|
|
54
|
+
config = MetricsConfig()
|
|
55
|
+
self.assertTrue(config.enabled)
|
|
56
|
+
self.assertEqual(config.export_interval, 60)
|
|
57
|
+
self.assertEqual(config.export_format, "json")
|
|
58
|
+
self.assertTrue(config.include_labels)
|
|
59
|
+
|
|
60
|
+
def test_custom_config(self):
|
|
61
|
+
config = MetricsConfig(
|
|
62
|
+
enabled=False,
|
|
63
|
+
export_interval=30,
|
|
64
|
+
export_format="prometheus",
|
|
65
|
+
include_labels=False
|
|
66
|
+
)
|
|
67
|
+
self.assertFalse(config.enabled)
|
|
68
|
+
self.assertEqual(config.export_interval, 30)
|
|
69
|
+
self.assertEqual(config.export_format, "prometheus")
|
|
70
|
+
self.assertFalse(config.include_labels)
|
|
71
|
+
|
|
72
|
+
def test_invalid_export_interval(self):
|
|
73
|
+
with self.assertRaises(ValueError):
|
|
74
|
+
MetricsConfig(export_interval=0)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestExecutionConfig(unittest.TestCase):
|
|
78
|
+
def test_default_config(self):
|
|
79
|
+
config = ExecutionConfig()
|
|
80
|
+
self.assertEqual(config.max_iterations, 10)
|
|
81
|
+
self.assertEqual(config.timeout, 300)
|
|
82
|
+
self.assertEqual(config.retry_attempts, 3)
|
|
83
|
+
self.assertEqual(config.retry_delay, 1.0)
|
|
84
|
+
self.assertEqual(config.parallel_workers, 4)
|
|
85
|
+
|
|
86
|
+
def test_custom_config(self):
|
|
87
|
+
config = ExecutionConfig(
|
|
88
|
+
max_iterations=20,
|
|
89
|
+
timeout=600,
|
|
90
|
+
retry_attempts=5,
|
|
91
|
+
retry_delay=2.0,
|
|
92
|
+
parallel_workers=8
|
|
93
|
+
)
|
|
94
|
+
self.assertEqual(config.max_iterations, 20)
|
|
95
|
+
self.assertEqual(config.timeout, 600)
|
|
96
|
+
self.assertEqual(config.retry_attempts, 5)
|
|
97
|
+
self.assertEqual(config.retry_delay, 2.0)
|
|
98
|
+
self.assertEqual(config.parallel_workers, 8)
|
|
99
|
+
|
|
100
|
+
def test_invalid_values(self):
|
|
101
|
+
with self.assertRaises(ValueError):
|
|
102
|
+
ExecutionConfig(max_iterations=0)
|
|
103
|
+
|
|
104
|
+
with self.assertRaises(ValueError):
|
|
105
|
+
ExecutionConfig(timeout=-1)
|
|
106
|
+
|
|
107
|
+
with self.assertRaises(ValueError):
|
|
108
|
+
ExecutionConfig(retry_attempts=0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestOrchestrationConfig(unittest.TestCase):
|
|
112
|
+
def test_default_config(self):
|
|
113
|
+
config = OrchestrationConfig()
|
|
114
|
+
self.assertEqual(config.profile, ConfigProfile.DEVELOPMENT)
|
|
115
|
+
self.assertEqual(config.log_level, LogLevel.INFO)
|
|
116
|
+
self.assertFalse(config.debug)
|
|
117
|
+
self.assertIsInstance(config.cache, CacheConfig)
|
|
118
|
+
self.assertIsInstance(config.metrics, MetricsConfig)
|
|
119
|
+
self.assertIsInstance(config.execution, ExecutionConfig)
|
|
120
|
+
|
|
121
|
+
def test_custom_config(self):
|
|
122
|
+
config = OrchestrationConfig(
|
|
123
|
+
profile=ConfigProfile.PRODUCTION,
|
|
124
|
+
log_level=LogLevel.ERROR,
|
|
125
|
+
debug=True,
|
|
126
|
+
cache=CacheConfig(enabled=False),
|
|
127
|
+
custom={"key": "value"}
|
|
128
|
+
)
|
|
129
|
+
self.assertEqual(config.profile, ConfigProfile.PRODUCTION)
|
|
130
|
+
self.assertEqual(config.log_level, LogLevel.ERROR)
|
|
131
|
+
self.assertTrue(config.debug)
|
|
132
|
+
self.assertFalse(config.cache.enabled)
|
|
133
|
+
self.assertEqual(config.custom["key"], "value")
|
|
134
|
+
|
|
135
|
+
def test_from_env(self):
|
|
136
|
+
os.environ["PYGEAI_PROFILE"] = "production"
|
|
137
|
+
os.environ["PYGEAI_LOG_LEVEL"] = "ERROR"
|
|
138
|
+
os.environ["PYGEAI_DEBUG"] = "true"
|
|
139
|
+
os.environ["PYGEAI_CACHE_ENABLED"] = "false"
|
|
140
|
+
os.environ["PYGEAI_CACHE_MAX_SIZE"] = "2000"
|
|
141
|
+
os.environ["PYGEAI_MAX_ITERATIONS"] = "15"
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
config = OrchestrationConfig.from_env()
|
|
145
|
+
self.assertEqual(config.profile, ConfigProfile.PRODUCTION)
|
|
146
|
+
self.assertEqual(config.log_level, LogLevel.ERROR)
|
|
147
|
+
self.assertTrue(config.debug)
|
|
148
|
+
self.assertFalse(config.cache.enabled)
|
|
149
|
+
self.assertEqual(config.cache.max_size, 2000)
|
|
150
|
+
self.assertEqual(config.execution.max_iterations, 15)
|
|
151
|
+
finally:
|
|
152
|
+
del os.environ["PYGEAI_PROFILE"]
|
|
153
|
+
del os.environ["PYGEAI_LOG_LEVEL"]
|
|
154
|
+
del os.environ["PYGEAI_DEBUG"]
|
|
155
|
+
del os.environ["PYGEAI_CACHE_ENABLED"]
|
|
156
|
+
del os.environ["PYGEAI_CACHE_MAX_SIZE"]
|
|
157
|
+
del os.environ["PYGEAI_MAX_ITERATIONS"]
|
|
158
|
+
|
|
159
|
+
def test_from_file(self):
|
|
160
|
+
config_data = {
|
|
161
|
+
"profile": "production",
|
|
162
|
+
"log_level": "WARNING",
|
|
163
|
+
"debug": False,
|
|
164
|
+
"cache": {"enabled": True, "max_size": 500},
|
|
165
|
+
"metrics": {"enabled": False},
|
|
166
|
+
"execution": {"max_iterations": 25}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
170
|
+
json.dump(config_data, f)
|
|
171
|
+
temp_path = f.name
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
config = OrchestrationConfig.from_file(temp_path)
|
|
175
|
+
self.assertEqual(config.profile, ConfigProfile.PRODUCTION)
|
|
176
|
+
self.assertEqual(config.log_level, LogLevel.WARNING)
|
|
177
|
+
self.assertFalse(config.debug)
|
|
178
|
+
self.assertEqual(config.cache.max_size, 500)
|
|
179
|
+
self.assertFalse(config.metrics.enabled)
|
|
180
|
+
self.assertEqual(config.execution.max_iterations, 25)
|
|
181
|
+
finally:
|
|
182
|
+
os.unlink(temp_path)
|
|
183
|
+
|
|
184
|
+
def test_from_file_not_found(self):
|
|
185
|
+
with self.assertRaises(FileNotFoundError):
|
|
186
|
+
OrchestrationConfig.from_file("nonexistent.json")
|
|
187
|
+
|
|
188
|
+
def test_to_file(self):
|
|
189
|
+
config = OrchestrationConfig(
|
|
190
|
+
profile=ConfigProfile.TEST,
|
|
191
|
+
log_level=LogLevel.DEBUG
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
195
|
+
path = Path(tmpdir) / "config.json"
|
|
196
|
+
config.to_file(path)
|
|
197
|
+
|
|
198
|
+
self.assertTrue(path.exists())
|
|
199
|
+
|
|
200
|
+
loaded = OrchestrationConfig.from_file(path)
|
|
201
|
+
self.assertEqual(loaded.profile, ConfigProfile.TEST)
|
|
202
|
+
self.assertEqual(loaded.log_level, LogLevel.DEBUG)
|
|
203
|
+
|
|
204
|
+
def test_merge(self):
|
|
205
|
+
base = OrchestrationConfig(
|
|
206
|
+
profile=ConfigProfile.DEVELOPMENT,
|
|
207
|
+
cache=CacheConfig(max_size=500)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
override = OrchestrationConfig(
|
|
211
|
+
log_level=LogLevel.ERROR,
|
|
212
|
+
cache=CacheConfig(enabled=False)
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
merged = base.merge(override)
|
|
216
|
+
|
|
217
|
+
self.assertEqual(merged.profile, ConfigProfile.DEVELOPMENT)
|
|
218
|
+
self.assertEqual(merged.log_level, LogLevel.ERROR)
|
|
219
|
+
self.assertFalse(merged.cache.enabled)
|
|
220
|
+
|
|
221
|
+
def test_production_profile(self):
|
|
222
|
+
config = OrchestrationConfig(profile=ConfigProfile.PRODUCTION)
|
|
223
|
+
profile_config = config.get_profile_config()
|
|
224
|
+
|
|
225
|
+
self.assertEqual(profile_config.log_level, LogLevel.WARNING)
|
|
226
|
+
self.assertFalse(profile_config.debug)
|
|
227
|
+
self.assertEqual(profile_config.execution.max_iterations, 20)
|
|
228
|
+
|
|
229
|
+
def test_test_profile(self):
|
|
230
|
+
config = OrchestrationConfig(profile=ConfigProfile.TEST)
|
|
231
|
+
profile_config = config.get_profile_config()
|
|
232
|
+
|
|
233
|
+
self.assertEqual(profile_config.log_level, LogLevel.DEBUG)
|
|
234
|
+
self.assertTrue(profile_config.debug)
|
|
235
|
+
self.assertFalse(profile_config.cache.enabled)
|
|
236
|
+
self.assertFalse(profile_config.metrics.enabled)
|
|
237
|
+
self.assertEqual(profile_config.execution.max_iterations, 5)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class TestConfigManager(unittest.TestCase):
|
|
241
|
+
def tearDown(self):
|
|
242
|
+
ConfigManager.reset()
|
|
243
|
+
|
|
244
|
+
def test_singleton(self):
|
|
245
|
+
manager1 = ConfigManager()
|
|
246
|
+
manager2 = ConfigManager()
|
|
247
|
+
self.assertIs(manager1, manager2)
|
|
248
|
+
|
|
249
|
+
def test_initialize_default(self):
|
|
250
|
+
manager = ConfigManager.initialize(from_env=False)
|
|
251
|
+
config = manager.get_config()
|
|
252
|
+
|
|
253
|
+
self.assertIsInstance(config, OrchestrationConfig)
|
|
254
|
+
self.assertEqual(config.profile, ConfigProfile.DEVELOPMENT)
|
|
255
|
+
|
|
256
|
+
def test_initialize_with_config(self):
|
|
257
|
+
custom_config = OrchestrationConfig(
|
|
258
|
+
profile=ConfigProfile.PRODUCTION,
|
|
259
|
+
log_level=LogLevel.ERROR
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
manager = ConfigManager.initialize(config=custom_config)
|
|
263
|
+
config = manager.get_config()
|
|
264
|
+
|
|
265
|
+
self.assertEqual(config.log_level, LogLevel.ERROR)
|
|
266
|
+
|
|
267
|
+
def test_initialize_from_file(self):
|
|
268
|
+
config_data = {"profile": "test", "log_level": "DEBUG"}
|
|
269
|
+
|
|
270
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
271
|
+
json.dump(config_data, f)
|
|
272
|
+
temp_path = f.name
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
manager = ConfigManager.initialize(config_file=temp_path)
|
|
276
|
+
config = manager.get_config()
|
|
277
|
+
|
|
278
|
+
self.assertEqual(config.profile, ConfigProfile.TEST)
|
|
279
|
+
self.assertEqual(config.log_level, LogLevel.DEBUG)
|
|
280
|
+
finally:
|
|
281
|
+
os.unlink(temp_path)
|
|
282
|
+
|
|
283
|
+
def test_set_config(self):
|
|
284
|
+
manager = ConfigManager.initialize(from_env=False)
|
|
285
|
+
|
|
286
|
+
new_config = OrchestrationConfig(log_level=LogLevel.CRITICAL)
|
|
287
|
+
manager.set_config(new_config)
|
|
288
|
+
|
|
289
|
+
config = manager.get_config()
|
|
290
|
+
self.assertEqual(config.log_level, LogLevel.CRITICAL)
|
|
291
|
+
|
|
292
|
+
def test_reset(self):
|
|
293
|
+
manager = ConfigManager.initialize(from_env=False)
|
|
294
|
+
manager.reset()
|
|
295
|
+
|
|
296
|
+
config = manager.get_config()
|
|
297
|
+
self.assertIsInstance(config, OrchestrationConfig)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
if __name__ == "__main__":
|
|
301
|
+
unittest.main()
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Tests for development utilities."""
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from pygeai_orchestration.core.base import BasePattern, PatternResult, PatternType
|
|
8
|
+
from pygeai_orchestration.dev.debug import DebugTracer, PatternInspector
|
|
9
|
+
from pygeai_orchestration.dev.templates import PatternTemplate, TemplateGenerator
|
|
10
|
+
from pygeai_orchestration.dev.testing import (
|
|
11
|
+
AsyncMockHelper,
|
|
12
|
+
MockAgent,
|
|
13
|
+
MockPattern,
|
|
14
|
+
PatternTestCase,
|
|
15
|
+
TestDataBuilder,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestTemplateGenerator(unittest.TestCase):
|
|
20
|
+
def test_generate_reflection_pattern(self):
|
|
21
|
+
code = TemplateGenerator.generate_reflection_pattern(
|
|
22
|
+
"MyReflection",
|
|
23
|
+
"Test reflection"
|
|
24
|
+
)
|
|
25
|
+
self.assertIn("MyReflection", code)
|
|
26
|
+
self.assertIn("ReflectionPattern", code)
|
|
27
|
+
self.assertIn("reflect", code)
|
|
28
|
+
self.assertIn("improve", code)
|
|
29
|
+
|
|
30
|
+
def test_generate_react_pattern(self):
|
|
31
|
+
code = TemplateGenerator.generate_react_pattern(
|
|
32
|
+
"MyReAct",
|
|
33
|
+
"Test ReAct"
|
|
34
|
+
)
|
|
35
|
+
self.assertIn("MyReAct", code)
|
|
36
|
+
self.assertIn("ReActPattern", code)
|
|
37
|
+
self.assertIn("think", code)
|
|
38
|
+
self.assertIn("act", code)
|
|
39
|
+
|
|
40
|
+
def test_generate_custom_pattern(self):
|
|
41
|
+
code = TemplateGenerator.generate_custom_pattern(
|
|
42
|
+
"MyCustom",
|
|
43
|
+
"Test custom"
|
|
44
|
+
)
|
|
45
|
+
self.assertIn("MyCustom", code)
|
|
46
|
+
self.assertIn("OrchestrationPattern", code)
|
|
47
|
+
self.assertIn("_execute_impl", code)
|
|
48
|
+
|
|
49
|
+
def test_generate_test_file(self):
|
|
50
|
+
code = TemplateGenerator.generate_test_file(
|
|
51
|
+
"MyPattern",
|
|
52
|
+
PatternTemplate.REFLECTION
|
|
53
|
+
)
|
|
54
|
+
self.assertIn("TestMyPattern", code)
|
|
55
|
+
self.assertIn("PatternTestCase", code)
|
|
56
|
+
self.assertIn("test_basic_execution", code)
|
|
57
|
+
|
|
58
|
+
def test_save_template(self):
|
|
59
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
60
|
+
output_path = Path(tmpdir) / "test_pattern.py"
|
|
61
|
+
code = "# Test code"
|
|
62
|
+
|
|
63
|
+
TemplateGenerator.save_template(code, output_path)
|
|
64
|
+
|
|
65
|
+
self.assertTrue(output_path.exists())
|
|
66
|
+
self.assertEqual(output_path.read_text(), code)
|
|
67
|
+
|
|
68
|
+
def test_save_template_no_overwrite(self):
|
|
69
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
70
|
+
output_path = Path(tmpdir) / "test_pattern.py"
|
|
71
|
+
output_path.write_text("existing")
|
|
72
|
+
|
|
73
|
+
with self.assertRaises(FileExistsError):
|
|
74
|
+
TemplateGenerator.save_template("new", output_path)
|
|
75
|
+
|
|
76
|
+
def test_save_template_overwrite(self):
|
|
77
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
78
|
+
output_path = Path(tmpdir) / "test_pattern.py"
|
|
79
|
+
output_path.write_text("existing")
|
|
80
|
+
|
|
81
|
+
TemplateGenerator.save_template("new", output_path, overwrite=True)
|
|
82
|
+
|
|
83
|
+
self.assertEqual(output_path.read_text(), "new")
|
|
84
|
+
|
|
85
|
+
def test_create_pattern(self):
|
|
86
|
+
files = TemplateGenerator.create_pattern(
|
|
87
|
+
PatternTemplate.REFLECTION,
|
|
88
|
+
"TestPattern",
|
|
89
|
+
"Test description",
|
|
90
|
+
include_tests=True
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
self.assertEqual(len(files), 2)
|
|
94
|
+
self.assertIn("testpattern.py", files)
|
|
95
|
+
self.assertIn("test_testpattern.py", files)
|
|
96
|
+
|
|
97
|
+
def test_create_pattern_no_tests(self):
|
|
98
|
+
files = TemplateGenerator.create_pattern(
|
|
99
|
+
PatternTemplate.CUSTOM,
|
|
100
|
+
"NoTests",
|
|
101
|
+
"No tests",
|
|
102
|
+
include_tests=False
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
self.assertEqual(len(files), 1)
|
|
106
|
+
self.assertIn("notests.py", files)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TestDebugTracer(unittest.TestCase):
|
|
110
|
+
def setUp(self):
|
|
111
|
+
self.tracer = DebugTracer()
|
|
112
|
+
|
|
113
|
+
def test_trace_event(self):
|
|
114
|
+
self.tracer.trace("test_event", "TestPattern", {"key": "value"})
|
|
115
|
+
|
|
116
|
+
traces = self.tracer.get_traces()
|
|
117
|
+
self.assertEqual(len(traces), 1)
|
|
118
|
+
self.assertEqual(traces[0]["event"], "test_event")
|
|
119
|
+
self.assertEqual(traces[0]["pattern"], "TestPattern")
|
|
120
|
+
|
|
121
|
+
def test_disabled_tracer(self):
|
|
122
|
+
tracer = DebugTracer(enabled=False)
|
|
123
|
+
tracer.trace("event")
|
|
124
|
+
|
|
125
|
+
self.assertEqual(len(tracer.get_traces()), 0)
|
|
126
|
+
|
|
127
|
+
def test_get_traces_filtered(self):
|
|
128
|
+
self.tracer.trace("event1", "Pattern1")
|
|
129
|
+
self.tracer.trace("event2", "Pattern2")
|
|
130
|
+
self.tracer.trace("event3", "Pattern1")
|
|
131
|
+
|
|
132
|
+
pattern1_traces = self.tracer.get_traces("Pattern1")
|
|
133
|
+
self.assertEqual(len(pattern1_traces), 2)
|
|
134
|
+
|
|
135
|
+
def test_clear_traces(self):
|
|
136
|
+
self.tracer.trace("event")
|
|
137
|
+
self.assertEqual(len(self.tracer.get_traces()), 1)
|
|
138
|
+
|
|
139
|
+
self.tracer.clear()
|
|
140
|
+
self.assertEqual(len(self.tracer.get_traces()), 0)
|
|
141
|
+
|
|
142
|
+
def test_format_traces(self):
|
|
143
|
+
self.tracer.trace("event1", "Pattern1", {"data": "value"})
|
|
144
|
+
|
|
145
|
+
formatted = self.tracer.format_traces()
|
|
146
|
+
self.assertIn("Debug Traces", formatted)
|
|
147
|
+
self.assertIn("event1", formatted)
|
|
148
|
+
self.assertIn("Pattern1", formatted)
|
|
149
|
+
|
|
150
|
+
def test_trace_execution_success(self):
|
|
151
|
+
with self.tracer.trace_execution("TestPattern", "test task"):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
traces = self.tracer.get_traces()
|
|
155
|
+
self.assertEqual(len(traces), 2)
|
|
156
|
+
self.assertEqual(traces[0]["event"], "execution_start")
|
|
157
|
+
self.assertEqual(traces[1]["event"], "execution_end")
|
|
158
|
+
|
|
159
|
+
def test_trace_execution_error(self):
|
|
160
|
+
with self.assertRaises(ValueError):
|
|
161
|
+
with self.tracer.trace_execution("TestPattern", "test"):
|
|
162
|
+
raise ValueError("Test error")
|
|
163
|
+
|
|
164
|
+
traces = self.tracer.get_traces()
|
|
165
|
+
self.assertGreaterEqual(len(traces), 2)
|
|
166
|
+
error_trace = [t for t in traces if t["event"] == "execution_error"]
|
|
167
|
+
self.assertEqual(len(error_trace), 1)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TestPatternInspector(unittest.TestCase):
|
|
171
|
+
async def test_inspect_pattern(self):
|
|
172
|
+
pattern = MockPattern()
|
|
173
|
+
|
|
174
|
+
inspection = PatternInspector.inspect_pattern(pattern)
|
|
175
|
+
|
|
176
|
+
self.assertEqual(inspection["class_name"], "MockPattern")
|
|
177
|
+
self.assertIn("methods", inspection)
|
|
178
|
+
self.assertIn("config", inspection)
|
|
179
|
+
|
|
180
|
+
async def test_format_inspection(self):
|
|
181
|
+
pattern = MockPattern()
|
|
182
|
+
inspection = PatternInspector.inspect_pattern(pattern)
|
|
183
|
+
|
|
184
|
+
formatted = PatternInspector.format_inspection(inspection)
|
|
185
|
+
|
|
186
|
+
self.assertIn("MockPattern", formatted)
|
|
187
|
+
self.assertIn("Methods:", formatted)
|
|
188
|
+
|
|
189
|
+
async def test_compare_results(self):
|
|
190
|
+
result1 = PatternResult(
|
|
191
|
+
success=True,
|
|
192
|
+
result="result1",
|
|
193
|
+
iterations=1,
|
|
194
|
+
metadata={"key1": "value1"}
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
result2 = PatternResult(
|
|
198
|
+
success=True,
|
|
199
|
+
result="result2",
|
|
200
|
+
iterations=2,
|
|
201
|
+
metadata={"key2": "value2"}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
comparison = PatternInspector.compare_results(result1, result2)
|
|
205
|
+
|
|
206
|
+
self.assertTrue(comparison["success_match"])
|
|
207
|
+
self.assertFalse(comparison["result_match"])
|
|
208
|
+
self.assertEqual(comparison["iterations_diff"], -1)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestMockAgent(unittest.IsolatedAsyncioTestCase):
|
|
212
|
+
async def test_generate(self):
|
|
213
|
+
agent = MockAgent(["response1", "response2"])
|
|
214
|
+
|
|
215
|
+
result1 = await agent.generate("prompt1")
|
|
216
|
+
result2 = await agent.generate("prompt2")
|
|
217
|
+
|
|
218
|
+
self.assertEqual(result1, "response1")
|
|
219
|
+
self.assertEqual(result2, "response2")
|
|
220
|
+
self.assertEqual(agent.call_count, 2)
|
|
221
|
+
|
|
222
|
+
async def test_generate_default_response(self):
|
|
223
|
+
agent = MockAgent()
|
|
224
|
+
result = await agent.generate("prompt")
|
|
225
|
+
|
|
226
|
+
self.assertEqual(result, "Mock response")
|
|
227
|
+
|
|
228
|
+
async def test_chat(self):
|
|
229
|
+
agent = MockAgent(["chat response"])
|
|
230
|
+
messages = [{"role": "user", "content": "test"}]
|
|
231
|
+
|
|
232
|
+
result = await agent.chat(messages)
|
|
233
|
+
|
|
234
|
+
self.assertEqual(result, "chat response")
|
|
235
|
+
|
|
236
|
+
async def test_reset(self):
|
|
237
|
+
agent = MockAgent()
|
|
238
|
+
await agent.generate("test")
|
|
239
|
+
|
|
240
|
+
agent.reset()
|
|
241
|
+
|
|
242
|
+
self.assertEqual(agent.call_count, 0)
|
|
243
|
+
self.assertEqual(len(agent.calls), 0)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TestMockPattern(unittest.IsolatedAsyncioTestCase):
|
|
247
|
+
async def test_mock_pattern_execution(self):
|
|
248
|
+
pattern = MockPattern(result="test result", success=True)
|
|
249
|
+
|
|
250
|
+
result = await pattern.execute("test task")
|
|
251
|
+
|
|
252
|
+
self.assertTrue(result.success)
|
|
253
|
+
self.assertEqual(result.result, "test result")
|
|
254
|
+
self.assertEqual(pattern.execution_count, 1)
|
|
255
|
+
|
|
256
|
+
async def test_mock_pattern_failure(self):
|
|
257
|
+
pattern = MockPattern(success=False)
|
|
258
|
+
|
|
259
|
+
result = await pattern.execute("test task")
|
|
260
|
+
|
|
261
|
+
self.assertFalse(result.success)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class TestPatternTestCase(PatternTestCase):
|
|
265
|
+
async def test_create_mock_agent(self):
|
|
266
|
+
agent = self.create_mock_agent(["response"])
|
|
267
|
+
|
|
268
|
+
result = await agent.generate("prompt")
|
|
269
|
+
|
|
270
|
+
self.assertEqual(result, "response")
|
|
271
|
+
|
|
272
|
+
async def test_assert_pattern_result(self):
|
|
273
|
+
result = PatternResult(
|
|
274
|
+
success=True,
|
|
275
|
+
result="output",
|
|
276
|
+
iterations=1
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
self.assert_pattern_result(result, success=True)
|
|
280
|
+
|
|
281
|
+
async def test_assert_agent_called(self):
|
|
282
|
+
agent = MockAgent()
|
|
283
|
+
await agent.generate("test prompt")
|
|
284
|
+
|
|
285
|
+
self.assert_agent_called(agent, min_calls=1, contains="test")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class TestCreateTestPattern(unittest.TestCase):
|
|
289
|
+
def test_create_test_pattern_direct(self):
|
|
290
|
+
pattern = MockPattern(result="test")
|
|
291
|
+
|
|
292
|
+
self.assertIsInstance(pattern, MockPattern)
|
|
293
|
+
self.assertIsInstance(pattern, BasePattern)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class TestAsyncMockHelper(unittest.IsolatedAsyncioTestCase):
|
|
297
|
+
async def test_create_async_mock(self):
|
|
298
|
+
mock = AsyncMockHelper.create_async_mock("return value")
|
|
299
|
+
|
|
300
|
+
result = await mock()
|
|
301
|
+
|
|
302
|
+
self.assertEqual(result, "return value")
|
|
303
|
+
|
|
304
|
+
async def test_create_agent_mock(self):
|
|
305
|
+
agent = AsyncMockHelper.create_agent_mock(["resp1", "resp2"])
|
|
306
|
+
|
|
307
|
+
result1 = await agent.generate("prompt1")
|
|
308
|
+
result2 = await agent.generate("prompt2")
|
|
309
|
+
|
|
310
|
+
self.assertEqual(result1, "resp1")
|
|
311
|
+
self.assertEqual(result2, "resp2")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class TestTestDataBuilder(unittest.TestCase):
|
|
315
|
+
def test_build_pattern_result(self):
|
|
316
|
+
result = TestDataBuilder.build_pattern_result(
|
|
317
|
+
success=True,
|
|
318
|
+
result="output",
|
|
319
|
+
custom_key="custom_value"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
self.assertTrue(result.success)
|
|
323
|
+
self.assertEqual(result.result, "output")
|
|
324
|
+
self.assertEqual(result.metadata["custom_key"], "custom_value")
|
|
325
|
+
|
|
326
|
+
def test_build_pattern_config(self):
|
|
327
|
+
config = TestDataBuilder.build_pattern_config(
|
|
328
|
+
name="TestPattern",
|
|
329
|
+
pattern_type=PatternType.REACT
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
self.assertEqual(config.name, "TestPattern")
|
|
333
|
+
self.assertEqual(config.pattern_type, PatternType.REACT)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
if __name__ == "__main__":
|
|
337
|
+
unittest.main()
|