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
+109
View File
@@ -0,0 +1,109 @@
"""LLM provider abstraction. Each provider wraps one vendor SDK."""
import os
from rich.console import Console
_console = Console()
class Provider:
name: str
def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str:
raise NotImplementedError
class AnthropicProvider(Provider):
name = "Claude"
def __init__(self):
import anthropic
self._client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str:
response = self._client.messages.create(
model="claude-sonnet-4-6",
max_tokens=max_tokens,
system=system,
messages=messages,
)
return response.content[0].text
class OpenAIProvider(Provider):
name = "ChatGPT"
def __init__(self):
from openai import OpenAI
self._client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str:
oai_messages = [{"role": "system", "content": system}] + messages
response = self._client.chat.completions.create(
model="gpt-4o",
max_tokens=max_tokens,
messages=oai_messages,
)
return response.choices[0].message.content
class GeminiProvider(Provider):
name = "Gemini"
def __init__(self):
import google.generativeai as genai
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
self._genai = genai
def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str:
model = self._genai.GenerativeModel(
model_name="gemini-2.0-flash",
system_instruction=system,
)
# Convert role "assistant" → "model" for Gemini
contents = [
{"role": "model" if m["role"] == "assistant" else "user", "parts": [m["content"]]}
for m in messages
]
response = model.generate_content(
contents,
generation_config=self._genai.types.GenerationConfig(max_output_tokens=max_tokens),
)
return response.text
class MistralProvider(Provider):
name = "Mistral"
def __init__(self):
from mistralai import Mistral
self._client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str:
mistral_messages = [{"role": "system", "content": system}] + messages
response = self._client.chat.complete(
model="mistral-large-latest",
max_tokens=max_tokens,
messages=mistral_messages,
)
return response.choices[0].message.content
_REGISTRY = [
("ANTHROPIC_API_KEY", AnthropicProvider),
("OPENAI_API_KEY", OpenAIProvider),
("GOOGLE_API_KEY", GeminiProvider),
("MISTRAL_API_KEY", MistralProvider),
]
def get_configured() -> list[Provider]:
"""Return instantiated providers for every API key that is set. Skips on init error."""
result = []
for env_var, cls in _REGISTRY:
if os.environ.get(env_var):
try:
result.append(cls())
except Exception as e:
_console.print(f"[yellow][Warnung] {cls.__name__} konnte nicht initialisiert werden: {e}[/yellow]")
return result