dp-zp-agent/tools/answers/checked_generation.py

99 lines
4.3 KiB
Python

"""One bounded corrective request based solely on retrieval evidence."""
import json
from tools.answers.answer_format import parse_answer, Answer, Claim
from tools.answers.context_policy import answer_context_issues
from tools.answers.render_answer import render_graph_answer
def generate_checked_answer(question: str, context: dict, prompt: str, complete) -> dict:
# Candidate provenance is used by the guard, but is not part of a Claim.
# Keep the model's selection shape identical to the actual output schema.
sent = {
**context,
"answer_candidates": [
{k: v for k, v in row.items() if k in Claim.model_fields}
for row in context.get("answer_candidates", [])
],
}
aggregation = context.get("aggregation")
if aggregation is not None:
sent["aggregation"] = {
k: v for k, v in aggregation.items() if k in {"count", "count_domain", "people", "complete"}
}
policy = "\nDo claims nevkladaj source_urls ani evidence_documents; source_urls patrí iba na vrchnú úroveň odpovede."
if (
aggregation
and aggregation.get("complete") is True
and type(aggregation.get("count")) is int
):
policy += (
"\nÚplná overená agregácia tohto dotazu má count="
+ str(aggregation["count"])
+ ". Pri status=ok zachovaj tento count; počet dodaných facts, claims ani answer_candidates ho nemení."
)
messages = [
{"role": "system", "content": prompt + policy},
{
"role": "user",
"content": json.dumps(
{"question": question, "graph_context": sent}, ensure_ascii=False
),
},
]
result = {"attempts": [], "accepted_for_delivery": False}
for number in range(2):
record = {}
text = ""
try:
response = complete(messages)
result["response"] = response
record["response"] = response
text = response["choices"][0]["message"]["content"].strip()
except Exception as error:
result["error"] = type(error).__name__
result["reason"] = "model_unavailable"
result["attempts"].append(record)
return result
result["attempts"].append(record)
try:
answer, normalized = parse_answer(text)
record["answer"] = answer
record["format_normalized"] = normalized
except (ValueError, TypeError) as error:
record["format_error"] = type(error).__name__
if number == 1:
result["error"] = "InvalidAnswerFormat: two invalid JSON/schema responses"
return result
feedback = (
"Vráť jediný platný JSON objekt podľa schémy, bez zmeny faktov z pôvodného kontextu. Schéma: "
+ json.dumps(Answer.model_json_schema(), ensure_ascii=False)
)
else:
issues = answer_context_issues(answer, context, check_text=False)
record["context_issues"] = issues
result["answer"] = answer
if not issues:
rendered = render_graph_answer(answer, context)
delivery_issues = answer_context_issues(rendered, context)
if delivery_issues:
issues = delivery_issues
record["context_issues"] = issues
else:
record["rendered_answer"] = rendered
result["model_answer"] = answer
result["answer"] = rendered
result["accepted_for_delivery"] = True
return result
if number == 1:
result["context_rejected"] = True
return result
feedback = (
"Odpoveď porušila pravidlá dodaného kontextu: " + ", ".join(issues) + ". "
"Oprav jediný JSON objekt výhradne z pôvodných podkladov. Použi iba source_urls zo sources. "
"Typ práce nie je keyword. answer_candidates dokladajú spojenie požadovaného názvu alebo roku a tagu cez tú istú osobu. "
"Answer ponechaj prázdny; text aj upozornenie zostaví aplikácia."
)
messages += [{"role": "assistant", "content": text}, {"role": "user", "content": feedback}]
return result