first commit

This commit is contained in:
workD12
2026-09-11 19:47:15 +03:00
commit 32754a1fe2
56 changed files with 3836 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
"""Тест Модуля 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())