This commit is contained in:
Team3
2026-07-04 16:50:50 +02:00
parent 8488737303
commit 92c69c1561
7 changed files with 298 additions and 37 deletions

View File

@@ -597,6 +597,32 @@ def _sink_json(result, path: Path, schema):
return val
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
text instruction still counts — same tolerance as _sink_or_file."""
text = _reply_text(result).strip()
d = _parse_subblocks(text)
if d:
atomic_write_text(path, text)
return d
return _parse_subblocks(_read(path)) or None
def _finder_material(folder, sources: list[str] | None, queries: list[str]) -> tuple[str, str, str]:
"""→ (material, backing, caps) for the Subblock-Research prompt. uni/projekt: corpus
excerpts INLINE — the finder had neither path nor excerpts and hunted the material per
call via glob/grep/bash (measured: 313 tool rounds, 52141 s vs 1535 s single-shot).
thema (or excerpt miss): web research as before, fail-open."""
ev = _evidence_pack(folder, sources, queries) if folder else ""
if ev:
material = "\n" + _prompt("Blocks-Source-Inline", excerpts=ev) + "\n"
backing = ("Backed by the SOURCE EXCERPTS above, not invented — leave out any "
"sub-point the excerpts do not support.")
return material, backing, "none"
return "", "Backed, not invented. Verify uncertain points via web search.", ("files" if folder else "full")
def _build_research_prompt(topic: str, out_path: Path, instructions: str, type: str, folder: Path | None, fokus: str = "", section: str = "") -> str:
if section:
# Section mode (uni/projekt): text directly in the prompt → small context, no file reading.
@@ -760,15 +786,15 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
return ("\n\nBEREITS ERFASST — liste diese NICHT erneut. Finde nur, was FEHLT:\n" + "\n".join(known))
# ONE finder round (3 slots, quorum 2) → count of NEW sub norms; None = no result/cancel.
async def _one_round(label, subset, assignment, paths, keys, known, extra_instr):
async def _one_round(label, subset, assignment, paths, keys, known, extra_instr, material, backing, round_caps):
chunk_idx = _title_index({num: title_by_num[num] for num in subset})
for p in paths:
p.unlink(missing_ok=True)
slots = [{
"key": k,
"prompt": _prompt("Subblock-Research", topic=topic, assignment=assignment, known=known, out_path=p, extra=_extra(extra_instr)),
"role": "quick", "capabilities": caps,
"payload": (lambda result, p=p: _parse_subblocks(_read(p)) or None),
"prompt": _prompt("Subblock-Research", topic=topic, assignment=assignment, known=known, material=material, backing=backing, extra=_extra(extra_instr)),
"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)
if is_cancelled() or not agent_texts:
@@ -818,6 +844,10 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
# Phase "Subblocks find": per package loop until 0 new subs / time cap.
async def _find(c, chunk):
assignment = "\n".join(f"- {entries[num]}" for num in chunk)
# Material once per package (excerpts are round-invariant; one query per block
# keeps the coverage guarantee of _evidence_pack).
material, backing, round_caps = await asyncio.to_thread(
_finder_material, folder, sources, [str(entries[num]) for num in chunk])
start = time.monotonic()
round_n = 0
while not is_cancelled():
@@ -825,7 +855,7 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
bekannt = await _known_block(chunk) if round_n > 1 else ""
paths = [work_dir / f"subblock-c{c}-r{round_n}-{i}.md" for i in (1, 2, 3)]
keys = [f"blocks-{topic}-{ns}subblock-c{c}-r{round_n}-{i}" for i in (1, 2, 3)]
new = await _one_round(f"{lbl}Subblocks package {c} R{round_n}", chunk, assignment, paths, keys, bekannt, instructions)
new = await _one_round(f"{lbl}Subblocks package {c} R{round_n}", chunk, assignment, paths, keys, bekannt, instructions, material, backing, round_caps)
if new is None:
if is_cancelled():
return False
@@ -1067,9 +1097,11 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
focus = (instructions + "\n\nDieser Block hat bisher nur sehr wenige belegte "
"Subbausteine. Suche gezielt nach WEITEREN belegbaren Kernaspekten, die "
"oben fehlen. Nimm NUR auf, was die Quellen wirklich hergeben — nicht aufblähen.")
material, backing, round_caps = await asyncio.to_thread(
_finder_material, folder, sources, [str(entries[num]) for num in lacking])
paths = [work_dir / f"subblock-x{k}-c{c}-{i}.md" for i in (1, 2, 3)]
keys = [f"blocks-{topic}-{ns}subblock-x{k}-c{c}-{i}" for i in (1, 2, 3)]
new = await _one_round(f"{lbl}Subblocks catch-up {c} X{k}", lacking, assignment, paths, keys, known, focus)
new = await _one_round(f"{lbl}Subblocks catch-up {c} X{k}", lacking, assignment, paths, keys, known, focus, material, backing, round_caps)
if not new:
return
await _select(lacking, keep_consensus=True)
@@ -1412,12 +1444,14 @@ async def _luecken_runde(ctx: GenContext, files: dict, title: str, luecken: list
"Aspekten des Blocks — nichts anderes:\n" + "\n".join(f"- {l}" for l in luecken))
known = ("\n\nBEREITS ERFASST — liste diese NICHT erneut:\n"
+ "\n".join(f"- {s}" for s in have)) if have else ""
material, backing, caps = await asyncio.to_thread(
_finder_material, folder, sources, [title] + list(luecken))
paths = [work_dir / f"luecken-{ns}r1-{i}.md" for i in (1, 2, 3)]
slots = [{
"key": f"blocks-{topic}-{ns}luecken-r1-{i}",
"prompt": _prompt("Subblock-Research", topic=topic, assignment=f"- {title}", known=known, out_path=p, extra=_extra(focus)),
"role": "quick", "capabilities": "files" if folder else "full",
"payload": (lambda result, p=p: _parse_subblocks(_read(p)) or None),
"prompt": _prompt("Subblock-Research", topic=topic, assignment=f"- {title}", known=known, material=material, backing=backing, extra=_extra(focus)),
"role": "quick", "capabilities": caps,
"payload": (lambda result, p=p: _sink_subs(result, p)),
} for i, p in zip((1, 2, 3), paths)]
agent_texts = await _race(topic, f"{lbl}Lücken-Nachfass", slots, 2,
_timeout("subblock", 1), provider, cancelled=is_cancelled, grace=CONSENSUS_GRACE)