update
This commit is contained in:
@@ -62,24 +62,27 @@ def _mk_race(finder_by_agent):
|
||||
for slot in slots:
|
||||
key, prompt = slot["key"], slot["prompt"]
|
||||
prompts.append((key, prompt))
|
||||
text = None
|
||||
fake_race.slots_seen.append(slot)
|
||||
if "-subblock-final-" in key:
|
||||
# no-tool judges reply as TEXT; the payload sink writes the j-file itself
|
||||
kons = re.search(r"Konsens \(≥2 finders\):\n(.*?)\nUnsicher", prompt, re.S)
|
||||
subs = [l[2:] for l in (kons.group(1).splitlines() if kons else [])
|
||||
if l.startswith("- ") and l != "- (keiner)"]
|
||||
if subs:
|
||||
text = "<!-- block: Alpha -->\n" + "\n".join(f"- {s}" for s in subs)
|
||||
elif "-r1-" in key:
|
||||
outs.append(slot["payload"]((0, text, "")))
|
||||
continue
|
||||
if "-r1-" in key:
|
||||
agent = int(key.rsplit("-", 1)[1])
|
||||
subs = finder_by_agent.get(agent) or []
|
||||
if subs:
|
||||
if subs and (m := _MD_PATH.search(prompt)):
|
||||
text = "<!-- block: Alpha -->\n" + "\n".join(f"- {s}" for s in subs)
|
||||
if text is not None and (m := _MD_PATH.search(prompt)):
|
||||
with open(m.group(1), "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
outs.append(slot["payload"](None))
|
||||
with open(m.group(1), "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
outs.append(slot["payload"](None))
|
||||
outs = [o for o in outs if o]
|
||||
return outs or None
|
||||
fake_race.slots_seen = []
|
||||
return fake_race, prompts
|
||||
|
||||
|
||||
@@ -293,3 +296,124 @@ async def test_round_cap_stops_endless_finders(sub_env, monkeypatch):
|
||||
"", wipe=False, ns="x-")
|
||||
max_round = max(int(k.split("-r")[1].split("-")[0]) for k, _ in prompts if "-r" in k and "-subblock-c" in k)
|
||||
assert max_round == blx.SUBBLOCK_MAX_ROUNDS
|
||||
|
||||
|
||||
# ── Inline-Evidenz für Judges (Token-Umbau) ──────────────────────────────────────────
|
||||
|
||||
def _corpus(tmp_path):
|
||||
d = tmp_path / "korpus"
|
||||
d.mkdir()
|
||||
(d / "Skript.txt").write_text(
|
||||
"Kapitel 1\nAlpha Grundlagen: der Kernbegriff.\nMehr Text dazu.\n\n"
|
||||
"Kapitel 2\nGamma Randnotiz ohne Bezug.\n", encoding="utf-8")
|
||||
(d / "Aufgaben.txt").write_text("Übung 1\nAlpha Vertiefung der Konzepte.\n", encoding="utf-8")
|
||||
return d
|
||||
|
||||
|
||||
def test_evidence_pack_selects_matching_sections(tmp_path):
|
||||
d = _corpus(tmp_path)
|
||||
pack = blx._evidence_pack(d, None, ["Alpha Grundlagen"])
|
||||
assert "── Skript.txt" in pack and "Kernbegriff" in pack
|
||||
pack2 = blx._evidence_pack(d, ["Aufgaben.txt"], ["Alpha"]) # genannte Quellen engen ein
|
||||
assert "Skript.txt" not in pack2 and "Aufgaben.txt" in pack2
|
||||
assert blx._evidence_pack(None, None, ["x"]) == "" # kein Korpus → Selbst-Recherche bleibt
|
||||
|
||||
|
||||
def test_evidence_pack_budget_and_guarantee(tmp_path):
|
||||
d = tmp_path / "korpus"
|
||||
d.mkdir()
|
||||
(d / "A.txt").write_text("Alpha wichtig. " * 50, encoding="utf-8")
|
||||
(d / "B.txt").write_text("Beta anderes Thema. " * 50, encoding="utf-8")
|
||||
pack = blx._evidence_pack(d, None, ["Alpha"], budget=10)
|
||||
assert "Alpha" in pack # Abdeckungs-Garantie schlägt das Budget
|
||||
assert "Beta" not in pack # Top-up respektiert das Budget
|
||||
|
||||
|
||||
def test_cite_ref_parses_positions(tmp_path):
|
||||
d = _corpus(tmp_path)
|
||||
files = blx._corpus_files(d, None)
|
||||
f, lo, hi = blx._cite_ref("Skript.txt, Übung 6.47, Z.2-3", files)
|
||||
assert f.name == "Skript.txt" and (lo, hi) == (2, 3)
|
||||
f2, lo2, hi2 = blx._cite_ref("Aufgaben.txt Zeile 2", files)
|
||||
assert f2.name == "Aufgaben.txt" and lo2 == hi2 == 2
|
||||
assert blx._cite_ref("Skript.txt, Übung 6.47", files) is None # keine Zeilenangabe
|
||||
assert blx._cite_ref("Z.5 irgendwo", files) is None # keine Datei
|
||||
# englische Zitierformen (Quellen sind nicht immer deutsch)
|
||||
f3, lo3, hi3 = blx._cite_ref("Skript.txt, line 2", files)
|
||||
assert f3.name == "Skript.txt" and lo3 == hi3 == 2
|
||||
f4, lo4, hi4 = blx._cite_ref("Aufgaben.txt, lines 1-2", files)
|
||||
assert f4.name == "Aufgaben.txt" and (lo4, hi4) == (1, 2)
|
||||
|
||||
|
||||
def test_cited_evidence_lines_and_fallback(tmp_path):
|
||||
d = _corpus(tmp_path)
|
||||
ev = blx._cited_evidence(d, None, ["Skript.txt, Z.2"], ["Alpha"])
|
||||
assert "── Skript.txt · Z." in ev and "Kernbegriff" in ev
|
||||
ev2 = blx._cited_evidence(d, None, ["ohne Position"], ["Alpha Grundlagen"])
|
||||
assert "Kernbegriff" in ev2 # Keyword-Fallback
|
||||
|
||||
|
||||
def test_sink_json_writes_only_valid(tmp_path):
|
||||
p = tmp_path / "level-final-c1.json"
|
||||
ok = blx._sink_json((0, 'Vorab {"levels": {"1": "beginner"}} nach', ""), p,
|
||||
lambda d: blx._levels_schema(d, {1}))
|
||||
assert ok == {1: "beginner"}
|
||||
assert json.loads(p.read_text(encoding="utf-8"))["levels"]["1"] == "beginner"
|
||||
bad = blx._sink_json((0, "kein json", ""), tmp_path / "x.json", lambda d: d)
|
||||
assert bad is None and not (tmp_path / "x.json").exists()
|
||||
|
||||
|
||||
async def test_clarify_inline_evidence_no_tools(sub_env, monkeypatch, tmp_path):
|
||||
"""Mit Korpus: Judges bekommen Auszüge inline und laufen ohne Tools (Text-Antwort);
|
||||
die j-Datei schreibt die Engine. Finder bleiben unverändert bei capabilities=files."""
|
||||
db, ctx, files = sub_env
|
||||
d = _corpus(tmp_path)
|
||||
monkeypatch.setattr(blx, "source_folder", lambda t: d)
|
||||
monkeypatch.setattr(blx, "load_source", lambda t: {"type": "uni"})
|
||||
fake, prompts = _mk_race({1: ["Alpha Grundlagen"], 2: ["Alpha Grundlagen"]})
|
||||
monkeypatch.setattr(blx, "_race", fake)
|
||||
raw = await blx._subblocks_block(ctx, lambda *a, **k: None, files, {1: "Alpha — Grundkonzept"},
|
||||
"", wipe=False, ns="x-", sources=["Skript.txt"])
|
||||
assert raw == {"Alpha": ["Alpha Grundlagen"]}
|
||||
judges = [s for s in fake.slots_seen if "-subblock-final-" in s["key"]]
|
||||
finders = [s for s in fake.slots_seen if "-r1-" in s["key"]]
|
||||
assert judges and all(s["capabilities"] == "none" for s in judges)
|
||||
assert "── Skript.txt" in judges[0]["prompt"]
|
||||
assert "ls/find" not in judges[0]["prompt"] # keine Selbst-Recherche-Anweisung mehr
|
||||
assert finders and all(s["capabilities"] == "files" for s in finders)
|
||||
assert list(tmp_path.glob("subblock-final-*-j*.md")) # Engine persistiert die Judge-Antwort
|
||||
|
||||
|
||||
async def test_facts_check_inline_cited_evidence(sub_env, monkeypatch, tmp_path):
|
||||
"""Facts-Check: zitierte Zeilenbereiche gehen inline mit, Judge läuft ohne Tools,
|
||||
die Check-Datei schreibt die Engine aus der Text-Antwort."""
|
||||
db, ctx, files = sub_env
|
||||
d = _corpus(tmp_path)
|
||||
facts = {"facts": [{"block": "Alpha", "subblock": "Sub Eins", "key_points": ["k"],
|
||||
"prerequisites": "", "hurdles": "",
|
||||
"cited_facts": [{"text": "T", "source": "Skript.txt, Z.2"}],
|
||||
"example_idea": ""}]}
|
||||
|
||||
async def fake_slot(ctx2, label, *, key, prompt, role, capabilities, payload, timeout, on_line=None):
|
||||
if "-facts-erg-" in key:
|
||||
return blx.FAILED, None
|
||||
(tmp_path / "facts-c0.json").write_text(json.dumps(facts), encoding="utf-8")
|
||||
return blx.OK, None
|
||||
|
||||
seen = []
|
||||
|
||||
async def fake_agent(key, prompt, timeout, provider="", role="", capabilities="", scope=None, label="", **kw):
|
||||
seen.append((key, capabilities, prompt))
|
||||
return (0, '{"ok": true}', "")
|
||||
|
||||
monkeypatch.setattr(blx, "run_single_slot", fake_slot)
|
||||
monkeypatch.setattr(blx, "run_agent", fake_agent)
|
||||
res = await blx._facts_block(ctx, lambda *a, **k: None, {"arbeit": tmp_path},
|
||||
{"Alpha": ["Sub Eins"]}, {"type": "uni"}, d, "", ns="x-")
|
||||
assert res is not None
|
||||
facts_map, discarded = res
|
||||
assert "Alpha" in facts_map and not discarded
|
||||
assert len(seen) == blx.FACTS_CHECK_PANEL
|
||||
key, caps, prompt = seen[0]
|
||||
assert caps == "none" and "── Skript.txt · Z." in prompt
|
||||
assert (tmp_path / "facts-check-c0-j1.json").exists() # Engine persistiert die Antwort
|
||||
|
||||
Reference in New Issue
Block a user