"""Тест Модуля 4: brain (LLM) + очистка Markdown + полная цепочка. Запуск (нужен ключ: файл .env со строкой OPENROUTER_API_KEY=sk-or-v1-...): python modules/brain/test_brain.py clean # тест очистки без сети python modules/brain/test_brain.py --ask "Привет!" # вопрос → ответ (raw + чистый + время) python modules/brain/test_brain.py --ask "Привет!" --play # и озвучить ответ python modules/brain/test_brain.py --model google/gemini-flash-1.5 --ask "..." python modules/brain/test_brain.py --mic # ПОЛНАЯ ЦЕПЬ: говоришь → слышишь ответ """ from __future__ import annotations import argparse import sys from pathlib import Path try: from modules.brain import clean_for_speech, get_provider except ImportError: sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from modules.brain import clean_for_speech, get_provider # --------------------------------------------------------------------------- def clean_selftest() -> None: """Проверка очистки Markdown без сети: грязные строки → чистый текст.""" cases = [ ("**Привет!** Как *дела*?", "Привет! Как дела?"), ("## Заголовок\n- пункт один\n- пункт два", "Заголовок пункт один пункт два"), ("1. Включи компьютер\n2. Открой программу", "Включи компьютер Открой программу"), ("Ссылка: [Google](https://google.com)", "Ссылка: Google"), ("Код: `python -m venv` и ```print(1)```", "Код: python -m venv и"), ("Смайл 😊 и стрелка ➡️ тут", "Смайл и стрелка тут"), ("Текст выше\n> Важная цитата\nТекст ниже", "Текст выше Важная цитата Текст ниже"), ("Раздел один\n---\nРаздел два", "Раздел один Раздел два"), ] ok = True for dirty, expected in cases: got = clean_for_speech(dirty) status = "OK " if got == expected else "FAIL" ok &= got == expected print(f"[{status}] {dirty!r}\n → {got!r}" + ("" if got == expected else f"\n ожидалось {expected!r}")) print("\nОЧИСТКА:", "ПРОЙДЕНА ✅" if ok else "ЕСТЬ ОШИБКИ ❌") # --------------------------------------------------------------------------- def ask_once(brain, question: str, play: bool, history=None): result = brain.ask(question, history=history) print(f"\nВопрос: {question}") print(f"Ответ ({result.generation_sec:.2f} c): {result.cleaned_text}") if result.raw_text != result.cleaned_text: print(f"(raw был: {result.raw_text!r})") if play: from modules.audio_io import Player from modules.tts import get_provider as get_tts tts = get_tts("edge") speech = tts.synthesize(result.cleaned_text) print(f"TTS: синтез {speech.generation_sec:.2f} c, аудио {speech.duration_sec:.1f} c") Player().play(speech.audio, speech.samplerate, blocking=True) return result def mic_loop(brain) -> None: """Полная цепочка: запись с микрофона → STT → LLM → TTS → воспроизведение.""" from modules.audio_io import Player, Recorder from modules.stt import get_provider as get_stt from modules.tts import get_provider as get_tts print("Загружаю STT (faster-whisper small)...") stt = get_stt("faster-whisper", model_size="small") tts = get_tts("edge") player = Player() rec = Recorder() history = [] print("\nГОТОВ. [Enter] → говори вопрос → [Enter] → услышишь ответ. Пустой Enter — выход.") while True: cmd = input("> ").strip() if cmd == "": break rec.start() input(" 🔴 запись... Enter = закончить ") audio = rec.stop() if len(audio) == 0: print(" Слишком коротко — повтори.") continue stt_res = stt.transcribe(audio) question = stt_res.text.strip() if not question: print(" STT ничего не расслышал. Повтори.") continue print(f" Ты: {question}") llm = brain.ask(question, history=history) print(f" AI ({llm.generation_sec:.2f} c): {llm.cleaned_text}") speech = tts.synthesize(llm.cleaned_text) print(f" TTS ({speech.generation_sec:.2f} c), играю...") player.play(speech.audio, speech.samplerate, blocking=True) history.append({"role": "user", "text": question}) history.append({"role": "assistant", "text": llm.cleaned_text}) history = history[-8:] # держим контекст, но ограничиваем def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser(description="Тест Модуля 4: brain") p.add_argument("mode", nargs="?", default="ask", choices=["ask", "clean", "mic"], help="clean = офлайн-тест очистки; ask = текстовый вопрос; mic = полная цепь") p.add_argument("--ask", "--question", dest="question", default="Привет! Кто ты и что умеешь?", help="вопрос к модели (режим ask)") p.add_argument("--model", default="google/gemma-3-27b-it", help="модель OpenRouter (deepseek/deepseek-chat-v3-0324, qwen/qwen-2.5-72b-instruct...)") p.add_argument("--play", action="store_true", help="озвучить ответ (edge-tts)") p.add_argument("--turns", type=int, default=0, help="сколько вопросов подряд в режиме ask с общей историей") return p.parse_args() def main() -> int: args = parse_args() if args.mode == "clean": clean_selftest() return 0 brain = get_provider("openrouter", model=args.model) print(f"Модель: {args.model}") if args.mode == "mic": mic_loop(brain) return 0 questions = [args.question] if args.turns > 1: extra = input(f"Введи ещё {args.turns - 1} вопроса через ';' : ") questions = [q.strip() for q in extra.split(";") if q.strip()] or questions history = [] for q in questions: result = ask_once(brain, q, play=args.play, history=history) history += [{"role": "user", "text": q}, {"role": "assistant", "text": result.cleaned_text}] return 0 if __name__ == "__main__": sys.exit(main())