Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions config/profiles/free_tier.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Free Tier Agent Profile
# A lightweight configuration for basic agent task execution
# Based on general.yaml with cost-effective model choices

# Default agent configurations
agents:
# Atomizer: Fast decision-making with Gemini Flash
atomizer:
llm:
model: openrouter/google/gemini-2.5-flash
temperature: 0.4
max_tokens: 8000
signature_instructions: "prompt_optimization.prompts.seed_prompts.atomizer_seed:ATOMIZER_PROMPT"
demos: "prompt_optimization.prompts.seed_prompts.atomizer_seed:ATOMIZER_DEMOS"

# Planner: Strategic planning with Gemini Flash
planner:
llm:
model: openrouter/google/gemini-2.5-flash
temperature: 0.4
max_tokens: 32000
signature_instructions: "prompt_optimization.prompts.seed_prompts.planner_seed:PLANNER_PROMPT"
demos: "prompt_optimization.prompts.seed_prompts.planner_seed:PLANNER_DEMOS"
agent_config:
max_subtasks: 12

toolkits:
- class_name: WebSearchToolkit
enabled: true
toolkit_config:
model: openrouter/openai/gpt-4o-mini
max_results: 5
search_context_size: medium
temperature: 1.0
max_tokens: 16000

# Default Executor (fallback for unmapped task types)
executor:
llm:
model: openrouter/google/gemini-2.5-flash
temperature: 0.6
max_tokens: 64000
prediction_strategy: react
signature_instructions: "prompt_optimization.prompts.seed_prompts.executor_seed:EXECUTOR_PROMPT"
demos: "prompt_optimization.prompts.seed_prompts.executor_seed:EXECUTOR_DEMOS"
agent_config:
max_executions: 10

# Default fundamental toolkits
toolkits:
- class_name: E2BToolkit
enabled: true
toolkit_config:
timeout: 600
max_lifetime_hours: 23.5
auto_reinitialize: true

- class_name: WebSearchToolkit
enabled: true
toolkit_config:
model: openrouter/openai/gpt-4o-mini
max_results: 5
search_context_size: medium
temperature: 1.0
max_tokens: 16000

- class_name: FileToolkit
enabled: true
toolkit_config:
enable_delete: false
max_file_size: 10485760 # 10MB

- class_name: CalculatorToolkit
enabled: true

# Aggregator: Synthesis with Gemini Flash
aggregator:
llm:
model: openrouter/google/gemini-2.5-flash
temperature: 0.0
max_tokens: 32000
signature_instructions: "prompt_optimization.prompts.seed_prompts.aggregator_seed:AGGREGATOR_PROMPT"
demos: "prompt_optimization.prompts.seed_prompts.aggregator_seed:AGGREGATOR_DEMOS"

# Verifier: Validation with Gemini Flash
verifier:
llm:
model: openrouter/google/gemini-2.5-flash
temperature: 0.0
max_tokens: 16000
signature_instructions: "prompt_optimization.prompts.seed_prompts.verifier_seed:VERIFIER_PROMPT"
demos: "prompt_optimization.prompts.seed_prompts.verifier_seed:VERIFIER_DEMOS"

# Runtime configuration
runtime:
max_depth: 6
verbose: true
enable_logging: true
log_level: INFO
timeout: 700

# Resilience
resilience:
retry:
enabled: true
max_attempts: 5
strategy: exponential_backoff
base_delay: 2.0
max_delay: 60.0

circuit_breaker:
enabled: true
failure_threshold: 5
recovery_timeout: 120.0
half_open_max_calls: 3

checkpoint:
enabled: true
storage_path: ${oc.env:ROMA_CHECKPOINT_PATH,.checkpoints}
max_checkpoints: 20
max_age_hours: 48.0
compress_checkpoints: true
verify_integrity: true

# Storage
storage:
base_path: ${oc.env:STORAGE_BASE_PATH,/opt/sentient}
max_file_size: 104857600 # 100MB

