docs: Q&A Sonnet по LLM-чанкингу (схема + 6 уточнений, решение к внедрению)
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This commit is contained in:
@@ -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 space<cut) else ov
|
||||||
|
return chunks
|
||||||
|
```
|
||||||
|
|
||||||
|
### У.2 — Нормализация для дедупа
|
||||||
|
```python
|
||||||
|
def normalize_entity(s):
|
||||||
|
s=s.strip()
|
||||||
|
s=re.sub(r'\s+',' ',s)
|
||||||
|
s=s.lower()
|
||||||
|
s=re.sub(r'[«»“”‘’"\' ]','"',s) # кавычки → "
|
||||||
|
s=re.sub(r'[—–−-]','-',s) # тире → -
|
||||||
|
s=s.replace('\u00ad','') # мягкий перенос
|
||||||
|
s=s.replace('ё','е') # Ё→Е (OCR/PDF)
|
||||||
|
return s
|
||||||
|
```
|
||||||
|
Дедуп: `seen=set()` — добавлять, только если `normalize_entity(v) not in seen`.
|
||||||
|
|
||||||
|
### У.3 — Источник истины для верификации
|
||||||
|
Полный текст файла ДО обфускации (не чанк).
|
||||||
|
```python
|
||||||
|
def verify(entity, full_text):
|
||||||
|
if entity in full_text: return True
|
||||||
|
return normalize_entity(entity) in normalize_entity(full_text)
|
||||||
|
```
|
||||||
|
Если сущность из двух чанков с разным форматированием — нормализованный ключ одинаков → дедуп оставит один; хранить длиннее (или первый).
|
||||||
|
|
||||||
|
### У.4 — Батчинг мелких в первой итерации
|
||||||
|
НЕ нужен. 270 мелких: 1 файл=1 вызов ≈ 270 вызовов ≈ 101с при 4 потоках; батч по 5 → 54 вызова ≈ 20с. Разница ~80с несущественна. Батчинг — вторая итерация.
|
||||||
|
|
||||||
|
### У.5 — Промпт при чанкинге
|
||||||
|
Блок «Already captured by regex» ОСТАВИТЬ в каждом вызове (снижает дублирование с regex).
|
||||||
|
Блок правил — как есть в первую итерацию (если API с system/user — правила в system, текст чанка в user; иначе как сейчас).
|
||||||
|
Первый шаг: промпт не менять, только текстовый блок = чанк.
|
||||||
|
|
||||||
|
### У.6 — Общее время и rate-limit
|
||||||
|
Расчёт: 270 мелких (1 вызов) + 30 крупных (≈4 чанка) = 390 вызовов.
|
||||||
|
- 4 потока × 2с: 390/4×2 ≈ 195с ≈ 3.5 мин.
|
||||||
|
- 8 потоков: ~1.5–2 мин.
|
||||||
|
Rate-limit свой LLM: внешнего нет, ограничение GPU. Начать с 4, поднять до 8, если latency не растёт (>3× от базового — снижать).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Итоговое решение (для внедрения, ждёт «делай»)
|
||||||
|
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.
|
||||||
@@ -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: короткий ответ + конкретика (алгоритм/числа/решение).
|
||||||
|
- Если что-то некритично для первой итерации — скажи явно «можно первым шагом упростить так».
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Промпт для Sonnet — вопрос по использованию LLM (без ссылок на файлы)
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
У нас сервис обфускации документов (удаление персональных данных из русских деловых документов).
|
||||||
|
Двухступенчатый детект ПДн:
|
||||||
|
1. Regex-паттерны (телефоны, ИНН, email, типовые формы — быстро, локально, дёшево).
|
||||||
|
2. LLM (общая NER-подстраховка) — сейчас обрабатывает только урезанный фрагмент:
|
||||||
|
- от каждого файла берутся первые ~3000 символов,
|
||||||
|
- всё вместе обрезается до ~8000 символов,
|
||||||
|
- выполняется ОДИН вызов LLM на весь набор.
|
||||||
|
|
||||||
|
Пользователь хочет, чтобы LLM обрабатывал ВСЕ данные (ВСЕ файлы целиком), не только первые 8000 символов.
|
||||||
|
LLM — своя, стоимость не важна. Важно качество распознавания ПДн и разумная архитектура вызова.
|
||||||
|
|
||||||
|
## Вопрос
|
||||||
|
Как лучше организовать вызов LLM, чтобы он БЕЗ потерь покрывал все документы (набор из сотен файлов, каждый до нескольких десятков страниц), сохраняя качество распознавания русских деловых документов?
|
||||||
|
|
||||||
|
Ограничения/требования к ответу:
|
||||||
|
- Не предлагай менять модель/external API — LLM своя, она остаётся.
|
||||||
|
- Подумай про: пакетирование файлов (размер батча), максимальную длину контекста на запрос,
|
||||||
|
как избежать обрезки данных, как не потерять качество при больших объёмах,
|
||||||
|
параллельность запросов (если релевантна), дедупликацию найденного между пакетами.
|
||||||
|
- Дай конкретную рекомендуемую схему (числа: размер батча, лимит символов на файл/пакет, параллельность).
|
||||||
|
- Оцени риски: токены/время, качество, риск пропуска.
|
||||||
|
- Сухо, по делу, без воды.
|
||||||
|
|
||||||
|
## Формат
|
||||||
|
- Секция «Рекомендуемая схема» — конкретный план с числами.
|
||||||
|
- Секция «Риски» — что может пойти не так и как смягчить.
|
||||||
|
- Секция «Минимум для старта» — если хочется просто и быстро, что достаточно сделать первым шагом.
|
||||||
Reference in New Issue
Block a user