# SPDX-License-Identifier: GPL-3.0-only """Summarisation providers. - OpenAICompatibleSummariser: any endpoint speaking the OpenAI /chat/completions API - OpenAI, Groq, Together, OpenRouter, LocalAI, vLLM, LM Studio, and Ollama's /v1 endpoint. - AnthropicSummariser: Claude via the official Anthropic SDK (Messages API). Anthropic does not expose an OpenAI-compatible endpoint, so it needs its own provider. """ from __future__ import annotations import httpx from ..models import Summary from .base import SUMMARY_SYSTEM, parse_summary, summary_user_prompt class OpenAICompatibleSummariser: """Talks the OpenAI Chat Completions API. Works with any compatible base_url.""" def __init__(self, base_url: str, api_key: str, model: str): self.base_url = base_url.rstrip("/") self.api_key = api_key self.model = model self.name = f"openai_compatible:{model}" def summarise(self, recording_id: str, transcript_text: str) -> Summary: headers = {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {} payload = { "model": self.model, "messages": [ {"role": "system", "content": SUMMARY_SYSTEM}, {"role": "user", "content": summary_user_prompt(transcript_text)}, ], "temperature": 0.2, # Honoured by OpenAI/Groq/vLLM/etc.; ignored by servers that don't support it. "response_format": {"type": "json_object"}, } resp = httpx.post( f"{self.base_url}/chat/completions", headers=headers, json=payload, timeout=120 ) resp.raise_for_status() content = resp.json()["choices"][0]["message"]["content"] return parse_summary(recording_id, self.name, content) class AnthropicSummariser: """Claude via the official Anthropic SDK. Default model: claude-opus-4-8.""" def __init__(self, api_key: str, model: str): import anthropic # imported lazily so the dep is only needed for this provider self._client = ( anthropic.Anthropic(api_key=api_key) if api_key else anthropic.Anthropic() ) self.model = model self.name = f"anthropic:{model}" def summarise(self, recording_id: str, transcript_text: str) -> Summary: resp = self._client.messages.create( model=self.model, max_tokens=2000, system=SUMMARY_SYSTEM, messages=[{"role": "user", "content": summary_user_prompt(transcript_text)}], ) text = next((b.text for b in resp.content if b.type == "text"), "") return parse_summary(recording_id, self.name, text)