v1.0.113: prompt_version in spec_events — provenance (who created what with which prompt)
This commit is contained in:
+27
-12
@@ -7,6 +7,15 @@
|
|||||||
<cfset data = deserializeJSON(body)>
|
<cfset data = deserializeJSON(body)>
|
||||||
<cfset ops = structKeyExists(data, "ops") ? data.ops : []>
|
<cfset ops = structKeyExists(data, "ops") ? data.ops : []>
|
||||||
<cfset supplementId = structKeyExists(data, "supplement_id") ? data.supplement_id : "">
|
<cfset supplementId = structKeyExists(data, "supplement_id") ? data.supplement_id : "">
|
||||||
|
<cfset promptId = structKeyExists(data, "prompt_id") AND len(data.prompt_id) ? data.prompt_id : "">
|
||||||
|
|
||||||
|
<!--- ALTER TABLE: добавить prompt_version если ещё нет --->
|
||||||
|
<cftry>
|
||||||
|
<cfquery datasource="baza">
|
||||||
|
ALTER TABLE spec_events ADD COLUMN IF NOT EXISTS prompt_version UUID
|
||||||
|
</cfquery>
|
||||||
|
<cfcatch></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
<cfset result = {ok: false, error: ""}>
|
<cfset result = {ok: false, error: ""}>
|
||||||
|
|
||||||
@@ -44,7 +53,7 @@
|
|||||||
<!--- full_replace: явные DELETE для аудита (массовый INSERT) --->
|
<!--- full_replace: явные DELETE для аудита (массовый INSERT) --->
|
||||||
<cfif url.mode EQ "full_replace">
|
<cfif url.mode EQ "full_replace">
|
||||||
<cfquery datasource="baza">
|
<cfquery datasource="baza">
|
||||||
INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, comment, status)
|
INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, comment, status, prompt_version)
|
||||||
SELECT
|
SELECT
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
||||||
@@ -52,7 +61,8 @@
|
|||||||
'DELETE',
|
'DELETE',
|
||||||
name_hash,
|
name_hash,
|
||||||
'full_replace',
|
'full_replace',
|
||||||
'applied'
|
'applied',
|
||||||
|
<cfif len(promptId)><cfqueryparam value="#promptId#" cfsqltype="cf_sql_varchar">::uuid<cfelse>NULL</cfif>
|
||||||
FROM spec_current
|
FROM spec_current
|
||||||
WHERE contract_id = <cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">
|
WHERE contract_id = <cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">
|
||||||
</cfquery>
|
</cfquery>
|
||||||
@@ -101,7 +111,7 @@
|
|||||||
<cfset ds = (structKeyExists(nr, 'date_start') AND len(nr.date_start)) ? nr.date_start : ''>
|
<cfset ds = (structKeyExists(nr, 'date_start') AND len(nr.date_start)) ? nr.date_start : ''>
|
||||||
|
|
||||||
<cfquery name="evt" datasource="baza">
|
<cfquery name="evt" datasource="baza">
|
||||||
INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status)
|
INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status, prompt_version)
|
||||||
VALUES (
|
VALUES (
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
||||||
@@ -109,7 +119,8 @@
|
|||||||
'ADD',
|
'ADD',
|
||||||
<cfqueryparam value="#serializeJSON(nr)#" cfsqltype="cf_sql_varchar">::jsonb,
|
<cfqueryparam value="#serializeJSON(nr)#" cfsqltype="cf_sql_varchar">::jsonb,
|
||||||
<cfqueryparam value="#structKeyExists(op,'comment') ? op.comment : ''#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#structKeyExists(op,'comment') ? op.comment : ''#" cfsqltype="cf_sql_varchar">,
|
||||||
'applied'
|
'applied',
|
||||||
|
<cfif len(promptId)><cfqueryparam value="#promptId#" cfsqltype="cf_sql_varchar">::uuid<cfelse>NULL</cfif>
|
||||||
)
|
)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
</cfquery>
|
</cfquery>
|
||||||
@@ -134,7 +145,7 @@
|
|||||||
<cfif NOT structKeyExists(oldValues, th)>
|
<cfif NOT structKeyExists(oldValues, th)>
|
||||||
<!--- target_hash не найден — создаём UNRESOLVED вместо падения --->
|
<!--- target_hash не найден — создаём UNRESOLVED вместо падения --->
|
||||||
<cfquery datasource="baza">
|
<cfquery datasource="baza">
|
||||||
INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status)
|
INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status, prompt_version)
|
||||||
VALUES (
|
VALUES (
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
||||||
@@ -142,7 +153,8 @@
|
|||||||
'UNRESOLVED',
|
'UNRESOLVED',
|
||||||
<cfqueryparam value="#serializeJSON(structKeyExists(op,'new_values') ? op.new_values : {})#" cfsqltype="cf_sql_varchar">::jsonb,
|
<cfqueryparam value="#serializeJSON(structKeyExists(op,'new_values') ? op.new_values : {})#" cfsqltype="cf_sql_varchar">::jsonb,
|
||||||
<cfqueryparam value="UPDATE target_hash not found: #th#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="UPDATE target_hash not found: #th#" cfsqltype="cf_sql_varchar">,
|
||||||
'pending'
|
'pending',
|
||||||
|
<cfif len(promptId)><cfqueryparam value="#promptId#" cfsqltype="cf_sql_varchar">::uuid<cfelse>NULL</cfif>
|
||||||
)
|
)
|
||||||
</cfquery>
|
</cfquery>
|
||||||
<cfset summary.unresolved++>
|
<cfset summary.unresolved++>
|
||||||
@@ -155,7 +167,7 @@
|
|||||||
<cfset newDate = structKeyExists(nv, 'date_start') AND len(nv.date_start) ? nv.date_start : old.date_start>
|
<cfset newDate = structKeyExists(nv, 'date_start') AND len(nv.date_start) ? nv.date_start : old.date_start>
|
||||||
|
|
||||||
<cfquery name="evt" datasource="baza">
|
<cfquery name="evt" datasource="baza">
|
||||||
INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, new_values, comment, status)
|
INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, new_values, comment, status, prompt_version)
|
||||||
VALUES (
|
VALUES (
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
||||||
@@ -164,7 +176,8 @@
|
|||||||
<cfqueryparam value="#th#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#th#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#serializeJSON(nv)#" cfsqltype="cf_sql_varchar">::jsonb,
|
<cfqueryparam value="#serializeJSON(nv)#" cfsqltype="cf_sql_varchar">::jsonb,
|
||||||
<cfqueryparam value="#structKeyExists(op,'comment') ? op.comment : ''#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#structKeyExists(op,'comment') ? op.comment : ''#" cfsqltype="cf_sql_varchar">,
|
||||||
'applied'
|
'applied',
|
||||||
|
<cfif len(promptId)><cfqueryparam value="#promptId#" cfsqltype="cf_sql_varchar">::uuid<cfelse>NULL</cfif>
|
||||||
)
|
)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
</cfquery>
|
</cfquery>
|
||||||
@@ -186,7 +199,7 @@
|
|||||||
<cfelseif action EQ "DELETE">
|
<cfelseif action EQ "DELETE">
|
||||||
<cfset th = op.target_hash>
|
<cfset th = op.target_hash>
|
||||||
<cfquery datasource="baza">
|
<cfquery datasource="baza">
|
||||||
INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, comment, status)
|
INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, comment, status, prompt_version)
|
||||||
VALUES (
|
VALUES (
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
||||||
@@ -194,7 +207,8 @@
|
|||||||
'DELETE',
|
'DELETE',
|
||||||
<cfqueryparam value="#th#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#th#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#structKeyExists(op,'comment') ? op.comment : ''#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#structKeyExists(op,'comment') ? op.comment : ''#" cfsqltype="cf_sql_varchar">,
|
||||||
'applied'
|
'applied',
|
||||||
|
<cfif len(promptId)><cfqueryparam value="#promptId#" cfsqltype="cf_sql_varchar">::uuid<cfelse>NULL</cfif>
|
||||||
)
|
)
|
||||||
</cfquery>
|
</cfquery>
|
||||||
<cfquery datasource="baza">
|
<cfquery datasource="baza">
|
||||||
@@ -206,7 +220,7 @@
|
|||||||
|
|
||||||
<cfelseif action EQ "UNRESOLVED">
|
<cfelseif action EQ "UNRESOLVED">
|
||||||
<cfquery datasource="baza">
|
<cfquery datasource="baza">
|
||||||
INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status)
|
INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status, prompt_version)
|
||||||
VALUES (
|
VALUES (
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#supplementId#" cfsqltype="cf_sql_varchar">,
|
||||||
@@ -214,7 +228,8 @@
|
|||||||
'UNRESOLVED',
|
'UNRESOLVED',
|
||||||
<cfqueryparam value="#serializeJSON(structKeyExists(op,'new_values') ? op.new_values : {})#" cfsqltype="cf_sql_varchar">::jsonb,
|
<cfqueryparam value="#serializeJSON(structKeyExists(op,'new_values') ? op.new_values : {})#" cfsqltype="cf_sql_varchar">::jsonb,
|
||||||
<cfqueryparam value="#structKeyExists(op,'reason') ? op.reason : ''#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#structKeyExists(op,'reason') ? op.reason : ''#" cfsqltype="cf_sql_varchar">,
|
||||||
'pending'
|
'pending',
|
||||||
|
<cfif len(promptId)><cfqueryparam value="#promptId#" cfsqltype="cf_sql_varchar">::uuid<cfelse>NULL</cfif>
|
||||||
)
|
)
|
||||||
</cfquery>
|
</cfquery>
|
||||||
<cfset summary.unresolved++>
|
<cfset summary.unresolved++>
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ LUCEE_URL = "https://contractor.luceek8s.dev.nubes.ru"
|
|||||||
|
|
||||||
|
|
||||||
def call_llm(current_spec, doc_text):
|
def call_llm(current_spec, doc_text):
|
||||||
"""Вызов LLM API. Возвращает {mode, ops} или кидает исключение."""
|
"""Вызов LLM API. Возвращает ({mode, ops}, prompt_id) или кидает исключение."""
|
||||||
prompt = build_prompt(current_spec, doc_text)
|
prompt, prompt_id = build_prompt(current_spec, doc_text)
|
||||||
payload = {
|
payload = {
|
||||||
"model": LLM_MODEL,
|
"model": LLM_MODEL,
|
||||||
"messages": [{"role": "user", "content": prompt}],
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
@@ -39,7 +39,7 @@ def call_llm(current_spec, doc_text):
|
|||||||
json_text = json_text.split("```json")[1].split("```")[0]
|
json_text = json_text.split("```json")[1].split("```")[0]
|
||||||
elif "```" in json_text:
|
elif "```" in json_text:
|
||||||
json_text = json_text.split("```")[1].split("```")[0]
|
json_text = json_text.split("```")[1].split("```")[0]
|
||||||
return json.loads(json_text.strip())
|
return json.loads(json_text.strip()), prompt_id
|
||||||
|
|
||||||
|
|
||||||
class Handler(BaseHTTPRequestHandler):
|
class Handler(BaseHTTPRequestHandler):
|
||||||
@@ -215,7 +215,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"error": error[0], "time_s": elapsed})
|
"error": error[0], "time_s": elapsed})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
llm_result = result[0]
|
llm_result, prompt_id = result[0]
|
||||||
ops = llm_result.get("ops", [])
|
ops = llm_result.get("ops", [])
|
||||||
mode = llm_result.get("mode", "partial")
|
mode = llm_result.get("mode", "partial")
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
# Применить
|
# Применить
|
||||||
apply_resp = httpx.post(
|
apply_resp = httpx.post(
|
||||||
f"{LUCEE_URL}/apply_events.cfm?contract_id={cid}&mode={mode}",
|
f"{LUCEE_URL}/apply_events.cfm?contract_id={cid}&mode={mode}",
|
||||||
json={"supplement_id": sid, "ops": ops}, timeout=30
|
json={"supplement_id": sid, "ops": ops, "prompt_id": prompt_id}, timeout=30
|
||||||
)
|
)
|
||||||
if apply_resp.status_code == 200:
|
if apply_resp.status_code == 200:
|
||||||
ar = apply_resp.json()
|
ar = apply_resp.json()
|
||||||
@@ -258,7 +258,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
length = int(self.headers.get("Content-Length", 0))
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
body = json.loads(self.rfile.read(length))
|
body = json.loads(self.rfile.read(length))
|
||||||
try:
|
try:
|
||||||
result = call_llm(body.get("current_spec", []), body.get("doc_text", ""))
|
result, prompt_id = call_llm(body.get("current_spec", []), body.get("doc_text", ""))
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-Type", "application/json")
|
self.send_header("Content-Type", "application/json")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|||||||
@@ -101,12 +101,13 @@ def _build_spec_text(current_spec: list) -> str:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def build_prompt(current_spec: list, doc_text: str) -> str:
|
def build_prompt(current_spec: list, doc_text: str) -> tuple:
|
||||||
"""
|
"""
|
||||||
Формирует промпт для LLM.
|
Формирует промпт для LLM.
|
||||||
1. Пробует получить активный промпт из БД (Lucee API).
|
1. Пробует получить активный промпт из БД (Lucee API).
|
||||||
2. При неудаче — fallback на хардкод.
|
2. При неудаче — fallback на хардкод.
|
||||||
Возвращает готовый текст промпта.
|
Возвращает (текст_промпта, prompt_id).
|
||||||
|
prompt_id — UUID версии промпта из БД, или "" если fallback.
|
||||||
"""
|
"""
|
||||||
is_first = len(current_spec) == 0
|
is_first = len(current_spec) == 0
|
||||||
role = "extract" if is_first else "diff"
|
role = "extract" if is_first else "diff"
|
||||||
@@ -114,11 +115,13 @@ def build_prompt(current_spec: list, doc_text: str) -> str:
|
|||||||
db = _fetch_prompt(role)
|
db = _fetch_prompt(role)
|
||||||
if db:
|
if db:
|
||||||
template = db["body"]
|
template = db["body"]
|
||||||
|
prompt_id = db.get("id", "")
|
||||||
else:
|
else:
|
||||||
template = FALLBACK_EXTRACT if is_first else FALLBACK_DIFF
|
template = FALLBACK_EXTRACT if is_first else FALLBACK_DIFF
|
||||||
|
prompt_id = ""
|
||||||
|
|
||||||
# Подстановка плейсхолдеров
|
# Подстановка плейсхолдеров
|
||||||
result = template.replace("{doc_text}", doc_text)
|
result = template.replace("{doc_text}", doc_text)
|
||||||
result = result.replace("{spec_current}", _build_spec_text(current_spec))
|
result = result.replace("{spec_current}", _build_spec_text(current_spec))
|
||||||
|
|
||||||
return result
|
return result, prompt_id
|
||||||
|
|||||||
@@ -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.112 — Lucee</span></span>
|
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.113 — Lucee</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
Reference in New Issue
Block a user