Files
drhider/History/sonnet/2026-08-20-sonnet-llm-qa.md
T

6.4 KiB
Raw Blame History

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, найти начало слова. Псевдокод:

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 — Нормализация для дедупа

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 — Источник истины для верификации

Полный текст файла ДО обфускации (не чанк).

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.52 мин. 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.