Add KNIEPUNKT Assistant with multi-LLM editorial workflow

Six-step weekly workflow (research → sources → storyline → draft →
quality → publication) supporting Claude, ChatGPT, Gemini, and Mistral
in parallel for creative steps. Web search via Anthropic tool for news
research. Episode index built from 34 existing KNIEPUNKT episodes for
redundancy checks. Sessions persisted as JSON for mid-workflow resume.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 23:54:23 +02:00
commit e08c484838
57 changed files with 3240 additions and 0 deletions
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""KNIEPUNKT Assistant — weekly editorial workflow for Dr. André Knie."""
from rich.console import Console
from rich.panel import Panel
from rich.prompt import Confirm
from kniepunkt.llm import get_client
from kniepunkt.providers import get_configured
from kniepunkt import episodes as ep_module
from kniepunkt import session as sess_module
from kniepunkt import research, storyline, drafting, quality, publication
console = Console()
def _header():
console.print(Panel(
"[bold cyan]KNIEPUNKT Assistant[/bold cyan]\n"
"[dim]Redaktioneller Workflow · Dr. André Knie[/dim]",
border_style="cyan",
))
def _build_or_load_index(client) -> list[dict]:
import json
from pathlib import Path
cache = Path("episodes_cache.json")
if cache.exists():
rebuild = Confirm.ask("\n[dim]Episode-Index vorhanden. Neu aufbauen?[/dim]", default=False)
if not rebuild:
with open(cache) as f:
index = json.load(f)
console.print(f"[green]Episode-Index: {len(index)} Episoden geladen[/green]")
return index
console.print("\n[yellow]Baue Episode-Index auf (einmalig, danach gecacht)...[/yellow]")
index = ep_module.build_index(client, force=True)
console.print(f"[green]Episode-Index erstellt: {len(index)} Episoden[/green]")
return index
def _run(client, providers: list, session: dict, index: list):
episodes_context = ep_module.format_for_context(index)
# ── 1. Research ──────────────────────────────────────────────────────────
if session["step"] == "research":
author_input = research.get_author_input()
news_digest = research.research_news(client, author_input, episodes_context)
console.print(Panel(news_digest, title="Nachrichten-Kandidaten", border_style="green"))
session["research"] = {"author_input": author_input, "news_digest": news_digest}
session["step"] = "sources"
sess_module.save(session)
# ── 2. Source assessment ─────────────────────────────────────────────────
if session["step"] == "sources":
source_assessment = research.assess_sources(client, session["research"]["news_digest"])
console.print(Panel(source_assessment, title="Quellenbewertung", border_style="yellow"))
if not Confirm.ask("\nWeiter zur Storyline-Entwicklung?", default=True):
console.print("[dim]Session gespeichert. Neustart setzt hier fort.[/dim]")
return
session["sources"] = source_assessment
session["step"] = "storyline"
sess_module.save(session)
# ── 3. Storyline ─────────────────────────────────────────────────────────
if session["step"] == "storyline":
results = storyline.generate_storylines(
providers,
session["research"]["news_digest"],
session["sources"],
session["research"]["author_input"],
episodes_context,
)
provider_name, choice, selected_text, adjustments = storyline.select_storyline(results)
session["storyline"] = {
"results": results,
"selected_provider": provider_name,
"choice": choice,
"selected_text": selected_text,
"adjustments": adjustments,
}
session["step"] = "draft"
sess_module.save(session)
# ── 4. Draft ─────────────────────────────────────────────────────────────
if session["step"] == "draft":
sl = session["storyline"]
draft_results = drafting.generate_drafts(
providers,
sl["selected_text"],
sl["choice"],
sl["adjustments"],
session["research"]["news_digest"],
session["sources"],
session["research"]["author_input"],
)
provider_name, draft = drafting.select_draft(draft_results)
console.print("\n[dim]Überarbeitungshinweise? (Enter = Entwurf übernehmen)[/dim]")
feedback = input().strip()
if feedback:
draft = drafting.enrich_draft(client, draft, feedback)
console.print(Panel(draft, title="Überarbeiteter Entwurf", border_style="green"))
session["draft_results"] = draft_results
session["draft"] = draft
session["step"] = "quality"
sess_module.save(session)
# ── 5. Quality review ────────────────────────────────────────────────────
if session["step"] == "quality":
quality_report = quality.review(
client,
session["draft"],
session["sources"],
episodes_context,
)
console.print(Panel(quality_report, title="Qualitätsprüfung", border_style="yellow"))
console.print("\n[dim]Weitere Überarbeitungen? (Enter = weiter zur Publikation)[/dim]")
feedback = input().strip()
if feedback:
draft = drafting.enrich_draft(client, session["draft"], feedback)
session["draft"] = draft
console.print(Panel(draft, title="Finalisierter Entwurf", border_style="green"))
session["quality"] = quality_report
session["step"] = "publication"
sess_module.save(session)
# ── 6. Publication prep ──────────────────────────────────────────────────
if session["step"] == "publication":
teasers = publication.generate_teasers(client, session["draft"])
console.print(Panel(teasers, title="Teaser-Varianten", border_style="cyan"))
visual_ideas = publication.generate_visual_ideas(client, session["draft"])
console.print(Panel(visual_ideas, title="Cover-Ideen", border_style="cyan"))
out_path = publication.save_package(
session["date"],
session["draft"],
teasers,
session["sources"],
visual_ideas,
)
console.print(Panel(
f"[green]Publikationspaket gespeichert:[/green] {out_path}\n\n"
"Enthält: Artikel · Teaser-Varianten · Cover-Ideen · Quellenbewertung",
title="✓ Fertig",
border_style="green",
))
session["publication"] = {"teasers": teasers, "visual_ideas": visual_ideas}
session["step"] = "done"
sess_module.save(session)
def main():
_header()
try:
client = get_client()
except RuntimeError as e:
console.print(f"[red]{e}[/red]\nBitte ANTHROPIC_API_KEY in der .env-Datei eintragen.")
return
providers = get_configured()
if not providers:
console.print("[red]Keine LLM-API-Keys konfiguriert. Bitte .env-Datei prüfen.[/red]")
return
provider_names = ", ".join(p.name for p in providers)
console.print(f"[green]Aktive Modelle: {provider_names}[/green]")
index = _build_or_load_index(client)
latest = sess_module.load_latest()
session = None
if latest and latest.get("step") not in ("done",):
if Confirm.ask(
f"\nGefundene Session vom [bold]{latest['date']}[/bold] "
f"(Schritt: [bold]{latest['step']}[/bold]). Fortfahren?",
default=True,
):
session = latest
if session is None:
session = sess_module.new_session()
console.print(f"\n[green]Neue Session: {session['date']}[/green]")
_run(client, providers, session, index)
if __name__ == "__main__":
main()