openher / engine /prompt_registry.py
kellyxiaowei's picture
Deploy OpenHer Gradio Space β€” gemma-4-E4B served on Modal
dff25f7 verified
Raw
History Blame Contribute Delete
3.67 kB
"""
PromptRegistry β€” Load prompt templates and signal configs from config/prompts/.
Uses string.Template ($variable) to avoid conflicts with JSON braces.
Falls back to hardcoded defaults if config files don't exist.
"""
from __future__ import annotations
from pathlib import Path
from string import Template
from typing import Optional
import yaml
_PROMPTS_DIR = Path(__file__).parent / "prompts"
_cache: dict[str, str] = {}
_signal_cache: Optional[dict] = None
def load_prompt(name: str, fallback: str = "") -> str:
"""Load prompt template from config/prompts/{name}.md, cache in memory."""
if name in _cache:
return _cache[name]
path = _PROMPTS_DIR / f"{name}.md"
if path.exists():
text = path.read_text(encoding="utf-8")
else:
text = fallback
_cache[name] = text
return text
def render_prompt(name: str, fallback: str = "", **kwargs) -> str:
"""Load prompt template + substitute $variables.
Uses safe_substitute: unknown $vars are left as-is (no KeyError).
"""
tmpl = load_prompt(name, fallback)
return Template(tmpl).safe_substitute(**kwargs)
def load_signal_config(fallback_signals: dict = None, fallback_drives: dict = None) -> dict:
"""Load signal_buckets.yaml β†’ structured config with labels + buckets + drives.
Returns:
{
'signals': {
'directness': {
'label': 'η›΄ζŽ₯ζ„Ÿ',
'emoji_label': '🎯 η›΄ζŽ₯εΊ¦',
'buckets': [(0.0, 0.33, '说话委婉含蓄...'), ...],
},
...
},
'drives': {
'connection': {'label': '联结', 'emoji_label': 'πŸ”— 联结'},
...
},
}
"""
global _signal_cache
if _signal_cache is not None:
return _signal_cache
path = _PROMPTS_DIR / "signal_buckets.yaml"
if path.exists():
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
result = {'signals': {}, 'drives': {}}
# Parse signals
for sig_name, sig_data in raw.get('signals', {}).items():
result['signals'][sig_name] = {
'label': sig_data.get('label', sig_name),
'emoji_label': sig_data.get('emoji_label', sig_name),
'emoji_label_en': sig_data.get('emoji_label_en', sig_name),
'low_anchor': sig_data.get('low_anchor', '低'),
'high_anchor': sig_data.get('high_anchor', '高'),
'low_anchor_en': sig_data.get('low_anchor_en', 'low'),
'high_anchor_en': sig_data.get('high_anchor_en', 'high'),
'buckets': [
(b['low'], b['high'], b['desc'])
for b in sig_data.get('buckets', [])
],
}
# Parse drives
for drv_name, drv_data in raw.get('drives', {}).items():
result['drives'][drv_name] = {
'label': drv_data.get('label', drv_name),
'emoji_label': drv_data.get('emoji_label', drv_name),
'emoji_label_en': drv_data.get('emoji_label_en', drv_name),
}
_signal_cache = result
return result
# Fallback: build from hardcoded values
result = {'signals': {}, 'drives': {}}
if fallback_signals:
result['signals'] = fallback_signals
if fallback_drives:
result['drives'] = fallback_drives
_signal_cache = result
return result
def reload():
"""Clear all caches β€” call after editing prompt files at runtime."""
global _signal_cache
_cache.clear()
_signal_cache = None