v1.0.111: llm_prompt.py reads active prompt from DB via Lucee API, fallback to hardcoded
This commit is contained in:
+61
-22
@@ -1,16 +1,14 @@
|
|||||||
"""llm_prompt.py — Формирование промпта для LLM-анализа ДС."""
|
"""llm_prompt.py — Формирование промпта для LLM-анализа ДС.
|
||||||
|
Читает активный промпт из БД (через Lucee API). При ошибке — fallback на хардкод."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
|
||||||
def build_prompt(current_spec: list, doc_text: str) -> str:
|
LUCEE_URL = os.environ.get("LUCEE_URL", "https://contractor.luceek8s.dev.nubes.ru")
|
||||||
is_first = len(current_spec) == 0
|
|
||||||
if is_first:
|
|
||||||
return _build_initial(doc_text)
|
|
||||||
else:
|
|
||||||
return _build_diff(current_spec, doc_text)
|
|
||||||
|
|
||||||
|
# ── Fallback-промпты (если БД недоступна) ──────────────────────
|
||||||
|
|
||||||
def _build_initial(doc_text: str) -> str:
|
FALLBACK_EXTRACT = """Ты — анализатор договоров облачного провайдера.
|
||||||
return f"""Ты — анализатор договоров облачного провайдера.
|
|
||||||
|
|
||||||
Ниже текст спецификации услуг из ПЕРВОГО документа (базовый договор).
|
Ниже текст спецификации услуг из ПЕРВОГО документа (базовый договор).
|
||||||
Извлеки ВСЕ строки спецификации в JSON-массив.
|
Извлеки ВСЕ строки спецификации в JSON-массив.
|
||||||
@@ -36,18 +34,7 @@ def _build_initial(doc_text: str) -> str:
|
|||||||
{doc_text}
|
{doc_text}
|
||||||
---"""
|
---"""
|
||||||
|
|
||||||
|
FALLBACK_DIFF = """Ты — анализатор допсоглашений к договорам облачного провайдера.
|
||||||
def _build_diff(current_spec: list, doc_text: str) -> str:
|
|
||||||
lines = []
|
|
||||||
for i, r in enumerate(current_spec):
|
|
||||||
lines.append(
|
|
||||||
f"[id: r{i+1}] {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"""Ты — анализатор допсоглашений к договорам облачного провайдера.
|
|
||||||
|
|
||||||
У тебя есть текущая спецификация услуг и текст нового допсоглашения (ДС).
|
У тебя есть текущая спецификация услуг и текст нового допсоглашения (ДС).
|
||||||
Твоя задача — определить, какие изменения вносит ДС в текущую спецификацию.
|
Твоя задача — определить, какие изменения вносит ДС в текущую спецификацию.
|
||||||
@@ -75,9 +62,61 @@ def _build_diff(current_spec: list, doc_text: str) -> str:
|
|||||||
7. price, qty, sum — ЧИСЛА (не строки).
|
7. price, qty, sum — ЧИСЛА (не строки).
|
||||||
|
|
||||||
ТЕКУЩАЯ СПЕЦИФИКАЦИЯ:
|
ТЕКУЩАЯ СПЕЦИФИКАЦИЯ:
|
||||||
{spec_text}
|
{spec_current}
|
||||||
|
|
||||||
ТЕКСТ ДОПСОГЛАШЕНИЯ:
|
ТЕКСТ ДОПСОГЛАШЕНИЯ:
|
||||||
---
|
---
|
||||||
{doc_text}
|
{doc_text}
|
||||||
---"""
|
---"""
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_prompt(role: str) -> dict | None:
|
||||||
|
"""Получить активный промпт из БД. Возвращает {id, body} или None."""
|
||||||
|
try:
|
||||||
|
with httpx.Client(http2=True, timeout=10, verify=False) as client:
|
||||||
|
resp = client.get(
|
||||||
|
f"{LUCEE_URL}/prompt.cfm",
|
||||||
|
params={"action": "get_active", "role": role},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
if data.get("ok") and data.get("body"):
|
||||||
|
return {"id": data.get("id", ""), "body": data["body"]}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_spec_text(current_spec: list) -> str:
|
||||||
|
"""Перечисление строк спецификации для подстановки в {spec_current}."""
|
||||||
|
lines = []
|
||||||
|
for i, r in enumerate(current_spec):
|
||||||
|
lines.append(
|
||||||
|
f"[id: r{i+1}] {r.get('name', '?')} | "
|
||||||
|
f"цена={r.get('price', '')} | объём={r.get('qty', '')} | "
|
||||||
|
f"сумма={r.get('sum', '')} | начало={r.get('date_start', '')}"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def build_prompt(current_spec: list, doc_text: str) -> str:
|
||||||
|
"""
|
||||||
|
Формирует промпт для LLM.
|
||||||
|
1. Пробует получить активный промпт из БД (Lucee API).
|
||||||
|
2. При неудаче — fallback на хардкод.
|
||||||
|
Возвращает готовый текст промпта.
|
||||||
|
"""
|
||||||
|
is_first = len(current_spec) == 0
|
||||||
|
role = "extract" if is_first else "diff"
|
||||||
|
|
||||||
|
db = _fetch_prompt(role)
|
||||||
|
if db:
|
||||||
|
template = db["body"]
|
||||||
|
else:
|
||||||
|
template = FALLBACK_EXTRACT if is_first else FALLBACK_DIFF
|
||||||
|
|
||||||
|
# Подстановка плейсхолдеров
|
||||||
|
result = template.replace("{doc_text}", doc_text)
|
||||||
|
result = result.replace("{spec_current}", _build_spec_text(current_spec))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -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.110 — Lucee</span></span>
|
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.111 — Lucee</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
Reference in New Issue
Block a user