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— изначальный planlyumi/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)
Риски
- Fuzzy match ошибается — врезает verbatim не в то место → плохой UX. Митигируется фича-флагом и логами для review.
- Telegram рендер — вставки в блок italic могут сломать parse_mode. Тестировать parse_mode=None vs Markdown.
- Перегруженный ответ — 5 citations × 100 chars verbatim = +500 chars в ответе. UX-вопрос как показать.
- 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