Reasoning Layer minimum — DEPLOYED 2 мая 2026
Дизайн (см. [[lyumi/reasoning_layer_design]]) предписывал не начинать до 9 мая. Камал решил: «не хочу промтом лечить, давай как-то технически» — и мы развернули минимум в тот же день. Архитектура есть. Real measurement отложен на observation week.
Триггер
Юзерский кейс утром 2 мая: «можно ли использовать СИЗ с истёкшим сертификатом ТР ТС 019, если по паспорту срок ещё 5 лет?» Lyumi ответила категоричным «нельзя использовать, выкинуть в хоз.нужды». Это reasoning mistake, не factual hallucination — факты правильные (ТР ТС 019 — техрегламент, ст. 156 УК РК — норма), но логика применения неверна.
v4 SQL grounding такие ошибки не ловит. Reflection (Haiku) тоже — она проверяет факты, не reasoning.
Что задеплоено (4 слоя, $0/мес, +20-50мс latency)
Слой 1: Source Authority Labels
Файл: source_authority.py (новый, 13 KB).
Функция classify_authority(metadata, title, doc_text) возвращает один из 4 ярлыков:
| Ярлык | Что | Регулярка-триггер |
|---|---|---|
[НОРМА] |
НПА РК, ТР ТС, кодексы, законы, приказы | Трудовой кодекс, ТР ТС 019, Приказ МТСЗН, ҚР ДСМ, [VKUP]\d{10} (Әділет ID) |
[ОПЕРАТИВНО] |
Норма + конкретный пункт/статья (для прямых цитат) | НОРМА + (статья N\|пункт N\|глава N) |
[МЕТОДИЧКА] |
Международные стандарты (рекомендации) | OSHA, IOGP, ISO, NIOSH, NEBOSH, NFPA, API N, ANSI, ILO, ACGIH, AIHA, WHO, IEC, OHSAS |
[КОММЕНТАРИЙ] |
Учебники, обзоры, презентации | default |
Интегрировано в retriever.format_context_for_llm — каждый hit идёт в LLM с меткой:
--- [НОРМА] Документ 1: Трудовой кодекс РК (релевантность: 0.85) ---
--- [МЕТОДИЧКА] Документ 2: OSHA 1910.146 (релевантность: 0.62) ---
Промпт-инструкция в llm.py (новая секция «МЕТКИ АВТОРИТЕТА ИСТОЧНИКОВ»): Lyumi обязана сохранять метки в финальном ответе — «По [НОРМА] Трудовому кодексу РК ст. 182…», «По [МЕТОДИЧКА] OSHA рекомендуется…, в РК это не обязательно».
15/15 unit tests прошли.
Слой 2A: Reasoning Rules (5 правил)
Файл: reasoning_validator.py (новый, 40 KB), функция validate(query, response).
Каждое правило — ReasoningRule dataclass:
@dataclass
class ReasoningRule:
rule_id: str
title: str
trigger_patterns: list[Pattern] # когда проверять
violation_patterns: list[Pattern] # что считать reasoning ошибкой
correction: str # текст уточнения в конец
suppress_if_present: list[Pattern] # если бот сам ОК — не срабатываем
severity: str = "warning" # warning|critical
short_alert: str = "" # для critical — prepend в начало
5 правил задеплоены:
| ID | Severity | Что ловит |
|---|---|---|
tr_ts_019_certificate_vs_lifetime |
critical | Категоричное «нельзя использовать СИЗ» при истёкшем сертификате — путаница «сертификат производителя» vs «срок службы изделия» |
koap_vs_uk_distinction |
warning | Смешение КоАП штрафа и УК статьи (например «штраф по ст. 156 УК») |
medosmotr_office_workers |
warning | «Все обязаны проходить медосмотр» — false для офисников без вредных факторов |
act_n1_authority |
warning | «Работодатель сам подписывает Н-1» / «сроки не регламентированы» |
ptw_storage_period |
warning | «Наряд-допуск можно выкидывать после закрытия» |
Severity = critical означает что short_alert prepend'ится в начало ответа (юзер видит ДО прочтения wrong статтей). Warning только append'ится в конец.
Prepend-стратегия для critical (ТР ТС 019) — fix после live теста: если ответ начинается с «нельзя использовать», append correction в конец слабо помогает (юзер мог прочитать только первую строку).
11/11 unit tests прошли (включая reggression test с реальным Sonnet ответом из live compare-теста).
Слой 2B: Positive Citation Check
Функция positive_citation_check(response, cards). Каждое НПА-цитирование в response (Приказ N, ст. N, ТР ТС, ГОСТ, OSHA, ISO, NFPA, IOGP, СТ РК, СанПиН, ҚР ДСМ) проверяется на физическое присутствие в текущих retrieval cards.
Это сильнее существующей verify_citations: та говорит «приказ существует в общей базе», positive check — «приказ существует И мы его реально достали для этого вопроса». Phantom приказы (типа «Приказ МЗСР №1descanso» — Sonnet вставил испанское слово) ловятся даже если в общей базе нет аналога.
Regex для citations поддерживает: - Падежные формы (Приказ/Приказу/Приказом) - Alphanumeric номера (1descanso, X-15) — типичный признак phantom от LLM
Слой 3: Static Claim Flagging
Функция static_claim_flagging(response, cards). Разбор response на абзацы (\n\n), regex поиск specifics (статьи, приказы, ГОСТ, ТР ТС, OSHA, ISO, NFPA, IOGP, ҚР ДСМ + метрические значения с единицами).
Если в абзаце есть specifics, но ни один не найден в cards → flag. Threshold: ≥3 flagged абзацев ИЛИ ≥30% от paragraphs_with_specifics → warning.
При триггере дописывается общее уведомление:
ℹ️ Часть конкретных ссылок в ответе не подтверждена источниками этого поиска. Это не значит что они ложные — возможно, упомянутые НПА просто не попали в выдачу retrieval'а. Перепроверь по официальной нормативке.
Pipeline после деплоя
Generate response (Sonnet/Opus)
↓ context уже содержит [НОРМА]/[ОПЕРАТИВНО]/[МЕТОДИЧКА]/[КОММЕНТАРИЙ] метки (Слой 1)
↓
fix_gender / fix_ru_to_kz / fix_url_wrapping
↓
verify_citations ← FACTUAL grounding (existing, март 21)
↓
reasoning_validate (5 правил, Слой 2A)
→ если violation → apply_corrections
• severity=warning → append correction в конец
• severity=critical → prepend short_alert в начало + append correction
→ log в logs/reasoning_validations_YYYY-MM-DD.jsonl
↓
[если NOT npa_hits] positive_citation_check + static_claim_flagging (Слои 2B + 3)
→ если any issue → append UNSUPPORTED_CITATION_NOTICE
↓
_split_for_telegram → send
Skip 2B+3 когда SQL grounding активен (npa_hits != []) — там structured ground truth, доп. проверки только шумят.
Сравнение с Harvey/OpenEvidence
| Компонент | Harvey | OpenEvidence | Lyumi (после 2 мая) |
|---|---|---|---|
| Reasoning rules вне промпта | ✅ | ✅ | ✅ |
| Детерминированная проверка | ✅ | ✅ | ✅ |
| Логирование для калибровки | ✅ | ✅ | ✅ JSONL |
| Source authority labels | ✅ | ✅ | ✅ Слой 1 |
| Severity stratification | ✅ | ✅ | ✅ warning/critical |
| Claim decomposition (LLM) | ✅ | ✅ | ❌ (вместо — static regex Слой 3) |
| Cross-reference на claim level | ✅ | ✅ | ⚠️ partial (Слой 2B negative + 3 positive) |
| Custom reasoning model | ✅ | ✅ | ❌ (overkill для нас) |
| Expert feedback loop | ✅ | ✅ | ⚠️ один эксперт (Камал) |
Coverage ~75% от full Harvey стека. Без LLM-decomposition coverage ниже, но trade-off в обмен на 0 latency / 0 cost / детерминированность.
Eval — honest assessment
3 evaluator'а написаны:
- evaluate_reasoning_layer.py — 12 синтетических сценариев. Pass на всех 4 слоях.
- compare_sonnet_opus.py — 5 reasoning queries, 2 модели. На 4/5 EQUAL, 1 OPUS лучше (ТР ТС 019). Opus ×6.5 дороже.
- compare_vanilla_lyumi_opus.py + evaluate_accuracy.py — 5 reasoning queries × 3 конфигурации (Vanilla / Lyumi Sonnet / Lyumi Opus) с positive/negative markers regex.
Итог 3-way eval (n=5):
| Конфигурация | Avg score | CORRECT | PARTIAL | INCORRECT | $/тест |
|---|---|---|---|---|---|
| Vanilla Sonnet | 0.65 | 60% | 20% | 20% | $0.04 |
| Lyumi Sonnet | 0.72 | 40% | 40% | 20% | $0.06 |
| Lyumi Opus | 0.73 | 40% | 60% | 0% | $0.67 |
Inconclusive. Avg score у Lyumi выше, но CORRECT count такой же. n=5 недостаточно для статистики, regex markers легко сдвигаются на ±0.2 при изменении формулировки.
Что устойчиво: - 0% INCORRECT у Lyumi Opus — единственная stable метрика. Opus + наша архитектура не выдаёт катастрофических ответов на reasoning. - Live prod test ТР ТС 019 (Камал, 2 мая) — Lyumi ответила правильно, использовала [НОРМА] метку, признала прошлую ошибку. Это prod-доказательство что слои работают, micro-test не показывает. - Vanilla дала 1 INCORRECT (наряд-допуск 30 суток вместо 1 года). Lyumi Sonnet тоже INCORRECT (формулировка), но в обоих случаях по существу не катастрофа.
Lesson: не тюнить markers до желаемой картинки. После 2-3 итераций tuning'а stop'нул — иначе cooking the books.
Тех-долг и TODO
Не сделано (отложено на Sprint A после 9 мая):
- G_reasoning категория в eval — собрать 30-50 reasoning queries с manual ground truth
- Расширить reasoning_rules до 15-20 правил (текущие 5 покрывают только hot-spots)
- Claim decomposition через Haiku — Слой 3 в полной форме (сейчас static regex). Только если G_reasoning eval покажет нужду.
- HSE expert feedback loop — формализация раз в неделю, после 50+ активных юзеров.
- lyumi/reasoning_validator.py → больше тестов с edge cases формулировок
Калибровка через observation week: JSONL логи reasoning_validations_*.jsonl за 7-14 дней дадут реальные данные для расширения rules.
Дельта по файлам
Новые:
- source_authority.py (13 KB)
- reasoning_validator.py (40 KB) — 5 правил + 3 функции (validate, positive_citation_check, static_claim_flagging)
- evaluate_reasoning_layer.py — 12-сценарный eval всех 4 слоёв
- compare_sonnet_opus.py — 2-way модель сравнение
- compare_vanilla_lyumi_opus.py — 3-way конфигурация сравнение
- evaluate_accuracy.py — accuracy с ground truth markers
Изменены:
- bot.py — импорт + 3 hook'а (text, photo, multi-photo) после verify_citations
- retriever.py — format_context_for_llm добавляет authority tags
- llm.py — секция «МЕТКИ АВТОРИТЕТА ИСТОЧНИКОВ» в системном промпте
Связи
- [[lyumi/reasoning_layer_design]] — дизайн (этот sprint реализовал минимум, дизайн остаётся актуальным для слоёв 4-5)
- [[lyumi/v4_structured_retrieval]] — factual grounding (parallel pillar)
- [[lyumi/sprints/2026-05-01-sql-acgih-ax41]] — предыдущий sprint (SQL grounding)
- [[lyumi/strategy_2026]] — общая стратегия
Что сегодня было ещё (контекст)
Reasoning layer — это четвёртый sprint дня. До него: 1. Sprint 1 multi-language SQL detection + cross-code disambig (4:00) 2. Reflection regression hot-fix — skip on SQL hits (4:30) 3. Multi-photo split + retry-aware Telegram send (5:00) 4. log_analysis.py — 4-channel pipeline breakdown (10:00) 5. Document Intelligence v2 — idempotency + photo verify + multi-doc stack + OCR Tesseract (12:00) 6. Reasoning Layer minimum (12:30-15:00)
Все шесть в проде. День.