omnimancer-cli 0.1.0__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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Persona Template Validation and Application system for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides robust validation and application logic for agent persona
|
|
5
|
+
templates with comprehensive error handling and rollback capabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any, Dict, List, Optional, Set
|
|
12
|
+
|
|
13
|
+
from omnimancer.core.models import ConfigTemplate, ConfigTemplateManager
|
|
14
|
+
|
|
15
|
+
from ..provider_registry import ProviderRegistry
|
|
16
|
+
from .config import AgentConfig
|
|
17
|
+
from .persona import (
|
|
18
|
+
AgentPersona,
|
|
19
|
+
PersonaCapability,
|
|
20
|
+
PersonaCategory,
|
|
21
|
+
PersonaConfiguration,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ValidationSeverity(Enum):
|
|
28
|
+
"""Severity levels for validation issues."""
|
|
29
|
+
|
|
30
|
+
INFO = "info"
|
|
31
|
+
WARNING = "warning"
|
|
32
|
+
ERROR = "error"
|
|
33
|
+
CRITICAL = "critical"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ValidationCategory(Enum):
|
|
37
|
+
"""Categories of validation issues."""
|
|
38
|
+
|
|
39
|
+
TEMPLATE_STRUCTURE = "template_structure"
|
|
40
|
+
REQUIRED_FIELDS = "required_fields"
|
|
41
|
+
MODEL_AVAILABILITY = "model_availability"
|
|
42
|
+
TOOL_COMPATIBILITY = "tool_compatibility"
|
|
43
|
+
PROVIDER_COMPATIBILITY = "provider_compatibility"
|
|
44
|
+
CONFIGURATION_CONSTRAINTS = "configuration_constraints"
|
|
45
|
+
RESOURCE_REQUIREMENTS = "resource_requirements"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ValidationIssue:
|
|
50
|
+
"""Represents a validation issue found during template validation."""
|
|
51
|
+
|
|
52
|
+
severity: ValidationSeverity
|
|
53
|
+
category: ValidationCategory
|
|
54
|
+
message: str
|
|
55
|
+
field_path: str = ""
|
|
56
|
+
suggestion: str = ""
|
|
57
|
+
fix_action: Optional[str] = None
|
|
58
|
+
details: Dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
61
|
+
"""Convert to dictionary representation."""
|
|
62
|
+
return {
|
|
63
|
+
"severity": self.severity.value,
|
|
64
|
+
"category": self.category.value,
|
|
65
|
+
"message": self.message,
|
|
66
|
+
"field_path": self.field_path,
|
|
67
|
+
"suggestion": self.suggestion,
|
|
68
|
+
"fix_action": self.fix_action,
|
|
69
|
+
"details": self.details,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class ValidationResult:
|
|
75
|
+
"""Result of template validation."""
|
|
76
|
+
|
|
77
|
+
is_valid: bool
|
|
78
|
+
issues: List[ValidationIssue] = field(default_factory=list)
|
|
79
|
+
warnings: List[ValidationIssue] = field(default_factory=list)
|
|
80
|
+
errors: List[ValidationIssue] = field(default_factory=list)
|
|
81
|
+
critical_issues: List[ValidationIssue] = field(default_factory=list)
|
|
82
|
+
|
|
83
|
+
def add_issue(self, issue: ValidationIssue) -> None:
|
|
84
|
+
"""Add a validation issue."""
|
|
85
|
+
self.issues.append(issue)
|
|
86
|
+
|
|
87
|
+
if issue.severity == ValidationSeverity.WARNING:
|
|
88
|
+
self.warnings.append(issue)
|
|
89
|
+
elif issue.severity == ValidationSeverity.ERROR:
|
|
90
|
+
self.errors.append(issue)
|
|
91
|
+
self.is_valid = False
|
|
92
|
+
elif issue.severity == ValidationSeverity.CRITICAL:
|
|
93
|
+
self.critical_issues.append(issue)
|
|
94
|
+
self.is_valid = False
|
|
95
|
+
|
|
96
|
+
def has_blocking_issues(self) -> bool:
|
|
97
|
+
"""Check if there are blocking issues that prevent template use."""
|
|
98
|
+
return len(self.errors) > 0 or len(self.critical_issues) > 0
|
|
99
|
+
|
|
100
|
+
def get_summary(self) -> str:
|
|
101
|
+
"""Get a summary of validation results."""
|
|
102
|
+
if self.is_valid and not self.warnings:
|
|
103
|
+
return "Template validation passed with no issues."
|
|
104
|
+
|
|
105
|
+
summary_parts = []
|
|
106
|
+
if self.critical_issues:
|
|
107
|
+
summary_parts.append(f"{len(self.critical_issues)} critical issues")
|
|
108
|
+
if self.errors:
|
|
109
|
+
summary_parts.append(f"{len(self.errors)} errors")
|
|
110
|
+
if self.warnings:
|
|
111
|
+
summary_parts.append(f"{len(self.warnings)} warnings")
|
|
112
|
+
|
|
113
|
+
return f"Template validation found: {', '.join(summary_parts)}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class PersonaValidator:
|
|
117
|
+
"""
|
|
118
|
+
Validates persona templates for integrity, compatibility, and correctness.
|
|
119
|
+
|
|
120
|
+
This class provides comprehensive validation of agent persona templates,
|
|
121
|
+
checking everything from basic structure to provider compatibility.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
template_manager: Optional[ConfigTemplateManager] = None,
|
|
127
|
+
provider_registry: Optional[ProviderRegistry] = None,
|
|
128
|
+
agent_config: Optional[AgentConfig] = None,
|
|
129
|
+
):
|
|
130
|
+
"""
|
|
131
|
+
Initialize the validator.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
template_manager: Configuration template manager
|
|
135
|
+
provider_registry: Provider registry for compatibility checks
|
|
136
|
+
agent_config: Agent configuration for validation context
|
|
137
|
+
"""
|
|
138
|
+
self.template_manager = template_manager or ConfigTemplateManager()
|
|
139
|
+
self.provider_registry = provider_registry or ProviderRegistry()
|
|
140
|
+
self.agent_config = agent_config
|
|
141
|
+
|
|
142
|
+
# Required fields for different validation levels
|
|
143
|
+
self.required_persona_fields = {
|
|
144
|
+
"id",
|
|
145
|
+
"name",
|
|
146
|
+
"description",
|
|
147
|
+
"category",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
self.required_configuration_fields = {
|
|
151
|
+
"template_id",
|
|
152
|
+
"primary_provider",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Cache for expensive validation operations
|
|
156
|
+
self._provider_cache: Dict[str, bool] = {}
|
|
157
|
+
self._model_cache: Dict[str, bool] = {}
|
|
158
|
+
|
|
159
|
+
def validate_persona(self, persona: AgentPersona) -> ValidationResult:
|
|
160
|
+
"""
|
|
161
|
+
Validate a complete persona instance.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
persona: The persona to validate
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
ValidationResult with all found issues
|
|
168
|
+
"""
|
|
169
|
+
result = ValidationResult(is_valid=True)
|
|
170
|
+
|
|
171
|
+
# Validate basic persona structure
|
|
172
|
+
self._validate_persona_structure(persona, result)
|
|
173
|
+
|
|
174
|
+
# Validate persona configuration
|
|
175
|
+
if persona.configuration:
|
|
176
|
+
self._validate_persona_configuration(persona.configuration, result)
|
|
177
|
+
|
|
178
|
+
# Validate template compatibility
|
|
179
|
+
if persona.configuration.template_id:
|
|
180
|
+
self._validate_template_compatibility(
|
|
181
|
+
persona.configuration.template_id,
|
|
182
|
+
persona.configuration,
|
|
183
|
+
result,
|
|
184
|
+
)
|
|
185
|
+
else:
|
|
186
|
+
result.add_issue(
|
|
187
|
+
ValidationIssue(
|
|
188
|
+
severity=ValidationSeverity.ERROR,
|
|
189
|
+
category=ValidationCategory.REQUIRED_FIELDS,
|
|
190
|
+
message="Persona configuration is missing",
|
|
191
|
+
field_path="configuration",
|
|
192
|
+
suggestion="Ensure persona has a valid configuration object",
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Validate capabilities
|
|
197
|
+
self._validate_capabilities(persona.capabilities, result)
|
|
198
|
+
|
|
199
|
+
# Validate provider compatibility
|
|
200
|
+
if persona.configuration:
|
|
201
|
+
self._validate_provider_compatibility(persona.configuration, result)
|
|
202
|
+
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
def validate_template(self, template_id: str) -> ValidationResult:
|
|
206
|
+
"""
|
|
207
|
+
Validate a configuration template.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
template_id: ID of the template to validate
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
ValidationResult with all found issues
|
|
214
|
+
"""
|
|
215
|
+
result = ValidationResult(is_valid=True)
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
template = self.template_manager.get_template(template_id)
|
|
219
|
+
if not template:
|
|
220
|
+
result.add_issue(
|
|
221
|
+
ValidationIssue(
|
|
222
|
+
severity=ValidationSeverity.CRITICAL,
|
|
223
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
224
|
+
message=f"Template '{template_id}' not found",
|
|
225
|
+
suggestion="Check that the template ID is correct and the template exists",
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
return result
|
|
229
|
+
|
|
230
|
+
# Validate template structure
|
|
231
|
+
self._validate_template_structure(template, result)
|
|
232
|
+
|
|
233
|
+
# Validate provider configurations
|
|
234
|
+
self._validate_template_providers(template, result)
|
|
235
|
+
|
|
236
|
+
# Validate recommended models
|
|
237
|
+
self._validate_template_models(template, result)
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
result.add_issue(
|
|
241
|
+
ValidationIssue(
|
|
242
|
+
severity=ValidationSeverity.CRITICAL,
|
|
243
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
244
|
+
message=f"Failed to load template '{template_id}': {str(e)}",
|
|
245
|
+
suggestion="Check template file for syntax errors or corruption",
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return result
|
|
250
|
+
|
|
251
|
+
def validate_configuration(self, config: PersonaConfiguration) -> ValidationResult:
|
|
252
|
+
"""
|
|
253
|
+
Validate a persona configuration.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
config: The configuration to validate
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
ValidationResult with all found issues
|
|
260
|
+
"""
|
|
261
|
+
result = ValidationResult(is_valid=True)
|
|
262
|
+
self._validate_persona_configuration(config, result)
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
def _validate_persona_structure(
|
|
266
|
+
self, persona: AgentPersona, result: ValidationResult
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Validate basic persona structure."""
|
|
269
|
+
for field in self.required_persona_fields:
|
|
270
|
+
if not hasattr(persona, field) or not getattr(persona, field):
|
|
271
|
+
result.add_issue(
|
|
272
|
+
ValidationIssue(
|
|
273
|
+
severity=ValidationSeverity.ERROR,
|
|
274
|
+
category=ValidationCategory.REQUIRED_FIELDS,
|
|
275
|
+
message=f"Required persona field '{field}' is missing or empty",
|
|
276
|
+
field_path=field,
|
|
277
|
+
suggestion=f"Provide a valid value for the '{field}' field",
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Validate ID format
|
|
282
|
+
if hasattr(persona, "id") and persona.id:
|
|
283
|
+
if not persona.id.isidentifier():
|
|
284
|
+
result.add_issue(
|
|
285
|
+
ValidationIssue(
|
|
286
|
+
severity=ValidationSeverity.ERROR,
|
|
287
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
288
|
+
message=f"Persona ID '{persona.id}' is not a valid identifier",
|
|
289
|
+
field_path="id",
|
|
290
|
+
suggestion="Use only letters, numbers, and underscores for persona IDs",
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Validate category
|
|
295
|
+
if hasattr(persona, "category") and persona.category:
|
|
296
|
+
if not isinstance(persona.category, PersonaCategory):
|
|
297
|
+
result.add_issue(
|
|
298
|
+
ValidationIssue(
|
|
299
|
+
severity=ValidationSeverity.WARNING,
|
|
300
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
301
|
+
message=f"Persona category should be a PersonaCategory enum",
|
|
302
|
+
field_path="category",
|
|
303
|
+
suggestion="Use PersonaCategory enum values",
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def _validate_persona_configuration(
|
|
308
|
+
self, config: PersonaConfiguration, result: ValidationResult
|
|
309
|
+
) -> None:
|
|
310
|
+
"""Validate persona configuration."""
|
|
311
|
+
for field in self.required_configuration_fields:
|
|
312
|
+
if not hasattr(config, field) or not getattr(config, field):
|
|
313
|
+
result.add_issue(
|
|
314
|
+
ValidationIssue(
|
|
315
|
+
severity=ValidationSeverity.ERROR,
|
|
316
|
+
category=ValidationCategory.REQUIRED_FIELDS,
|
|
317
|
+
message=f"Required configuration field '{field}' is missing or empty",
|
|
318
|
+
field_path=f"configuration.{field}",
|
|
319
|
+
suggestion=f"Provide a valid value for the '{field}' field",
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Validate numeric constraints
|
|
324
|
+
if (
|
|
325
|
+
hasattr(config, "temperature_override")
|
|
326
|
+
and config.temperature_override is not None
|
|
327
|
+
):
|
|
328
|
+
if not 0.0 <= config.temperature_override <= 2.0:
|
|
329
|
+
result.add_issue(
|
|
330
|
+
ValidationIssue(
|
|
331
|
+
severity=ValidationSeverity.ERROR,
|
|
332
|
+
category=ValidationCategory.CONFIGURATION_CONSTRAINTS,
|
|
333
|
+
message=f"Temperature override {config.temperature_override} is outside valid range (0.0-2.0)",
|
|
334
|
+
field_path="configuration.temperature_override",
|
|
335
|
+
suggestion="Set temperature between 0.0 and 2.0",
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if (
|
|
340
|
+
hasattr(config, "max_tokens_override")
|
|
341
|
+
and config.max_tokens_override is not None
|
|
342
|
+
):
|
|
343
|
+
if config.max_tokens_override <= 0:
|
|
344
|
+
result.add_issue(
|
|
345
|
+
ValidationIssue(
|
|
346
|
+
severity=ValidationSeverity.ERROR,
|
|
347
|
+
category=ValidationCategory.CONFIGURATION_CONSTRAINTS,
|
|
348
|
+
message=f"Max tokens override {config.max_tokens_override} must be positive",
|
|
349
|
+
field_path="configuration.max_tokens_override",
|
|
350
|
+
suggestion="Set max tokens to a positive integer",
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if hasattr(config, "timeout_override") and config.timeout_override is not None:
|
|
355
|
+
if config.timeout_override <= 0:
|
|
356
|
+
result.add_issue(
|
|
357
|
+
ValidationIssue(
|
|
358
|
+
severity=ValidationSeverity.ERROR,
|
|
359
|
+
category=ValidationCategory.CONFIGURATION_CONSTRAINTS,
|
|
360
|
+
message=f"Timeout override {config.timeout_override} must be positive",
|
|
361
|
+
field_path="configuration.timeout_override",
|
|
362
|
+
suggestion="Set timeout to a positive number of seconds",
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def _validate_template_compatibility(
|
|
367
|
+
self,
|
|
368
|
+
template_id: str,
|
|
369
|
+
config: PersonaConfiguration,
|
|
370
|
+
result: ValidationResult,
|
|
371
|
+
) -> None:
|
|
372
|
+
"""Validate that configuration is compatible with its template."""
|
|
373
|
+
try:
|
|
374
|
+
template = self.template_manager.get_template(template_id)
|
|
375
|
+
if not template:
|
|
376
|
+
result.add_issue(
|
|
377
|
+
ValidationIssue(
|
|
378
|
+
severity=ValidationSeverity.ERROR,
|
|
379
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
380
|
+
message=f"Referenced template '{template_id}' not found",
|
|
381
|
+
field_path="configuration.template_id",
|
|
382
|
+
suggestion="Check that the template ID is correct",
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Check if primary provider exists in template
|
|
388
|
+
if config.primary_provider not in template.provider_configs:
|
|
389
|
+
result.add_issue(
|
|
390
|
+
ValidationIssue(
|
|
391
|
+
severity=ValidationSeverity.ERROR,
|
|
392
|
+
category=ValidationCategory.PROVIDER_COMPATIBILITY,
|
|
393
|
+
message=f"Primary provider '{config.primary_provider}' not available in template '{template_id}'",
|
|
394
|
+
field_path="configuration.primary_provider",
|
|
395
|
+
suggestion=f"Use one of: {', '.join(template.provider_configs.keys())}",
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Check fallback providers
|
|
400
|
+
if hasattr(config, "fallback_providers") and config.fallback_providers:
|
|
401
|
+
for provider in config.fallback_providers:
|
|
402
|
+
if provider not in template.provider_configs:
|
|
403
|
+
result.add_issue(
|
|
404
|
+
ValidationIssue(
|
|
405
|
+
severity=ValidationSeverity.WARNING,
|
|
406
|
+
category=ValidationCategory.PROVIDER_COMPATIBILITY,
|
|
407
|
+
message=f"Fallback provider '{provider}' not available in template '{template_id}'",
|
|
408
|
+
field_path="configuration.fallback_providers",
|
|
409
|
+
suggestion=f"Remove '{provider}' or use one of: {', '.join(template.provider_configs.keys())}",
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
result.add_issue(
|
|
415
|
+
ValidationIssue(
|
|
416
|
+
severity=ValidationSeverity.WARNING,
|
|
417
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
418
|
+
message=f"Could not validate template compatibility: {str(e)}",
|
|
419
|
+
field_path="configuration.template_id",
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def _validate_capabilities(
|
|
424
|
+
self, capabilities: Set[PersonaCapability], result: ValidationResult
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Validate persona capabilities."""
|
|
427
|
+
if not capabilities:
|
|
428
|
+
result.add_issue(
|
|
429
|
+
ValidationIssue(
|
|
430
|
+
severity=ValidationSeverity.WARNING,
|
|
431
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
432
|
+
message="Persona has no defined capabilities",
|
|
433
|
+
field_path="capabilities",
|
|
434
|
+
suggestion="Define at least one capability for the persona",
|
|
435
|
+
)
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Check for conflicting capabilities
|
|
439
|
+
conflicting_pairs = [
|
|
440
|
+
(PersonaCapability.FAST_RESPONSE, PersonaCapability.LARGE_CONTEXT),
|
|
441
|
+
(
|
|
442
|
+
PersonaCapability.COST_EFFICIENT,
|
|
443
|
+
PersonaCapability.HIGH_TEMPERATURE,
|
|
444
|
+
),
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
for cap1, cap2 in conflicting_pairs:
|
|
448
|
+
if cap1 in capabilities and cap2 in capabilities:
|
|
449
|
+
result.add_issue(
|
|
450
|
+
ValidationIssue(
|
|
451
|
+
severity=ValidationSeverity.WARNING,
|
|
452
|
+
category=ValidationCategory.CONFIGURATION_CONSTRAINTS,
|
|
453
|
+
message=f"Conflicting capabilities: {cap1.value} and {cap2.value}",
|
|
454
|
+
field_path="capabilities",
|
|
455
|
+
suggestion=f"Consider removing one of the conflicting capabilities",
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
def _validate_provider_compatibility(
|
|
460
|
+
self, config: PersonaConfiguration, result: ValidationResult
|
|
461
|
+
) -> None:
|
|
462
|
+
"""Validate provider compatibility."""
|
|
463
|
+
# Check if primary provider is available
|
|
464
|
+
if not self._is_provider_available(config.primary_provider):
|
|
465
|
+
result.add_issue(
|
|
466
|
+
ValidationIssue(
|
|
467
|
+
severity=ValidationSeverity.ERROR,
|
|
468
|
+
category=ValidationCategory.PROVIDER_COMPATIBILITY,
|
|
469
|
+
message=f"Primary provider '{config.primary_provider}' is not available",
|
|
470
|
+
field_path="configuration.primary_provider",
|
|
471
|
+
suggestion="Check provider installation and API key configuration",
|
|
472
|
+
fix_action=f"install_{config.primary_provider}_provider",
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# Check fallback providers
|
|
477
|
+
if hasattr(config, "fallback_providers") and config.fallback_providers:
|
|
478
|
+
unavailable_providers = []
|
|
479
|
+
for provider in config.fallback_providers:
|
|
480
|
+
if not self._is_provider_available(provider):
|
|
481
|
+
unavailable_providers.append(provider)
|
|
482
|
+
|
|
483
|
+
if unavailable_providers:
|
|
484
|
+
result.add_issue(
|
|
485
|
+
ValidationIssue(
|
|
486
|
+
severity=ValidationSeverity.WARNING,
|
|
487
|
+
category=ValidationCategory.PROVIDER_COMPATIBILITY,
|
|
488
|
+
message=f"Fallback providers not available: {', '.join(unavailable_providers)}",
|
|
489
|
+
field_path="configuration.fallback_providers",
|
|
490
|
+
suggestion="Remove unavailable providers or install/configure them",
|
|
491
|
+
details={"unavailable_providers": unavailable_providers},
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
def _validate_template_structure(
|
|
496
|
+
self, template: ConfigTemplate, result: ValidationResult
|
|
497
|
+
) -> None:
|
|
498
|
+
"""Validate template structure."""
|
|
499
|
+
if not template.provider_configs:
|
|
500
|
+
result.add_issue(
|
|
501
|
+
ValidationIssue(
|
|
502
|
+
severity=ValidationSeverity.CRITICAL,
|
|
503
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
504
|
+
message="Template has no provider configurations",
|
|
505
|
+
suggestion="Add at least one provider configuration to the template",
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
def _validate_template_providers(
|
|
510
|
+
self, template: ConfigTemplate, result: ValidationResult
|
|
511
|
+
) -> None:
|
|
512
|
+
"""Validate template provider configurations."""
|
|
513
|
+
for provider_id, provider_config in template.provider_configs.items():
|
|
514
|
+
if not isinstance(provider_config, dict):
|
|
515
|
+
result.add_issue(
|
|
516
|
+
ValidationIssue(
|
|
517
|
+
severity=ValidationSeverity.ERROR,
|
|
518
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
519
|
+
message=f"Provider config for '{provider_id}' is not a dictionary",
|
|
520
|
+
field_path=f"provider_configs.{provider_id}",
|
|
521
|
+
suggestion="Ensure provider configuration is a valid dictionary",
|
|
522
|
+
)
|
|
523
|
+
)
|
|
524
|
+
continue
|
|
525
|
+
|
|
526
|
+
# Check for required provider config fields
|
|
527
|
+
required_fields = ["temperature", "max_tokens"]
|
|
528
|
+
for field in required_fields:
|
|
529
|
+
if field not in provider_config:
|
|
530
|
+
result.add_issue(
|
|
531
|
+
ValidationIssue(
|
|
532
|
+
severity=ValidationSeverity.WARNING,
|
|
533
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
534
|
+
message=f"Provider '{provider_id}' missing recommended field '{field}'",
|
|
535
|
+
field_path=f"provider_configs.{provider_id}.{field}",
|
|
536
|
+
suggestion=f"Add '{field}' to provider configuration",
|
|
537
|
+
)
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
def _validate_template_models(
|
|
541
|
+
self, template: ConfigTemplate, result: ValidationResult
|
|
542
|
+
) -> None:
|
|
543
|
+
"""Validate template recommended models."""
|
|
544
|
+
if (
|
|
545
|
+
not hasattr(template, "recommended_models")
|
|
546
|
+
or not template.recommended_models
|
|
547
|
+
):
|
|
548
|
+
result.add_issue(
|
|
549
|
+
ValidationIssue(
|
|
550
|
+
severity=ValidationSeverity.WARNING,
|
|
551
|
+
category=ValidationCategory.TEMPLATE_STRUCTURE,
|
|
552
|
+
message="Template has no recommended models",
|
|
553
|
+
field_path="recommended_models",
|
|
554
|
+
suggestion="Add recommended models for better user experience",
|
|
555
|
+
)
|
|
556
|
+
)
|
|
557
|
+
return
|
|
558
|
+
|
|
559
|
+
for provider, model in template.recommended_models.items():
|
|
560
|
+
if not self._is_model_available(provider, model):
|
|
561
|
+
result.add_issue(
|
|
562
|
+
ValidationIssue(
|
|
563
|
+
severity=ValidationSeverity.WARNING,
|
|
564
|
+
category=ValidationCategory.MODEL_AVAILABILITY,
|
|
565
|
+
message=f"Recommended model '{model}' for provider '{provider}' may not be available",
|
|
566
|
+
field_path=f"recommended_models.{provider}",
|
|
567
|
+
suggestion="Verify model availability or update recommendation",
|
|
568
|
+
)
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
def _is_provider_available(self, provider_id: str) -> bool:
|
|
572
|
+
"""Check if a provider is available."""
|
|
573
|
+
if provider_id in self._provider_cache:
|
|
574
|
+
return self._provider_cache[provider_id]
|
|
575
|
+
|
|
576
|
+
try:
|
|
577
|
+
# Check if provider is registered
|
|
578
|
+
provider_info = self.provider_registry.get_provider_info(provider_id)
|
|
579
|
+
available = provider_info is not None
|
|
580
|
+
|
|
581
|
+
# Additional checks could be added here (API keys, installation, etc.)
|
|
582
|
+
|
|
583
|
+
self._provider_cache[provider_id] = available
|
|
584
|
+
return available
|
|
585
|
+
except Exception:
|
|
586
|
+
self._provider_cache[provider_id] = False
|
|
587
|
+
return False
|
|
588
|
+
|
|
589
|
+
def _is_model_available(self, provider_id: str, model_id: str) -> bool:
|
|
590
|
+
"""Check if a model is available for a provider."""
|
|
591
|
+
cache_key = f"{provider_id}:{model_id}"
|
|
592
|
+
if cache_key in self._model_cache:
|
|
593
|
+
return self._model_cache[cache_key]
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
# This would need integration with actual model availability checking
|
|
597
|
+
# For now, assume basic models are available
|
|
598
|
+
basic_models = {
|
|
599
|
+
"claude": [
|
|
600
|
+
"claude-3-sonnet-20240229",
|
|
601
|
+
"claude-3-haiku-20240307",
|
|
602
|
+
],
|
|
603
|
+
"openai": ["gpt-4", "gpt-3.5-turbo"],
|
|
604
|
+
"gemini": ["gemini-pro"],
|
|
605
|
+
"perplexity": ["pplx-7b-online", "pplx-70b-online"],
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
available = (
|
|
609
|
+
provider_id in basic_models and model_id in basic_models[provider_id]
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
self._model_cache[cache_key] = available
|
|
613
|
+
return available
|
|
614
|
+
except Exception:
|
|
615
|
+
self._model_cache[cache_key] = False
|
|
616
|
+
return False
|
|
617
|
+
|
|
618
|
+
def clear_cache(self) -> None:
|
|
619
|
+
"""Clear validation caches."""
|
|
620
|
+
self._provider_cache.clear()
|
|
621
|
+
self._model_cache.clear()
|
|
622
|
+
|
|
623
|
+
def get_validation_report(self, result: ValidationResult) -> str:
|
|
624
|
+
"""Generate a formatted validation report."""
|
|
625
|
+
if not result.issues:
|
|
626
|
+
return "✅ Validation passed with no issues."
|
|
627
|
+
|
|
628
|
+
report_lines = [f"📋 Validation Report: {result.get_summary()}", ""]
|
|
629
|
+
|
|
630
|
+
if result.critical_issues:
|
|
631
|
+
report_lines.append("🚨 Critical Issues:")
|
|
632
|
+
for issue in result.critical_issues:
|
|
633
|
+
report_lines.append(f" • {issue.message}")
|
|
634
|
+
if issue.suggestion:
|
|
635
|
+
report_lines.append(f" 💡 {issue.suggestion}")
|
|
636
|
+
report_lines.append("")
|
|
637
|
+
|
|
638
|
+
if result.errors:
|
|
639
|
+
report_lines.append("❌ Errors:")
|
|
640
|
+
for issue in result.errors:
|
|
641
|
+
report_lines.append(f" • {issue.message}")
|
|
642
|
+
if issue.suggestion:
|
|
643
|
+
report_lines.append(f" 💡 {issue.suggestion}")
|
|
644
|
+
report_lines.append("")
|
|
645
|
+
|
|
646
|
+
if result.warnings:
|
|
647
|
+
report_lines.append("⚠️ Warnings:")
|
|
648
|
+
for issue in result.warnings:
|
|
649
|
+
report_lines.append(f" • {issue.message}")
|
|
650
|
+
if issue.suggestion:
|
|
651
|
+
report_lines.append(f" 💡 {issue.suggestion}")
|
|
652
|
+
report_lines.append("")
|
|
653
|
+
|
|
654
|
+
return "\n".join(report_lines)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
# Global validator instance
|
|
658
|
+
_global_validator: Optional[PersonaValidator] = None
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def get_persona_validator() -> PersonaValidator:
|
|
662
|
+
"""Get the global persona validator instance."""
|
|
663
|
+
global _global_validator
|
|
664
|
+
if _global_validator is None:
|
|
665
|
+
_global_validator = PersonaValidator()
|
|
666
|
+
return _global_validator
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def set_persona_validator(validator: PersonaValidator) -> None:
|
|
670
|
+
"""Set the global persona validator instance."""
|
|
671
|
+
global _global_validator
|
|
672
|
+
_global_validator = validator
|