Перейти к содержанию

Verbatim Quote Mode — Design Document

Статус: Design phase. Foundation из Citations API Phase 2 готов (3 мая) — metadata из stream пишется в usage_out["citations"]. Данная фича — next sprint, лежит поверх.

Предыдущие депы: - ✅ #67 Sprint Direct — Anthropic direct API - ✅ #69 Citations API integration — server-side enforcement, documents в user message - ✅ Citations Phase 2 foundation — stream parser извлекает CitationBlock metadata

Проблема (которую решаем)

Paraphrase distortion: Sonnet является интерпретатором — перефразирует источник, даже когда цитирует. Риск: - Бот говорит «Приказ »1019 требует обучения не реже раза в год» - Источник реально говорит: «Обучение проводится периодически, но не реже чем в 12 месяцев, в случае »»экстренных обстоятельств — чаще» - Интерпретация верная, но nuance утрачен

Citations API это не решает — он просто привязывает cited_text к document_index. Но cited_text может быть любым фрагментом documents — не обязательно то что перефразировано в ответе.

Решение — verbatim quote mode

Для каждого factual claim в ответе бот выдаёт ДВЕ версии: 1. Интерпретация (как сейчас) — перефразированный текст Люми в её стиле 2. Verbatim quote (новое) — дословная цитата из источника (cited_text из Citations API)

Schema (output от LLM)

{
  "claims": [
    {
      "claim": "Приказ 1019 требует обучения не реже раза в год",
      "verbatim_quote": "Обучение проводится периодически, но не реже чем в 12 месяцев",
      "source": "Приказ »1019 МЗ РК, п.6",
      "document_index": 0,
      "is_paraphrased": true,
      "distortion_risk": "low"
    }
  ],
  "unverified_claims": [
    {"claim": "...", "reason": "no supporting citation"}
  ]
}

Реализация — два пути

Путь A: structured outputs в system prompt

Добавить в LYUMI_SYSTEM_PROMPT инструкцию «отвечай в JSON schema X». Антропик Sonnet 4.5 поддерживает structured outputs.

Плюсы: простая реализация, работает на уровне промпта. Минусы: ломает стиль Люми (бот раньше отвечал прозой, теперь в JSON → рендер в Telegram страждет). Требует переписать весь промпт + cache miss (новый prefix).

Путь B: post-processing вторым LLM call (Haiku verifier)

Основной Sonnet отвечает как сейчас (прозой). Потом Haiku verifier проходит по ответу: 1. Extract claims (claim_1, claim_2...) 2. Для каждого — find в retrieved chunks ближайший verbatim fragment 3. Добавить в ответ «[источник: "...\nПриказ 1019 п.6]» к кластеру фразы

Плюсы: не ломает прозу Люми. Работает с Citations API metadata напрямую. Минусы: +1 LLM call (Haiku ~$0.005). +200-500мс latency. Risk: extract claims это тоже интерпретация.

Путь C: hybrid — использовать Citations API metadata без доп LLM call

Citations API уже возвращает cited_text + позицию в ответе Люми. Mechanical: 1. Получить citations из usage_out["citations"] 2. Для каждой citation — found в ответе положение рядом с перефразированным фрагментом 3. Инжект «[источник: "verbatim_text"]» после фрагмента

Плюсы: не ломает прозу. Ноль доп LLM. Дешёво в production. Минусы: поиск «рядом с перефразированным» — fuzzy matching, может ошибаться.

Рекомендация

Путь C — hybrid mechanical использует уже имеющуюся инфраструктуру (Citations Phase 2 metadata) без доп расходов.

MVP foundation: 1. В bot.py после streaming добавить inject_verbatim_quotes(response, citations) helper 2. Для каждой citation: - Найти в response position предложения, которое лежит ближе всего к cited_text по смыслу (cosine similarity через bge-m3, или fuzzy match) - Инжектировать \nв» оригинале: "{cited_text[:100]}..." ({document_title})_ после этого предложения 3. Feature flag USE_VERBATIM_QUOTES — default off, gradual rollout

Интеграция

Place in pipeline (bot.py):

response = full_response or None
# ... existing post-processing (gender, ru_kz, urls, numerical, reasoning_validator)

# === Sprint Truth #72: Verbatim quote mode ===
if USE_VERBATIM_QUOTES and stream_usage.get("citations"):
    response = inject_verbatim_quotes(response, stream_usage["citations"])
    log_metric("verbatim_injected", count=len(citations), ...)

Связанные wiki

  • lyumi/sprints/2026-05-02-sprint-truth-roadmap — изначальный plan
  • lyumi/sprints/2026-05-03-sprint-truth-day — журнал большого дня (Citations Phase 2 реализован)
  • lyumi/trust_first_principle — философия

Реализация — план на будущее

Sprint A (Verbatim Foundation): 1.5-2 часа 1. inject_verbatim_quotes() helper в bot.py 2. fuzzy match через SequenceMatcher (stdlib) или token overlap (легко) 3. USE_VERBATIM_QUOTES feature flag 4. log_metric("verbatim_injected", count, samples)

Sprint B (Refinement): 1-2 дня по логам 1. Посмотреть verbatim_injected из прода — где fuzzy match промахивается 2. Тюнинг: cosine similarity vs token overlap vs SequenceMatcher 3. Output formatting: инлайн footnote vs блок в конце

Sprint C (Polish): время от времени 1. UI: прячет verbatim в expandable section (Telegram inline button «Показать источник») 2. Combine с reasoning_validator и numerical_verification — unified verification report

Измерение success

  • Прямые: в metrics_*.jsonl видим сколько verbatim quotes инжектировано
  • Качественные: feedback rate 👍 выростает для вопросов с verbatim vs без
  • Trust: юзеры пишут «вижу фрагмент из Приказа — легче доверять» (явный feedback)

Риски

  1. Fuzzy match ошибается — врезает verbatim не в то место → плохой UX. Митигируется фича-флагом и логами для review.
  2. Telegram рендер — вставки в блок italic могут сломать parse_mode. Тестировать parse_mode=None vs Markdown.
  3. Перегруженный ответ — 5 citations × 100 chars verbatim = +500 chars в ответе. UX-вопрос как показать.
  4. Citations не приходят — если USE_CITATIONS_API=false, нет metadata. Foundation работает только когда оба флага ON.

Не делать

  • Не двигаться в Путь A (structured outputs) — ломает стиль Люми
  • Не в Путь B (Haiku verifier) — +costs, +latency без ясного win vs C
  • Не делать verbatim mode «всегда-он» — хорошо бы feature flag, gradual rollout