From fa843bc367c2bbfe1e0235faf30a2b8ac0ef638e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 20 Jun 2026 11:04:33 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BF=D1=80=D0=BE=D0=BC=D0=BF=D1=82?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=BB=D1=8F=20initial=20(ADD)=20=D0=B8=20=D0=94?= =?UTF-8?q?=D0=A1=20(diff)=20(v1.0.75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/llm_prompt.py | 83 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 deploy/llm_prompt.py diff --git a/deploy/llm_prompt.py b/deploy/llm_prompt.py new file mode 100644 index 0000000..b7cddd0 --- /dev/null +++ b/deploy/llm_prompt.py @@ -0,0 +1,83 @@ +"""llm_prompt.py — Формирование промпта для LLM-анализа ДС.""" + + +def build_prompt(current_spec: list, doc_text: str) -> str: + is_first = len(current_spec) == 0 + if is_first: + return _build_initial(doc_text) + else: + return _build_diff(current_spec, doc_text) + + +def _build_initial(doc_text: str) -> str: + return f"""Ты — анализатор договоров облачного провайдера. + +Ниже текст спецификации услуг из ПЕРВОГО документа (базовый договор). +Извлеки ВСЕ строки спецификации в JSON-массив. + +Верни СТРОГО JSON без пояснений. Не используй markdown-блоки. + +ФОРМАТ: +{{ + "mode": "partial", + "ops": [ + {{"action": "ADD", "new_row": {{"name": "полное наименование", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}}, "comment": ""}} + ] +}} + +ПРАВИЛА: +1. Извлеки КАЖДУЮ строку таблицы спецификации как отдельную ADD-операцию. +2. Пропускай итоговые строки ("Итого...") и строки с подписями. +3. Если ячейка пустая — ставь null (не пиши 0). +4. price, qty, sum — ЧИСЛА, не строки. + +ТЕКСТ ДОКУМЕНТА: +--- +{doc_text} +---""" + + +def _build_diff(current_spec: list, doc_text: str) -> str: + lines = [] + for r in current_spec: + lines.append( + f"[hash: {r.get('hash', '?')}] {r.get('name', '?')} | " + f"цена={r.get('price', '')} | объём={r.get('qty', '')} | " + f"сумма={r.get('sum', '')} | начало={r.get('date_start', '')}" + ) + spec_text = "\n".join(lines) + + return f"""Ты — анализатор допсоглашений к договорам облачного провайдера. + +У тебя есть текущая спецификация услуг и текст нового допсоглашения (ДС). +Твоя задача — определить, какие изменения вносит ДС в текущую спецификацию. + +Верни СТРОГО JSON без пояснений. Не используй markdown-блоки. + +ФОРМАТ: +{{ + "mode": "partial" | "full_replace", + "ops": [ + {{"action": "ADD", "new_row": {{"name": "...", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}}, "comment": "..."}}, + {{"action": "UPDATE", "target_hash": "hash_строки", "new_values": {{"price": число}}, "comment": "..."}}, + {{"action": "DELETE", "target_hash": "hash_строки", "comment": "..."}}, + {{"action": "UNRESOLVED", "new_values": {{"name": "...", "price": число, ...}}, "reason": "почему не смог сопоставить"}} + ] +}} + +ПРАВИЛА: +1. mode = "full_replace" — если в тексте есть фразы: «в следующей редакции», «заменить приложение», «излагается в следующей редакции». При full_replace — опиши ВСЕ новые строки как ADD. +2. mode = "partial" — если ДС меняет только отдельные строки. +3. Для UPDATE/DELETE — находи соответствующую строку по target_hash. +4. new_values в UPDATE — только ИЗМЕНЁННЫЕ поля (не все). +5. Если не можешь однозначно сопоставить строку — action: UNRESOLVED с reason. +6. Пропускай итоговые строки и подписи. +7. price, qty, sum — ЧИСЛА (не строки). + +ТЕКУЩАЯ СПЕЦИФИКАЦИЯ: +{spec_text} + +ТЕКСТ ДОПСОГЛАШЕНИЯ: +--- +{doc_text} +---"""