postgres:
enabled: ${oc.env:POSTGRES_ENABLED,true}
connection_url: ${oc.env:DATABASE_URL,postgresql+asyncpg://localhost/roma_dspy}
pool_size: 10
max_overflow: 20

# Observability
observability:
mlflow:
enabled: ${oc.env:MLFLOW_ENABLED,false}
tracking_uri: ${oc.env:MLFLOW_TRACKING_URI,http://mlflow:5000}
experiment_name: ROMA-Free-Tier
log_traces: true
log_compiles: true
log_evals: true

# Logging
logging:
level: ${oc.env:LOG_LEVEL,INFO}
log_dir: ${oc.env:LOG_DIR,logs}
console_format: detailed
file_format: json
serialize: true
rotation: 500 MB
retention: 90 days
colorize: true
backtrace: true
diagnose: false
2 changes: 1 addition & 1 deletion config/profiles/general.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ runtime:
verbose: true
enable_logging: true
log_level: INFO
timeout: 120
timeout: 700

# Resilience
resilience:
Expand Down
5 changes: 2 additions & 3 deletions src/roma_dspy/api/execution_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from roma_dspy.core.storage.postgres_storage import PostgresStorage
from roma_dspy.core.engine.dag import TaskDAG
from roma_dspy.config.manager import ConfigManager
from roma_dspy.config.utils import config_to_dict
from roma_dspy.types import ExecutionStatus, TaskStatus


Expand Down Expand Up @@ -157,9 +158,7 @@ async def start_execution(
max_depth=max_depth,
profile=config_profile,
experiment_name=experiment_name,
config=config.model_dump()
if hasattr(config, "model_dump")
else dict(config),
config=config_to_dict(config),
metadata=enhanced_metadata,
)

Expand Down
45 changes: 40 additions & 5 deletions src/roma_dspy/config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,40 @@ def interpolate_config(config: DictConfig, context: Dict[str, Any]) -> DictConfi
return config_with_context


def _convert_to_serializable(obj: Any) -> Any:
"""
Recursively convert Pydantic models and dataclasses to serializable dicts.

This handles nested structures that OmegaConf.structured() cannot process.
"""
from dataclasses import is_dataclass, asdict
from pydantic import BaseModel

if isinstance(obj, BaseModel):
# Pydantic BaseModel - use model_dump
return {k: _convert_to_serializable(v) for k, v in obj.model_dump().items()}
elif is_dataclass(obj) and not isinstance(obj, type):
# Pydantic dataclass or stdlib dataclass instance
try:
# Try to get fields and convert recursively
result = {}
for field_name in obj.__dataclass_fields__:
value = getattr(obj, field_name)
result[field_name] = _convert_to_serializable(value)
return result
except Exception:
# Fallback to asdict if available
return asdict(obj)
elif isinstance(obj, dict):
return {k: _convert_to_serializable(v) for k, v in obj.items()}
elif isinstance(obj, (list, tuple)):
return [_convert_to_serializable(item) for item in obj]
elif isinstance(obj, Path):
return str(obj)
else:
return obj


def config_to_dict(
config: Union[ROMAConfig, DictConfig], resolve: bool = True
) -> Dict[str, Any]:
Expand All @@ -55,11 +89,12 @@ def config_to_dict(
Returns:
Plain dictionary representation
"""
if isinstance(config, ROMAConfig):
# Convert Pydantic model to OmegaConf first
config = OmegaConf.structured(config)

return OmegaConf.to_container(config, resolve=resolve)
if isinstance(config, DictConfig):
return OmegaConf.to_container(config, resolve=resolve)

# For ROMAConfig and other Pydantic/dataclass objects,
# recursively convert to dict to handle nested Pydantic models
return _convert_to_serializable(config)


def validate_config_file(config_path: Path) -> bool:
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/test_execution_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ def mock_storage(self):
def mock_config_manager(self):
"""Create mock config manager."""
from unittest.mock import MagicMock
from omegaconf import OmegaConf

manager = MagicMock()
mock_config = MagicMock()
mock_config.model_dump.return_value = {"test": "config"}
# Use OmegaConf directly so config_to_dict can serialize it
mock_config = OmegaConf.create({"test": "config", "observability": None})
manager.load_config.return_value = mock_config
return manager

Expand Down