Spaces:
Sleeping
Sleeping
| """ | |
| Provider-specific prompt loader (spec #3). | |
| Different models follow instructions differently, so each LLM capability has a | |
| family-specific prompt template under `prompts/`: | |
| prompts/<task>_<family>.txt e.g. resume_tailor_kimi.txt | |
| prompts/<task>_default.txt optional shared fallback | |
| Families: claude | kimi | nvidia. Templates use <<TOKEN>> placeholders (NOT | |
| Python str.format braces) so the literal JSON braces inside the templates never | |
| collide with substitution. | |
| If no template file exists for a (task, family), `render_prompt` returns None and | |
| the caller falls back to its built-in inline prompt — so this layer is purely | |
| additive and never breaks existing behaviour. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from typing import Optional | |
| _PROMPTS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "prompts") | |
| FAMILIES = ("claude", "kimi", "nvidia") | |
| TASKS = ("jd_analysis", "resume_tailor", "repair", "jobalytics_repair") | |
| def family_for(name_or_model: str) -> str: | |
| """Map a provider name or model id to a prompt family.""" | |
| s = (name_or_model or "").lower() | |
| if "claude" in s or "anthropic" in s: | |
| return "claude" | |
| if "kimi" in s or "moonshot" in s: | |
| return "kimi" | |
| # GLM / Qwen / DeepSeek / Step / GPT-OSS / MiniMax are all NVIDIA-hosted, | |
| # OpenAI-compatible endpoints — they share the strict "nvidia" style. | |
| return "nvidia" | |
| def load_prompt(task: str, family: str) -> Optional[str]: | |
| """Read prompts/<task>_<family>.txt, falling back to <task>_default.txt.""" | |
| for fam in (family, "default"): | |
| path = os.path.join(_PROMPTS_DIR, f"{task}_{fam}.txt") | |
| if os.path.exists(path): | |
| try: | |
| with open(path, encoding="utf-8") as f: | |
| return f.read() | |
| except Exception: | |
| return None | |
| return None | |
| def render_prompt(task: str, family: str, **tokens) -> Optional[str]: | |
| """Load a template and substitute <<TOKEN>> placeholders. | |
| tokens are passed as token_name=value; the template placeholder is the | |
| UPPER-CASED token name wrapped in << >>, e.g. jd_text -> <<JD_TEXT>>. | |
| Returns None if no template exists (caller uses its inline prompt). | |
| """ | |
| tmpl = load_prompt(task, family) | |
| if tmpl is None: | |
| return None | |
| out = tmpl | |
| for key, val in tokens.items(): | |
| out = out.replace(f"<<{key.upper()}>>", "" if val is None else str(val)) | |
| return out | |
| def has_prompt(task: str, family: str) -> bool: | |
| return load_prompt(task, family) is not None | |