"""Step 1 (SPEC L-10): deterministic rules from `config/agent_rules.yaml`, before any LLM call.""" from pathlib import Path import regex import yaml from pydantic import BaseModel, ConfigDict, Field, field_validator class Rule(BaseModel): model_config = ConfigDict(extra="forbid", frozen=True) reason: str pattern: str @field_validator("pattern") @classmethod def _compiles(cls, value: str) -> str: try: regex.compile(value) except regex.error as e: raise ValueError(f"bad pattern: {e}") from e return value class AgentRules(BaseModel): model_config = ConfigDict(extra="forbid", frozen=True) max_length: int = Field(default=500, gt=0) rules: list[Rule] def load_rules(config_dir: Path) -> AgentRules: with (config_dir / "agent_rules.yaml").open(encoding="utf-8") as f: return AgentRules.model_validate(yaml.safe_load(f)) TOO_LONG = "слишком длинный запрос" class Prefilter: def __init__(self, rules: AgentRules) -> None: self._max = rules.max_length self._rules = [(r.reason, regex.compile(r.pattern, regex.IGNORECASE)) for r in rules.rules] def check(self, prompt: str) -> str | None: """The reason to refuse, or None when the prompt may go on to the guard.""" if len(prompt) > self._max: return TOO_LONG for reason, pattern in self._rules: if pattern.search(prompt, timeout=0.1): return reason return None