diff --git a/History/2026-08-20-sonnet-llm-qa.md b/History/2026-08-20-sonnet-llm-qa.md new file mode 100644 index 0000000..93a3f9e --- /dev/null +++ b/History/2026-08-20-sonnet-llm-qa.md @@ -0,0 +1,105 @@ +# 2026-08-20 — Вопрос-ответ по LLM-чанкингу (Q&A) + +Контекст: сервис обфускации, двухступенчатый детект ПДн (regex + LLM). Пользователь +хочет, чтобы LLM обрабатывал ВСЕ данные, а не урезку 8000 символов. +Обсуждение с Sonnet: основной вопрос + 6 уточнений. + +Предыдущие файлы: +- History/2026-08-20-sonnet-query-llm-pattern.md — рекомендуемая схема +- History/2026-08-20-sonnet-query-llm-followup.md — уточнения перед внедрением + +--- + +## Исходная проблема +`scan_llm_ner` (drhider/scanner.py) обрезал: +- каждый файл до первых 3000 символов (`t[:3000]`); +- всё вместе до 8000 символов (`combined[:8000]`); +- один вызов LLM на весь набор. + +Итог: при 299 файлах LLM «видел» только ~8000 символов (~0.1–0.3% данных). +Пользователь: «всё затевалось чтобы ЛЛМ ВСЁ обрабатывал». → убрать урезку. + +## Рекомендуемая схема (Sonnet, основной ответ) +- Отказаться от глобального батчинга → «один файл = один или несколько вызовов LLM», параллельно. +- Чанкинг per-file: чанк 6000 символов, overlap 400–500 символов. +- Дедупликация по файлу + верификация (найденная строка реально в тексте → отсекает галлюцинации). +- Батчинг мелких файлов — опционально, второй шаг. + +## Уточнения (У.1–У.6) и ответы Sonnet + +### У.1 — Алгоритм разбивки на чанки +Приоритет границ: `\n\n` → `\n` → `. ` → `? ` → `! ` → `; ` → `, ` → ` ` (пробел — крайний). +Ищем ближайшую границу с конца в диапазоне `[size//2 .. size]`. Жёсткий разрез — только если нет ни одного пробела. +Overlap: отступить назад на overlap, найти начало слова. +Псевдокод: +```python +BOUNDARIES = ['\n\n', '\n', '. ', '? ', '! ', '; ', ', ', ' '] +def split_into_chunks(text, size=6000, overlap=500): + chunks=[]; start=0 + while start < len(text): + end=min(start+size, len(text)) + if end==len(text): + chunks.append(text[start:]); break + cut=None + for b in BOUNDARIES: + pos=text.rfind(b, start+size//2, end) + if pos!=-1: + cut=pos+len(b); break + if cut is None: cut=end + chunks.append(text[start:cut]) + ov=max(start, cut-overlap) + space=text.find(' ', ov) + start=(space+1) if (space!=-1 and space3× от базового — снижать). + +--- + +## Итоговое решение (для внедрения, ждёт «делай») +1. `scanner.py scan_llm_ner`: убрать `[:3000]`/`[:8000]`; + по каждому файлу из `all_texts` разбить на чанки (6000/500, split_into_chunks); + параллельные вызовы (ThreadPool, concurrency 4, крупные вперёд); + для каждого чанка — полный промпт + «regex-найденные»; + собрать сущности, normalize-дедуп, verify против полного текста файла, в mapping. +2. Добавить хелперы split_into_chunks / normalize_entity / verify в scanner.py. +3. Не дублировать regex-найденное; считать токены/время LLM корректно (llm_active/elapsed). +4. Прогнать тест на малом наборе (не прод): сколько сущностей добавит LLM, время. + +Статус: НЕ внедрено (ждёт «делай»). Версия при внедрении — v0.0.57. diff --git a/History/2026-08-20-sonnet-query-llm-followup.md b/History/2026-08-20-sonnet-query-llm-followup.md new file mode 100644 index 0000000..7a040b8 --- /dev/null +++ b/History/2026-08-20-sonnet-query-llm-followup.md @@ -0,0 +1,46 @@ +# Промпт для Sonnet — уточнения по LLM-чанкингу (перед внедрением) + +Ты дал рекомендуемую схему (чанкинг 6000 chars, overlap 500, параллельность 4–8, +дедуп + верификация). Перед внедрением уточни практические детали. Отвечай КРАТКО, +по пунктам, с конкретикой (алгоритм/числа). Без воды. + +## У.1 — Разбивка на чанки: по чему резать +Ты сказал «разбивать по абзацам/предложениям, не по символам». Но в PDF-выгрузках +текст часто сливается в один-два гигантских «абзаца» (нет \n). Дай КОНКРЕТНЫЙ алгоритм +разбивки произвольного текста (в т.ч. без \n) на чанки ~6000 символов с overlap 500: +- по каким границам резать в приоритете (\n\n, \n, '. ', '? ', '; ', потом символы)? +- как применить overlap без разрыва слова? +- псевдокод функции split_into_chunks(text, size=6000, overlap=500). + +## У.2 — Дедупликация при overlap и некорректном форматировании +LLM может вернуть один и тот же value из двух чанков с РАЗНЫМ форматированием +(лишние пробелы, регистр, тире/дефис). Проверки `strip().lower()` недостаточно. +Как нормализовать перед дедупом (например, сжать пробелы, унифицировать кавычки/тире)? +Дай конкретный набор нормализаций для русских деловых текстов. + +## У.3 — Верификация против исходника +Проверку «найденного значения нет в исходном тексте → отбросить (галлюцинация)» делать +против ИСХОДНОГО извлечённого текста файла (до обфускации), верно? Или против чанка? +Уточни, что есть «источник истины» для верификации, и как быть, если value найден в +нескольких чанках, но слегка отличается (нормализованное сравнение). + +## У.4 — Малые файлы: нужен ли батчинг в первую итерацию +Набор сотен мелких файлов (<1000 символов). Батчинг 5–8 в один вызов ускорит, но усложнит +(деградация разметки, больший ответ). Для ПЕРВОЙ итерации можно ли обойтись «1 файл = 1 вызов» +без батчинга, даже если файлов сотни? Оцени, насколько медленнее будет без батчинга. + +## У.5 — Промпт при пофайловом чанкинге +Текущий промпт (в коде) содержит блоки «Already captured by regex» + большой список правил. +При пофайловом/почанковом вызове эти блоки повторяются в каждом запросе. Оставить промпт +как есть (меняя только текст), или его надо упростить под чанк (одна папка уже нашла — regex)? +Повлияет ли повтор правил на качество/токены? + +## У.6 — Прогресс и общее время +300 файлов, многие >6000 символов → сотни LLM-вызовов × по 1–2с = минуты-десятки минут. +Пользователь готов ждать (LLM своя). Но оцени: с параллельностью 4 и ~5 вызовов/файл на +крупных — реальное общее время для набора ~300 файлов (в т.ч. 270 мелких + 30 крупных). +И: не упрёмся ли в rate-limit своего LLM-эндпоинта при 4-8 параллельных. + +## Формат +- По каждому У.1–У.6: короткий ответ + конкретика (алгоритм/числа/решение). +- Если что-то некритично для первой итерации — скажи явно «можно первым шагом упростить так». diff --git a/History/2026-08-20-sonnet-query-llm-pattern.md b/History/2026-08-20-sonnet-query-llm-pattern.md new file mode 100644 index 0000000..f14267d --- /dev/null +++ b/History/2026-08-20-sonnet-query-llm-pattern.md @@ -0,0 +1,30 @@ +# Промпт для Sonnet — вопрос по использованию LLM (без ссылок на файлы) + +## Контекст +У нас сервис обфускации документов (удаление персональных данных из русских деловых документов). +Двухступенчатый детект ПДн: +1. Regex-паттерны (телефоны, ИНН, email, типовые формы — быстро, локально, дёшево). +2. LLM (общая NER-подстраховка) — сейчас обрабатывает только урезанный фрагмент: + - от каждого файла берутся первые ~3000 символов, + - всё вместе обрезается до ~8000 символов, + - выполняется ОДИН вызов LLM на весь набор. + +Пользователь хочет, чтобы LLM обрабатывал ВСЕ данные (ВСЕ файлы целиком), не только первые 8000 символов. +LLM — своя, стоимость не важна. Важно качество распознавания ПДн и разумная архитектура вызова. + +## Вопрос +Как лучше организовать вызов LLM, чтобы он БЕЗ потерь покрывал все документы (набор из сотен файлов, каждый до нескольких десятков страниц), сохраняя качество распознавания русских деловых документов? + +Ограничения/требования к ответу: +- Не предлагай менять модель/external API — LLM своя, она остаётся. +- Подумай про: пакетирование файлов (размер батча), максимальную длину контекста на запрос, + как избежать обрезки данных, как не потерять качество при больших объёмах, + параллельность запросов (если релевантна), дедупликацию найденного между пакетами. +- Дай конкретную рекомендуемую схему (числа: размер батча, лимит символов на файл/пакет, параллельность). +- Оцени риски: токены/время, качество, риск пропуска. +- Сухо, по делу, без воды. + +## Формат +- Секция «Рекомендуемая схема» — конкретный план с числами. +- Секция «Риски» — что может пойти не так и как смягчить. +- Секция «Минимум для старта» — если хочется просто и быстро, что достаточно сделать первым шагом.