feat: VoiceCoach v2 - OpenClaw gateway + Speaches STT/TTS

- pipeline.py: completely rewritten to use Speaches STT/TTS and OpenClaw LLM
- main.py: v2 with OPENCLAW_TOKEN support
- requirements.txt: slimmed (removed anthropic, faster-whisper direct deps)
- Dockerfile: slimmer build (no model pre-download)
- whisper_stt.py, piper_tts.py: removed (replaced by Speaches API)
- docker-compose.yml: v2 with speaches + openclaw services, piper-tts removed
- openclaw-agent/system_prompt.md: new directory with agent system prompt
This commit is contained in:
2026-04-01 08:04:59 -05:00
parent 68c3731c65
commit f62772663d
9 changed files with 371 additions and 273 deletions
+26 -29
View File
@@ -1,11 +1,13 @@
"""
Entry point for the Pipecat VoiceCoach agent.
Runs both the token HTTP server (for web clients) and the voice pipeline.
VoiceCoach pipeline entry point -- v2.
Runs token server + pipeline with unique identity per session.
"""
import asyncio
import logging
import os
import threading
import uuid
from pathlib import Path
import structlog
import uvicorn
@@ -22,8 +24,9 @@ LIVEKIT_API_KEY = os.environ["LIVEKIT_API_KEY"]
LIVEKIT_API_SECRET = os.environ["LIVEKIT_API_SECRET"]
LIVEKIT_WS_URL = os.getenv("LIVEKIT_URL", "ws://livekit-server:7880")
LIVEKIT_PUBLIC_URL = os.getenv("LIVEKIT_PUBLIC_URL", "wss://voice.hobbs.farm")
OPENCLAW_TOKEN = os.getenv("OPENCLAW_TOKEN", "")
# --- Token HTTP server (serves /token for web clients) ---
# --- Token HTTP server ---
token_app = FastAPI(title="VoiceCoach Token Service")
@@ -39,17 +42,15 @@ class TokenResponse(BaseModel):
@token_app.get("/health")
async def health():
return {"status": "ok"}
return {"status": "ok", "version": "2.0"}
@token_app.post("/token")
async def get_token(req: TokenRequest):
token = livekit_api.AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET)
token.with_grants(livekit_api.VideoGrants(
room_join=True,
room="voicecoach",
can_publish=True,
can_subscribe=True,
room_join=True, room="voicecoach",
can_publish=True, can_subscribe=True,
))
token.identity = req.identity
return TokenResponse(token=token.to_jwt(), url=LIVEKIT_PUBLIC_URL)
@@ -59,35 +60,31 @@ def run_token_server():
uvicorn.run(token_app, host="0.0.0.0", port=8080, log_level="info")
# --- Main pipeline ---
# --- Main ---
async def main():
anthropic_key = os.environ["ANTHROPIC_API_KEY"]
# Start token server in a background thread
server_thread = threading.Thread(target=run_token_server, daemon=True)
server_thread.start()
logger.info("Token server started on :8080")
# Generate agent's own LiveKit token
token = livekit_api.AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET)
token.with_grants(livekit_api.VideoGrants(room_join=True, room="voicecoach"))
token.identity = "voicecoach-agent"
livekit_token = token.to_jwt()
while True:
agent_identity = f"voicecoach-agent-{uuid.uuid4().hex[:8]}"
token = livekit_api.AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET)
token.with_grants(livekit_api.VideoGrants(room_join=True, room="voicecoach"))
token.identity = agent_identity
livekit_token = token.to_jwt()
session_context = {
"baseline_context": "",
"session_history": "",
"current_mode": "coaching mode",
}
logger.info("Starting VoiceCoach v2 pipeline", identity=agent_identity)
try:
await run_voice_coach_pipeline(
livekit_url=LIVEKIT_WS_URL,
livekit_token=livekit_token,
openclaw_token=OPENCLAW_TOKEN,
)
except Exception as e:
logger.error("Pipeline error, restarting in 35s", error=str(e))
logger.info("Starting VoiceCoach pipeline", livekit_url=LIVEKIT_WS_URL)
await run_voice_coach_pipeline(
livekit_url=LIVEKIT_WS_URL,
livekit_token=livekit_token,
anthropic_key=anthropic_key,
session_context=session_context,
)
await asyncio.sleep(35)
if __name__ == "__main__":