import json import logging import re from typing import Any import httpx from . import profiles from .config import settings log = logging.getLogger("mac-worker.ollama") def _strip_codefence(s: str) -> str: s = s.strip() if s.startswith("```"): s = re.sub(r"^```(?:json)?", "", s, count=1).strip() if s.endswith("```"): s = s[:-3].strip() return s def _parse_json(text: str) -> dict: text = _strip_codefence(text) try: return json.loads(text) except json.JSONDecodeError: m = re.search(r"\{.*\}", text, re.S) if m: return json.loads(m.group(0)) raise def _truncate(text: str, max_chars: int = 60000) -> str: if len(text) <= max_chars: return text head = text[: max_chars // 2] tail = text[-max_chars // 2 :] return head + "\n\n[... gekürzt ...]\n\n" + tail async def _chat(prompt: str) -> str: url = f"{settings.ollama_url.rstrip('/')}/api/generate" payload = { "model": settings.ollama_model, "prompt": prompt, "stream": False, "options": {"temperature": 0.2}, } log.info("Ollama generate model=%s prompt_len=%d", settings.ollama_model, len(prompt)) async with httpx.AsyncClient(timeout=settings.ollama_timeout) as c: r = await c.post(url, json=payload) r.raise_for_status() data = r.json() return data.get("response", "") async def summarize(transcript: str, title: str = "", profile_name: str | None = None) -> dict[str, Any]: profile = profiles.get(profile_name) prompt = profile.summary_prompt.format( title=title or "(ohne Titel)", transcript=_truncate(transcript), ) log.info("Summarize profile=%s", profile.name) raw = await _chat(prompt) try: return _parse_json(raw) except Exception: # noqa: BLE001 log.warning("Failed to parse summary JSON — gebe Roh-Antwort als _raw zurück") return {"_raw": raw.strip(), "_parse_error": True} async def make_protocol( transcript: str, summary: dict, title: str = "", profile_name: str | None = None ) -> dict: profile = profiles.get(profile_name) prompt = profile.protocol_prompt.format( title=title or "(ohne Titel)", summary_json=json.dumps(summary, ensure_ascii=False), transcript=_truncate(transcript), ) log.info("Protocol profile=%s", profile.name) raw = await _chat(prompt) try: return _parse_json(raw) except Exception: # noqa: BLE001 log.warning("Failed to parse protocol JSON — Fallback auf Summary") fallback = dict(summary) if isinstance(summary, dict) else {} fallback.setdefault("title", title) return fallback async def health() -> dict: url = f"{settings.ollama_url.rstrip('/')}/api/tags" async with httpx.AsyncClient(timeout=10) as c: r = await c.get(url) r.raise_for_status() return r.json()