File size: 18,767 Bytes
56fd459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# Tests for ankigen_core/agents/config.py

import pytest
import json
import yaml
import tempfile
import os
from pathlib import Path
from unittest.mock import patch, MagicMock, mock_open
from dataclasses import asdict

from ankigen_core.agents.config import AgentPromptTemplate, AgentConfigManager
from ankigen_core.agents.base import AgentConfig


# Test AgentPromptTemplate
def test_agent_prompt_template_creation():
    """Test basic AgentPromptTemplate creation"""
    template = AgentPromptTemplate(
        system_prompt="You are a {role} expert.",
        user_prompt_template="Please analyze: {content}",
        variables={"role": "mathematics"}
    )
    
    assert template.system_prompt == "You are a {role} expert."
    assert template.user_prompt_template == "Please analyze: {content}"
    assert template.variables == {"role": "mathematics"}


def test_agent_prompt_template_defaults():
    """Test AgentPromptTemplate with default values"""
    template = AgentPromptTemplate(
        system_prompt="System prompt",
        user_prompt_template="User prompt"
    )
    
    assert template.variables == {}


def test_agent_prompt_template_render_system_prompt():
    """Test rendering system prompt with variables"""
    template = AgentPromptTemplate(
        system_prompt="You are a {role} expert specializing in {subject}.",
        user_prompt_template="User prompt",
        variables={"role": "mathematics"}
    )
    
    rendered = template.render_system_prompt(subject="calculus")
    assert rendered == "You are a mathematics expert specializing in calculus."


def test_agent_prompt_template_render_system_prompt_override():
    """Test rendering system prompt with variable override"""
    template = AgentPromptTemplate(
        system_prompt="You are a {role} expert.",
        user_prompt_template="User prompt",
        variables={"role": "mathematics"}
    )
    
    rendered = template.render_system_prompt(role="physics")
    assert rendered == "You are a physics expert."


def test_agent_prompt_template_render_system_prompt_missing_variable():
    """Test rendering system prompt with missing variable"""
    template = AgentPromptTemplate(
        system_prompt="You are a {role} expert in {missing_var}.",
        user_prompt_template="User prompt"
    )
    
    with patch('ankigen_core.logging.logger') as mock_logger:
        rendered = template.render_system_prompt(role="mathematics")
        
        # Should return original prompt and log error
        assert rendered == "You are a {role} expert in {missing_var}."
        mock_logger.error.assert_called_once()


def test_agent_prompt_template_render_user_prompt():
    """Test rendering user prompt with variables"""
    template = AgentPromptTemplate(
        system_prompt="System prompt",
        user_prompt_template="Analyze this {content_type}: {content}",
        variables={"content_type": "text"}
    )
    
    rendered = template.render_user_prompt(content="Sample content")
    assert rendered == "Analyze this text: Sample content"


def test_agent_prompt_template_render_user_prompt_missing_variable():
    """Test rendering user prompt with missing variable"""
    template = AgentPromptTemplate(
        system_prompt="System prompt",
        user_prompt_template="Analyze {content} for {missing_var}"
    )
    
    with patch('ankigen_core.logging.logger') as mock_logger:
        rendered = template.render_user_prompt(content="test")
        
        # Should return original prompt and log error
        assert rendered == "Analyze {content} for {missing_var}"
        mock_logger.error.assert_called_once()


# Test AgentConfigManager
@pytest.fixture
def temp_config_dir():
    """Create a temporary directory for config testing"""
    with tempfile.TemporaryDirectory() as tmp_dir:
        yield tmp_dir


@pytest.fixture
def agent_config_manager(temp_config_dir):
    """Create AgentConfigManager with temporary directory"""
    return AgentConfigManager(config_dir=temp_config_dir)


def test_agent_config_manager_init(temp_config_dir):
    """Test AgentConfigManager initialization"""
    manager = AgentConfigManager(config_dir=temp_config_dir)
    
    assert manager.config_dir == Path(temp_config_dir)
    assert isinstance(manager.configs, dict)
    assert isinstance(manager.prompt_templates, dict)
    
    # Check that default directories are created
    assert (Path(temp_config_dir) / "defaults").exists()


def test_agent_config_manager_init_default_dir():
    """Test AgentConfigManager initialization with default directory"""
    with patch('pathlib.Path.mkdir') as mock_mkdir:
        manager = AgentConfigManager()
        
        assert manager.config_dir == Path("config/agents")
        mock_mkdir.assert_called()


