[core] Safety: лексикон + LLM-сигнал, кризисный скрипт, recall 100% на наборе #4

Closed
opened 2026-10-03 14:38:52 +03:00 by Eleboba · 0 comments
Owner

Описание

Guardrail №2: safety до LLM на каждом входящем. При acute отвечает фиксированный скрипт, LLM не генерирует. SPEC §3, §7.

Желаемый результат

  • app/core/safety.py: check(text_scrubbed, llm_signal=None) -> SafetyResult(level: none|possible|acute, matched: list[str]); итог — максимум из лексикона и LLM.
  • Лексикон config/lexicon/risk_ru.yaml: категории (суицид, самоповреждение, насилие/угроза другим, оружие, домашнее насилие) × уровень; нормализация (lowercase, ё→е, леммы/стемминг через простые правила, опечатки), отрицания («не хочу умирать» не должно давать acute, но possible допустим).
  • Кризисный скрипт в config/texts/crisis.yaml: 112, телефон доверия 8-800-2000-122, «вы не одни», что врач уже уведомлён; для family — ресурсы по домашнему насилию.
  • Тесты tests/test_safety.py: ≥ 40 фраз (из демо: «всё бессмысленно, устал от всего» → possible минимум), recall acute = 100%.

Ограничения и риски

Ложноположительные допустимы, пропуск — нет. Сырой текст не логировать.


Лог выполнения

2026-10-03

2026-10-03

  • Что сделано
    • app/core/safety.py: check(text: ScrubbedText, llm_signal: RiskLevel | None = None) -> SafetyResult (frozen: level, categories, matched_rules, negated_rules, source), итог = max(лексикон, LLM); crisis_reply(categories, audience="patient"|"family") -> CrisisScript (готовый text + части).
    • Нормализация: lowercase, ё→е, латинские буквы-двойники в русских словах → кириллица, транслит целиком латинских слов (hochu umeret'), пунктуация → пробел, «жииить» → «жить».
    • Лексикон config/lexicon/risk_ru.yaml: 6 категорий (suicide, self_harm, harm_others, weapon_access, domestic_violence, hopelessness), ~50 правил на основах слов + частые опечатки, ветеранский сленг («стать двухсотым», «лучше бы я там остался», «к пацанам туда»), комбинация «оружие + любой сигнал риска → acute».
    • Отрицания: окно до 4 слов, не через запятую/точку, только через «связки» (о, думаю, мыслей, у меня...); снижает acute → possible, никогда до none. «мыслей о суициде нет» — отрицание после совпадения, «мысли о суициде не отпускают» — остаётся acute. Если в сообщении есть хотя бы одно неотрицаемое прочтение — acute.
    • Кризисный скрипт config/texts/crisis.yaml: варианты patient.default / patient.harm_others / patient.domestic_violence / family.default / family.domestic_violence; 112, телефон доверия 8-800-2000-122 (бесплатно, круглосуточно), 102 для насилия, «врач уже уведомлён», строка заземления; для семьи — не уговаривать «потерпеть».
  • Результат
    • Eval-набор tests/data/safety_cases.yaml: 99 фраз (64 acute / 18 possible, из них 6 отрицаний / 17 none); recall acute = 100%, в т.ч. после scrub(); ни одна none-фраза не acute; демо-фразы откалиброваны. ~0,1–1 мс на сообщение.
    • tests/test_safety.py, make lint test зелёные.
  • Заметки/решения
    • Лексикон и тексты помечены «черновик, утверждает психиатр (#8)».
    • Ложноположительные осознанно допустимы (например «убить время», «ствол дерева», «автомат»).
    • Номер горячей линии по домашнему насилию не добавлял — пусть психиатр (#8) подтвердит актуальный.
## Описание Guardrail №2: safety до LLM на каждом входящем. При `acute` отвечает фиксированный скрипт, LLM не генерирует. SPEC §3, §7. ## Желаемый результат - `app/core/safety.py`: `check(text_scrubbed, llm_signal=None) -> SafetyResult(level: none|possible|acute, matched: list[str])`; итог — максимум из лексикона и LLM. - Лексикон `config/lexicon/risk_ru.yaml`: категории (суицид, самоповреждение, насилие/угроза другим, оружие, домашнее насилие) × уровень; нормализация (lowercase, ё→е, леммы/стемминг через простые правила, опечатки), отрицания («не хочу умирать» не должно давать acute, но possible допустим). - Кризисный скрипт в `config/texts/crisis.yaml`: 112, телефон доверия 8-800-2000-122, «вы не одни», что врач уже уведомлён; для `family` — ресурсы по домашнему насилию. - Тесты `tests/test_safety.py`: ≥ 40 фраз (из демо: «всё бессмысленно, устал от всего» → possible минимум), **recall acute = 100%**. ## Ограничения и риски Ложноположительные допустимы, пропуск — нет. Сырой текст не логировать. --- ## Лог выполнения ### 2026-10-03 ### 2026-10-03 - **Что сделано** - `app/core/safety.py`: `check(text: ScrubbedText, llm_signal: RiskLevel | None = None) -> SafetyResult` (frozen: `level`, `categories`, `matched_rules`, `negated_rules`, `source`), итог = `max(лексикон, LLM)`; `crisis_reply(categories, audience="patient"|"family") -> CrisisScript` (готовый `text` + части). - Нормализация: lowercase, ё→е, латинские буквы-двойники в русских словах → кириллица, транслит целиком латинских слов (`hochu umeret'`), пунктуация → пробел, «жииить» → «жить». - Лексикон `config/lexicon/risk_ru.yaml`: 6 категорий (suicide, self_harm, harm_others, weapon_access, domestic_violence, hopelessness), ~50 правил на основах слов + частые опечатки, ветеранский сленг («стать двухсотым», «лучше бы я там остался», «к пацанам туда»), комбинация «оружие + любой сигнал риска → acute». - Отрицания: окно до 4 слов, не через запятую/точку, только через «связки» (о, думаю, мыслей, у меня...); снижает acute → possible, никогда до none. «мыслей о суициде нет» — отрицание после совпадения, «мысли о суициде не отпускают» — остаётся acute. Если в сообщении есть хотя бы одно неотрицаемое прочтение — acute. - Кризисный скрипт `config/texts/crisis.yaml`: варианты patient.default / patient.harm_others / patient.domestic_violence / family.default / family.domestic_violence; 112, телефон доверия 8-800-2000-122 (бесплатно, круглосуточно), 102 для насилия, «врач уже уведомлён», строка заземления; для семьи — не уговаривать «потерпеть». - **Результат** - Eval-набор `tests/data/safety_cases.yaml`: 99 фраз (64 acute / 18 possible, из них 6 отрицаний / 17 none); recall acute = 100%, в т.ч. после `scrub()`; ни одна none-фраза не acute; демо-фразы откалиброваны. ~0,1–1 мс на сообщение. - `tests/test_safety.py`, `make lint test` зелёные. - **Заметки/решения** - Лексикон и тексты помечены «черновик, утверждает психиатр (#8)». - Ложноположительные осознанно допустимы (например «убить время», «ствол дерева», «автомат»). - Номер горячей линии по домашнему насилию не добавлял — пусть психиатр (#8) подтвердит актуальный.
Eleboba added this to the M1 Каркас milestone 2026-10-03 14:38:52 +03:00
Eleboba added the safetysize/1P0corefeature labels 2026-10-03 14:38:52 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: MedITron/PTSRaiAgent#4