[Wake/VAD/STT 与 Live runtime]:完成真实重复语音运行,包含云端ASR/TTS开关、run-live和临时上下文测试

This commit is contained in:
mkbk
2026-06-17 20:00:55 +08:00
parent ac97daa1e7
commit 4b7cd18a0f
20 changed files with 1043 additions and 68 deletions
+32 -3
View File
@@ -12,11 +12,12 @@ from .conversation import ConversationContext
from .llm import MockLlmProvider, OpenAICompatibleLlmProvider
from .models import AudioFrame, ProviderError
from .pipeline import VoicePipeline
from .runtime import build_live_runtime
from .speech_models import check_speech_models, model_status_errors
from .stt import MetadataSttProvider
from .stt import MetadataSttProvider, SherpaOnnxSttProvider
from .transport import MemoryAudioTransport, sounddevice_device_report
from .tts import SineTtsProvider
from .vad import EnergyVadProvider, VadRecorder
from .vad import EnergyVadProvider, SherpaOnnxVadProvider, VadRecorder
from .wakeword import KeywordWakeWordProvider
@@ -31,6 +32,8 @@ def main(argv: list[str] | None = None) -> int:
model_check = subparsers.add_parser("model-check", help="Validate local speech model files")
model_check.add_argument("--models-dir", default=None, help="Speech models directory. Defaults to .env or models")
subparsers.add_parser("device-check", help="Validate local microphone and speaker availability")
live = subparsers.add_parser("run-live", help="Run real repeated live voice conversation")
live.add_argument("--once", action="store_true", help="Run one completed live turn and exit")
smoke = subparsers.add_parser("llm-smoke", help="Call configured OpenAI/NewAPI endpoint")
smoke.add_argument("--message", default="用一句中文回复:小杰在线。")
smoke.add_argument("--no-stream", action="store_true")
@@ -50,6 +53,10 @@ def main(argv: list[str] | None = None) -> int:
"llm_stream": config.llm_stream,
"llm_api_key_present": bool(config.llm_api_key),
"asset_dir": str(config.asset_dir),
"speech_provider": config.speech_provider,
"asr_model": config.asr_model,
"tts_model": config.tts_model,
"tts_voice": config.tts_voice,
"speech_models_dir": str(config.speech_models_dir),
},
ensure_ascii=False,
@@ -73,8 +80,17 @@ def main(argv: list[str] | None = None) -> int:
models_dir = Path(args.models_dir) if args.models_dir else config.speech_models_dir
status = check_speech_models(models_dir, require_sherpa=True)
errors = model_status_errors(status)
provider_load_checked = False
if not errors:
try:
SherpaOnnxVadProvider(models_dir).load()
SherpaOnnxSttProvider(str(models_dir)).load()
provider_load_checked = True
except ProviderError as exc:
errors.append(exc)
data = status.to_json()
data["errors"] = [str(error) for error in errors]
data["provider_load_checked"] = provider_load_checked
print(json.dumps(data, ensure_ascii=False, sort_keys=True))
return 1 if errors else 0
@@ -83,6 +99,15 @@ def main(argv: list[str] | None = None) -> int:
print(json.dumps(report, ensure_ascii=False, sort_keys=True))
return 0 if report["ok"] else 1
if args.command == "run-live":
config = AppConfig.from_dotenv(args.env_file)
try:
summary = build_live_runtime(config).run(once=args.once)
except ProviderError as exc:
print(json.dumps({"ok": False, "code": exc.code.value, "message": exc.message}, ensure_ascii=False, sort_keys=True))
return 1
return 0 if summary.completed_turns > 0 or summary.interrupted else 1
if args.command == "acceptance":
result = run_acceptance()
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
@@ -104,6 +129,10 @@ def main(argv: list[str] | None = None) -> int:
audio_output_device=config.audio_output_device,
asset_dir=config.asset_dir,
log_dir=config.log_dir,
speech_provider=config.speech_provider,
asr_model=config.asr_model,
tts_model=config.tts_model,
tts_voice=config.tts_voice,
speech_models_dir=config.speech_models_dir,
context_max_messages=config.context_max_messages,
context_max_chars=config.context_max_chars,
@@ -155,7 +184,7 @@ def run_acceptance() -> dict[str, object]:
def find_secret_leaks(root: Path) -> list[str]:
completed = subprocess.run(["git", "ls-files"], cwd=root, check=True, stdout=subprocess.PIPE, text=True)
pattern = re.compile(r"sk-[A-Za-z0-9_\\-]{16,}")
pattern = re.compile(r"(?:sk|tp)-[A-Za-z0-9_\\-]{16,}")
leaks: list[str] = []
for rel in completed.stdout.splitlines():
path = root / rel