from __future__ import annotations import re from pathlib import Path from typing import Any PROMPT_ROOT = Path(__file__).resolve().parent.parent / "prompts" _TOKEN_RE = re.compile(r"{{\s*([A-Za-z_][A-Za-z0-9_]*)\s*}}") def load_prompt_template(relative_path: str) -> str: path = (PROMPT_ROOT / relative_path).resolve() if not path.is_relative_to(PROMPT_ROOT.resolve()): raise ValueError(f"Invalid prompt path: {relative_path}") return path.read_text(encoding="utf-8") def render_prompt_template(template: str, **context: Any) -> str: def _replace(match: re.Match[str]) -> str: value = context.get(match.group(1), "") return "" if value is None else str(value) return _TOKEN_RE.sub(_replace, template) def render_prompt(relative_path: str, **context: Any) -> str: return render_prompt_template(load_prompt_template(relative_path), **context)