e08c484838
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>
110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""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
|