def test_agent_config_manager_ensure_config_dir(temp_config_dir):
    """Test _ensure_config_dir method"""
    manager = AgentConfigManager(config_dir=temp_config_dir)
    
    # Should create defaults directory
    defaults_dir = Path(temp_config_dir) / "defaults"
    assert defaults_dir.exists()


def test_agent_config_manager_load_configs_from_yaml(agent_config_manager):
    """Test loading configurations from YAML file"""
    config_data = {
        "agents": {
            "test_agent": {
                "instructions": "Test instructions",
                "model": "gpt-4o",
                "temperature": 0.8,
                "timeout": 45.0
            }
        },
        "prompt_templates": {
            "test_template": {
                "system_prompt": "System: {role}",
                "user_prompt_template": "User: {input}",
                "variables": {"role": "assistant"}
            }
        }
    }
    
    config_file = agent_config_manager.config_dir / "test_config.yaml"
    with open(config_file, 'w') as f:
        yaml.safe_dump(config_data, f)
    
    agent_config_manager._load_configs_from_file("test_config.yaml")
    
    # Check agent config was loaded
    assert "test_agent" in agent_config_manager.configs
    config = agent_config_manager.configs["test_agent"]
    assert config.name == "test_agent"
    assert config.instructions == "Test instructions"
    assert config.model == "gpt-4o"
    assert config.temperature == 0.8
    assert config.timeout == 45.0
    
    # Check prompt template was loaded
    assert "test_template" in agent_config_manager.prompt_templates
    template = agent_config_manager.prompt_templates["test_template"]
    assert template.system_prompt == "System: {role}"
    assert template.user_prompt_template == "User: {input}"
    assert template.variables == {"role": "assistant"}


def test_agent_config_manager_load_configs_from_json(agent_config_manager):
    """Test loading configurations from JSON file"""
    config_data = {
        "agents": {
            "json_agent": {
                "instructions": "JSON instructions",
                "model": "gpt-3.5-turbo",
                "temperature": 0.5
            }
        }
    }
    
    config_file = agent_config_manager.config_dir / "test_config.json"
    with open(config_file, 'w') as f:
        json.dump(config_data, f)
    
    agent_config_manager._load_configs_from_file("test_config.json")
    
    # Check agent config was loaded
    assert "json_agent" in agent_config_manager.configs
    config = agent_config_manager.configs["json_agent"]
    assert config.name == "json_agent"
    assert config.instructions == "JSON instructions"
    assert config.model == "gpt-3.5-turbo"
    assert config.temperature == 0.5


def test_agent_config_manager_load_nonexistent_file(agent_config_manager):
    """Test loading from non-existent file"""
    with patch('ankigen_core.logging.logger') as mock_logger:
        agent_config_manager._load_configs_from_file("nonexistent.yaml")
        
        mock_logger.warning.assert_called_once()
        assert "not found" in mock_logger.warning.call_args[0][0]


def test_agent_config_manager_load_invalid_yaml(agent_config_manager):
    """Test loading from invalid YAML file"""
    config_file = agent_config_manager.config_dir / "invalid.yaml"
    with open(config_file, 'w') as f:
        f.write("invalid: yaml: content: [")
    
    with patch('ankigen_core.logging.logger') as mock_logger:
        agent_config_manager._load_configs_from_file("invalid.yaml")
        
        mock_logger.error.assert_called_once()


def test_agent_config_manager_get_config(agent_config_manager):
    """Test getting agent configuration"""
    # Add a test config
    test_config = AgentConfig(
        name="test_agent",
        instructions="Test instructions",
        model="gpt-4o"
    )
    agent_config_manager.configs["test_agent"] = test_config
    
    # Test getting existing config
    retrieved_config = agent_config_manager.get_config("test_agent")
    assert retrieved_config == test_config
    
    # Test getting non-existent config
    missing_config = agent_config_manager.get_config("missing_agent")
    assert missing_config is None


def test_agent_config_manager_get_prompt_template(agent_config_manager):
    """Test getting prompt template"""
    # Add a test template
    test_template = AgentPromptTemplate(
        system_prompt="Test system",
        user_prompt_template="Test user",
        variables={"var": "value"}
    )
    agent_config_manager.prompt_templates["test_template"] = test_template
    
    # Test getting existing template
    retrieved_template = agent_config_manager.get_prompt_template("test_template")
    assert retrieved_template == test_template
    
    # Test getting non-existent template
    missing_template = agent_config_manager.get_prompt_template("missing_template")
    assert missing_template is None


