Amsha 3.0.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.
- amsha/__init__.py +0 -0
- amsha/common/__init__.py +4 -0
- amsha/common/logger.py +273 -0
- amsha/configuration/application/configuration_manager.py +57 -0
- amsha/configuration/domain/models/amsha_app_config.py +9 -0
- amsha/configuration/domain/models/amsha_job_config.py +35 -0
- amsha/configuration/domain/models/amsha_llm_config.py +38 -0
- amsha/configuration/exceptions/amsha_configuration_exception.py +16 -0
- amsha/configuration/infrastructure/strict_validator.py +50 -0
- amsha/crew_forge/__init__.py +4 -0
- amsha/crew_forge/dependency/__init__.py +0 -0
- amsha/crew_forge/domain/__init__.py +0 -0
- amsha/crew_forge/domain/enum/__init__.py +0 -0
- amsha/crew_forge/domain/models/__init__.py +0 -0
- amsha/crew_forge/domain/models/agent_data.py +34 -0
- amsha/crew_forge/domain/models/crew_data.py +30 -0
- amsha/crew_forge/domain/models/mcp_data.py +49 -0
- amsha/crew_forge/domain/models/task_data.py +22 -0
- amsha/crew_forge/exceptions/__init__.py +24 -0
- amsha/crew_forge/exceptions/crew_configuration_exception.py +39 -0
- amsha/crew_forge/exceptions/crew_execution_exception.py +38 -0
- amsha/crew_forge/exceptions/crew_forge_exception.py +37 -0
- amsha/crew_forge/exceptions/crew_manager_exception.py +38 -0
- amsha/crew_forge/exceptions/error_context.py +206 -0
- amsha/crew_forge/exceptions/input_preparation_exception.py +38 -0
- amsha/crew_forge/knowledge/__init__.py +0 -0
- amsha/crew_forge/knowledge/amsha_crew_docling_source.py +136 -0
- amsha/crew_forge/knowledge/amsha_json_knowledge_source.py +56 -0
- amsha/crew_forge/orchestrator/__init__.py +0 -0
- amsha/crew_forge/orchestrator/file/__init__.py +0 -0
- amsha/crew_forge/orchestrator/file/amsha_crew_file_application.py +223 -0
- amsha/crew_forge/orchestrator/file/atomic_crew_file_manager.py +128 -0
- amsha/crew_forge/orchestrator/file/file_crew_orchestrator.py +153 -0
- amsha/crew_forge/orchestrator/flow/__init__.py +3 -0
- amsha/crew_forge/orchestrator/flow/flow_crew_pipeline.py +134 -0
- amsha/crew_forge/protocols/__init__.py +0 -0
- amsha/crew_forge/protocols/crew_application.py +99 -0
- amsha/crew_forge/protocols/crew_manager.py +69 -0
- amsha/crew_forge/protocols/crew_orchestrator.py +72 -0
- amsha/crew_forge/protocols/input_preparation_service.py +61 -0
- amsha/crew_forge/seeding/__init__.py +0 -0
- amsha/crew_forge/seeding/parser/__init__.py +0 -0
- amsha/crew_forge/seeding/parser/crew_parser.py +48 -0
- amsha/crew_forge/service/__init__.py +0 -0
- amsha/crew_forge/service/atomic_yaml_builder.py +77 -0
- amsha/crew_forge/service/base_crew_orchestrator.py +322 -0
- amsha/crew_forge/service/crew_builder_service.py +247 -0
- amsha/crew_forge/service/shared_input_preparation_service.py +203 -0
- amsha/crew_forge/service/shared_json_file_service.py +179 -0
- amsha/crew_forge/service/shared_llm_initialization_service.py +159 -0
- amsha/crew_forge/service/tool_registry.py +58 -0
- amsha/crew_monitor/__init__.py +13 -0
- amsha/crew_monitor/service/__init__.py +0 -0
- amsha/crew_monitor/service/amsha_event_listener.py +238 -0
- amsha/crew_monitor/service/contribution_analyzer.py +109 -0
- amsha/crew_monitor/service/crew_performance_monitor.py +165 -0
- amsha/crew_monitor/service/reporting_tool.py +164 -0
- amsha/execution_runtime/__init__.py +0 -0
- amsha/execution_runtime/domain/__init__.py +2 -0
- amsha/execution_runtime/domain/execution_handle.py +29 -0
- amsha/execution_runtime/domain/execution_mode.py +9 -0
- amsha/execution_runtime/service/__init__.py +1 -0
- amsha/execution_runtime/service/runtime_engine.py +89 -0
- amsha/execution_state/__init__.py +0 -0
- amsha/execution_state/domain/__init__.py +2 -0
- amsha/execution_state/domain/enums.py +12 -0
- amsha/execution_state/domain/execution_state.py +60 -0
- amsha/execution_state/service/__init__.py +1 -0
- amsha/execution_state/service/state_manager.py +67 -0
- amsha/llm_factory/__init__.py +0 -0
- amsha/llm_factory/adapters/__init__.py +0 -0
- amsha/llm_factory/adapters/crewai_adapter.py +22 -0
- amsha/llm_factory/adapters/lmstudio_lifecycle_client.py +125 -0
- amsha/llm_factory/dependency/__init__.py +0 -0
- amsha/llm_factory/dependency/llm_container.py +33 -0
- amsha/llm_factory/domain/__init__.py +0 -0
- amsha/llm_factory/domain/llm_type.py +5 -0
- amsha/llm_factory/domain/model/__init__.py +0 -0
- amsha/llm_factory/domain/model/llm_build_result.py +17 -0
- amsha/llm_factory/domain/model/llm_model_capabilities.py +15 -0
- amsha/llm_factory/domain/model/llm_model_config.py +17 -0
- amsha/llm_factory/domain/model/llm_output_config.py +19 -0
- amsha/llm_factory/domain/model/llm_parameters.py +12 -0
- amsha/llm_factory/domain/model/llm_type.py +7 -0
- amsha/llm_factory/domain/model/llm_use_case_config.py +9 -0
- amsha/llm_factory/domain/model/lmstudio_lifecycle_config.py +19 -0
- amsha/llm_factory/domain/provider_protocol.py +21 -0
- amsha/llm_factory/exceptions.py +2 -0
- amsha/llm_factory/service/__init__.py +0 -0
- amsha/llm_factory/service/llm_builder.py +75 -0
- amsha/llm_factory/settings/__init__.py +0 -0
- amsha/llm_factory/settings/llm_settings.py +62 -0
- amsha/llm_factory/utils/__init__.py +0 -0
- amsha/llm_factory/utils/deprecated_compat.py +60 -0
- amsha/llm_factory/utils/llm_utils.py +35 -0
- amsha/output_process/__init__.py +0 -0
- amsha/output_process/evaluation/__init__.py +0 -0
- amsha/output_process/evaluation/evaluation_aggregate_tool.py +0 -0
- amsha/output_process/evaluation/evaluation_processing_tool.py +0 -0
- amsha/output_process/evaluation/evaluation_report_tool.py +0 -0
- amsha/output_process/optimization/__init__.py +0 -0
- amsha/output_process/optimization/json_cleaner_utils.py +182 -0
- amsha/output_process/validation/__init__.py +0 -0
- amsha/output_process/validation/crew_validator.py +0 -0
- amsha/output_process/validation/json_output_validator.py +0 -0
- amsha/utils/__init__.py +0 -0
- amsha/utils/json_utils.py +50 -0
- amsha/utils/utf8_utils.py +51 -0
- amsha/utils/yaml_utils.py +19 -0
- amsha-3.0.0.dist-info/METADATA +173 -0
- amsha-3.0.0.dist-info/RECORD +114 -0
- amsha-3.0.0.dist-info/WHEEL +5 -0
- amsha-3.0.0.dist-info/licenses/LICENSE +21 -0
- amsha-3.0.0.dist-info/top_level.txt +1 -0
amsha/__init__.py
ADDED
|
File without changes
|
amsha/common/__init__.py
ADDED
amsha/common/logger.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive logging for Amsha (stdlib-only).
|
|
3
|
+
|
|
4
|
+
This module provides structured logging with metrics tracking, execution tracing,
|
|
5
|
+
and performance monitoring - going beyond simple print replacement.
|
|
6
|
+
|
|
7
|
+
Basic Usage:
|
|
8
|
+
from amsha.common.logger import get_logger, log_execution, MetricsLogger
|
|
9
|
+
|
|
10
|
+
logger = get_logger("crew_forge")
|
|
11
|
+
logger.info("Operation successful", extra={"crew_name": "test", "tokens": 1500})
|
|
12
|
+
|
|
13
|
+
@log_execution(logger, "build_crew")
|
|
14
|
+
def build_crew(name):
|
|
15
|
+
# Automatically logs start, duration, and completion
|
|
16
|
+
pass
|
|
17
|
+
"""
|
|
18
|
+
from typing import Optional, Dict, Any, Callable
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
import time
|
|
22
|
+
import functools
|
|
23
|
+
from logging.handlers import RotatingFileHandler
|
|
24
|
+
|
|
25
|
+
_configured = False
|
|
26
|
+
_module_loggers: dict = {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# Structured Logging Formatter
|
|
31
|
+
# ============================================================================
|
|
32
|
+
|
|
33
|
+
class StructuredFormatter(logging.Formatter):
|
|
34
|
+
"""
|
|
35
|
+
Custom formatter that includes extra fields in log output.
|
|
36
|
+
|
|
37
|
+
Formats log records to show structured metadata in a readable way:
|
|
38
|
+
timestamp | logger_name | level | message | key1=value1 key2=value2
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# Standard logging record attributes to exclude from extra fields
|
|
42
|
+
EXCLUDED_ATTRS = {
|
|
43
|
+
'name', 'msg', 'args', 'created', 'filename', 'funcName', 'levelname',
|
|
44
|
+
'levelno', 'lineno', 'module', 'msecs', 'message', 'pathname', 'process',
|
|
45
|
+
'processName', 'relativeCreated', 'thread', 'threadName', 'exc_info',
|
|
46
|
+
'exc_text', 'stack_info', 'asctime', 'taskName'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
50
|
+
# Format base message with standard format
|
|
51
|
+
base_message = super().format(record)
|
|
52
|
+
|
|
53
|
+
# Extract extra fields
|
|
54
|
+
extra_fields = {}
|
|
55
|
+
for key, value in record.__dict__.items():
|
|
56
|
+
if key not in self.EXCLUDED_ATTRS:
|
|
57
|
+
extra_fields[key] = value
|
|
58
|
+
|
|
59
|
+
# If there are extra fields, append them to the message
|
|
60
|
+
if extra_fields:
|
|
61
|
+
# Format extra fields as key=value pairs
|
|
62
|
+
extra_str = " | ".join(f"{k}={v}" for k, v in sorted(extra_fields.items()))
|
|
63
|
+
return f"{base_message} | {extra_str}"
|
|
64
|
+
|
|
65
|
+
return base_message
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _configure() -> None:
|
|
69
|
+
"""
|
|
70
|
+
Configure the root Amsha logger with a rotating file handler and console handler.
|
|
71
|
+
Idempotent - only runs once per process.
|
|
72
|
+
"""
|
|
73
|
+
global _configured
|
|
74
|
+
if _configured:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
level = os.getenv("AMSHA_LOG_LEVEL", "INFO")
|
|
78
|
+
root = logging.getLogger("Amsha")
|
|
79
|
+
root.setLevel(level.upper())
|
|
80
|
+
root.propagate = False
|
|
81
|
+
|
|
82
|
+
formatter = StructuredFormatter(
|
|
83
|
+
fmt='%(asctime)s | %(name)s | %(levelname)s | %(message)s',
|
|
84
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
logs_dir = os.getenv("AMSHA_LOG_DIR", "logs")
|
|
88
|
+
os.makedirs(logs_dir, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
file_handler = RotatingFileHandler(
|
|
91
|
+
os.path.join(logs_dir, "amsha.log"),
|
|
92
|
+
maxBytes=50 * 1024 * 1024,
|
|
93
|
+
backupCount=30,
|
|
94
|
+
encoding="utf-8",
|
|
95
|
+
)
|
|
96
|
+
file_handler.setFormatter(formatter)
|
|
97
|
+
root.addHandler(file_handler)
|
|
98
|
+
|
|
99
|
+
# Only add a console handler if one isn't already attached (avoids duplicate
|
|
100
|
+
# console output when the root handler is configured elsewhere).
|
|
101
|
+
if not any(isinstance(h, logging.StreamHandler) for h in root.handlers):
|
|
102
|
+
console = logging.StreamHandler()
|
|
103
|
+
console.setFormatter(formatter)
|
|
104
|
+
root.addHandler(console)
|
|
105
|
+
|
|
106
|
+
_configured = True
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_logger(module_name: Optional[str] = None, log_level: Optional[str] = None) -> logging.Logger:
|
|
110
|
+
"""
|
|
111
|
+
Get or create a logger instance for Amsha.
|
|
112
|
+
|
|
113
|
+
All logs are written to logs/amsha.log and console (path overridable via
|
|
114
|
+
the AMSHA_LOG_DIR environment variable).
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
module_name: Optional module name for hierarchical logging (e.g., "crew_forge")
|
|
118
|
+
log_level: Optional log level override (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
119
|
+
Defaults to environment variable AMSHA_LOG_LEVEL or INFO
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Configured logger instance
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
>>> logger = get_logger() # Root Amsha logger
|
|
126
|
+
>>> logger.info("Application started")
|
|
127
|
+
|
|
128
|
+
>>> crew_logger = get_logger("crew_forge")
|
|
129
|
+
>>> crew_logger.debug("Building crew", extra={"crew_name": "test", "tokens": 1500})
|
|
130
|
+
"""
|
|
131
|
+
global _module_loggers
|
|
132
|
+
|
|
133
|
+
_configure()
|
|
134
|
+
|
|
135
|
+
logger = logging.getLogger(f"Amsha.{module_name}" if module_name else "Amsha")
|
|
136
|
+
|
|
137
|
+
if log_level:
|
|
138
|
+
logger.setLevel(log_level.upper())
|
|
139
|
+
|
|
140
|
+
# Cache module loggers to avoid recreation
|
|
141
|
+
if module_name and module_name not in _module_loggers:
|
|
142
|
+
_module_loggers[module_name] = logger
|
|
143
|
+
|
|
144
|
+
return logger
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def log_execution(logger: logging.Logger, operation_name: str) -> Callable:
|
|
148
|
+
"""
|
|
149
|
+
Decorator to automatically log function execution with timing and error handling.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
logger: Logger instance to use
|
|
153
|
+
operation_name: Human-readable operation name
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Decorated function
|
|
157
|
+
|
|
158
|
+
Usage:
|
|
159
|
+
@log_execution(logger, "build_crew")
|
|
160
|
+
def build_atomic_crew(self, crew_name):
|
|
161
|
+
# Automatically logs start, duration, success/failure
|
|
162
|
+
return crew
|
|
163
|
+
"""
|
|
164
|
+
def decorator(func: Callable) -> Callable:
|
|
165
|
+
@functools.wraps(func)
|
|
166
|
+
def wrapper(*args, **kwargs):
|
|
167
|
+
start_time = time.time()
|
|
168
|
+
logger.debug(f"{operation_name} started", extra={
|
|
169
|
+
"operation": operation_name,
|
|
170
|
+
"function": func.__name__
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
result = func(*args, **kwargs)
|
|
175
|
+
duration = time.time() - start_time
|
|
176
|
+
|
|
177
|
+
logger.info(f"{operation_name} completed", extra={
|
|
178
|
+
"operation": operation_name,
|
|
179
|
+
"duration_seconds": round(duration, 4),
|
|
180
|
+
"status": "success"
|
|
181
|
+
})
|
|
182
|
+
return result
|
|
183
|
+
except Exception as e:
|
|
184
|
+
duration = time.time() - start_time
|
|
185
|
+
logger.error(f"{operation_name} failed", extra={
|
|
186
|
+
"operation": operation_name,
|
|
187
|
+
"duration_seconds": round(duration, 4),
|
|
188
|
+
"error_type": type(e).__name__,
|
|
189
|
+
"error_message": str(e),
|
|
190
|
+
"status": "failed"
|
|
191
|
+
}, exc_info=True)
|
|
192
|
+
raise
|
|
193
|
+
return wrapper
|
|
194
|
+
return decorator
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class MetricsLogger:
|
|
198
|
+
"""Helper class for logging structured metrics and performance data."""
|
|
199
|
+
|
|
200
|
+
def __init__(self, logger: logging.Logger):
|
|
201
|
+
self.logger = logger
|
|
202
|
+
|
|
203
|
+
def log_execution_metrics(self, crew_name: str, execution_id: str,
|
|
204
|
+
metrics: Dict[str, Any], duration: float):
|
|
205
|
+
"""
|
|
206
|
+
Log comprehensive execution metrics.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
crew_name: Name of the crew
|
|
210
|
+
execution_id: Unique execution identifier
|
|
211
|
+
metrics: Dict containing general and gpu metrics
|
|
212
|
+
duration: Execution duration in seconds
|
|
213
|
+
"""
|
|
214
|
+
general = metrics.get("general", {})
|
|
215
|
+
self.logger.info("Execution metrics captured", extra={
|
|
216
|
+
"crew_name": crew_name,
|
|
217
|
+
"execution_id": execution_id,
|
|
218
|
+
"duration_seconds": round(duration, 4),
|
|
219
|
+
"total_tokens": general.get("total_tokens", 0),
|
|
220
|
+
"prompt_tokens": general.get("prompt_tokens", 0),
|
|
221
|
+
"completion_tokens": general.get("completion_tokens", 0),
|
|
222
|
+
"cpu_usage_percent": general.get("cpu_usage_end_percent", 0),
|
|
223
|
+
"memory_change_mb": general.get("memory_usage_change_mb", 0),
|
|
224
|
+
"has_gpu_metrics": bool(metrics.get("gpu", {}))
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
def log_llm_config(self, model_name: str, config: Dict[str, Any]):
|
|
228
|
+
"""
|
|
229
|
+
Log LLM configuration details.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
model_name: Clean model name
|
|
233
|
+
config: Dict containing LLM configuration
|
|
234
|
+
"""
|
|
235
|
+
self.logger.info("LLM configuration applied", extra={
|
|
236
|
+
"model_name": model_name,
|
|
237
|
+
"model": config.get("model"),
|
|
238
|
+
"temperature": config.get("temperature"),
|
|
239
|
+
"top_p": config.get("top_p"),
|
|
240
|
+
"max_tokens": config.get("max_completion_tokens"),
|
|
241
|
+
"stream": config.get("stream", False),
|
|
242
|
+
"has_base_url": bool(config.get("base_url") or config.get("endpoint"))
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
def log_file_operation(self, operation: str, file_path: str,
|
|
246
|
+
status: str, **extra_data):
|
|
247
|
+
"""
|
|
248
|
+
Log file I/O operations with context.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
operation: Operation type (e.g., "validation", "save", "clean")
|
|
252
|
+
file_path: Path to the file
|
|
253
|
+
status: Operation status (success, failed)
|
|
254
|
+
**extra_data: Additional context
|
|
255
|
+
"""
|
|
256
|
+
self.logger.info(f"File operation: {operation}", extra={
|
|
257
|
+
"operation": operation,
|
|
258
|
+
"file_path": file_path,
|
|
259
|
+
"status": status,
|
|
260
|
+
**extra_data
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def reset_logger():
|
|
265
|
+
"""
|
|
266
|
+
Reset the logger configuration. Primarily for testing purposes.
|
|
267
|
+
"""
|
|
268
|
+
global _configured, _module_loggers
|
|
269
|
+
root = logging.getLogger("Amsha")
|
|
270
|
+
for handler in list(root.handlers):
|
|
271
|
+
root.removeHandler(handler)
|
|
272
|
+
_configured = False
|
|
273
|
+
_module_loggers = {}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Union, Dict, Any, Type, TypeVar
|
|
3
|
+
import json
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from amsha.configuration.infrastructure.strict_validator import StrictConfigValidator
|
|
8
|
+
from amsha.configuration.exceptions.amsha_configuration_exception import AmshaConfigurationException
|
|
9
|
+
|
|
10
|
+
T = TypeVar('T', bound=BaseModel)
|
|
11
|
+
|
|
12
|
+
class ConfigurationManager:
|
|
13
|
+
"""
|
|
14
|
+
Manages loading and strict validation of Amsha Configurations.
|
|
15
|
+
Requires configurations to be perfectly valid to proceed.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def load_from_dict(model_class: Type[T], data: Dict[str, Any], config_name: str = "Configuration") -> T:
|
|
20
|
+
"""
|
|
21
|
+
Load configuration from a dictionary strictly.
|
|
22
|
+
"""
|
|
23
|
+
validator = StrictConfigValidator()
|
|
24
|
+
# This will raise an AmshaConfigurationException if it fails
|
|
25
|
+
validated_model = validator.validate_strictly(model_class, data, config_name)
|
|
26
|
+
|
|
27
|
+
# Optionally log auditor traces
|
|
28
|
+
import logging
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
for log_entry in validator.audit_log:
|
|
31
|
+
logger.debug(log_entry)
|
|
32
|
+
|
|
33
|
+
return validated_model
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def load_from_json(model_class: Type[T], path: Union[str, Path], config_name: str = "Configuration") -> T:
|
|
37
|
+
"""Load configuration from a JSON file strictly."""
|
|
38
|
+
path = Path(path)
|
|
39
|
+
if not path.exists():
|
|
40
|
+
raise AmshaConfigurationException(f"Configuration file not found: {path}")
|
|
41
|
+
|
|
42
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
43
|
+
data = json.load(f)
|
|
44
|
+
|
|
45
|
+
return ConfigurationManager.load_from_dict(model_class, data, config_name)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def load_from_yaml(model_class: Type[T], path: Union[str, Path], config_name: str = "Configuration") -> T:
|
|
49
|
+
"""Load configuration from a YAML file strictly."""
|
|
50
|
+
path = Path(path)
|
|
51
|
+
if not path.exists():
|
|
52
|
+
raise AmshaConfigurationException(f"Configuration file not found: {path}")
|
|
53
|
+
|
|
54
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
55
|
+
data = yaml.safe_load(f)
|
|
56
|
+
|
|
57
|
+
return ConfigurationManager.load_from_dict(model_class, data, config_name)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
class AmshaAppConfig(BaseModel):
|
|
4
|
+
"""
|
|
5
|
+
Main Application Configuration for Amsha.
|
|
6
|
+
Requires strict validation with no silent default fallbacks for critical paths.
|
|
7
|
+
"""
|
|
8
|
+
domain_root_path: str = Field(..., description="Root path for domain configurations (e.g., crew_configs)")
|
|
9
|
+
output_dir_path: str = Field(..., description="Path to output directory")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import List, Dict, Union, Any, Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class CrewStep(BaseModel):
|
|
5
|
+
task_key: str = Field(..., description="Task reference key")
|
|
6
|
+
agent_key: str = Field(..., description="Agent reference key")
|
|
7
|
+
|
|
8
|
+
class CrewDefinition(BaseModel):
|
|
9
|
+
steps: List[CrewStep] = Field(..., description="List of steps executed by the crew")
|
|
10
|
+
knowledge_sources: Optional[List[str]] = Field(None, description="List of knowledge source paths")
|
|
11
|
+
input: Optional[Union[Dict[str, Any], List[Dict[str, Any]]]] = Field(None, description="Input definitions for the crew")
|
|
12
|
+
memory: bool = Field(
|
|
13
|
+
False,
|
|
14
|
+
description="Whether the crew uses CrewAI's unified memory",
|
|
15
|
+
)
|
|
16
|
+
checkpoint: Optional[Union[bool, Dict[str, Any]]] = Field(
|
|
17
|
+
None,
|
|
18
|
+
description=(
|
|
19
|
+
"Checkpoint configuration: a bool to enable/disable CrewAI checkpointing, "
|
|
20
|
+
"or a dict with keys enabled/on_events/provider/location/max_checkpoints."
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
class AmshaJobConfig(BaseModel):
|
|
25
|
+
"""
|
|
26
|
+
Job Orchestration Configuration.
|
|
27
|
+
Requires explicit validation of all structured job definitions.
|
|
28
|
+
"""
|
|
29
|
+
crew_name: str = Field(..., description="Name of the overall crew execution job")
|
|
30
|
+
usecase: str = Field(..., description="Usecase context for execution")
|
|
31
|
+
module_name: str = Field(..., description="Module name for categorization")
|
|
32
|
+
output_filepath: str = Field(..., description="File path indicating where final output is saved")
|
|
33
|
+
|
|
34
|
+
crews: Dict[str, CrewDefinition] = Field(..., description="Dictionary mapping crew identifiers to their definitions")
|
|
35
|
+
pipeline: List[str] = Field(..., description="Ordered list of crews defining execution sequence")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Dict, List, Any, Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class LLMModelDefinition(BaseModel):
|
|
5
|
+
base_url: Optional[str] = Field(default=None, description="Base URL of the LLM API endpoint")
|
|
6
|
+
model: str = Field(..., description="Model identifier string")
|
|
7
|
+
api_key: Optional[str] = Field(default=None, description="API key")
|
|
8
|
+
api_key_env: Optional[str] = Field(default=None, description="Environment variable name for the API key")
|
|
9
|
+
api_version: Optional[str] = Field(default=None, description="API version string")
|
|
10
|
+
output_config: Optional[Dict[str, Any]] = Field(default=None, description="Output file naming and organization configuration")
|
|
11
|
+
|
|
12
|
+
class LLMTypeSection(BaseModel):
|
|
13
|
+
default: str = Field(..., description="The default model key to use for this type")
|
|
14
|
+
models: Dict[str, LLMModelDefinition] = Field(..., description="Dictionary of available models by key")
|
|
15
|
+
|
|
16
|
+
class LLMConfigSection(BaseModel):
|
|
17
|
+
creative: LLMTypeSection = Field(..., description="Creative LLM configurations")
|
|
18
|
+
evaluation: LLMTypeSection = Field(..., description="Evaluation LLM configurations")
|
|
19
|
+
|
|
20
|
+
class LLMParametersTypeSection(BaseModel):
|
|
21
|
+
temperature: float = Field(..., description="Model temperature")
|
|
22
|
+
top_p: float = Field(..., description="Top P sampling parameter")
|
|
23
|
+
max_completion_tokens: Optional[int] = Field(default=None, description="Maximum completion tokens")
|
|
24
|
+
presence_penalty: Optional[float] = Field(default=None, description="Presence penalty parameter")
|
|
25
|
+
frequency_penalty: Optional[float] = Field(default=None, description="Frequency penalty parameter")
|
|
26
|
+
stop: Optional[List[str]] = Field(default=None, description="Stop sequences")
|
|
27
|
+
|
|
28
|
+
class LLMParametersSection(BaseModel):
|
|
29
|
+
creative: LLMParametersTypeSection = Field(..., description="Parameters for creative models")
|
|
30
|
+
evaluation: LLMParametersTypeSection = Field(..., description="Parameters for evaluation models")
|
|
31
|
+
|
|
32
|
+
class AmshaLLMConfig(BaseModel):
|
|
33
|
+
"""
|
|
34
|
+
LLM API and Parameters Configuration.
|
|
35
|
+
Strictly validates connections and parameters without silent defaults.
|
|
36
|
+
"""
|
|
37
|
+
llm: LLMConfigSection = Field(..., description="Configured LLM connections by type")
|
|
38
|
+
llm_parameters: LLMParametersSection = Field(..., description="Configured LLM hyper-parameters by type")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class AmshaException(Exception):
|
|
2
|
+
"""Base exception for all Amsha errors"""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class AmshaConfigurationException(AmshaException):
|
|
6
|
+
"""Specific strict configuration validation error"""
|
|
7
|
+
def __init__(self, message: str, errors: list[str] = None):
|
|
8
|
+
super().__init__(message)
|
|
9
|
+
self.errors = errors or []
|
|
10
|
+
|
|
11
|
+
def __str__(self):
|
|
12
|
+
base_msg = super().__str__()
|
|
13
|
+
if self.errors:
|
|
14
|
+
error_details = "\n".join(f" - {err}" for err in self.errors)
|
|
15
|
+
return f"{base_msg}\nValidation Errors:\n{error_details}"
|
|
16
|
+
return base_msg
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Type, Any, Dict, List
|
|
3
|
+
from pydantic import BaseModel, ValidationError
|
|
4
|
+
|
|
5
|
+
from amsha.configuration.exceptions.amsha_configuration_exception import AmshaConfigurationException
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class StrictConfigValidator:
|
|
10
|
+
"""
|
|
11
|
+
Validator that checks input data against a Pydantic model.
|
|
12
|
+
Unlike RobustConfigValidator, this validator strictly enforces
|
|
13
|
+
that all required fields are present and valid. Any failure
|
|
14
|
+
will accumulate errors and raise an AmshaConfigurationException
|
|
15
|
+
to terminate execution early and prevent silent crashes.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.audit_log: List[str] = []
|
|
19
|
+
|
|
20
|
+
def validate_strictly(self, model_class: Type[BaseModel], input_data: Dict[str, Any], config_name: str = "Configuration") -> BaseModel:
|
|
21
|
+
"""
|
|
22
|
+
Validates input_data against model_class.
|
|
23
|
+
Raises AmshaConfigurationException if any validation fails.
|
|
24
|
+
"""
|
|
25
|
+
if input_data is None:
|
|
26
|
+
raise AmshaConfigurationException(f"{config_name} cannot be empty or None.")
|
|
27
|
+
|
|
28
|
+
if not isinstance(input_data, dict):
|
|
29
|
+
raise AmshaConfigurationException(f"Expected dictionary for {config_name}, got {type(input_data).__name__}.")
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
# Pydantic will perform strict validation based on the model.
|
|
33
|
+
# Missing fields configured with `Field(...)` will throw ValidationErrors.
|
|
34
|
+
validated_model = model_class(**input_data)
|
|
35
|
+
self.audit_log.append(f"[VALID] {config_name} passed strict validation.")
|
|
36
|
+
return validated_model
|
|
37
|
+
|
|
38
|
+
except ValidationError as e:
|
|
39
|
+
# Extract detailed errors from Pydantic
|
|
40
|
+
detailed_errors = []
|
|
41
|
+
for err in e.errors():
|
|
42
|
+
loc = ".".join([str(x) for x in err.get("loc", [])])
|
|
43
|
+
msg = err.get("msg", "")
|
|
44
|
+
detailed_errors.append(f"Field '{loc}': {msg}")
|
|
45
|
+
|
|
46
|
+
error_msg = f"❌ Strict Validation Failed for {config_name}."
|
|
47
|
+
logger.error(error_msg)
|
|
48
|
+
|
|
49
|
+
# Immediately raise our custom exception with the accumulated details
|
|
50
|
+
raise AmshaConfigurationException(error_msg, errors=detailed_errors) from e
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# src/nikhil/amsha/toolkit/crew_forge/domain/agent_data.py
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from amsha.crew_forge.domain.models.mcp_data import McpServerConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentRequest(BaseModel):
|
|
10
|
+
role: str = Field(..., description="The role or persona of the agent.")
|
|
11
|
+
goal: str = Field(..., description="The primary objective or goal of the agent.")
|
|
12
|
+
backstory: str = Field(..., description="The backstory or context for the agent.")
|
|
13
|
+
usecase: Optional[str] = Field(None, description="The Usecase or context for the agent for its use")
|
|
14
|
+
|
|
15
|
+
# Execution tuning
|
|
16
|
+
max_iter: Optional[int] = Field(None, description="Maximum number of iterations for the agent.")
|
|
17
|
+
max_rpm: Optional[int] = Field(None, description="Maximum requests per minute.")
|
|
18
|
+
max_execution_time: Optional[int] = Field(None, description="Maximum execution time (seconds).")
|
|
19
|
+
max_retry_limit: Optional[int] = Field(None, description="Maximum number of retries on tool failure.")
|
|
20
|
+
respect_context_window: Optional[bool] = Field(None, description="Respect the context window when sending messages.")
|
|
21
|
+
allow_delegation: Optional[bool] = Field(None, description="Allow the agent to delegate tasks to other agents.")
|
|
22
|
+
|
|
23
|
+
# Capabilities
|
|
24
|
+
reasoning: Optional[bool] = Field(None, description="Enable reasoning model.")
|
|
25
|
+
max_reasoning_attempts: Optional[int] = Field(None, description="Maximum reasoning attempts when reasoning is enabled.")
|
|
26
|
+
multimodal: Optional[bool] = Field(None, description="Enable multimodal (image/audio) inputs.")
|
|
27
|
+
skills: Optional[List[str]] = Field(None, description="Skill names or paths to attach to the agent.")
|
|
28
|
+
tools: Optional[List[str]] = Field(None, description="Tool names resolved against the tool registry.")
|
|
29
|
+
mcp_servers: Optional[List[McpServerConfig]] = Field(None, description="MCP server configurations for this agent.")
|
|
30
|
+
|
|
31
|
+
# Prompt customization
|
|
32
|
+
system_template: Optional[str] = Field(None, description="System template override.")
|
|
33
|
+
prompt_template: Optional[str] = Field(None, description="Prompt template override.")
|
|
34
|
+
response_template: Optional[str] = Field(None, description="Response template override.")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# src/nikhil/amsha/toolkit/crew_forge/domain/crew_data.py
|
|
2
|
+
from typing import Any, Dict, Optional, Union
|
|
3
|
+
|
|
4
|
+
from crewai.llms.base_llm import BaseLLM
|
|
5
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CrewData(BaseModel):
|
|
9
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
10
|
+
llm: BaseLLM = Field(..., description="Model name")
|
|
11
|
+
module_name: str = Field(..., description="The name of module")
|
|
12
|
+
output_dir_path: Optional[str] = Field(..., description="output dir path")
|
|
13
|
+
memory: bool = Field(
|
|
14
|
+
default=False,
|
|
15
|
+
description="Whether the crew uses CrewAI's unified memory",
|
|
16
|
+
)
|
|
17
|
+
checkpoint: Optional[Union[bool, Dict[str, Any]]] = Field(
|
|
18
|
+
default=None,
|
|
19
|
+
description=(
|
|
20
|
+
"Checkpoint configuration: a bool to enable/disable CrewAI checkpointing, "
|
|
21
|
+
"or a dict with keys enabled/on_events/provider/location/max_checkpoints."
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
tracing: Optional[bool] = Field(
|
|
25
|
+
default=None,
|
|
26
|
+
description=(
|
|
27
|
+
"Enable CrewAI native tracing (sends full prompt/response content "
|
|
28
|
+
"to CrewAI's hosted dashboard). Off by default — requires crewai login."
|
|
29
|
+
),
|
|
30
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# src/nikhil/amsha/crew_forge/domain/models/mcp_data.py
|
|
2
|
+
"""MCP server configuration models for YAML config-as-code.
|
|
3
|
+
|
|
4
|
+
These models constrain what's expressible in YAML — no raw dict passthrough.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class McpServerConfig(BaseModel):
|
|
12
|
+
"""Structured MCP server configuration — Amsha's YAML-facing schema.
|
|
13
|
+
|
|
14
|
+
``transport`` discriminates which fields are relevant:
|
|
15
|
+
- ``stdio``: ``command`` + ``args`` + ``env``
|
|
16
|
+
- ``http`` / ``sse``: ``url`` + ``headers``
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
transport: Literal["stdio", "http", "sse"] = Field(
|
|
20
|
+
...,
|
|
21
|
+
description="MCP transport type.",
|
|
22
|
+
)
|
|
23
|
+
# stdio fields
|
|
24
|
+
command: Optional[str] = Field(
|
|
25
|
+
None,
|
|
26
|
+
description="Command to execute for stdio transport (e.g. 'python', 'node').",
|
|
27
|
+
)
|
|
28
|
+
args: Optional[List[str]] = Field(
|
|
29
|
+
None,
|
|
30
|
+
description="Command arguments for stdio transport.",
|
|
31
|
+
)
|
|
32
|
+
env: Optional[Dict[str, str]] = Field(
|
|
33
|
+
None,
|
|
34
|
+
description="Environment variables for stdio transport (replaces parent env).",
|
|
35
|
+
)
|
|
36
|
+
# http / sse fields
|
|
37
|
+
url: Optional[str] = Field(
|
|
38
|
+
None,
|
|
39
|
+
description="Server URL for http/sse transport.",
|
|
40
|
+
)
|
|
41
|
+
headers: Optional[Dict[str, str]] = Field(
|
|
42
|
+
None,
|
|
43
|
+
description="HTTP headers for http/sse transport (e.g. auth tokens).",
|
|
44
|
+
)
|
|
45
|
+
# common
|
|
46
|
+
connect_timeout: int = Field(
|
|
47
|
+
30,
|
|
48
|
+
description="Connection timeout in seconds.",
|
|
49
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# src/nikhil/amsha/toolkit/crew_forge/domain/task_data.py
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TaskRequest(BaseModel):
|
|
8
|
+
name: str = Field(..., description="The name of the task.")
|
|
9
|
+
description: str = Field(..., description="Description of the task to be performed.")
|
|
10
|
+
expected_output: str = Field(..., description="The expected output or result of the task.")
|
|
11
|
+
usecase: Optional[str] = Field(None, description="The Usecase or context for the task for its use")
|
|
12
|
+
|
|
13
|
+
# Execution
|
|
14
|
+
context: Optional[List[str]] = Field(None, description="Names of prerequisite TaskRequests whose output feeds this task.")
|
|
15
|
+
tools: Optional[List[str]] = Field(None, description="Tool names resolved against the tool registry. Task-level tools override agent-level tools.")
|
|
16
|
+
async_execution: Optional[bool] = Field(None, description="Execute this task asynchronously.")
|
|
17
|
+
human_input: Optional[bool] = Field(None, description="Require human input before final output.")
|
|
18
|
+
markdown: Optional[bool] = Field(None, description="Render the task output as markdown.")
|
|
19
|
+
|
|
20
|
+
# Guardrails
|
|
21
|
+
guardrail: Optional[str] = Field(None, description="LLM-based guardrail description for validating task output.")
|
|
22
|
+
guardrail_max_retries: Optional[int] = Field(None, description="Maximum retries when the guardrail fails.")
|