f62772663d
- 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
92 lines
2.5 KiB
Python
Executable File
92 lines
2.5 KiB
Python
Executable File
"""
|
|
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
|
|
from fastapi import FastAPI
|
|
from pydantic import BaseModel
|
|
from livekit import api as livekit_api
|
|
|
|
from pipeline import run_voice_coach_pipeline
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = structlog.get_logger()
|
|
|
|
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 ---
|
|
|
|
token_app = FastAPI(title="VoiceCoach Token Service")
|
|
|
|
|
|
class TokenRequest(BaseModel):
|
|
identity: str = "jeremy"
|
|
|
|
|
|
class TokenResponse(BaseModel):
|
|
token: str
|
|
url: str
|
|
|
|
|
|
@token_app.get("/health")
|
|
async def health():
|
|
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,
|
|
))
|
|
token.identity = req.identity
|
|
return TokenResponse(token=token.to_jwt(), url=LIVEKIT_PUBLIC_URL)
|
|
|
|
|
|
def run_token_server():
|
|
uvicorn.run(token_app, host="0.0.0.0", port=8080, log_level="info")
|
|
|
|
|
|
# --- Main ---
|
|
|
|
async def main():
|
|
server_thread = threading.Thread(target=run_token_server, daemon=True)
|
|
server_thread.start()
|
|
logger.info("Token server started on :8080")
|
|
|
|
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()
|
|
|
|
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))
|
|
|
|
await asyncio.sleep(35)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|