This commit is contained in:
Team3
2026-06-04 22:24:37 +02:00
parent 5a794cc40c
commit ab85cb530f
6 changed files with 130 additions and 66 deletions

View File

@@ -19,9 +19,9 @@ ALLOWED_FORMATS = [
FORMAT_META = {
"OnePager": {"pages": "1 Seite", "time": "~5 Min"},
"Cheatsheet": {"pages": "1 Seite", "time": "~10 Min"},
"MiniGuide": {"pages": "3-4 Seiten", "time": "~15 Min"},
"Guide": {"pages": "15-250 Seiten", "time": "variabel"},
"EndGuide": {"pages": "120-150 Seiten", "time": "~6h"},
"MiniGuide": {"pages": "3-5 Seiten", "time": "~15-25 Min"},
"Guide": {"pages": "10-30 Seiten", "time": "variabel"},
"EndGuide": {"pages": "50-250 Seiten", "time": "variabel"},
}
AGENT_TIMEOUT = 3600

View File

@@ -79,34 +79,57 @@ SCHRITT 3: Schreibe eine vollständige Wissensdatei nach {cache_path}.
Die Datei muss als alleinige Faktenbasis für einen Lern-Guide ausreichen. Erfasse: Zweck, Architektur, Abläufe, wichtige Dateien, Konfiguration, Befehle, Datenstrukturen, Besonderheiten. Lass nichts Wichtiges aus. Schreibe NUR diese eine Datei."""
def _build_generator_prompt(topic: str, format_name: str, html_path: Path, instructions: str = "", project_content: str | None = None) -> str:
spec = (TEMPLATES_DIR / "Format" / f"{format_name}.md").read_text(encoding="utf-8")
reference = (TEMPLATES_DIR / "Referenz" / f"{format_name}.md").read_text(encoding="utf-8")
extra = f"\n\nZUSÄTZLICHE ANWEISUNGEN VOM NUTZER:\n{instructions}\n" if instructions else ""
def _research_line(topic: str, project_content: str | None) -> str:
if project_content:
research_line = (
return (
f'Die folgenden PROJEKT-INHALTE sind die Quelle der Wahrheit für "{topic}". '
"Nutze sie als primäre Faktenbasis. Recherchiere per Websuche nur ergänzend, "
"um fehlende oder sich ändernde Fakten (z. B. aktuelle Versionsnummern externer Tools) zu prüfen.\n\n"
f"PROJEKT-INHALTE (Quelle der Wahrheit):\n{project_content}"
)
else:
research_line = f'Recherchiere zuerst die aktuelle Version und aktuelle Fakten zu "{topic}" per Websuche, damit Versionsnummern und Angaben stimmen.'
return f'Recherchiere zuerst die aktuelle Version und aktuelle Fakten zu "{topic}" per Websuche, damit Versionsnummern und Angaben stimmen.'
def _build_inventory_prompt(topic: str, format_name: str, inventory_path: Path, instructions: str = "", project_content: str | None = None) -> str:
spec = (TEMPLATES_DIR / "Format" / f"{format_name}.md").read_text(encoding="utf-8")
extra = f"\n\nZUSÄTZLICHE ANWEISUNGEN VOM NUTZER:\n{instructions}\n" if instructions else ""
return f"""Erstelle das Themeninventar für einen Lern-Guide zum Thema "{topic}" im Format "{format_name}".
{_research_line(topic, project_content)}
Das Inventar ist eine durchnummerierte Liste der Bausteine (Konzepte/Funktionen/Features), die der Guide abdecken muss — bereits in sinnvolle Teile/Sektionen gruppiert. Die ABDECKUNGS-Regel der Format-Spezifikation bestimmt die Auswahl; wie viele Punkte das sind, hängt vom Thema ab. Ein anderer Agent schreibt den Guide später strikt nach diesem Inventar — jeder Punkt wird eine Sektion.
Schreibe NUR die Inventar-Datei nach: {inventory_path}
Kein HTML, kein weasyprint.
FORMAT-SPEZIFIKATION (relevant sind ABDECKUNG und Seitenrahmen):
{spec}
{extra}"""
def _build_generator_prompt(topic: str, format_name: str, html_path: Path, inventory: str, instructions: str = "", project_content: str | None = None) -> str:
spec = (TEMPLATES_DIR / "Format" / f"{format_name}.md").read_text(encoding="utf-8")
reference = (TEMPLATES_DIR / "Referenz" / f"{format_name}.md").read_text(encoding="utf-8")
extra = f"\n\nZUSÄTZLICHE ANWEISUNGEN VOM NUTZER:\n{instructions}\n" if instructions else ""
return f"""Erstelle einen Lern-Guide zum Thema "{topic}" im Format "{format_name}".
{research_line}
{_research_line(topic, project_content)}
Schreibe die HTML-Datei nach: {html_path}
Schreibe NUR die HTML-Datei. Führe KEIN weasyprint aus, erzeuge KEINE PDF. Das übernimmt ein anderer Prozess.
THEMENINVENTAR (verbindlich — von einem Recherche-Agenten erstellt):
Jeder Inventar-Punkt muss im Guide als eigene Sektion umgesetzt werden. Nicht zusammenfassen, nicht kürzen, nichts weglassen. Der Umfang des Guides folgt aus diesem Inventar innerhalb des Seitenrahmens der Spezifikation.
{inventory}
FORMAT-SPEZIFIKATION:
{spec}
REFERENZ-IMPLEMENTIERUNG (Stil-Vorlage, adaptiere für "{topic}"):
REFERENZ-IMPLEMENTIERUNG (NUR Stil-Vorlage: Bausteine, CSS, Tonalität. Umfang und Struktur kommen aus dem INVENTAR, nicht aus der Referenz):
{reference}
{extra}"""
@@ -222,9 +245,27 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio
return
project_content = cache_path.read_text(encoding="utf-8")
# Step 1: Generator-Agent erstellt HTML
# Step 1: Inventar-Agent recherchiert die Bausteine des Themas
inventory_path = html_path.with_suffix(".inventar.md")
await _set_progress(guide_id, "Erstelle Themeninventar…")
current_step = "Inventar"
inv_prompt = _build_inventory_prompt(topic, format_name, inventory_path, instructions, project_content)
returncode, inv_out, inv_err = await run_agent(guide_id, inv_prompt, AGENT_TIMEOUT, provider=provider, role="fast", capabilities="full")
if guide_id in _cancelled:
return
if returncode != 0:
await _fail(guide_id, _claude_error("Inventar-Fehler", returncode, inv_out, inv_err))
return
if not inventory_path.exists():
await _fail(guide_id, "Themeninventar wurde nicht erstellt")
return
inventory = inventory_path.read_text(encoding="utf-8")
# Step 2: Generator-Agent erstellt HTML nach Inventar
await _set_progress(guide_id, "Generiere HTML…")
gen_prompt = _build_generator_prompt(topic, format_name, html_path, instructions, project_content)
current_step = "Generierung"
gen_prompt = _build_generator_prompt(topic, format_name, html_path, inventory, instructions, project_content)
returncode, stdout, stderr = await run_agent(guide_id, gen_prompt, AGENT_TIMEOUT, provider=provider, role="guide", capabilities="full")
if guide_id in _cancelled:

View File

@@ -168,6 +168,7 @@ async def remove(guide_id: str):
raise HTTPException(404, "Guide nicht gefunden")
html_path, pdf_path = final_paths(guide["topic"], guide["format"])
html_path.unlink(missing_ok=True)
html_path.with_suffix(".inventar.md").unlink(missing_ok=True)
pdf_path.unlink(missing_ok=True)
await delete_progress(guide_id)
await delete_guide(guide_id)