"""Shared utilities for context builders.""" from pathlib import Path from typing import Any, Optional class TemplateManager: """Loads .txt templates from one or more directories. Directories are processed in order; later directories override templates with the same name from earlier ones. """ def __init__(self, *directories: Optional[str]): """ Initializes the TemplateManager with the given directories. If there are multiple directories, the templates from the later directories will override the templates from the earlier directories. """ self.templates: dict[str, str] = {} for d in directories: if d: path = Path(d) if path.exists(): self._load_from_directory(path) def _load_from_directory(self, directory: Path) -> None: for txt_file in directory.glob("*.txt"): with open(txt_file, "r") as f: self.templates[txt_file.stem] = f.read() def get_template(self, name: str) -> str: if name not in self.templates: raise ValueError(f"Template '{name}' not found") return self.templates[name] def prog_attr(program: Any, key: str, default: Any = "") -> Any: """Read an attribute from a Program object or a plain dict.""" if hasattr(program, key): return getattr(program, key) if isinstance(program, dict): return program.get(key, default) return default def format_artifacts(program: Any, heading: str = "##", max_len: int = 2000) -> str: """Format evaluator artifacts (e.g. feedback) into markdown sections.""" artifacts = prog_attr(program, "artifacts", None) if not artifacts: return "" sections = [] for key, value in artifacts.items(): if value is None: continue text = str(value) if len(text) > max_len: text = text[:max_len] + "\n... (truncated)" if key == "feedback": sections.append(f"{heading} Evaluator Feedback\n{text}") else: sections.append(f"{heading} {key}\n{text}") if not sections: return "" return "\n" + "\n\n".join(sections) + "\n"