This commit is contained in:
team3
2026-07-12 04:20:07 +02:00
parent 14bcf1b8e9
commit a284e03225
29 changed files with 866 additions and 329 deletions

View File

@@ -136,12 +136,19 @@ async def _recherche_runde(ctx: llm.Kontext, runde: int) -> int:
inhalt = str(q.get("inhalt", "")).strip()
if len(inhalt) < 500: # leere/dünne Funde sind keine Quelle
continue
url = str(q.get("url", ""))[:500]
# URL-Dedup: dieselbe Seite über Runden erneut geholt liefert leicht
# abweichende Extrakte (anderer Content-Hash) und zählte dann als
# „unabhängiger" Beleg im ≥2-Quellen-Konsens (Lernen: eine URL 4×).
if url and db.one("SELECT id FROM quellen WHERE topic=? AND url=?",
(ctx.topic, url)):
continue
snap = _snapshot_schreiben(ctx.topic, inhalt)
if snap is None:
continue
titel = str(q.get("titel", ""))[:200]
db.insert("quellen", topic=ctx.topic, art="web", titel=titel,
url=str(q.get("url", ""))[:500], snapshot=snap[0], hash=snap[1],
url=url, snapshot=snap[0], hash=snap[1],
runde=runde, status="neu", rolle=_rolle(titel, lens))
neu += 1
return neu
@@ -331,38 +338,76 @@ def messen(ctx: llm.Kontext) -> list[dict]:
return befunde
async def _beleg_nachsuchen(ctx: llm.Kontext, soll_id: int) -> bool:
"""Gezielter Beleg-Judge in noch nicht zitierten Quellen; ein bestätigter
Punkt ohne Fund wird zum Kandidaten zurückgestuft (Konsens-Regel bleibt
hart). → True nur, wenn sich etwas geändert hat."""
p = db.one("SELECT * FROM soll WHERE id=?", (soll_id,))
if not p:
async def _belege_nachsuchen(ctx: llm.Kontext, soll_ids: list[int]) -> bool:
"""Gezielte Beleg-Judges, gebündelt: EIN Call je Quelle mit ALLEN dort noch
offenen Punkten — Punkt×Quelle einzeln waren 3072 Calls / 27 % der
Themen-Tokens (Lernen). Negativ-Cache soll.geprueft: erfolglos geprüfte
(Punkt, Quelle)-Paare werden über Repair-Iterationen nie wiederholt.
Bestätigte Punkte ohne Fund in irgendeiner Quelle → zurück zu Kandidat."""
punkte = [p for s in dict.fromkeys(soll_ids)
if (p := db.one("SELECT * FROM soll WHERE id=?", (s,)))]
if not punkte:
return False
belegte = {bl["quelle"] for bl in db.uj(p["belege"])}
andere = [q for q in db.query("SELECT * FROM quellen WHERE topic=?", (ctx.topic,))
if q["id"] not in belegte]
for q in andere:
quellen = db.query("SELECT * FROM quellen WHERE topic=?", (ctx.topic,))
bewegt = False
async def eine_quelle(q: dict) -> None:
nonlocal bewegt
faellig = []
for p in punkte:
belegte = {bl["quelle"] for bl in db.uj(p["belege"])}
if q["id"] not in belegte and q["id"] not in db.uj(p["geprueft"]):
faellig.append(p)
if not faellig:
return
text = quelltext(q)
liste = "\n".join(f"{p['id']}: {p['punkt']}" for p in faellig)
res = await llm.call(ctx, stage="soll_beleg", template="Korpus-Soll-Beleg",
schritt="soll", role="judge", item=f"s{p['id']}-q{q['id']}",
werte={"punkt": p["punkt"],
"text": quelltext(q)[:SOLL_CHUNK_CHARS]},
erwartet=dict)
zitat = str((res or {}).get("zitat", "")).strip()
if zitat and textkit.finde_zitat(quelltext(q), zitat) is not None:
belege = db.uj(p["belege"]) + [{"quelle": q["id"], "zitat": zitat}]
db.update("soll", "id", p["id"], belege=db.j(belege))
return True
if p["status"] == "bestaetigt":
db.update("soll", "id", p["id"], status="kandidat")
return True
return False
schritt="soll", role="judge", item=f"q{q['id']}",
n=len(faellig),
werte={"punkte": liste, "text": text[:SOLL_CHUNK_CHARS]},
erwartet=list)
if res is None:
return # Ausfall: nicht als „geprüft" verbuchen
gefunden: dict[int, str] = {}
for e in res:
if not isinstance(e, dict):
continue
zitat = str(e.get("zitat", "")).strip()
if zitat and e.get("soll") in {p["id"] for p in faellig} \
and textkit.finde_zitat(text, zitat) is not None:
gefunden[e["soll"]] = zitat
for p in faellig:
frisch = db.one("SELECT * FROM soll WHERE id=?", (p["id"],))
if p["id"] in gefunden:
belege = db.uj(frisch["belege"]) + [{"quelle": q["id"],
"zitat": gefunden[p["id"]]}]
db.update("soll", "id", p["id"], belege=db.j(belege))
bewegt = True
else:
geprueft = sorted(set(db.uj(frisch["geprueft"])) | {q["id"]})
db.update("soll", "id", p["id"], geprueft=db.j(geprueft))
await llm.alle(eine_quelle(q) for q in quellen)
for p in punkte:
frisch = db.one("SELECT * FROM soll WHERE id=?", (p["id"],))
if frisch["status"] != "bestaetigt" \
or len(db.uj(frisch["belege"])) > len(db.uj(p["belege"])):
continue
belegte = {bl["quelle"] for bl in db.uj(frisch["belege"])}
rest = [q for q in quellen if q["id"] not in belegte
and q["id"] not in db.uj(frisch["geprueft"])]
if not rest: # wirklich überall erfolglos gesucht — Konsens-Regel bleibt hart
db.update("soll", "id", p["id"], status="kandidat")
bewegt = True
return bewegt
async def reparieren(ctx: llm.Kontext, befunde: list[dict]) -> bool:
ctx.ebene = EBENE
punkte = [int(b["item"]) for b in befunde if b["art"] == "soll_wenig_belege"]
offene = [int(b["item"]) for b in befunde if b["art"] == "soll_kandidat_offen"]
bewegt = any(await llm.alle(_beleg_nachsuchen(ctx, s) for s in punkte + offene))
bewegt = await _belege_nachsuchen(ctx, punkte + offene)
if offene: # neu falten: die Nachrunden im Konsens erzwingen die Zuordnung
await _konsens(ctx)
noch = db.query("SELECT id FROM soll WHERE topic=? AND status='kandidat'",