def test_agent_config_manager_list_configs(agent_config_manager):
    """Test listing all agent configurations"""
    # Add test configs
    config1 = AgentConfig(name="agent1", instructions="Instructions 1")
    config2 = AgentConfig(name="agent2", instructions="Instructions 2")
    agent_config_manager.configs["agent1"] = config1
    agent_config_manager.configs["agent2"] = config2
    
    config_names = agent_config_manager.list_configs()
    assert set(config_names) == {"agent1", "agent2"}


def test_agent_config_manager_list_prompt_templates(agent_config_manager):
    """Test listing all prompt templates"""
    # Add test templates
    template1 = AgentPromptTemplate(system_prompt="S1", user_prompt_template="U1")
    template2 = AgentPromptTemplate(system_prompt="S2", user_prompt_template="U2")
    agent_config_manager.prompt_templates["template1"] = template1
    agent_config_manager.prompt_templates["template2"] = template2
    
    template_names = agent_config_manager.list_prompt_templates()
    assert set(template_names) == {"template1", "template2"}


def test_agent_config_manager_update_config(agent_config_manager):
    """Test updating agent configuration"""
    # Add initial config
    initial_config = AgentConfig(
        name="test_agent",
        instructions="Initial instructions",
        temperature=0.7
    )
    agent_config_manager.configs["test_agent"] = initial_config
    
    # Update config
    updates = {"temperature": 0.9, "timeout": 60.0}
    updated_config = agent_config_manager.update_config("test_agent", updates)
    
    assert updated_config.temperature == 0.9
    assert updated_config.timeout == 60.0
    assert updated_config.instructions == "Initial instructions"  # Unchanged
    
    # Verify it's stored
    assert agent_config_manager.configs["test_agent"] == updated_config


def test_agent_config_manager_update_nonexistent_config(agent_config_manager):
    """Test updating non-existent agent configuration"""
    updates = {"temperature": 0.9}
    updated_config = agent_config_manager.update_config("missing_agent", updates)
    
    assert updated_config is None


def test_agent_config_manager_save_config_to_file(agent_config_manager):
    """Test saving configuration to file"""
    # Add test configs
    config1 = AgentConfig(name="agent1", instructions="Instructions 1", temperature=0.7)
    config2 = AgentConfig(name="agent2", instructions="Instructions 2", model="gpt-3.5-turbo")
    agent_config_manager.configs["agent1"] = config1
    agent_config_manager.configs["agent2"] = config2
    
    # Save to file
    output_file = "test_output.yaml"
    agent_config_manager.save_config_to_file(output_file)
    
    # Verify file was created
    saved_file_path = agent_config_manager.config_dir / output_file
    assert saved_file_path.exists()
    
    # Verify content
    with open(saved_file_path, 'r') as f:
        saved_data = yaml.safe_load(f)
    
    assert "agents" in saved_data
    assert "agent1" in saved_data["agents"]
    assert "agent2" in saved_data["agents"]
    assert saved_data["agents"]["agent1"]["instructions"] == "Instructions 1"
    assert saved_data["agents"]["agent1"]["temperature"] == 0.7
    assert saved_data["agents"]["agent2"]["model"] == "gpt-3.5-turbo"


def test_agent_config_manager_load_config_from_dict(agent_config_manager):
    """Test loading configuration from dictionary"""
    config_dict = {
        "agents": {
            "dict_agent": {
                "instructions": "From dict",
                "model": "gpt-4",
                "temperature": 0.3,
                "max_tokens": 1000,
                "timeout": 25.0,
                "retry_attempts": 2,
                "enable_tracing": False
            }
        },
        "prompt_templates": {
            "dict_template": {
                "system_prompt": "Dict system",
                "user_prompt_template": "Dict user",
                "variables": {"key": "value"}
            }
        }
    }
    
    agent_config_manager.load_config_from_dict(config_dict)
    
    # Check agent config
    assert "dict_agent" in agent_config_manager.configs
    config = agent_config_manager.configs["dict_agent"]
    assert config.name == "dict_agent"
    assert config.instructions == "From dict"
    assert config.model == "gpt-4"
    assert config.temperature == 0.3
    assert config.max_tokens == 1000
    assert config.timeout == 25.0
    assert config.retry_attempts == 2
    assert config.enable_tracing is False
    
    # Check prompt template
    assert "dict_template" in agent_config_manager.prompt_templates
    template = agent_config_manager.prompt_templates["dict_template"]
    assert template.system_prompt == "Dict system"
    assert template.user_prompt_template == "Dict user"
    assert template.variables == {"key": "value"}


