Ctrl K
ComunidadGuías…

test_pipeline_bridge.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Full mic-free chain with the BRIDGE brain backend (not claude -p).

Runs the real wake + STT + TTS models, but builds the brain from sim-transport
configuration. Proves wake -> STT -> bridge-brain -> TTS works end to end without
a mic, without claude -p, and without a live Claude session — the exact shape of
the eventual Syl integration, minus the ssh transport.

Loads models, so run it under a memory cap, e.g.:
  systemd-run --user --scope -p MemoryMax=1500M -p MemorySwapMax=0 \
    nice -n19 ionice -c3 .venv/bin/python test_pipeline_bridge.py
"""

from __future__ import annotations

import sys
import tempfile
from pathlib import Path

import brain_bridge
import pipeline
from sim_persona import SimPersona

PROJECT = Path(__file__).resolve().parent
results: list[tuple[bool, str]] = []


def check(ok: bool, detail: str) -> None:
    results.append((ok, detail))
    print(f"  [{'PASS' if ok else 'FAIL'}] {detail}")


def main() -> int:
    d = Path(tempfile.mkdtemp(prefix="voice-pipe-bridge-"))
    inbox = d / "manual-inbox.jsonl"
    reply = d / "voice-reply.txt"

    sim = SimPersona(inbox, reply, poll_s=0.05)
    sim.start()

    # Swap the module-level brain for the config-driven factory. run_pipeline()
    # calls brain() by name in this module's namespace, so the full chain exercises
    # the same transport selection as a deployed bridge without touching repo config.
    pipeline.brain = brain_bridge.build_brain(
        {
            "brain_transport": "sim",
            "brain_persona": "syl",
            "brain_inbox_path": str(inbox),
            "brain_reply_path": str(reply),
            "brain_timeout_s": 20,
            "brain_poll_s": 0.05,
        },
        system_prompt=pipeline.SYL_VOICE_SYSTEM_PROMPT,
    )

    clip = str(PROJECT / "test_audio" / "clip_jarvis.wav")
    if not Path(clip).exists():
        # Synthesize the prompt clip with Piper if the builder's clip is gone.
        pipeline.speak("hey jarvis, what is two plus two?", clip)

    try:
        # Pin the wake word so the test is independent of config.json's active one.
        r = pipeline.run_pipeline(
            clip, out_wav_path=str(d / "reply.wav"), wake_word="hey_jarvis"
        )
    finally:
        sim.stop()

    check(r["wake_fired"], f"wake fired on the clip (score {r['wake_score']})")
    check(bool(r["transcript"]), f"STT produced text: {r['transcript']!r}")
    check(
        r["reply"] == "Two plus two is four.",
        f"bridge brain answered correctly: {r['reply']!r}",
    )
    check(
        bool(r["output_wav"]) and Path(r["output_wav"]).exists(),
        f"TTS wrote a reply wav: {r['output_wav']}",
    )

    print(
        "\ntimings (s): " + ", ".join(f"{k}={v:.2f}" for k, v in r["timings_s"].items())
    )
    n_pass = sum(1 for ok, _ in results if ok)
    print(f"=== {n_pass}/{len(results)} checks passed ===")
    return 0 if n_pass == len(results) else 1


if __name__ == "__main__":
    sys.exit(main())