"""Shared AI response parsing utilities — used by all provider implementations.""" from __future__ import annotations import json import re from ai.base import ClassificationResult def strip_code_fences(text: str) -> str: """Remove markdown code fences (```json ... ```) from *text*.""" text = re.sub(r"```(?:json)?\s*", "", text) text = re.sub(r"```", "", text) return text.strip() def parse_classification(raw: str) -> ClassificationResult: """Parse a classification JSON response into a ClassificationResult. Tolerates markdown code fences and extracts the first JSON object found. Returns an empty ClassificationResult on any parse failure. """ raw = strip_code_fences(raw) match = re.search(r"\{.*\}", raw, re.DOTALL) if match: try: data = json.loads(match.group()) return ClassificationResult( topics=data.get("assigned_topics", []), suggested_new_topics=data.get("new_topic_suggestions", []), reasoning=data.get("reasoning", ""), ) except json.JSONDecodeError: pass return ClassificationResult() def parse_suggestions(raw: str) -> list[str]: """Parse a topic-suggestion JSON response into a list of topic name strings. Tolerates markdown code fences. Returns an empty list on parse failure. """ raw = strip_code_fences(raw) match = re.search(r"\{.*\}", raw, re.DOTALL) if match: try: data = json.loads(match.group()) return data.get("suggested_topics", []) except json.JSONDecodeError: pass return []