File size: 4,339 Bytes
f39e265 be97847 e287216 f39e265 87149fa 4638978 e287216 be97847 d7d27a3 be97847 d7d27a3 f39e265 63808a2 87149fa d7d27a3 f39e265 e287216 f39e265 87149fa f39e265 be97847 d7d27a3 63808a2 87149fa f39e265 9d8bbd6 e287216 9d8bbd6 e287216 f39e265 e287216 f39e265 e287216 f39e265 87149fa f39e265 be97847 d7d27a3 63808a2 87149fa f39e265 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | """Application configuration, loaded once from the environment."""
import os
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
PROJECT_ROOT = Path(__file__).parent
DEFAULT_MODEL = "openrouter/free"
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
DEFAULT_TEMPERATURE = 0.0
# Drafting a cover letter is writing, not analysis: a little sampling gives
# natural prose. Kept low so it does not start inventing detail.
DEFAULT_DRAFTING_TEMPERATURE = 0.4
# The preset actions (ATS keyword tables, detailed reviews) run long, so 2048
# truncated them mid-answer. This leaves room for the profile, job description,
# and conversation history while avoiding an unnecessarily large response cap.
DEFAULT_MAX_TOKENS = 20000
# Prose skill output gets its own response cap.
DEFAULT_SKILL_MAX_TOKENS = 6500
# ATS returns a structured classification for every keyword, so it needs more
# output room than the prose skills.
DEFAULT_ATS_MAX_TOKENS = 12000
DEFAULT_LANGFUSE_HOST = "https://cloud.langfuse.com"
DEFAULT_CHECKPOINT_DB = "checkpoints.sqlite"
# Turns of conversation replayed to the model. Older turns stay in the
# checkpoint (nothing is deleted) but are not resent.
DEFAULT_MAX_HISTORY_TURNS = 10
# Routing and retrieval. ONNX runtime, so no torch dependency.
DEFAULT_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
DEFAULT_RETRIEVAL_TOP_K = 10
class ConfigError(RuntimeError):
"""Raised when the environment is missing or misconfigured."""
@dataclass(frozen=True)
class Settings:
openrouter_api_key: str
candidate_name: str
model: str
temperature: float
drafting_temperature: float
max_tokens: int
skill_max_tokens: int
ats_max_tokens: int
max_history_turns: int
embedding_model: str
retrieval_top_k: int
checkpoint_db: Path
langfuse_public_key: str
langfuse_secret_key: str
langfuse_host: str
@property
def tracing_enabled(self) -> bool:
"""Tracing turns itself on only when both Langfuse keys are present."""
return bool(self.langfuse_public_key and self.langfuse_secret_key)
def _get_float(name, default):
raw = os.getenv(name)
if raw is None or not raw.strip():
return default
try:
return float(raw)
except ValueError:
raise ConfigError(f"{name} must be a number, got {raw!r}")
def _get_int(name, default):
raw = os.getenv(name)
if raw is None or not raw.strip():
return default
try:
return int(raw)
except ValueError:
raise ConfigError(f"{name} must be an integer, got {raw!r}")
@lru_cache(maxsize=2)
def get_settings(require_api_key=True) -> Settings:
api_key = os.getenv("OPENROUTER_API_KEY", "").strip()
if require_api_key and not api_key:
raise ConfigError("OPENROUTER_API_KEY is not set. Add it to your .env file.")
return Settings(
openrouter_api_key=api_key,
candidate_name=os.getenv("CANDIDATE_NAME", "").strip() or "the candidate",
model=os.getenv("OPENROUTER_MODEL", "").strip() or DEFAULT_MODEL,
temperature=_get_float("LLM_TEMPERATURE", DEFAULT_TEMPERATURE),
drafting_temperature=_get_float("LLM_DRAFTING_TEMPERATURE", DEFAULT_DRAFTING_TEMPERATURE),
max_tokens=_get_int("LLM_MAX_TOKENS", DEFAULT_MAX_TOKENS),
skill_max_tokens=_get_int("SKILL_MAX_TOKENS", DEFAULT_SKILL_MAX_TOKENS),
ats_max_tokens=_get_int("ATS_MAX_TOKENS", DEFAULT_ATS_MAX_TOKENS),
max_history_turns=_get_int("MAX_HISTORY_TURNS", DEFAULT_MAX_HISTORY_TURNS),
embedding_model=os.getenv("EMBEDDING_MODEL", "").strip() or DEFAULT_EMBEDDING_MODEL,
retrieval_top_k=_get_int("RETRIEVAL_TOP_K", DEFAULT_RETRIEVAL_TOP_K),
checkpoint_db=PROJECT_ROOT / (
os.getenv("CHECKPOINT_DB", "").strip() or DEFAULT_CHECKPOINT_DB
),
langfuse_public_key=os.getenv("LANGFUSE_PUBLIC_KEY", "").strip(),
langfuse_secret_key=os.getenv("LANGFUSE_SECRET_KEY", "").strip(),
# LANGFUSE_HOST is the SDK's own name; LANGFUSE_BASE_URL is accepted as an alias.
langfuse_host=(
os.getenv("LANGFUSE_HOST", "").strip()
or os.getenv("LANGFUSE_BASE_URL", "").strip()
or DEFAULT_LANGFUSE_HOST
),
)
|