update
This commit is contained in:
@@ -31,7 +31,7 @@ from jsonio import parse_json_text, read_json_file as _json_file
|
||||
from paths import arbeit_dir, blocks_path, question_pattern_path, project_dir, subblocks_path, source_path, source_crawl_dir, safe_folder
|
||||
from crawl import crawl
|
||||
from pipeline import (
|
||||
CANCELLED, FAILED, OK, GenContext, _extra, _gather_progress, _yesno_schema, _log, _prompt, _race,
|
||||
CANCELLED, FAILED, OK, GenContext, _detached, _extra, _gather_progress, _yesno_schema, _log, _prompt, _race,
|
||||
_relevance_schema, _runde_schema, _semaphore, _str_list, _levels_schema, _timeout, run_single_slot,
|
||||
)
|
||||
from textkit import (
|
||||
@@ -597,6 +597,39 @@ def _sink_json(result, path: Path, schema):
|
||||
return val
|
||||
|
||||
|
||||
async def _panel_2of3(tasks: dict, sink, outs_now, norm) -> None:
|
||||
"""Panel-Welle „first 2 agree": kehrt zurück, sobald zwei vorliegende Verdicts
|
||||
übereinstimmen — die dritte Stimme kann die Mehrheit dann nicht mehr kippen. Sonst
|
||||
(Dissens) wird weiter gewartet. Der Langsamste bestimmte jede Welle (gemessen: 98 s
|
||||
bei ok-p50 ~50 s). Nachzügler laufen detached weiter; ihr File dient nur dem Resume.
|
||||
tasks: {Task: judge_nr} · sink(j, result) persistiert · outs_now() liest Verdicts ·
|
||||
norm(verdict) macht sie vergleichbar."""
|
||||
offen = dict(tasks)
|
||||
while offen:
|
||||
done, _rest = await asyncio.wait(list(offen), return_when=asyncio.FIRST_COMPLETED)
|
||||
for t in done:
|
||||
j = offen.pop(t)
|
||||
try:
|
||||
r = t.result()
|
||||
except Exception: # noqa: BLE001 — Panel ist fail-open, Ausfall = fehlende Stimme
|
||||
continue
|
||||
if isinstance(r, tuple):
|
||||
sink(j, r)
|
||||
outs = [norm(s) for s in outs_now()]
|
||||
if len(outs) >= 2 and any(outs[a] == outs[b]
|
||||
for a in range(len(outs)) for b in range(a + 1, len(outs))):
|
||||
break
|
||||
for t, j in offen.items(): # Dritter läuft weiter — sein File zählt fürs Resume
|
||||
async def _warte(t=t, j=j):
|
||||
try:
|
||||
r = await t
|
||||
if isinstance(r, tuple):
|
||||
sink(j, r)
|
||||
except (asyncio.CancelledError, Exception): # noqa: BLE001
|
||||
pass
|
||||
_detached(asyncio.create_task(_warte()))
|
||||
|
||||
|
||||
def _sink_subs(result, path: Path):
|
||||
"""Finder reply as TEXT (marker format), persisted to `path` for audit/diagnosis.
|
||||
File fallback: a tool-capable agent (thema web mode) that wrote the file despite the
|
||||
@@ -796,7 +829,23 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
|
||||
"role": "quick", "capabilities": round_caps,
|
||||
"payload": (lambda result, p=p: _sink_subs(result, p)),
|
||||
} for k, p in zip(keys, paths)]
|
||||
agent_texts = await _race(topic, label, slots, 2, _timeout("subblock", len(subset)), provider, cancelled=is_cancelled, grace=CONSENSUS_GRACE)
|
||||
|
||||
async def _fold_late(d: dict) -> None:
|
||||
"""Dritte Stimme nachbuchen statt warten (ersetzte grace=300, gemessen 73 s/Runde):
|
||||
Mentions sind additiv; ein Fund, den nur der Nachzügler hat, bleibt Einzelfund
|
||||
und läuft durchs Clarify-Quellen-Gate — verfälscht wird nichts."""
|
||||
for marker, subs in d.items():
|
||||
num = _resolve_title(chunk_idx, marker)
|
||||
if num is None:
|
||||
continue
|
||||
seen_late = set()
|
||||
for sub in subs:
|
||||
sn = _norm_title(sub)
|
||||
if sn and sn not in seen_late:
|
||||
seen_late.add(sn)
|
||||
await db.upsert_subblock(topic, norm_by_num[num], sn, title_by_num[num], sub)
|
||||
|
||||
agent_texts = await _race(topic, label, slots, 2, _timeout("subblock", len(subset)), provider, cancelled=is_cancelled, late=_fold_late)
|
||||
if is_cancelled() or not agent_texts:
|
||||
return None
|
||||
rows_before = {num: await db.list_subblocks(topic, norm_by_num[num]) for num in subset}
|
||||
@@ -1846,16 +1895,29 @@ async def _facts_block(ctx, set_p, files: dict, raw: dict, q: dict, folder, inst
|
||||
fk[f] = ek[f]
|
||||
return raw
|
||||
|
||||
def _inline_source(queries: list[str]) -> tuple[str, str]:
|
||||
"""Korpus-Auszüge INLINE statt Datei-Recherche → (source, capabilities). Die
|
||||
Tool-Agenten (bash/read) verloren sich messbar in Reasoning-Schleifen (bis 39k
|
||||
Zeichen) und endeten mit leerem Turn — Retry-Wellen à 60–90 s seriell pro Block.
|
||||
No-Tool-Calls mit Inline-Material hatten 0 solcher Fälle (Muster: Facts-Check).
|
||||
Fail-open: ohne Treffer bleibt die alte Selbst-Recherche."""
|
||||
ev = _evidence_pack(folder, sources, queries) if folder else ""
|
||||
if ev:
|
||||
return _prompt("Blocks-Source-Inline", excerpts=ev), "none"
|
||||
return source, caps
|
||||
|
||||
# Phase "Facts find": 1 generator per chunk.
|
||||
async def _find(ci, idxs):
|
||||
fp = raw_path(ci)
|
||||
if _facts_schema(_json_file(fp)):
|
||||
return True
|
||||
subs_total = sum(len(blocks[i][1]) for i in idxs)
|
||||
f_source, f_caps = await asyncio.to_thread(
|
||||
_inline_source, [blocks[i][0] for i in idxs] + [s for i in idxs for s in blocks[i][1]])
|
||||
status, _r = await run_single_slot(
|
||||
ctx, f"{lbl}Facts {ci}", key=f"blocks-{topic}-{ns}facts-c{ci}",
|
||||
prompt=_prompt("Facts-Research", topic=topic, source=source, blocks=block_text(idxs), out_path=fp, extra=_extra(instructions)),
|
||||
role="quick", capabilities=caps,
|
||||
prompt=_prompt("Facts-Research", topic=topic, source=f_source, blocks=block_text(idxs), out_path=fp, extra=_extra(instructions)),
|
||||
role="quick", capabilities=f_caps,
|
||||
payload=lambda result, p=fp: _sink_or_file(result, p, _facts_schema),
|
||||
timeout=_timeout("content", subs_total))
|
||||
return status != FAILED and _facts_schema(_json_file(fp)) is not None
|
||||
@@ -1883,10 +1945,12 @@ async def _facts_block(ctx, set_p, files: dict, raw: dict, q: dict, folder, inst
|
||||
for fk in fm.values())
|
||||
for bt, fm in per.items())
|
||||
subs_total = sum(len(blocks[i][1]) for i in idxs)
|
||||
e_source, e_caps = await asyncio.to_thread(
|
||||
_inline_source, [blocks[i][0] for i in idxs] + [s for i in idxs for s in blocks[i][1]])
|
||||
await run_single_slot(
|
||||
ctx, f"{lbl}Facts supplement {ci}", key=f"blocks-{topic}-{ns}facts-erg-c{ci}",
|
||||
prompt=_prompt("Facts-Supplement", topic=topic, source=source, blocks=block, out_path=ep, extra=_extra(instructions)),
|
||||
role="quick", capabilities=caps,
|
||||
prompt=_prompt("Facts-Supplement", topic=topic, source=e_source, blocks=block, out_path=ep, extra=_extra(instructions)),
|
||||
role="quick", capabilities=e_caps,
|
||||
payload=lambda result, p=ep: _sink_or_file(result, p, _facts_schema),
|
||||
timeout=_timeout("content", subs_total))
|
||||
|
||||
@@ -1913,16 +1977,17 @@ async def _facts_block(ctx, set_p, files: dict, raw: dict, q: dict, folder, inst
|
||||
ev = _cited_evidence(folder, sources, cites, fallback) if folder else ""
|
||||
c_source = _prompt("Blocks-Source-Inline", excerpts=ev) if ev else source
|
||||
pending = [j for j in panel if _facts_check_schema(_json_file(chk_path(ci, j))) is None]
|
||||
rs = await asyncio.gather(*[
|
||||
tmap = {asyncio.create_task(
|
||||
run_agent(f"blocks-{topic}-{ns}facts-check-c{ci}-j{j}",
|
||||
_prompt("Facts-Check", topic=topic, source=c_source, facts=facts_text, out_path=chk_path(ci, j), extra=_extra(instructions)),
|
||||
_timeout("content_check", len(per)), provider=provider, role="judge",
|
||||
capabilities="none" if ev else caps,
|
||||
scope=topic, label=f"{lbl}Facts check {ci}/{j}")
|
||||
for j in pending], return_exceptions=True)
|
||||
for j, r in zip(pending, rs):
|
||||
if isinstance(r, tuple):
|
||||
_sink_json(r, chk_path(ci, j), _facts_check_schema)
|
||||
scope=topic, label=f"{lbl}Facts check {ci}/{j}")): j
|
||||
for j in pending}
|
||||
await _panel_2of3(tmap, lambda j, r: _sink_json(r, chk_path(ci, j), _facts_check_schema),
|
||||
lambda: [s for j in panel
|
||||
if (s := _facts_check_schema(_json_file(chk_path(ci, j)))) is not None],
|
||||
lambda s: {tuple(x) for x in s})
|
||||
outs = [s for j in panel if (s := _facts_check_schema(_json_file(chk_path(ci, j)))) is not None]
|
||||
bvotes: dict[str, int] = {}
|
||||
vvotes: dict[str, int] = {}
|
||||
@@ -1970,10 +2035,13 @@ async def _facts_block(ctx, set_p, files: dict, raw: dict, q: dict, folder, inst
|
||||
goal.append(f"BLOCK: {bt}\nSUBBAUSTEINE:\n" + "\n".join(f"- {s}" for s in affected_subs))
|
||||
if not goal:
|
||||
return
|
||||
x_source, x_caps = await asyncio.to_thread(
|
||||
_inline_source, [bt for bt in rel_by] + [s for subs in rel_by.values()
|
||||
for s in subs if _norm_title(s) in subs_norm])
|
||||
await run_single_slot(
|
||||
ctx, f"{lbl}Facts-Fix {ci}", key=f"blocks-{topic}-{ns}facts-fix-c{ci}",
|
||||
prompt=_prompt("Facts-Research", topic=topic, source=source, blocks="\n\n".join(goal), out_path=fix_path(ci), extra=_extra(instructions)),
|
||||
role="quick", capabilities=caps,
|
||||
prompt=_prompt("Facts-Research", topic=topic, source=x_source, blocks="\n\n".join(goal), out_path=fix_path(ci), extra=_extra(instructions)),
|
||||
role="quick", capabilities=x_caps,
|
||||
payload=lambda result, p=fix_path(ci): _sink_or_file(result, p, _facts_schema),
|
||||
timeout=_timeout("content", len(subs_norm)))
|
||||
await _gather_progress([_fix(ci) for ci in correctable], len(correctable), _report_p(set_p, topic, "Facts fix"))
|
||||
@@ -3266,15 +3334,16 @@ async def _artefacts_block(ctx: GenContext, set_p, files: dict, sidecar: dict, i
|
||||
pending = [j for j in (1, 2, 3)[:FACTS_CHECK_PANEL] if _example_check_schema(_json_file(cpath(j))) is None]
|
||||
if pending:
|
||||
# ground truth (facts) is fully inline → no tools, text reply, engine persists
|
||||
rs = await asyncio.gather(*[
|
||||
tmap = {asyncio.create_task(
|
||||
run_agent(f"blocks-{topic}-{ns}artifact-example-check-c{ci}-j{j}",
|
||||
_prompt("Artifact-Example-Check", topic=topic, facts=block_text(idxs), examples=examples_txt, out_path=cpath(j), extra=_extra(instructions)),
|
||||
_timeout("content_check", len(items)), provider=provider, role="judge", capabilities="none",
|
||||
scope=topic, label=f"{lbl}Beispiel-Check {ci}/{j}")
|
||||
for j in pending], return_exceptions=True)
|
||||
for j, r in zip(pending, rs):
|
||||
if isinstance(r, tuple):
|
||||
_sink_json(r, cpath(j), _example_check_schema)
|
||||
scope=topic, label=f"{lbl}Beispiel-Check {ci}/{j}")): j
|
||||
for j in pending}
|
||||
await _panel_2of3(tmap, lambda j, r: _sink_json(r, cpath(j), _example_check_schema),
|
||||
lambda: [s for j in (1, 2, 3)[:FACTS_CHECK_PANEL]
|
||||
if (s := _example_check_schema(_json_file(cpath(j)))) is not None],
|
||||
lambda s: frozenset(s))
|
||||
outs = [s for j in (1, 2, 3)[:FACTS_CHECK_PANEL] if (s := _example_check_schema(_json_file(cpath(j)))) is not None]
|
||||
if not outs:
|
||||
return items # no exam possible → keep (best-effort)
|
||||
|
||||
Reference in New Issue
Block a user