JAA-ATS-Tool / src /provider_prompts.py
saitejatirunagari's picture
Add model-independent LLM provider abstraction with schema validation
9bf4a3d
Raw
History Blame
2.57 kB
"""
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