import re def safe_extract_text(content) -> str: """Bezpiecznie wyciąga tekst z odpowiedzi LLM (obsługuje zarówno str jak i list z Gemini).""" if isinstance(content, str): return content.strip() if isinstance(content, list): texts = [] for item in content: if isinstance(item, dict) and "text" in item: texts.append(str(item["text"])) elif isinstance(item, str): texts.append(item) return " ".join(texts).strip() # Fallback dla innych typów return str(content).strip() def extract_markdown_and_sanitize(raw_content: str, min_length: int = 50) -> str: """ Ekstrahuje blok Markdown i przeprowadza sanity checks (długość, typowe odmowy LLM). Zgłasza ValueError, co pozwala mechanizmowi Watchdog na ponowienie próby. """ if "[NO_CHANGE]" in raw_content: return "[NO_CHANGE]" # Ekstrakcja bloku Markdown (jeśli obecny) md_match = re.search( r"```(?:markdown|md|html)?\s*\n?(.*?)```", raw_content, re.DOTALL | re.IGNORECASE, ) if md_match: extracted = md_match.group(1).strip() else: # Awaryjne usuwanie backticków extracted = raw_content.strip() if extracted.startswith("```"): first_newline = extracted.find("\n") if first_newline != -1 and first_newline < 20: extracted = extracted[first_newline + 1 :] else: extracted = extracted[3:] if extracted.endswith("```"): extracted = extracted[:-3] extracted = extracted.strip() # Sanity checks if len(extracted) < min_length: raise ValueError( f"Wygenerowany tekst jest zbyt krótki ({len(extracted)} znaków). Prawdopodobnie błąd generowania." ) refusals = [ "nie mogę", "przykro mi", "as an ai", "jako model językowy", "i cannot", "nie jestem w stanie", "przepraszam", "nie potrafię", ] extracted_lower = extracted.lower() # Jeśli odpowiedź jest bardzo krótka (np. < 500 znaków) i zawiera frazę odmowy if len(extracted) < 500 and any(r in extracted_lower for r in refusals): raise ValueError("LLM zwrócił odmowę lub halucynację zamiast poprawnej treści.") return extracted