fix: Pipeline — Dachzeile, Infobox, Read-More-Position, Quellen-Format v2

This commit is contained in:
Hermes Agent
2026-06-23 22:39:31 +02:00
parent be19c0ec83
commit 3f66ba877e
+69 -45
View File
@@ -5,30 +5,12 @@ article_pipeline.py — Stage 0: Input-Preparation für den BSN-Artikel-Workflow
Liest Topic, PDFs, URLs und baut einen strukturierten Prompt für den Hermes-Agenten, Liest Topic, PDFs, URLs und baut einen strukturierten Prompt für den Hermes-Agenten,
der dann via delegate_task Research (Stage 1) und Write+QC (Stage 2+3) durchführt. der dann via delegate_task Research (Stage 1) und Write+QC (Stage 2+3) durchführt.
Stage 4 (Git, Verify) macht der Agent selbst. Stage 4 (Git, Verify) macht der Agent selbst.
Usage:
# Thema + PDF
python3 article_pipeline.py --topic "Gamegenic × TCC" --pdf pm.pdf --rubric "Nachrichten"
# Thema + URL
python3 article_pipeline.py --topic "Neue Kennerspiel-Nominierungen" --url "https://..."
# Nur Thema (autonome Recherche)
python3 article_pipeline.py --topic "Spiel des Jahres 2026" --rubric "Nachrichten"
# Batch (JSON)
python3 article_pipeline.py --batch topics.json
Output: Strukturierter Prompt nach stdout (wird vom Hermes-Cronjob als Context injiziert)
Exit-Codes: 0 = Prompt bereit, 1 = Fehler in Stage 0
""" """
import argparse import argparse
import json import json
import os import os
import sys import sys
import subprocess
import tempfile
from datetime import datetime from datetime import datetime
WORKSPACE = "/home/hermes/workspace" WORKSPACE = "/home/hermes/workspace"
@@ -46,23 +28,6 @@ def ocr_pdf(pdf_path: str) -> str:
return "\n\n".join(texts) return "\n\n".join(texts)
def extract_url_text(url: str) -> str:
"""URL-Inhalt via 5-Stufen-Eskalation abrufen."""
import urllib.request
import urllib.error
# Stufe 1: curl direkt
try:
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.read().decode("utf-8", errors="ignore")[:5000]
except Exception:
pass
# Stufe 2-5 wäre hier — für Stage 0 reicht Stufe 1
return f"[URL nicht direkt abrufbar: {url}]"
def build_prompt(topic: str, sources: list[str], rubric: str, def build_prompt(topic: str, sources: list[str], rubric: str,
category_id: int, image_urls: list[str], category_id: int, image_urls: list[str],
deadline: str | None, deadline: str | None,
@@ -72,7 +37,48 @@ def build_prompt(topic: str, sources: list[str], rubric: str,
now = datetime.now() now = datetime.now()
date_str = now.strftime("%d. %B %Y") date_str = now.strftime("%d. %B %Y")
slug = topic.lower().replace(" ", "_").replace(":", "").replace("×", "x")[:40] slug = topic.lower().replace(" ", "_").replace(":", "").replace("×", "x")[:40]
date_slug = now.strftime("%Y-%m-%d")
filename = f"artikel_{slug}_{date_slug}.html"
# --- WRITE+QC CONTEXT (most complex part) ---
write_context = (
"STRUKTUR (exakt einhalten, nummeriert):\n"
"1. <h1>TITEL</h1>\n"
'2. DACHZEILE: <div style="font-size:1.1rem;color:#555;margin-bottom:1.5rem;'
'border-bottom:2px solid #e0e0e0;padding-bottom:1rem;">'
'Zusammenfassung in 1-2 Sätzen</div>\n'
'3. <p class="meta" style="font-size:0.85rem;color:#888;margin-bottom:2rem;">'
'DATUM · Lesezeit: ca. N Minuten</p>\n'
'4. Einleitungs-Absatz <p>\n'
'5. INFOBOX (PFLICHT bei Unternehmen/Produkt-News): '
'<div style="background:#f0f4f8;border:1px solid #d0d8e0;border-radius:6px;'
'padding:1.2rem 1.5rem;margin:2rem 0;">'
'<h3 style="margin-top:0;color:#20228A;font-family:Arial,sans-serif;font-size:1.15rem;">'
'NAME — Fakten</h3><table style="width:100%;border-collapse:collapse;">'
'<tr><td style="padding:0.3rem 0.5rem;font-weight:bold;color:#444;width:160px;">'
'Label</td><td style="padding:0.3rem 0.5rem;">Wert</td></tr></table></div>\n'
'6. ALLE Inhalts-Abschnitte (<h2> + <p>)\n'
'7. <hr id="system-readmore" /> <-- NACH allen Inhalten, VOR Quellen!\n'
'8. Quellen-Überschrift: <span style="font-size:1.17em;font-weight:bold;'
'display:block;margin:1em 0;"><strong>Quellen</strong></span>\n'
'9. JEDE Quelle als: <li><a href="URL" target="_blank" rel="noopener" '
'style="color:#0075FF;">Name</a> — Beschreibung</li>\n'
'10. <p class="qc"><!-- QC: Quelle1=AAA|URL1, Quelle2=AA|URL2 --></p>\n'
'11. <p style="margin-top:2.5rem;padding-top:1rem;border-top:1px solid #ddd;'
'font-size:0.9rem;color:#777;"><em>Recherchestand: DATUM. '
'Basierend auf QUELLEN.</em></p>\n'
'\n'
'KRITISCHE REGELN (QC-Fail bei Verstoß):\n'
'- Read-More NACH allen Inhalten, VOR Quellen (NICHT nach Absatz 1!)\n'
'- JEDE Quelle als <a href> mit target="_blank" — KEINE unverlinkten Quellen!\n'
'- Dachzeile + Infobox sind PFLICHT\n'
'- Quellen-Überschrift: font-size:1.17em (NICHT 0.9rem)\n'
'- Kein "XML API v2", kein "Betriebsgeheimnis", keine Emojis\n'
'- Datei endet mit </html>\n'
f'- Rubrik: {rubric}'
)
# --- BUILD OUTPUT ---
lines = [] lines = []
lines.append(f"Führe den BSN-Artikel-Workflow für folgendes Thema aus:") lines.append(f"Führe den BSN-Artikel-Workflow für folgendes Thema aus:")
lines.append(f"**Thema:** {topic}") lines.append(f"**Thema:** {topic}")
@@ -80,7 +86,7 @@ def build_prompt(topic: str, sources: list[str], rubric: str,
lines.append(f"**Rubrik:** {rubric} (Joomla catid={category_id})") lines.append(f"**Rubrik:** {rubric} (Joomla catid={category_id})")
if deadline: if deadline:
lines.append(f"**Deadline:** {deadline}") lines.append(f"**Deadline:** {deadline}")
lines.append(f"**Dateiname:** artikel_{slug}_{now.strftime('%Y-%m-%d')}.html") lines.append(f"**Dateiname:** {filename}")
lines.append("") lines.append("")
# PDF-OCR-Ergebnisse # PDF-OCR-Ergebnisse
@@ -106,11 +112,24 @@ def build_prompt(topic: str, sources: list[str], rubric: str,
lines.append("") lines.append("")
lines.append("Nutze **delegate_task** um einen Research-Subagent zu starten:") lines.append("Nutze **delegate_task** um einen Research-Subagent zu starten:")
lines.append("") lines.append("")
lines.append(f"- goal: \"Recherchiere zum Thema: {topic}. Sammle Fakten aus allen Quellen, bewerte sie (AAA/AA/A), ergänze BGG-Daten (Designer, Verlag, Jahr). Erstelle Research-Brief in /tmp/research_brief.md. Bei unvollständigen Kern-Fakten → Exit 1.\"") lines.append(
lines.append("- context: Enthält PDF-Texte und URLs (siehe oben). Nutze 5-Stufen-Eskalation für unerreichbare URLs. web_search site:boardgamegeek.com für BGG-Daten.") f'- goal: "Recherchiere zum Thema: {topic}. '
f'Sammle Fakten aus allen Quellen, bewerte sie (AAA/AA/A), '
f'ergänze BGG-Daten (Designer, Verlag, Jahr). '
f'Erstelle Research-Brief in /tmp/research_brief.md. '
f'Bei unvollständigen Kern-Fakten → Exit 1."'
)
lines.append(
'- context: "Enthält PDF-Texte und URLs (siehe oben). '
'Nutze 5-Stufen-Eskalation für unerreichbare URLs. '
'web_search site:boardgamegeek.com für BGG-Daten."'
)
lines.append('- toolsets: ["web", "terminal", "file", "search", "skills"]') lines.append('- toolsets: ["web", "terminal", "file", "search", "skills"]')
lines.append("") lines.append("")
lines.append("**WICHTIG:** Warte bis Sub-Agent #1 fertig ist. Prüfe /tmp/research_brief.md. Bei Exit-Code 1 → MELDUNG an Basti, NICHT fortfahren.") lines.append(
"**WICHTIG:** Warte bis Sub-Agent #1 fertig ist. "
"Prüfe /tmp/research_brief.md. Bei Exit-Code 1 → MELDUNG an Basti, NICHT fortfahren."
)
lines.append("") lines.append("")
# STAGE 2+3: Write+QC # STAGE 2+3: Write+QC
@@ -118,8 +137,15 @@ def build_prompt(topic: str, sources: list[str], rubric: str,
lines.append("") lines.append("")
lines.append("Nutze **delegate_task** für einen Write+QC-Subagent:") lines.append("Nutze **delegate_task** für einen Write+QC-Subagent:")
lines.append("") lines.append("")
lines.append(f'- goal: "Schreibe HTML-Artikel zum Thema: {topic}. Nutze Research-Brief aus /tmp/research_brief.md. Datei: artikel_{slug}_{now.strftime("%Y-%m-%d")}.html in /home/hermes/workspace/. Danach: bash /home/hermes/workspace/verify_article.sh artikel_{slug}_{now.strftime("%Y-%m-%d")}.html. Bei Fehlern korrigieren, max 3 Versuche."') lines.append(
lines.append(f'- context: "HTML-Regeln: KEIN XML API v2, KEIN Betriebsgeheimnis, KEINE Emojis. <p class=\\"meta\\"> mit Datum+Lesezeit. Genau 1× <hr id=\\"system-readmore\\" />. Quellen als <span><strong>Quellen</strong></span> + <ol> mit <a target=\\"_blank\\">. <p class=\\"qc\\"> mit Quellen-Ratings. Datei endet mit </html>. Rubrik: {rubric}."') f'- goal: "Schreibe HTML-Artikel zum Thema: {topic}. '
f'Nutze Research-Brief aus /tmp/research_brief.md. '
f'Datei: /home/hermes/workspace/{filename}. '
f'Danach: bash /home/hermes/workspace/verify_article.sh '
f'/home/hermes/workspace/{filename}. '
f'Bei Fehlern korrigieren, max 3 Versuche."'
)
lines.append(f'- context: """\n{write_context}\n"""')
lines.append('- toolsets: ["web", "terminal", "file", "search", "skills"]') lines.append('- toolsets: ["web", "terminal", "file", "search", "skills"]')
lines.append("") lines.append("")
lines.append("**WICHTIG:** Warte bis Sub-Agent #2 fertig. Prüfe Exit-Code und Datei.") lines.append("**WICHTIG:** Warte bis Sub-Agent #2 fertig. Prüfe Exit-Code und Datei.")
@@ -129,15 +155,13 @@ def build_prompt(topic: str, sources: list[str], rubric: str,
lines.append("## STAGE 4: Deliver") lines.append("## STAGE 4: Deliver")
lines.append("") lines.append("")
lines.append("1. Git-Commit:") lines.append("1. Git-Commit:")
lines.append(f" ```bash") lines.append(f" cd /home/hermes/workspace && git add {filename}")
lines.append(f" cd /home/hermes/workspace && git add artikel_{slug}_{now.strftime('%Y-%m-%d')}.html")
lines.append(f' git commit -m "artikel: {topic[:50]}"') lines.append(f' git commit -m "artikel: {topic[:50]}"')
lines.append(f" ```")
lines.append("2. Prüfen ob Article-Server erreichbar:") lines.append("2. Prüfen ob Article-Server erreichbar:")
lines.append(" curl -s 'http://185.162.249.159:8890/?key=br3ttsp1el-n3ws-2026' | head -3") lines.append(" curl -s 'http://185.162.249.159:8890/?key=br3ttsp1el-n3ws-2026' | head -3")
lines.append("3. Basti benachrichtigen per send_message (Telegram):") lines.append("3. Basti benachrichtigen per send_message (Telegram):")
lines.append(f" ✅ Artikel fertig: {topic}") lines.append(f" ✅ Artikel fertig: {topic}")
lines.append(f" 📄 /home/hermes/workspace/artikel_{slug}_{now.strftime('%Y-%m-%d')}.html") lines.append(f" 📄 /home/hermes/workspace/{filename}")
lines.append(" 📊 verify_article.sh: 0 Fehler") lines.append(" 📊 verify_article.sh: 0 Fehler")
return "\n".join(lines) return "\n".join(lines)