v3.2.0: новый LLM-промпт (STEP 1/2, фикс. типы, NOT PII, already_found), убран хардкод НУБЕС
Deploy drhider / validate (push) Waiting to run

This commit is contained in:
2026-07-12 11:11:43 +04:00
parent 0c2b636d31
commit 5c426cb037
18 changed files with 682 additions and 14 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ class TwoPassObfuscator:
if text and not text.startswith("[DOC binary"):
scanner.scan_regex(text, self._mapping)
# LLM-сканирование (медленное, сетевое — только если есть клиент)
# LLM-сканирование (получает уже найденное regex'ом чтобы не дублировать)
if self._llm_client:
scanner.scan_llm_ner(all_texts, self._mapping, self._llm_client)
+27 -12
View File
@@ -66,10 +66,6 @@ def scan_regex(text: str, mapping: Dict[str, str]) -> None:
if not original or original in mapping:
continue
# «НУБЕС» — Исполнитель, НЕ заменяем
if re.search(r'НУБЕС|NUBES', original, re.IGNORECASE):
continue
mapping[original] = generate_company(original)
# ── ФИО (Фамилия И.О.) ──
@@ -99,20 +95,39 @@ def scan_llm_ner(all_texts: Dict[str, str], mapping: Dict[str, str], llm_client)
f"FILE: {fname}\n{t[:3000]}" for fname, t in all_texts.items()
)
# ── Универсальный промпт — без фиксированного списка типов ──
# ── Универсальный промпт: STEP 1 (Исполнитель) + STEP 2 (ПДн) ──
already_found = "\n".join(f"- {k}" for k in mapping.keys()) if mapping else "(none)"
prompt = (
"You are a PII (Personally Identifiable Information) detector. "
"Find ALL sensitive or private information in the text below.\n\n"
"You are a PII (Personally Identifiable Information) detector for Russian legal documents.\n\n"
"STEP 1 — Identify the service provider:\n"
"Read the contract and find which company/person is labeled 'Исполнитель' "
"(the party providing services). "
"This party's own name is NOT PII — do NOT include it in results.\n\n"
"STEP 2 — Find all PII that is NOT already captured below.\n\n"
"Already captured by regex (skip these exact strings):\n"
f"{already_found}\n\n"
"Return a JSON array. Each item must have:\n"
' - "type": short snake_case label describing the entity\n'
' (e.g. person_name, phone, email, address, inn, company, passport,\n'
' contract_number, employee_id, bank_account — WHATEVER you see)\n'
' - "type": MUST be one of: person_name, company, phone, email, address,\n'
' inn, kpp, ogrn, bik, passport, contract_number, bank_account, other_id\n'
' Use "other_id" only if nothing else fits. If unsure — do NOT include.\n'
' - "value": the EXACT string from the text (copy verbatim)\n\n'
"NOT PII — do NOT include these (legal roles and terms, not private data):\n"
"- Roles: Исполнитель, Заказчик, Стороны, Сторона, Покупатель, Поставщик\n"
"- Titles: Генеральный директор, Директор, Президент, действующего на основании\n"
"- Legal terms: Услуги, Договор, Приложение, Спецификация, НДС, Устава\n"
"- The 'Исполнитель' company name identified in STEP 1\n\n"
"Rules:\n"
"- Copy value VERBATIM — same spaces, punctuation, case as in text\n"
"- If same value appears multiple times — include only once\n"
"- Invent the type label yourself based on what you see\n"
"- Same value include only once\n"
"- Return ONLY the JSON array, no other text, no markdown wrapping\n\n"
f"Text:\n{combined[:8000]}"
)