Files
Kniepunkt/kniepunkt/providers.py
T
ankn e08c484838 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>
2026-04-24 23:54:23 +02:00

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