- audio: switch VAD to webrtcvad with RMS gate + fallback to RMS - audio: honor FOLLOWUP_TIMEOUT — short silence wait after bot response - llm: retry with exponential backoff on network errors and 5xx - llm: VOICE_MAX_TOKENS env (default 300) instead of hardcoded 150 - tts: optional VAD-based barge-in (BARGE_IN_ENABLED, off by default) - tts: remove dead start_barge_in_listener / was_barge_in helpers - config: drop AGENT/LUSYA_AGENT — routing happens via session_key - modes: remove unused imports, pass FOLLOWUP_TIMEOUT to follow-up record() - docs: full rewrite of README and CLAUDE.md to match current architecture
90 lines
3.4 KiB
Python
90 lines
3.4 KiB
Python
import os
|
|
import sys
|
|
import logging
|
|
import requests as _requests
|
|
from dotenv import load_dotenv
|
|
from groq import Groq
|
|
|
|
load_dotenv()
|
|
|
|
# Логгер — ошибки в файл + короткое сообщение в консоль
|
|
LOG_FILE = os.getenv("LOG_FILE", "errors.log")
|
|
|
|
logging.basicConfig(
|
|
level=logging.WARNING,
|
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
handlers=[
|
|
logging.FileHandler(LOG_FILE, encoding="utf-8"),
|
|
],
|
|
)
|
|
log = logging.getLogger("cosmo")
|
|
|
|
# OpenClaw Gateway — Cosmo (по умолчанию)
|
|
# Роутинг к нужному агенту делается через x-openclaw-session-key, поэтому AGENT не нужен.
|
|
GATEWAY_URL = os.getenv("GATEWAY_URL", "http://192.168.31.103:18789")
|
|
GATEWAY_TOKEN = os.getenv("GATEWAY_TOKEN")
|
|
VOICE_MODEL = os.getenv("VOICE_MODEL", "openai/gpt-4o-mini")
|
|
|
|
# OpenClaw Gateway — Люся
|
|
LUSYA_GATEWAY_URL = os.getenv("LUSYA_GATEWAY_URL", "http://192.168.31.103:18790")
|
|
LUSYA_GATEWAY_TOKEN = os.getenv("LUSYA_GATEWAY_TOKEN", GATEWAY_TOKEN)
|
|
LUSYA_VOICE_MODEL = os.getenv("LUSYA_VOICE_MODEL", VOICE_MODEL)
|
|
|
|
# Keep-alive HTTP сессии — переиспользуют TCP/TLS соединения
|
|
def _make_session(token: str) -> _requests.Session:
|
|
s = _requests.Session()
|
|
s.headers.update({
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json",
|
|
})
|
|
return s
|
|
|
|
|
|
# Конфиги агентов по имени
|
|
AGENTS = {
|
|
"cosmo": {
|
|
"name": "Cosmo",
|
|
"gateway_url": GATEWAY_URL,
|
|
"voice_model": VOICE_MODEL,
|
|
"session_key": os.getenv("COSMO_SESSION_KEY", "agent:main:voice:home"),
|
|
"tts_voice": os.getenv("COSMO_TTS_VOICE", ""),
|
|
"session": _make_session(GATEWAY_TOKEN),
|
|
},
|
|
"lusya": {
|
|
"name": "Люся",
|
|
"gateway_url": LUSYA_GATEWAY_URL,
|
|
"voice_model": LUSYA_VOICE_MODEL,
|
|
"session_key": os.getenv("LUSYA_SESSION_KEY", "agent:wife:voice:home"),
|
|
"tts_voice": os.getenv("LUSYA_TTS_VOICE", ""),
|
|
"session": _make_session(LUSYA_GATEWAY_TOKEN),
|
|
},
|
|
}
|
|
|
|
# Audio (на Pi: PulseAudio BT sink)
|
|
AUDIO_SINK = os.getenv("AUDIO_SINK", "")
|
|
|
|
# VAD
|
|
SILENCE_THRESHOLD = int(os.getenv("SILENCE_THRESHOLD", "500"))
|
|
SILENCE_DURATION = float(os.getenv("SILENCE_DURATION", "1.5"))
|
|
MAX_DURATION = int(os.getenv("MAX_DURATION", "15"))
|
|
FOLLOWUP_TIMEOUT = float(os.getenv("FOLLOWUP_TIMEOUT", "8"))
|
|
VAD_AGGRESSIVENESS = int(os.getenv("VAD_AGGRESSIVENESS", "2")) # webrtcvad 0..3
|
|
|
|
# LLM
|
|
VOICE_MAX_TOKENS = int(os.getenv("VOICE_MAX_TOKENS", "300"))
|
|
LLM_RETRIES = int(os.getenv("LLM_RETRIES", "3"))
|
|
|
|
# Barge-in (прерывание TTS голосом)
|
|
# Работает только при разнесённых колонке/мике или в наушниках — иначе эхо собственного TTS
|
|
# будет триггерить прерывание. По умолчанию выключен.
|
|
BARGE_IN_ENABLED = os.getenv("BARGE_IN_ENABLED", "false").lower() in ("1", "true", "yes")
|
|
BARGE_IN_THRESHOLD = int(os.getenv("BARGE_IN_THRESHOLD", "1500")) # RMS, обычно > SILENCE_THRESHOLD
|
|
BARGE_IN_WARMUP = float(os.getenv("BARGE_IN_WARMUP", "0.8")) # сек пропуска в начале TTS
|
|
|
|
# Groq client
|
|
groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))
|
|
|
|
if not GATEWAY_TOKEN:
|
|
print("❌ GATEWAY_TOKEN не задан в .env")
|
|
sys.exit(1)
|