feat(sprint6b-D): soft-insertion counter + message meta_json

- thread_state.soft_insertion_count: растёт при боковом ответе (soft_insertion=true
  в STATE_JSON без смены шага/слотов), сбрасывается при продвижении или handoff
- При soft_insertion_count >= 3 в системный промпт ветки добавляется SOFT_INSERTION_NUDGE
  — явная инструкция вернуть пациента к вопросу текущего шага
- state_machine.parse_branch_response читает флаг soft_insertion из STATE_JSON
- Новая колонка message.meta_json: {router_intent_code, served_intent_code, step_code, events}
  — хранит снимок маршрутизации каждой реплики ассистента
- «Песочница»: бейджи событий (sticky / soft_insertion / hard_handoff / resumed /
  routing_loop / validation_blocked) над каждым ответом ассистента

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AR 15 M4
2026-04-25 20:24:22 +05:00
parent 3c71372ec8
commit 85c3ec0222
12 changed files with 257 additions and 13 deletions
+16 -1
View File
@@ -16,15 +16,30 @@
3. Ровно одна служебная строка, начинающаяся с `STATE_JSON:` и валидным JSON-объектом:
```
STATE_JSON: {"state_after": "<код_следующего_шага>", "slots_updated": {"slot1": "value1", ...}}
STATE_JSON: {"state_after": "<код_следующего_шага>", "slots_updated": {"slot1": "value1"}, "soft_insertion": false}
```
- `state_after` — код шага, на котором пациент окажется ПОСЛЕ твоей реплики. Должен быть из списка допустимых переходов текущего шага (тебе это передаётся в блоке `[ТЕКУЩЕЕ СОСТОЯНИЕ]`).
- `slots_updated` — только те слоты, которые узнал из этой реплики. Старые не перечисляй.
- `soft_insertion``true`, если ты ответил на короткий боковой вопрос пациента (см. ниже), не двигая сценарий вперёд. Иначе — `false` или поле опускается.
- Значения — строки или примитивы. Неизвестное не придумывай.
Служебная строка `STATE_JSON:` вырезается парсером, пациент её не видит.
## Боковые вопросы (soft-insertion)
Пациент посреди записи может спросить что-то «параллельное», не относящееся к текущему шагу: цена приёма, адрес клиники, часы работы, длительность приёма, какие документы взять. Это не повод уходить в другую ветку — отвечай сам, на одну-две фразы, опираясь на выдержки из базы знаний (если поданы), и тут же мягко возвращай пациента к вопросу текущего шага.
В таком ответе:
- `state_after` оставь равным текущему шагу (мы остаёмся на месте).
- `slots_updated` — пустой объект (новые сценарные слоты не собрали).
- Поставь `soft_insertion: true`.
Пример: на шаге `qualify` пациент спросил «а сколько стоит приём?» — ответь коротко по цене, верни внимание на повод и специалиста, в STATE_JSON: `{"state_after": "qualify", "slots_updated": {}, "soft_insertion": true}`.
Если в системном сообщении присутствует блок `[ВОЗВРАТ К СЦЕНАРИЮ]` — это значит, пациент уже подряд несколько раз отклонялся в боковые вопросы. На этой реплике уверенно верни его к вопросу шага одной фразой и не давай длинных пояснений по сторонней теме.
## Условия выхода (exit conditions)
Важно: обычные бытовые жалобы пациента («болит горло», «болит ухо», «насморк», «плохо слышу», «болит зуб») — это **повод записи**, а не смена темы. Такие реплики внутри сценария не уводят в другие ветки — они фиксируются в слот `reason` и сопровождаются коротким выражением сочувствия на шаге `qualify`.