"""Live synthetic functional checks; separate from corpus accuracy metrics.""" import argparse import concurrent.futures import copy import json import os from pathlib import Path from tools.rag.evaluation.rag_runner import request_json, OPENWEBUI_URL, DEFAULT_MODEL from tools.graphrag.evaluation.reproducibility import sha256_file from tools.answers.checked_generation import generate_checked_answer from tools.answers.context_policy import joined_candidates HERE = Path(__file__).resolve().parents[1] / "answers" def scenarios(): base = { "status": "ok", "reason": None, "complete": True, "truncated": False, "truncation_reasons": [], "index_version": {"version": "synthetic-functional-fixture"}, "sources": [ {"source_id": "S1", "document_path": "a.md", "source_url": "https://example.test/a"} ], "facts": [ { "person": "Eva Vzorová", "title": "Vyhľadávanie informácií", "year": 2026, "document_path": "a.md", "source_url": "https://example.test/a", } ], "aggregation": None, "answer_candidates": [], } query = "Uveď názov práce osoby Eva Vzorová a rok práce." result = [] for name, fields in [ ("complete", {}), ("incomplete_without_truncated", {"complete": False}), ("truncated", {"complete": False, "truncated": True, "truncation_reasons": ["limit"]}), ("unknown_completeness", {"complete": None}), ]: context = copy.deepcopy(base) context.update(fields) result.append( { "id": name, "question": query, "context": context, "expected": { "status": "ok", "claims": [ {"person": "Eva Vzorová", "title": "Vyhľadávanie informácií", "year": 2026} ], }, } ) context = copy.deepcopy(base) context.update( complete=False, truncated=True, truncation_reasons=["evidence_limit"], aggregation={"count": 2, "complete": True}, ) result.append( { "id": "exact_count_partial_evidence", "question": "Aký je počet vyhovujúcich osôb?", "context": context, "expected": {"status": "ok", "count": 2}, } ) context = copy.deepcopy(base) context["sources"].append( {"source_id": "S2", "document_path": "child.md", "source_url": "https://example.test/child"} ) context["facts"].append( { "person": "Eva Vzorová", "keyword": "python", "document_path": "child.md", "source_url": "https://example.test/child", } ) context["answer_candidates"] = joined_candidates( context["facts"], {"mentions_title": True, "mentions_keyword": True} ) result.append( { "id": "same_person_child_tag", "question": "Uveď názov práce osoby Eva Vzorová v roku 2026 s kľúčovým slovom python.", "context": context, "expected": { "status": "ok", "claims": [ { "person": "Eva Vzorová", "title": "Vyhľadávanie informácií", "year": 2026, "keyword": "python", } ], }, } ) context = copy.deepcopy(context) context["facts"][0].pop("title") context["answer_candidates"] = joined_candidates( context["facts"], {"years": [2026], "mentions_keyword": True} ) result.append( { "id": "same_person_year_child_tag", "question": "Ktorá osoba spĺňa kľúčové slovo python a rok práce 2026?", "context": context, "expected": { "status": "ok", "claims": [{"person": "Eva Vzorová", "year": 2026, "keyword": "python"}], }, } ) result.append( { "id": "type_is_not_keyword", "question": "Uveď názov diplomového projektu osoby Eva Vzorová a rok.", "context": copy.deepcopy(base), "expected": {"status": "ok", "no_keyword": True}, } ) context = copy.deepcopy(base) context.update(status="no_answer", reason="unknown_person", facts=[], sources=[]) result.append( { "id": "no_answer", "question": "Akú prácu má Neexistujúca Osoba?", "context": context, "expected": {"status": "no_answer"}, } ) return result def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path) args = parser.parse_args() from tools.runtime.report_files import report_output args.output = report_output(args.output, "context_scenarios") files = [ Path(__file__), HERE / "graph_answer_prompt.txt", HERE / "context_policy.py", HERE / "checked_generation.py", HERE / "answer_format.py", HERE / "render_answer.py", ] hashes = {str(p): sha256_file(p) for p in files} declared = scenarios() args.output.parent.mkdir(parents=True, exist_ok=True) args.output.with_suffix(".declaration.json").write_text( json.dumps( {"method": __doc__, "model": DEFAULT_MODEL, "hashes": hashes, "scenarios": declared}, ensure_ascii=False, indent=2, ) + "\n" ) prompt = (HERE / "graph_answer_prompt.txt").read_text() def run(case): def complete(messages): return request_json( OPENWEBUI_URL, method="POST", timeout=90, max_attempts=2, headers={ "Authorization": "Bearer " + os.environ["OPENWEBUI_API_KEY"], "Content-Type": "application/json", }, payload={ "model": DEFAULT_MODEL, "temperature": 0, "stream": False, "response_format": {"type": "json_object"}, "messages": messages, }, ) out = generate_checked_answer(case["question"], case["context"], prompt, complete) answer = out.get("answer", {}) expected = case["expected"] ok = answer.get("status") == expected["status"] for binding in expected.get("claims", []): ok &= any( all(claim.get(k) == v for k, v in binding.items()) for claim in answer.get("claims", []) ) if "count" in expected: ok &= answer.get("count") == expected["count"] if expected.get("no_keyword"): ok &= not any(c.get("keyword") for c in answer.get("claims", [])) return {"id": case["id"], **out, "passed": bool(ok and out["accepted_for_delivery"])} with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: rows = list(executor.map(run, declared)) assert hashes == {str(p): sha256_file(p) for p in files} output = { "method": __doc__, "questions": rows, "summary": {"total": len(rows), "passed": sum(r["passed"] for r in rows)}, } args.output.write_text(json.dumps(output, ensure_ascii=False, indent=2) + "\n") print(json.dumps(output["summary"])) if __name__ == "__main__": main()