115 lines
5.1 KiB
Python
115 lines
5.1 KiB
Python
"""Bündeln: Claim mehrerer Tasks je Call, Teil-Antwort-Retry statt Verwerfen."""
|
|
import asyncio
|
|
|
|
from backend import db, engine, graph, guide
|
|
from .conftest import topic_anlegen
|
|
|
|
|
|
def _tasks_anlegen(topic, run_id, n=5):
|
|
return [db.insert("tasks", run_id=run_id, topic=topic, knoten="llm_pruefung",
|
|
item=f"p{i}", payload=db.j({"baustein_id": i}))
|
|
for i in range(n)]
|
|
|
|
|
|
def test_buendel_claim_mittel_nimmt_drei():
|
|
topic = topic_anlegen()
|
|
run_id = db.insert("runs", topic=topic, status="running")
|
|
ids = _tasks_anlegen(topic, run_id)
|
|
g = graph.get()
|
|
db.execute("UPDATE tasks SET status='laufend' WHERE id=?", ids[0])
|
|
t = dict(db.one("SELECT * FROM tasks WHERE id=?", ids[0]))
|
|
mitglieder = engine._buendel_claimen(g, t, g.knoten["llm_pruefung"])
|
|
assert len(mitglieder) == 2 # Klasse mittel = 3 → Haupt-Task + 2
|
|
laufend = db.query("SELECT id FROM tasks WHERE topic=? AND status='laufend'",
|
|
topic)
|
|
assert len(laufend) == 3
|
|
assert db.one("SELECT COUNT(*) c FROM tasks WHERE topic=? AND "
|
|
"status='offen'", topic)["c"] == 2
|
|
|
|
|
|
def test_buendel_claim_nur_fuer_optin_worker():
|
|
topic = topic_anlegen()
|
|
run_id = db.insert("runs", topic=topic, status="running")
|
|
for i in range(3): # writer: Klasse lang → nie bündeln
|
|
db.insert("tasks", run_id=run_id, topic=topic, knoten="writer",
|
|
item=f"w{i}", payload=db.j({"baustein_id": i}))
|
|
g = graph.get()
|
|
t = dict(db.one("SELECT * FROM tasks WHERE topic=? AND item='w0'", topic))
|
|
assert engine._buendel_claimen(g, t, g.knoten["writer"]) == []
|
|
|
|
|
|
def test_teil_kaputt_wird_neu_dann_fehler():
|
|
topic = topic_anlegen()
|
|
run_id = db.insert("runs", topic=topic, status="running")
|
|
ids = _tasks_anlegen(topic, run_id, n=2)
|
|
db.execute("UPDATE tasks SET status='laufend' WHERE topic=?", topic)
|
|
g = graph.get()
|
|
kn = g.knoten["llm_pruefung"]
|
|
t = dict(db.one("SELECT * FROM tasks WHERE id=?", ids[0]))
|
|
t["_buendel"] = [dict(db.one("SELECT * FROM tasks WHERE id=?", ids[1]))]
|
|
erg = engine.Ergebnis(daten={"befunde": 0},
|
|
teil_status={ids[0]: "fertig", ids[1]: "neu"})
|
|
engine._abschluss_schreiben(g, t, kn, erg)
|
|
haupt = db.one("SELECT * FROM tasks WHERE id=?", ids[0])
|
|
teil = db.one("SELECT * FROM tasks WHERE id=?", ids[1])
|
|
assert haupt["status"] == "fertig"
|
|
assert teil["status"] == "offen" and teil["versuch"] == 1
|
|
# zweiter kaputter Durchlauf → sichtbar 'fehler' (max_versuche=2)
|
|
db.execute("UPDATE tasks SET status='laufend' WHERE id=?", ids[1])
|
|
t2 = dict(db.one("SELECT * FROM tasks WHERE id=?", ids[0]))
|
|
t2["_buendel"] = [dict(db.one("SELECT * FROM tasks WHERE id=?", ids[1]))]
|
|
engine._abschluss_schreiben(g, t2, kn, engine.Ergebnis(
|
|
teil_status={ids[1]: "neu"}))
|
|
assert db.one("SELECT status FROM tasks WHERE id=?", ids[1])["status"] == "fehler"
|
|
|
|
|
|
async def test_llm_pruefung_buendel_teilantwort(monkeypatch):
|
|
"""Fehlt eine Section in der Antwort, geht NUR ihr Task auf 'neu'."""
|
|
topic = topic_anlegen()
|
|
run_id = db.insert("runs", topic=topic, status="running")
|
|
b1 = db.insert("bausteine", topic=topic, titel="B1")
|
|
b2 = db.insert("bausteine", topic=topic, titel="B2")
|
|
db.insert("sections", baustein_id=b1, text="Text eins.")
|
|
db.insert("sections", baustein_id=b2, text="Text zwei.")
|
|
t1 = db.insert("tasks", run_id=run_id, topic=topic, knoten="llm_pruefung",
|
|
item="p1", payload=db.j({"baustein_id": b1}), status="laufend")
|
|
t2 = db.insert("tasks", run_id=run_id, topic=topic, knoten="llm_pruefung",
|
|
item="p2", payload=db.j({"baustein_id": b2}), status="laufend")
|
|
from backend import llm as llm_mod
|
|
|
|
async def antwort(**kw):
|
|
assert f"=== SECTION {b2} ===" in kw["werte"]["sections"]
|
|
return f"===OK===\nSECTION: {b1}" # b2 fehlt in beiden Stimmen
|
|
|
|
monkeypatch.setattr(llm_mod, "call", antwort)
|
|
task = dict(db.one("SELECT * FROM tasks WHERE id=?", t1))
|
|
task["_buendel"] = [dict(db.one("SELECT * FROM tasks WHERE id=?", t2))]
|
|
erg = await guide.llm_pruefung(task)
|
|
assert erg.teil_status[t1] == "fertig"
|
|
assert erg.teil_status[t2] == "neu"
|
|
|
|
|
|
async def test_worker_exception_setzt_buendel_zurueck():
|
|
topic = topic_anlegen()
|
|
run_id = db.insert("runs", topic=topic, status="running")
|
|
ids = _tasks_anlegen(topic, run_id, n=2)
|
|
db.execute("UPDATE tasks SET status='laufend' WHERE topic=?", topic)
|
|
g = graph.get()
|
|
|
|
async def kaputt(task):
|
|
raise RuntimeError("boom")
|
|
|
|
alt = engine.WORKER["guide.llm_pruefung"]
|
|
engine.WORKER["guide.llm_pruefung"] = kaputt
|
|
try:
|
|
t = dict(db.one("SELECT * FROM tasks WHERE id=?", ids[0]))
|
|
t["_buendel"] = [dict(db.one("SELECT * FROM tasks WHERE id=?", ids[1]))]
|
|
await engine._ausfuehren(g, t, g.knoten["llm_pruefung"],
|
|
asyncio.Semaphore(1), asyncio.Semaphore(1),
|
|
asyncio.Event())
|
|
finally:
|
|
engine.WORKER["guide.llm_pruefung"] = alt
|
|
for tid in ids:
|
|
z = db.one("SELECT * FROM tasks WHERE id=?", tid)
|
|
assert z["status"] == "offen" and z["versuch"] == 1
|