Rifqi Hafizuddin
feat(guardrails): add input guard, out_of_scope intent, and refusal handling
0bbc27b | """Canned, language-matched refusals for the guardrail layer. | |
| Centralized so the three refusal sites β the `out_of_scope` router branch, the | |
| input-guard block, and the content-filter fallback β speak with one voice. | |
| Deterministic on purpose (no LLM): the refusal must not call the same model that | |
| just refused or failed. Language is picked with a tiny marker check (EN default, | |
| ID when Indonesian markers are present), mirroring the lightweight detector in | |
| `handlers/help.py` β enough for a one-line reply without an LLM round-trip. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| # Subset of help.py's ID marker set β enough to pick EN vs ID for a short refusal. | |
| # Includes the common Indonesian injection verbs ("lupakan"=forget, "abaikan"=ignore) | |
| # so an ID-language jailbreak still gets an ID-language refusal. | |
| _ID_MARKERS = frozenset({ | |
| "yang", "dan", "apa", "gimana", "bagaimana", "kenapa", "mengapa", "aku", "saya", | |
| "tolong", "ini", "itu", "untuk", "dengan", "tidak", "nggak", "enggak", "bisa", | |
| "mau", "buat", "dari", "kamu", "berapa", "kapan", "siapa", "dimana", "adalah", | |
| "akan", "sudah", "belum", "lupakan", "abaikan", "kredensial", "tunjukkan", | |
| }) | |
| def _is_indonesian(message: str) -> bool: | |
| tokens = re.findall(r"[a-z']+", (message or "").lower()) | |
| return any(t in _ID_MARKERS for t in tokens) | |
| # Off-topic / out-of-scope: the request is benign but outside what the assistant does. | |
| _OUT_OF_SCOPE = { | |
| "en": ( | |
| "That's outside what I can help with β I'm a data assistant, so I can only work " | |
| "with the sources you've connected to Data Eyond. Ask me a question about your " | |
| "data, or type /help to see what I can do." | |
| ), | |
| "id": ( | |
| "Itu di luar yang bisa saya bantu β saya asisten data, jadi saya hanya bisa " | |
| "bekerja dengan sumber data yang Anda hubungkan ke Data Eyond. Ajukan pertanyaan " | |
| "tentang data Anda, atau ketik /help untuk melihat yang bisa saya lakukan." | |
| ), | |
| } | |
| # Blocked: a manipulation / injection / secret-extraction / abuse attempt. | |
| _BLOCKED = { | |
| "en": ( | |
| "I can't help with that request. I'm here to analyze the data you've connected β " | |
| "ask me a question about your data and I'll take it from there." | |
| ), | |
| "id": ( | |
| "Saya tidak bisa membantu permintaan tersebut. Saya di sini untuk menganalisis " | |
| "data yang Anda hubungkan β ajukan pertanyaan tentang data Anda dan saya bantu." | |
| ), | |
| } | |
| def out_of_scope_message(message: str) -> str: | |
| """Refusal for a benign but out-of-scope request (the `out_of_scope` intent).""" | |
| return _OUT_OF_SCOPE["id" if _is_indonesian(message) else "en"] | |
| def blocked_message(message: str) -> str: | |
| """Refusal for a blocked request (injection / secrets / abuse / content-filter).""" | |
| return _BLOCKED["id" if _is_indonesian(message) else "en"] | |