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
13 changes: 11 additions & 2 deletions openevolve/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,18 @@ class Config:
@classmethod
def from_yaml(cls, path: Union[str, Path]) -> "Config":
"""Load configuration from a YAML file"""
with open(path, "r") as f:
config_path = Path(path).resolve()
with open(config_path, "r") as f:
config_dict = yaml.safe_load(f)
return cls.from_dict(config_dict)
config = cls.from_dict(config_dict)

# Resolve template_dir relative to config file location
if config.prompt.template_dir:
template_path = Path(config.prompt.template_dir)
if not template_path.is_absolute():
config.prompt.template_dir = str((config_path.parent / template_path).resolve())

return config

@classmethod
def from_dict(cls, config_dict: Dict[str, Any]) -> "Config":
Expand Down
12 changes: 10 additions & 2 deletions openevolve/prompt/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

import os
import json
import logging
from pathlib import Path
from typing import Dict, List, Optional, Union, Any

logger = logging.getLogger(__name__)

# Base system message template for evolution
BASE_SYSTEM_TEMPLATE = """You are an expert software developer tasked with iteratively improving a codebase.
Your job is to analyze the current program and suggest improvements based on feedback from previous attempts.
Expand Down Expand Up @@ -185,8 +188,13 @@ def __init__(self, custom_template_dir: Optional[str] = None):
self._load_from_directory(self.default_dir)

# 2. Override with custom templates (if provided)
if self.custom_dir and self.custom_dir.exists():
self._load_from_directory(self.custom_dir)
if self.custom_dir:
if self.custom_dir.exists():
self._load_from_directory(self.custom_dir)
else:
logger.warning(
f"Custom template directory does not exist, using default prompt."
)

def _load_from_directory(self, directory: Path) -> None:
"""Load all templates and fragments from a directory"""
Expand Down
119 changes: 119 additions & 0 deletions tests/test_template_dir_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Tests for template_dir path resolution
"""

import unittest
import tempfile
import yaml
from pathlib import Path

from openevolve.config import Config


class TestTemplateDirResolution(unittest.TestCase):
"""Test that template_dir paths are resolved relative to config file location"""

def test_relative_template_dir_resolved_to_config_location(self):
"""Relative paths in template_dir should resolve relative to config file"""
# Create a temporary directory structure
with tempfile.TemporaryDirectory() as tmpdir:
config_dir = Path(tmpdir) / "config_subdir"
config_dir.mkdir()
config_file = config_dir / "test_config.yaml"

# Write config with relative template_dir
config_data = {
"prompt": {"template_dir": "templates"},
"llm": {"models": [{"name": "gpt-4", "weight": 1.0}]},
}
with open(config_file, "w") as f:
yaml.dump(config_data, f)

# Load config
config = Config.from_yaml(config_file)

# Template_dir should be resolved relative to config file location
expected_path = str((config_dir / "templates").resolve())
self.assertEqual(config.prompt.template_dir, expected_path)

def test_absolute_template_dir_unchanged(self):
"""Absolute paths in template_dir should remain unchanged"""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "test_config.yaml"
absolute_template_path = "/absolute/path/to/templates"

# Write config with absolute template_dir
config_data = {
"prompt": {"template_dir": absolute_template_path},
"llm": {"models": [{"name": "gpt-4", "weight": 1.0}]},
}
with open(config_file, "w") as f:
yaml.dump(config_data, f)

# Load config
config = Config.from_yaml(config_file)

# Absolute path should remain unchanged
self.assertEqual(config.prompt.template_dir, absolute_template_path)

def test_null_template_dir_unchanged(self):
"""Null template_dir should remain None"""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "test_config.yaml"

# Write config with null template_dir
config_data = {
"prompt": {"template_dir": None},
"llm": {"models": [{"name": "gpt-4", "weight": 1.0}]},
}
with open(config_file, "w") as f:
yaml.dump(config_data, f)

# Load config
config = Config.from_yaml(config_file)

# None should remain None
self.assertIsNone(config.prompt.template_dir)

def test_nested_relative_template_dir(self):
"""Nested relative paths should resolve correctly"""
with tempfile.TemporaryDirectory() as tmpdir:
config_dir = Path(tmpdir) / "configs"
config_dir.mkdir()
config_file = config_dir / "test_config.yaml"

# Write config with nested relative path
config_data = {
"prompt": {"template_dir": "../templates/custom"},
"llm": {"models": [{"name": "gpt-4", "weight": 1.0}]},
}
with open(config_file, "w") as f:
yaml.dump(config_data, f)

# Load config
config = Config.from_yaml(config_file)

# Should resolve to <tmpdir>/templates/custom
expected_path = str((config_dir / "../templates/custom").resolve())
self.assertEqual(config.prompt.template_dir, expected_path)

def test_real_example_config(self):
"""Test with real example config file"""
# This test uses the actual llm_prompt_optimization example
config_path = "examples/llm_prompt_optimization/config.yaml"
if not Path(config_path).exists():
self.skipTest(f"Example config not found: {config_path}")

config = Config.from_yaml(config_path)

# Should resolve to examples/llm_prompt_optimization/templates
expected_dir = Path("examples/llm_prompt_optimization/templates").resolve()
actual_dir = Path(config.prompt.template_dir)

self.assertEqual(actual_dir, expected_dir)
# Verify the resolved path is absolute
self.assertTrue(actual_dir.is_absolute())


if __name__ == "__main__":
unittest.main()