docs: переезд в docs/ — SPRINTS, architecture (v1/v2/v3), examples

- SPRINTS.md → docs/SPRINTS.md
- GRAPH_ARCHITECTURE.md → docs/architecture/GRAPH_ARCHITECTURE_v1.md
- GRAPH_ARCHITECTURE_v2.md → docs/architecture/GRAPH_ARCHITECTURE_v2.md
- Новый docs/architecture/GRAPH_ARCHITECTURE_v3.md (билингв. термины + ссылки на примеры)
- Новые docs/examples/: 01 базовая запись, 02 цена во время записи (soft vs hard),
  03 запись ребёнка (guard), 04 простой general_info
- README обновлён: ссылки на новые пути + раздел «Документация»

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AR 15 M4
2026-04-26 21:50:16 +05:00
parent 82bba34937
commit f348570b1b
9 changed files with 2427 additions and 3 deletions
+966
View File
@@ -0,0 +1,966 @@
# Пример 04 · Простые информационные запросы (general_info)
> Связано с [`GRAPH_ARCHITECTURE_v3.md`](../architecture/GRAPH_ARCHITECTURE_v3.md), разделы 1, 2, 6. Демонстрирует **простейший случай**: ветка `general_info` без машины состояний — одна реплика пациента, ретривер, ответ, `done`. Никаких слотов, никаких tool calls, никаких guard'ов. Это та точка, с которой реально удобно запускать систему: классификатор + RAG + одна шаблонная ветка.
## О чём этот пример
Это сборник коротких самостоятельных диалогов по одному типу запросов — общая информация (часы, адрес, проезд, телефоны, что брать с собой, чего клиника не делает). У всех у них одна и та же траектория:
1. **Роутер** классифицирует реплику как `general_info`.
2. Запускается ветка `general_info`, шаг `answer` (он же единственный).
3. **Ретривер** достаёт чанки из соответствующего раздела вики (`/wiki/contacts/**`, `/wiki/branches/**`, `/wiki/hours/**`, `/wiki/preparation/**`, `/wiki/services/_general/**`).
4. Модель формирует ответ строго по найденным чанкам.
5. `state_after = done`.
Цель — на каждой реплике показать:
- **решение роутера** (router) — какое намерение распознано;
- **работу ретривера** (retriever) — какие чанки достаются и из каких разделов вики;
- **структурированный ответ модели** — `reply`, `state_after`, `slots_updated`;
- **итоговое состояние треда** (thread_state).
Слоты у `general_info` практически не используются — одно поле `info_topic` модель проставляет, чтобы ретривер при последующих репликах в том же треде не лез в посторонние разделы. Если в ходе ответа возникает запрос на запись («а можно тогда записаться?»), это уже **жёсткий переход** в `new_booking` (см. примеры 05+).
## Конфигурация ветки general_info
```yaml
intent: general_info
steps:
answer:
wiki_sources:
- /wiki/contacts/**
- /wiki/branches/**
- /wiki/hours/**
- /wiki/preparation/**
- /wiki/services/_general/**
tools: []
next: done
done:
wiki_sources: []
tools: []
```
## Начальное состояние (общее для всех примеров ниже)
Перед каждым диалогом в `thread_state` пусто — это первый ход свежего треда:
```json
{
"thread_id": "T-9XXX",
"current_intent": null,
"current_step": null,
"slots": {},
"handoff_count": 0,
"suspended_intent": null,
"resumable_step": null,
"resumable_slots": null
}
```
После ответа `current_intent = "general_info"`, `current_step = "done"`, и тред физически не закрывается — пациент может задать следующий вопрос. Если он задаст ещё один информационный вопрос, тот пройдёт по той же траектории заново (роутер его классифицирует, ветка отработает и снова уйдёт в `done`).
---
# A · Часы работы
## A.1 · «Во сколько вы открываетесь?»
**Решение роутера:** `general_info`. Чёткий вопрос про режим работы, никаких других намерений.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/hours/**')`.
- top-k: 2.
- Достались чанки:
- `hours/clinic-czetkina-9.md` (score 0.93) — режим работы филиала на Клары Цеткин 9.
- `hours/clinic-pirogova.md` (score 0.71) — режим работы филиала «Пирогова».
Модели достаточно первого чанка. Второй пригодится, если пациент уточнит про другой адрес.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Здравствуйте! Клиника на Клары Цеткин 9 открывается в 8:00 в будни и в 9:00 в выходные. Закрываемся в 21:00 в будни и в 18:00 в выходные. Если интересует другой филиал — подскажу отдельно.",
"state_after": "done",
"slots_updated": {
"info_topic": "hours"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "hours"
}
}
```
---
## A.2 · «До скольки работает клиника на Цеткина?»
**Решение роутера:** `general_info`. Конкретика про филиал — это всё ещё информационный вопрос, не запись.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/hours/**', '/wiki/branches/**')`.
- top-k: 2.
- Достались чанки:
- `hours/clinic-czetkina-9.md` (score 0.94) — режим работы.
- `branches/czetkina-9.md` (score 0.78) — карточка филиала с адресом.
Реплика содержит «Цеткина» — в индексе совпадает по названию филиала, поэтому ретривер уверенно вытаскивает нужную карточку.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Клиника на Клары Цеткин 9 работает до 21:00 в будни и до 18:00 в выходные.",
"state_after": "done",
"slots_updated": {
"info_topic": "hours",
"branch_mention": "Клары Цеткин 9"
}
}
```
Заметим: `branch_mention` модель кладёт в слоты, чтобы при возможном следующем вопросе («а проезд как?») ретривер мог сразу дать чанк именно про этот филиал, а не про другой.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "hours",
"branch_mention": "Клары Цеткин 9"
}
}
```
---
## A.3 · «У вас по выходным открыто?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/hours/**')`.
- top-k: 2.
- Достались чанки:
- `hours/clinic-czetkina-9.md` (score 0.86).
- `hours/clinic-pirogova.md` (score 0.81).
Здесь пациент не уточнил филиал, поэтому в ответе разумно перечислить оба.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Да, мы работаем и в субботу, и в воскресенье. На Клары Цеткин 9 — с 9:00 до 18:00, в Клинике Пирогова — с 9:00 до 17:00. Расписание конкретного врача может отличаться, при записи подскажу.",
"state_after": "done",
"slots_updated": {
"info_topic": "hours"
}
}
```
Замечание: фраза «расписание конкретного врача может отличаться» — это **зашитая в промпт ветки оговорка** (canned disclaimer), не результат RAG. Часы работы клиники и слоты конкретного врача — разные вещи, и модель должна на этом не зависать.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "hours"
}
}
```
---
## A.4 · «Завтра праздник, вы работаете?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/hours/**')`.
- top-k: 3.
- Достались чанки:
- `hours/clinic-czetkina-9.md` (score 0.74) — обычный режим.
- `hours/holidays-2026.md` (score 0.42) — праздничный календарь, низкий score (документ короткий, неточное совпадение по датам).
- `hours/clinic-pirogova.md` (score 0.71).
Score праздничного чанка — ниже порога уверенности (0.50). По правилу шага: **если ни один чанк не покрывает запрос полностью — отвечаем шаблоном «уточним и перезвоним»**, а не выдумываем расписание праздников.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "В обычные дни мы работаем с 8:00 до 21:00. В праздничные дни режим может меняться. Подскажите, пожалуйста, на какую дату вы планируете визит — я уточню у регистратуры и сообщу вам в течение дня.",
"state_after": "done",
"slots_updated": {
"info_topic": "hours",
"needs_followup": true
}
}
```
Слот `needs_followup: true` — сигнал для оператора в инбоксе, что тред нужно дозакрыть человеком. На дашборде такие треды попадают в очередь «требуется уточнение».
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "hours",
"needs_followup": true
}
}
```
---
# B · Адрес и филиалы
## B.1 · «Где вы находитесь?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/branches/**')`.
- top-k: 3.
- Достались чанки:
- `branches/_index.md` (score 0.88) — общий перечень филиалов.
- `branches/czetkina-9.md` (score 0.71).
- `branches/pirogova.md` (score 0.69).
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "У нас два адреса в Перми: ул. Клары Цеткин, 9 (основная клиника, ЛОР, сурдология, отоневрология) и Клиника Пирогова, ул. Героев Хасана 24 (терапия, аллергология, телемед). Подсказать, какой ближе к вам или какая специальность нужна?",
"state_after": "done",
"slots_updated": {
"info_topic": "branches"
}
}
```
Финальный встречный вопрос — лёгкий, без обязательного ответа. Если пациент проигнорирует, тред просто останется в `done`. Если ответит — пойдёт ещё один проход через роутера.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "branches"
}
}
```
---
## B.2 · «Какой у вас точный адрес на Цеткина?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/branches/**')`.
- top-k: 1.
- Достались чанки:
- `branches/czetkina-9.md` (score 0.96) — карточка филиала: адрес, ориентиры, вход.
Высокий score, точный матч — достаточно одного чанка.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Адрес: г. Пермь, ул. Клары Цеткин, 9. Вход со стороны двора, ориентир — голубая вывеска «Клиника Ухо, Горло, Нос». Регистратура на первом этаже.",
"state_after": "done",
"slots_updated": {
"info_topic": "branches",
"branch_mention": "Клары Цеткин 9"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "branches",
"branch_mention": "Клары Цеткин 9"
}
}
```
---
## B.3 · «Сколько у вас филиалов в городе?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/branches/**')`.
- top-k: 1.
- Достались чанки:
- `branches/_index.md` (score 0.90) — общий перечень.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "В Перми у нас два филиала: основная клиника на ул. Клары Цеткин, 9, и Клиника Пирогова на ул. Героев Хасана, 24. Других филиалов и онлайн-клиник в других городах у нас нет — приём только в этих двух.",
"state_after": "done",
"slots_updated": {
"info_topic": "branches"
}
}
```
Замечание: пациенты иногда спрашивают «вы же есть в Москве?», путая клинику с одноимёнными. Ответ намеренно явно говорит «других филиалов нет» — чтобы не оставить надежду на не-существующее.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "branches"
}
}
```
---
## B.4 · «Как вас на Яндекс Картах искать?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/branches/**', '/wiki/contacts/**')`.
- top-k: 2.
- Достались чанки:
- `contacts/online-presence.md` (score 0.84) — карточка организации, ссылки на 2ГИС / Я.Карты.
- `branches/_index.md` (score 0.66).
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "На Яндекс Картах ищите по запросу «Клиника Ухо Горло Нос Пермь» — найдутся обе наших точки: на Клары Цеткин 9 и на Героев Хасана 24. Можно сразу проложить маршрут оттуда.",
"state_after": "done",
"slots_updated": {
"info_topic": "branches"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "branches"
}
}
```
---
# C · Как добраться и парковка
## C.1 · «На метро как до вас доехать?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/branches/**')`.
- top-k: 2.
- Достались чанки:
- `branches/czetkina-9.md` (score 0.62) — раздел «как добраться».
- `branches/pirogova.md` (score 0.59).
Ни один чанк не упоминает метро — потому что **в Перми метро нет**. Ретривер вытащил то, что про транспорт ближе всего по теме.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "В Перми метро нет, поэтому удобнее всего добираться на автобусе, троллейбусе или такси. До Клары Цеткин 9 — остановка «Площадь Восстания» (5 минут пешком), маршруты 14, 32, 36, 67. До Клиники Пирогова — остановка «Героев Хасана», маршруты 19, 27. Подсказать что-то конкретнее, если знаете, откуда поедете?",
"state_after": "done",
"slots_updated": {
"info_topic": "transit"
}
}
```
Это полезный пограничный кейс: вопрос содержит **ложное допущение** (метро). Промпт ветки требует мягко поправить пациента, не оставляя его в недоумении, и сразу предложить рабочую альтернативу.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "transit"
}
}
```
---
## C.2 · «Парковка у вас есть?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/branches/**')`.
- top-k: 2.
- Достались чанки:
- `branches/czetkina-9.md` (score 0.81) — раздел «парковка».
- `branches/pirogova.md` (score 0.78) — раздел «парковка».
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Да, парковка есть на обоих филиалах. На Клары Цеткин 9 — небольшая бесплатная парковка во дворе клиники, обычно есть свободные места. У Клиники Пирогова — общая платная парковка торгового центра рядом, въезд с ул. Героев Хасана. Подсказать, какой филиал вам удобнее?",
"state_after": "done",
"slots_updated": {
"info_topic": "parking"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "parking"
}
}
```
---
## C.3 · «Как пешком от центра дойти до Цеткина?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/branches/**')`.
- top-k: 1.
- Достались чанки:
- `branches/czetkina-9.md` (score 0.79) — раздел «как добраться» → подраздел «пешком».
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "От Комсомольского проспекта дойти можно за 12–15 минут: вниз по Сибирской до Клары Цеткин, направо до дома 9. Удобнее всего ориентироваться на Яндекс Карты — там же увидите маршрут.",
"state_after": "done",
"slots_updated": {
"info_topic": "transit",
"branch_mention": "Клары Цеткин 9"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "transit",
"branch_mention": "Клары Цеткин 9"
}
}
```
---
# D · Контакты
## D.1 · «Дайте телефон регистратуры»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/contacts/**')`.
- top-k: 1.
- Достались чанки:
- `contacts/registratura.md` (score 0.95) — телефоны регистратуры по филиалам.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Регистратура на Клары Цеткин 9 — +7 (342) 204-00-00. Регистратура Клиники Пирогова — +7 (342) 204-67-16. Звонят с 8:00 до 21:00 в будни. Если удобнее, можно записаться прямо здесь — я помогу.",
"state_after": "done",
"slots_updated": {
"info_topic": "contacts"
}
}
```
Хвостовая фраза «можно записаться прямо здесь» — это soft-cross-sell, разрешённая в ветке `general_info`. Пациент свободен её проигнорировать; если откликнется — следующая реплика уйдёт в `new_booking` через роутера.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "contacts"
}
}
```
---
## D.2 · «У вас WhatsApp есть?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/contacts/**')`.
- top-k: 1.
- Достались чанки:
- `contacts/messengers.md` (score 0.93) — мессенджеры, в которых отвечает клиника.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Да, мы отвечаем в WhatsApp по номеру +7 (342) 204-00-00 — это тот же телефон регистратуры. Также есть Telegram: @ugn_clinic. Уведомления о приёме приходят на тот номер, который вы оставите при записи.",
"state_after": "done",
"slots_updated": {
"info_topic": "contacts"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "contacts"
}
}
```
---
## D.3 · «А сайт у вас какой?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/contacts/**')`.
- top-k: 1.
- Достались чанки:
- `contacts/online-presence.md` (score 0.91).
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Сайт клиники: ugn-clinic.ru. Там можно посмотреть прайс, расписание врачей и записаться. Если хотите — могу записать вас прямо сейчас, без перехода на сайт.",
"state_after": "done",
"slots_updated": {
"info_topic": "contacts"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "contacts"
}
}
```
---
# E · Документы и подготовка к приёму
## E.1 · «Что нужно с собой принести?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/preparation/**')`.
- top-k: 2.
- Достались чанки:
- `preparation/what-to-bring.md` (score 0.92) — общий список.
- `preparation/лор-первичный.md` (score 0.65) — для ЛОРа отдельно.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "На приём возьмите паспорт (для пациентов с 14 лет) и, если есть, выписки или результаты предыдущих обследований по теме визита. Полис ОМС не нужен — клиника частная. Если есть конкретная специальность, к которой записываетесь, могу подсказать, что взять именно для неё.",
"state_after": "done",
"slots_updated": {
"info_topic": "preparation"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "preparation"
}
}
```
---
## E.2 · «Полис ОМС у вас принимают?»
**Решение роутера:** `general_info`. Это ещё не вопрос про цену в чистом виде — пациент уточняет принцип, не считает деньги. Но граница тонкая, см. замечание ниже.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/preparation/**', '/wiki/services/_general/**')`.
- top-k: 2.
- Достались чанки:
- `preparation/what-to-bring.md` (score 0.74) — там есть строка про ОМС.
- `services/_general/payment-options.md` (score 0.81) — формы оплаты, ДМС.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Нет, мы частная клиника и по полису ОМС не работаем. Принимаем оплату наличными, картой и переводом. По ДМС — работаем не со всеми страховыми, нужно уточнять по конкретной компании.",
"state_after": "done",
"slots_updated": {
"info_topic": "preparation"
}
}
```
Замечание: следующая реплика «а с какими страховыми работаете?» — это уже **жёсткий переход** в `price_question` (там есть отдельная ветвь про ДМС-покрытие). На текущей реплике мы аккуратно касаемся темы, но не углубляемся.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "preparation"
}
}
```
---
## E.3 · «Сыну 16, можно без меня прийти?»
**Решение роутера:** `general_info`. Вопрос про порядок приёма несовершеннолетних — это организационный, не медицинский и не запись.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/preparation/**', '/wiki/_faq/**')`.
- top-k: 2.
- Достались чанки:
- `_faq/teenagers-without-parents.md` (score 0.94) — соответствует в точности.
- `preparation/what-to-bring.md` (score 0.61).
Чанк `teenagers-without-parents.md` — это статья из раздела ЧаВо (см. источник `vrachi-kliniki-...md`, пункт «Если пациенту 16-17 лет»), индексированная в общий пул `general_info`.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "С 15 лет подросток может прийти на приём один и сам подписать договор — нужен паспорт. Если родители смогут позже подъехать или прислать документы — это тоже подойдёт. Записать сына на удобное время?",
"state_after": "done",
"slots_updated": {
"info_topic": "preparation"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "preparation"
}
}
```
---
# F · Что клиника не делает
## F.1 · «У вас можно сделать бронхоскопию?»
**Решение роутера:** `general_info`. По формальному признаку — пациент спрашивает про услугу. Но это не запись и не цена, это уточнение «делаете ли вы такое в принципе». Промпт роутера явно относит подобные формулировки к `general_info`, чтобы не запускать механизм записи на услугу, которой нет.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/services/_general/**')`.
- top-k: 2.
- Достались чанки:
- `services/_general/not-performed.md` (score 0.97) — список процедур, которые НЕ проводятся.
- `services/_general/scope.md` (score 0.62) — общий профиль клиники.
Чанк `not-performed.md` собран из соответствующего раздела вики (см. источник «Процедуры, которые в клинике НЕ проводятся» в `skripty-...md`). Бронхоскопия там есть в явном виде.
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Бронхоскопию у нас не делают — это исследование с эндоскопом по слизистым трахеи и бронхов, его нужно искать в специализированных пульмонологических центрах. Если вас беспокоит кашель или дыхание — мы можем записать к пульмонологу на консультацию, он уже подскажет, куда дальше.",
"state_after": "done",
"slots_updated": {
"info_topic": "scope_of_services"
}
}
```
Это **важный сценарий для honesty-eval'а**: модель не должна выдумывать «да, конечно, запишем» в надежде, что пациент придёт и разберётся на месте. Прямой отказ + полезная альтернатива.
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "scope_of_services"
}
}
```
---
## F.2 · «Криодеструкцию миндалин делаете?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/services/_general/**')`.
- top-k: 1.
- Достались чанки:
- `services/_general/not-performed.md` (score 0.94).
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Нет, криодеструкцию миндалин в нашей клинике не проводят. Если вас беспокоит хронический тонзиллит — можем записать на приём к ЛОР-врачу: он осмотрит, предложит варианты лечения из тех, что у нас доступны, и подскажет, куда обратиться, если понадобится именно эта процедура.",
"state_after": "done",
"slots_updated": {
"info_topic": "scope_of_services"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "scope_of_services"
}
}
```
---
## F.3 · «Электрофорез носа можно у вас?»
**Решение роутера:** `general_info`.
**Активная ветка / шаг:** `general_info` / `answer`.
**Ретривер:**
- Фильтр: `doc_path STARTS WITH any('/wiki/services/_general/**')`.
- top-k: 1.
- Достались чанки:
- `services/_general/not-performed.md` (score 0.91) — электрофорез в списке «не проводится».
**Вызовы инструментов:** нет.
**Структурированный ответ модели:**
```json
{
"reply": "Нет, электрофорез у нас не делают. Из физиопроцедур по ЛОР-профилю мы предлагаем промывания, орошения, лечебные ингаляции — если интересно, могу подробнее рассказать или сразу записать на приём.",
"state_after": "done",
"slots_updated": {
"info_topic": "scope_of_services"
}
}
```
**`thread_state` после хода:**
```json
{
"current_intent": "general_info",
"current_step": "done",
"slots": {
"info_topic": "scope_of_services"
}
}
```
---
## Что показал этот пример
- **Одна реплика — один проход.** Ветка `general_info` не имеет машины состояний: единственный «осмысленный» шаг `answer` сразу уводит в `done`. Это самый дешёвый путь в системе и логичная точка запуска первой версии бота.
- **Слоты почти не используются.** Поле `info_topic` нужно ровно для того, чтобы при следующем вопросе в том же треде ретривер знал, в какую сторону смотреть в первую очередь. Опционально — `branch_mention`, чтобы помнить, про какой филиал шла речь.
- **Ретривер делает основную работу.** Все факты в ответе должны быть из чанков, не из памяти модели. Если score лучшего чанка ниже порога — модель уходит в шаблон «уточним и перезвоним» с `needs_followup=true` (см. A.4).
- **Пограничные кейсы важнее, чем happy path.** Метро в Перми (которого нет — C.1), услуги, которых клиника не предоставляет (F), праздничные дни без чанка (A.4) — именно на них модель ломается чаще всего и именно их полезно держать в eval-наборе с самого начала.
- **Soft cross-sell разрешён, но мягкий.** Хвост «могу записать прямо сейчас» — нормальная практика для информационных ответов. Жёстко продавать запись — нет.
## Что важно проверять в eval-наборе на этом примере
- Роутер **не уводит** информационные вопросы в `new_booking` или `price_question` (классическая ошибка — на «сколько у вас стоит» в чистом виде это уже `price_question`, а на «какие у вас услуги» — `general_info`; границы должны быть чёткими).
- Все факты в `reply` находимы в одном из чанков, попавших в контекст. Хорошая метрика — `groundedness` (доля утверждений, для которых есть прямое подтверждение в источниках).
- При отсутствии релевантных чанков модель отвечает шаблоном «уточним», а не выдумывает (см. A.4 и логика `needs_followup`).
- Ответы не превышают 3–4 предложений. `general_info` — не место для лекций.
- Слот `info_topic` проставлен корректно (`hours`, `branches`, `transit`, `parking`, `contacts`, `preparation`, `scope_of_services`) — эта же таксономия используется в дашборде «о чём чаще всего спрашивают».
- На пограничных кейсах (метро в Перми, услуги, которых нет, праздничные дни) ответ не выдаёт ложных утверждений — это критичный безопасный минимум для запуска.