dp-zp-agent/test/test_combined_answers.py
2026-11-15 16:27:08 +01:00

116 lines
6.7 KiB
Python

"""ZP Wiki + CRZP in one answer: order, labels, clarification, follow-ups, CRZP timeout."""
import time
import pytest
from tools.answers import combined as C
WIKI = {"status": "ok", "reason": None, "accepted_for_delivery": True, "claims": [], "index_version": {"version": "v"},
"complete": True, "truncated": False, "truncation_reasons": [],
"answer": "**Ján Pták — Diplomová práca 2027**\n\nVedúci práce: Daniel Hladek.\n\nZdroje: https://zp.test/jan",
"source_urls": ["https://zp.test/jan"],
"delivery_check": {"layer": "delivery", "code": "complete", "request_fingerprint": "x"}}
@pytest.fixture
def combo(monkeypatch):
monkeypatch.setenv("SEARCH_API_KEY", "combined-test-key")
monkeypatch.setenv("ANSWER_WITH_CRZP", "1")
state = {"part": None, "match": {"name": "Ján Pták", "student": True}}
monkeypatch.setattr("tools.crzp.answering.wiki_bridge.wiki_match", lambda q, db_file=None: state["match"])
monkeypatch.setattr(C, "crzp_part", lambda q, person, complete=None, student=True: state["part"])
state["deliver"] = lambda q, **kw: dict(WIKI)
return state
def ask(combo, question, token=None):
return C.answer(question, engine="rag", context_token=token, deliver=lambda q, **kw: combo["deliver"](q, **kw))
def test_wiki_first_then_the_marked_crzp_part(combo):
combo["part"] = {"mode": "person", "text": "- bakalárska práca 2025 „IoT“ — vedúci Pavol Galajda; oponent Daniel Hládek",
"rows": [], "urls": ["https://opac.test/1"], "people": [], "works": []}
result = ask(combo, "Kto vedie Jána Ptáka?")
text = result["answer"]
assert text.index("Vedúci práce: Daniel Hladek") < text.index("**V CRZP**") < text.index("Pavol Galajda")
assert result["source_urls"] == ["https://zp.test/jan", "https://opac.test/1"] and result["status"] == "ok"
import packages.claude.client as client
client.validate_metadata(result)
assert client.validated_delivery_check(result, "Kto vedie Jána Ptáka?", "rag")
def test_without_crzp_the_wiki_answer_is_unchanged_but_for_wording(combo):
result = ask(combo, "Kedy mal Ján Pták posledné stretnutie?")
assert "V CRZP" not in result["answer"] and result["answer"].startswith("**Ján Pták")
def test_a_work_only_crzp_has_is_asked_for_and_the_choice_continues(combo, monkeypatch):
combo["part"] = {"mode": "person", "text": "x", "rows": [], "urls": [], "people": [],
"works": [{"source": "crzp", "label": "bakalárska práca 2025 „IoT“", "type": "bachelor_thesis", "key": "k"}]}
monkeypatch.setattr(C, "wiki_works", lambda person: {"Diplomová práca 2027": {"type": "diploma_thesis", "topic": "Agent"}})
asked = ask(combo, "Aké technológie využíva Ján Pták vo svojej práci?")
assert asked["status"] == "ambiguous" and "1. Diplomová práca 2027 „Agent“ (ZP Wiki)" in asked["answer"]
assert "2. bakalárska práca 2025 „IoT“ (CRZP)" in asked["answer"]
seen = []
combo["deliver"] = lambda q, **kw: (seen.append(q), dict(WIKI))[1]
ask(combo, "1", asked["conversation_context"]["token"])
assert seen == ["Aké technológie využíva Ján Pták vo svojej diplomovej práci?"]
chosen = []
monkeypatch.setattr(C, "_answer_choice", lambda q, c, **kw: chosen.append(c) or dict(WIKI))
ask(combo, "tú bakalársku", asked["conversation_context"]["token"])
assert chosen[0]["source"] == "crzp"
def test_schools_of_the_people_of_the_previous_answer(combo):
combo["part"] = {"mode": "similar", "text": "- Maksym Zatirka — Technická univerzita v Košiciach: „X“", "rows": [],
"urls": [], "people": [("Eva Malá", "Slovenská technická univerzita v Bratislave")]}
first = ask(combo, "Kto pracoval na podobnej téme ako Ján Pták?")
combo["match"] = None
schools = ask(combo, "a z akých škôl boli tí študenti?", first["conversation_context"]["token"])
assert "- Ján Pták — Technická univerzita v Košiciach (KEMT FEI)" in schools["answer"]
assert "- Eva Malá — Slovenská technická univerzita v Bratislave" in schools["answer"]
def test_a_slow_crzp_does_not_hold_the_wiki_answer(combo, monkeypatch):
monkeypatch.setattr(C, "CRZP_SECONDS", 0.5)
monkeypatch.setattr(C, "crzp_part", lambda *a, **k: (time.sleep(4), None)[1])
started = time.monotonic()
result = ask(combo, "Kto vedie Jána Ptáka?")
assert result["status"] == "ok" and time.monotonic() - started < 2.0
def test_a_forged_combined_token_is_refused(combo):
first = ask(combo, "Kto vedie Jána Ptáka?")
token = first["conversation_context"]["token"]
assert ask(combo, "a z akých škôl?", token[:-3] + "abc")["reason"] == "invalid_context"
def test_crzp_people_and_keywords_link_to_the_wiki_graph_by_code():
from tools.crzp.storage.graph import link_pairs
rows = [{"school": "Technická univerzita v Košiciach", "authors": ["Pták, Ján"], "supervisors": ["Galajda, Pavol"],
"opponents": [], "keywords": ["Speech Recognition", "iot"]},
{"school": "Iná univerzita", "authors": ["Pták, Jan"], "supervisors": [], "opponents": [], "keywords": []}]
people, keywords = link_pairs(rows, ["Ján Pták", "Daniel Hladek"], ["rozpoznávanie reči", "Dialógový systém"])
assert people == [{"crzp": "Pták, Ján", "wiki": "Ján Pták"}] # only at TUKE, the namesake elsewhere is not linked
assert keywords == [{"key": "speech recognition", "wiki": "rozpoznávanie reči"}] # through the reviewed synonyms
def test_the_wiki_question_which_person_wins_over_crzp(combo):
combo["match"] = None
combo["part"] = {"mode": "explicit", "status": "ambiguous", "text": "Menu „Ján“ zodpovedá v CRZP…", "rows": [],
"urls": [], "people": []}
combo["deliver"] = lambda q, **kw: {**WIKI, "status": "ambiguous", "reason": "ambiguous_person",
"answer": "Ktorého Jána myslíš? Ján Holp, Ján Pták."}
result = ask(combo, "Na čom pracoval Ján?")
assert result["answer"] == "Ktorého Jána myslíš? Ján Holp, Ján Pták."
combo["deliver"] = lambda q, **kw: {**WIKI, "status": "ambiguous", "reason": "missing_subject",
"answer": "Uveď meno študenta."}
combo["part"] = {**combo["part"], "status": "ok", "text": "Školitelia prác …"}
assert "Školitelia prác" in ask(combo, "Kto viedol najviac prác o strojovom učení?")["answer"]
def test_an_unknown_person_goes_to_crzp_only_for_a_thesis_question():
assert C.THESIS.search(C.normalized("O čom je bakalárska práca Jána Nováka?"))
assert not C.THESIS.search(C.normalized("Na čom pracuje Peter Novák?"))
assert not C.ABOUT.search(C.normalized("Čomu sa venuje Jakub Schwarc?"))