v1.0.166: classify prompt v2 (LLM-suggested) + safe JSON parse with auto-repair

This commit is contained in:
2026-06-24 09:04:31 +04:00
parent fe1f76fa1d
commit c2c92d0a37
3 changed files with 32 additions and 11 deletions
+2 -2
View File
@@ -46,7 +46,7 @@ def _ensure_classify_prompt():
execute(""" execute("""
INSERT INTO prompts (role, name, body, is_active, notes) VALUES ( INSERT INTO prompts (role, name, body, is_active, notes) VALUES (
'classify', 'default-v1', 'classify', 'default-v1',
'Ты — классификатор договорных документов. Ниже фрагмент текста документа.\n\nОпредели:\n1. doc_type: "contract" (договор), "supplement" (допсоглашение/допник), "specification" (спецификация/приложение), "other"\n2. own_number: номер ЭТОГО документа (например "МЭС-123-2024" или "ДС №2")\n3. parent_number: номер родительского договора (для допников/спецификаций — "к Договору №..."). Для самого договора — null.\n4. doc_date: дата документа в формате YYYY-MM-DD\n5. counterparty: название контрагента (из поля "Заказчик", "Арендатор" или аналогичного)\n6. confidence: "ok" если уверен, "low" если сомневаешься\n\nВерни СТРОГО JSON без пояснений:\n{\n "doc_type": "...",\n "own_number": "..." или null,\n "parent_number": "..." или null,\n "doc_date": "YYYY-MM-DD" или null,\n "counterparty": "..." или null,\n "confidence": "ok" или "low"\n}\n\nДОКУМЕНТ:\n---\n{header_text}\n---', 'Ты — система классификации договорных документов. Проанализируй текст и верни СТРОГО ВАЛИДНЫЙ JSON ОДНОЙ СТРОКОЙ (без переносов строк, без markdown, без лишних пробелов в начале/конце).\n\nПоля:\n- doc_type: "contract" (договор) / "supplement" (допсоглашение) / "specification" (спецификация/приложение) / "other"\n- own_number: номер ЭТОГО документа, строка без лишних пробелов (или null)\n- parent_number: номер родительского договора из фраз «к Договору №...» (или null)\n- doc_date: дата в YYYY-MM-DD. «27 февраля 2026» → 2026-02-27 (или null)\n- counterparty: полное название контрагента (Заказчик/Арендатор), без сокращений (или null)\n\nПример вывода:\n{"doc_type":"supplement","own_number":"1","parent_number":"01300_2","doc_date":"2026-02-27","counterparty":"АО XXX003"}\n\nДОКУМЕНТ:\n---\n{header_text}\n---',
true, 'Авто-создан: classify prompt' true, 'Авто-создан: classify prompt v2 — LLM сам предложил'
) )
""") """)
+29 -8
View File
@@ -50,13 +50,7 @@ def _call_llm_classify(header_text):
data = resp.json() data = resp.json()
raw = data.get("choices", [{}])[0].get("message", {}).get("content", "") raw = data.get("choices", [{}])[0].get("message", {}).get("content", "")
# LLM может обернуть JSON в markdown-блок ```json ... ``` return _safe_json_parse(raw)
json_text = raw
if "```json" in json_text:
json_text = json_text.split("```json")[1].split("```")[0]
elif "```" in json_text:
json_text = json_text.split("```")[1].split("```")[0]
return json.loads(json_text.strip())
def classify_batch(batch_id): def classify_batch(batch_id):
@@ -103,7 +97,34 @@ def classify_batch(batch_id):
return {"ok": True, "total": total, "done": done, "failed": failed} return {"ok": True, "total": total, "done": done, "failed": failed}
def _smart_extract(elements_json): def _safe_json_parse(raw):
"""Parse LLM response, fixing common JSON errors (multiline, markdown, unescaped chars)."""
if not raw:
raise ValueError("empty LLM response")
# Strip markdown wrappers
text = raw.strip()
if "```json" in text:
text = text.split("```json")[1].split("```")[0].strip()
elif "```" in text:
text = text.split("```")[1].split("```")[0].strip()
# Try strict parse first
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# Fix common issues: unescaped newlines inside strings, unterminated strings
# Replace literal newlines inside JSON values
import re as _re
# Collapse multiline to single line
text = _re.sub(r'\n\s*', ' ', text)
# Remove trailing commas before closing braces
text = _re.sub(r',\s*}', '}', text)
text = _re.sub(r',\s*]', ']', text)
return json.loads(text)
""" """
Умная выжимка текста для классификации (решение Q3 от Opus). Умная выжимка текста для классификации (решение Q3 от Opus).
+1 -1
View File
@@ -75,7 +75,7 @@
<body> <body>
<div class="topbar"> <div class="topbar">
<img src="/nubes-logo.svg" alt="Nubes"> <img src="/nubes-logo.svg" alt="Nubes">
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.165 — Lucee</span></span> <span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.166 — Lucee</span></span>
</div> </div>
<div class="content"> <div class="content">