def test_agent_config_manager_validate_config():
    """Test configuration validation"""
    manager = AgentConfigManager()
    
    # Valid config
    valid_config = {
        "name": "test_agent",
        "instructions": "Test instructions",
        "model": "gpt-4o",
        "temperature": 0.7
    }
    assert manager._validate_config(valid_config) is True
    
    # Invalid config - missing required fields
    invalid_config = {
        "name": "test_agent"
        # Missing instructions
    }
    assert manager._validate_config(invalid_config) is False
    
    # Invalid config - invalid temperature
    invalid_temp_config = {
        "name": "test_agent",
        "instructions": "Test instructions",
        "temperature": 2.0  # > 1.0
    }
    assert manager._validate_config(invalid_temp_config) is False


def test_agent_config_manager_create_default_generator_configs(temp_config_dir):
    """Test creation of default generator configurations"""
    manager = AgentConfigManager(config_dir=temp_config_dir)
    
    # Should create defaults/generators.yaml
    generators_file = Path(temp_config_dir) / "defaults" / "generators.yaml"
    assert generators_file.exists()
    
    # Check content
    with open(generators_file, 'r') as f:
        data = yaml.safe_load(f)
    
    assert "agents" in data
    # Should have at least the subject expert agent
    assert any("subject_expert" in name.lower() for name in data["agents"].keys())


def test_agent_config_manager_create_default_judge_configs(temp_config_dir):
    """Test creation of default judge configurations"""
    manager = AgentConfigManager(config_dir=temp_config_dir)
    
    # Should create defaults/judges.yaml
    judges_file = Path(temp_config_dir) / "defaults" / "judges.yaml"
    assert judges_file.exists()
    
    # Check content
    with open(judges_file, 'r') as f:
        data = yaml.safe_load(f)
    
    assert "agents" in data
    # Should have judge agents
    assert any("judge" in name.lower() for name in data["agents"].keys())


def test_agent_config_manager_create_default_enhancer_configs(temp_config_dir):
    """Test creation of default enhancer configurations"""
    manager = AgentConfigManager(config_dir=temp_config_dir)
    
    # Should create defaults/enhancers.yaml
    enhancers_file = Path(temp_config_dir) / "defaults" / "enhancers.yaml"
    assert enhancers_file.exists()
    
    # Check content
    with open(enhancers_file, 'r') as f:
        data = yaml.safe_load(f)
    
    assert "agents" in data
    # Should have enhancement agents
    assert any("enhancement" in name.lower() or "revision" in name.lower() for name in data["agents"].keys())


# Integration tests
def test_agent_config_manager_full_workflow(temp_config_dir):
    """Test complete configuration management workflow"""
    manager = AgentConfigManager(config_dir=temp_config_dir)
    
    # 1. Load configs from dict
    config_data = {
        "agents": {
            "workflow_agent": {
                "instructions": "Workflow instructions",
                "model": "gpt-4o",
                "temperature": 0.8
            }
        },
        "prompt_templates": {
            "workflow_template": {
                "system_prompt": "You are {role}",
                "user_prompt_template": "Process: {content}",
                "variables": {"role": "assistant"}
            }
        }
    }
    manager.load_config_from_dict(config_data)
    
    # 2. Update config
    manager.update_config("workflow_agent", {"timeout": 45.0})
    
    # 3. Get config and template
    config = manager.get_config("workflow_agent")
    template = manager.get_prompt_template("workflow_template")
    
    assert config.timeout == 45.0
    assert template.variables["role"] == "assistant"
    
    # 4. Save to file
    manager.save_config_to_file("workflow_output.yaml")
    
    # 5. Verify saved content
    saved_file = Path(temp_config_dir) / "workflow_output.yaml"
    with open(saved_file, 'r') as f:
        saved_data = yaml.safe_load(f)
    
    assert saved_data["agents"]["workflow_agent"]["timeout"] == 45.0
    assert saved_data["prompt_templates"]["workflow_template"]["variables"]["role"] == "assistant"