update
This commit is contained in:
@@ -656,7 +656,18 @@ def _finder_material(folder, sources: list[str] | None, queries: list[str]) -> t
|
||||
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:
|
||||
def material_folder(topic: str) -> Path | None:
|
||||
"""Korpus fürs Inline-Material: echte Quelle (uni/projekt/link) oder bei thema die
|
||||
Fundstellen der Research-Reader (arbeit/material/*.txt). Nur für Evidence-Packs —
|
||||
Konsens-Gate und QA messen unverändert gegen die konfigurierte Quelle (Messinvarianz)."""
|
||||
f = source_folder(topic)
|
||||
if f is not None:
|
||||
return f
|
||||
d = arbeit_dir(topic) / "material"
|
||||
return d if d.is_dir() and any(d.glob("*.txt")) else None
|
||||
|
||||
|
||||
def _build_research_prompt(topic: str, out_path: Path, instructions: str, type: str, folder: Path | None, fokus: str = "", section: str = "", material_path: Path | None = None) -> str:
|
||||
if section:
|
||||
# Section mode (uni/projekt): text directly in the prompt → small context, no file reading.
|
||||
source = section
|
||||
@@ -664,6 +675,13 @@ def _build_research_prompt(topic: str, out_path: Path, instructions: str, type:
|
||||
source = _prompt(_SOURCE_TEMPLATE[type], project=folder)
|
||||
else:
|
||||
source = _prompt("Blocks-Source-Thema", topic=topic)
|
||||
if material_path is not None:
|
||||
# thema: Fundstellen sichern — sie werden das Inline-Material der Folgeschritte
|
||||
# (Finder/Facts liefen sonst mit eigener Websuche → Reasoning-Schleifen, Retries)
|
||||
source += ("\n\nALSO write the file " + str(material_path) + " as you research: for every "
|
||||
"source you use, append one line with the URL followed by the relevant excerpt "
|
||||
"(plain text). Later steps back all facts ONLY against this material — "
|
||||
"an excerpt you skip here cannot back anything later.")
|
||||
return _prompt(
|
||||
"Blocks-Research",
|
||||
topic=topic, source=source, blocks_path=out_path, focus=fokus, extra=_extra(instructions),
|
||||
@@ -790,6 +808,7 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
|
||||
topic, provider, is_cancelled = ctx.topic, ctx.provider, ctx.is_cancelled
|
||||
work_dir = files["arbeit"]
|
||||
folder = source_folder(topic)
|
||||
mat = material_folder(topic) # thema: Research-Fundstellen als Inline-Korpus
|
||||
caps = "files" if folder else "full"
|
||||
# Source for the evidence exam in the clarify step (discards invented/unsupportable subs).
|
||||
_type = load_source(topic).get("type", "thema")
|
||||
@@ -896,7 +915,7 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
|
||||
# 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])
|
||||
_finder_material, mat, sources, [str(entries[num]) for num in chunk])
|
||||
start = time.monotonic()
|
||||
round_n = 0
|
||||
while not is_cancelled():
|
||||
@@ -1031,9 +1050,9 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
|
||||
if pending:
|
||||
# Inline evidence: corpus excerpts in the prompt (no self-research); the judge
|
||||
# answers as TEXT, the engine persists the j-file (resume + majority unchanged).
|
||||
ev = _evidence_pack(folder, sources,
|
||||
ev = _evidence_pack(mat, sources,
|
||||
[title_by_num[num] for num in chunk]
|
||||
+ [s for num in chunk for s in shown_by_num.get(num, [])]) if folder else ""
|
||||
+ [s for num in chunk for s in shown_by_num.get(num, [])]) if mat else ""
|
||||
j_source = _prompt("Blocks-Source-Inline", excerpts=ev) if ev else source
|
||||
|
||||
def _sink(result, p):
|
||||
@@ -1147,7 +1166,7 @@ async def _subblocks_block(ctx: GenContext, set_p, files: dict, entries: dict, i
|
||||
"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])
|
||||
_finder_material, mat, 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, material, backing, round_caps)
|
||||
@@ -1494,7 +1513,7 @@ async def _luecken_runde(ctx: GenContext, files: dict, title: str, luecken: list
|
||||
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))
|
||||
_finder_material, material_folder(topic), 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}",
|
||||
@@ -1901,7 +1920,8 @@ async def _facts_block(ctx, set_p, files: dict, raw: dict, q: dict, folder, inst
|
||||
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 ""
|
||||
mat = material_folder(topic)
|
||||
ev = _evidence_pack(mat, sources, queries) if mat else ""
|
||||
if ev:
|
||||
return _prompt("Blocks-Source-Inline", excerpts=ev), "none"
|
||||
return source, caps
|
||||
@@ -1974,7 +1994,8 @@ async def _facts_block(ctx, set_p, files: dict, raw: dict, q: dict, folder, inst
|
||||
cites = [bf.get("source", "") for fm in per.values() for fk in fm.values()
|
||||
for bf in fk.get("cited_facts", [])]
|
||||
fallback = [blocks[i][0] for i in idxs] + [fk["sub"] for fm in per.values() for fk in fm.values()]
|
||||
ev = _cited_evidence(folder, sources, cites, fallback) if folder else ""
|
||||
mat = material_folder(topic)
|
||||
ev = _cited_evidence(mat, sources, cites, fallback) if mat 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]
|
||||
tmap = {asyncio.create_task(
|
||||
|
||||
@@ -209,6 +209,10 @@ async def _research_once(ctx: GenContext, flow: Flow, q: dict, folder, instructi
|
||||
work_dir = flow.work_dir
|
||||
caps = "files" if folder else "full"
|
||||
p = work_dir / f"research-{tag}.md"
|
||||
mp = None
|
||||
if folder is None: # thema: Fundstellen als Material für die Inline-Folgeschritte sichern
|
||||
(work_dir / "material").mkdir(parents=True, exist_ok=True)
|
||||
mp = work_dir / "material" / f"research-{tag}.txt"
|
||||
stop = asyncio.Event()
|
||||
buf: list[str] = [] # assistant text streamed live from the JSON events
|
||||
|
||||
@@ -237,7 +241,7 @@ async def _research_once(ctx: GenContext, flow: Flow, q: dict, folder, instructi
|
||||
await run_single_slot(
|
||||
ctx, f"Research {tag}", key=f"blocks-{ctx.topic}-research-{tag}",
|
||||
prompt=_build_research_prompt(ctx.topic, p, instructions, q["type"], folder,
|
||||
fokus=fokus, section=section),
|
||||
fokus=fokus, section=section, material_path=mp),
|
||||
role="quick", capabilities=caps,
|
||||
payload=(lambda result, p=p: _file_payload(p)),
|
||||
timeout=RESEARCH_RUNTIME, on_line=_on_line,
|
||||
|
||||
@@ -372,6 +372,41 @@ async def test_clarify_inline_evidence_no_tools(sub_env, monkeypatch, tmp_path):
|
||||
assert list(tmp_path.glob("subblock-final-*-j*.md")) # Engine persistiert die Judge-Antwort
|
||||
|
||||
|
||||
async def test_thema_nutzt_research_material_inline(sub_env, monkeypatch, tmp_path):
|
||||
"""thema mit Research-Fundstellen (arbeit/material/*.txt): Finder bekommt sie inline
|
||||
und läuft ohne Tools — vorher eigene Websuche pro Call (Reasoning-Schleifen, Retries)."""
|
||||
db, ctx, files = sub_env
|
||||
monkeypatch.setattr(blx, "source_folder", lambda t: None)
|
||||
md = tmp_path / "arbeit" / "material"
|
||||
md.mkdir(parents=True)
|
||||
(md / "research-1.txt").write_text(
|
||||
"https://example.org/alpha\nAlpha Grundlagen: der Kernbegriff, gut belegt.\n",
|
||||
encoding="utf-8")
|
||||
monkeypatch.setattr(blx, "arbeit_dir", lambda t: tmp_path / "arbeit")
|
||||
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-")
|
||||
assert raw == {"Alpha": ["Alpha Grundlagen"]}
|
||||
finders = [s for s in fake.slots_seen if "-r1-" in s["key"]]
|
||||
assert finders and all(s["capabilities"] == "none" for s in finders)
|
||||
assert "── research-1.txt" in finders[0]["prompt"] # Fundstellen inline
|
||||
|
||||
|
||||
def test_material_folder_fallbacks(monkeypatch, tmp_path):
|
||||
"""Echte Quelle gewinnt; sonst arbeit/material mit Inhalt; sonst None."""
|
||||
monkeypatch.setattr(blx, "source_folder", lambda t: tmp_path / "quelle")
|
||||
assert blx.material_folder("t") == tmp_path / "quelle"
|
||||
monkeypatch.setattr(blx, "source_folder", lambda t: None)
|
||||
monkeypatch.setattr(blx, "arbeit_dir", lambda t: tmp_path / "arbeit")
|
||||
assert blx.material_folder("t") is None # kein Material-Ordner
|
||||
md = tmp_path / "arbeit" / "material"
|
||||
md.mkdir(parents=True)
|
||||
assert blx.material_folder("t") is None # leer zählt nicht
|
||||
(md / "research-1.txt").write_text("x", encoding="utf-8")
|
||||
assert blx.material_folder("t") == md
|
||||
|
||||
|
||||
async def test_panel_2of3_kehrt_bei_einigkeit_zurueck(tmp_path):
|
||||
"""Zwei übereinstimmende Verdicts → Rückkehr ohne den langsamen Dritten; sein
|
||||
Ergebnis wird detached nachpersistiert (Resume)."""
|
||||
@@ -418,6 +453,7 @@ async def test_facts_check_inline_cited_evidence(sub_env, monkeypatch, tmp_path)
|
||||
die Check-Datei schreibt die Engine aus der Text-Antwort."""
|
||||
db, ctx, files = sub_env
|
||||
d = _corpus(tmp_path)
|
||||
monkeypatch.setattr(blx, "source_folder", lambda t: d)
|
||||
facts = {"facts": [{"block": "Alpha", "subblock": "Sub Eins", "key_points": ["k"],
|
||||
"prerequisites": "", "hurdles": "",
|
||||
"cited_facts": [{"text": "T", "source": "Skript.txt, Z.2"}],
|
||||
@@ -455,6 +491,7 @@ async def test_facts_find_inline_evidence_no_tools(sub_env, monkeypatch, tmp_pat
|
||||
verloren sich in Reasoning-Schleifen und endeten mit leerem Turn (Retry-Wellen)."""
|
||||
db, ctx, files = sub_env
|
||||
d = _corpus(tmp_path)
|
||||
monkeypatch.setattr(blx, "source_folder", lambda t: d)
|
||||
seen = []
|
||||
facts = {"facts": [{"block": "Alpha", "subblock": "Sub Eins", "key_points": ["k"],
|
||||
"prerequisites": "", "hurdles": "",
|
||||
|
||||
Reference in New Issue
Block a user