# SPDX-License-Identifier: GPL-3.0-only """Provider interfaces plus the shared summary prompt and a tolerant JSON parser. Keeping the prompt and parser here means every LLM provider produces the same Summary shape regardless of whether it is OpenAI-compatible, Anthropic, or a local model. """ from __future__ import annotations import json import re from typing import Protocol from ..models import Summary, Transcript SUMMARY_SYSTEM = ( "You are a meeting-notes assistant. Given a transcript, produce a concise overview, " "the key points, and any action items. Respond with ONLY a JSON object of the form " '{"overview": string, "key_points": [string], "action_items": [string]} and nothing ' "else." ) def summary_user_prompt(transcript_text: str) -> str: return f"Transcript:\n\n{transcript_text}\n\nProduce the JSON summary now." def parse_summary(recording_id: str, model: str, raw: str) -> Summary: """Parse an LLM reply into a Summary, tolerating prose or code-fences around the JSON.""" data: dict = {} match = re.search(r"\{.*\}", raw, re.DOTALL) if match: try: data = json.loads(match.group(0)) except json.JSONDecodeError: data = {} return Summary( recording_id=recording_id, model=model, overview=str(data.get("overview") or raw.strip()[:1000]), key_points=[str(x) for x in (data.get("key_points") or [])], action_items=[str(x) for x in (data.get("action_items") or [])], ) class Transcriber(Protocol): name: str def transcribe(self, audio_path: str, language: str | None = None) -> Transcript: ... class Summariser(Protocol): name: str def summarise(self, recording_id: str, transcript_text: str) -> Summary: ...