feat: add FastAPI token server to pipecat-agent
Adds a lightweight HTTP token server (port 8080) alongside the voice pipeline so web clients can obtain LiveKit access tokens without a separate service. Adds fastapi and uvicorn to requirements. Adds LIVEKIT_PUBLIC_URL env var (wss://voice.hobbs.farm) to docker-compose so tokens carry the correct public WebSocket URL for browser clients. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,7 @@ services:
|
|||||||
- SPEECHBRAIN_SERVICE_URL=http://speechbrain-service:8001
|
- SPEECHBRAIN_SERVICE_URL=http://speechbrain-service:8001
|
||||||
- PIPER_SERVICE_URL=http://piper-tts:8003
|
- PIPER_SERVICE_URL=http://piper-tts:8003
|
||||||
- WHISPER_MODEL=medium
|
- WHISPER_MODEL=medium
|
||||||
|
- LIVEKIT_PUBLIC_URL=wss://voice.hobbs.farm
|
||||||
depends_on:
|
depends_on:
|
||||||
- livekit-server
|
- livekit-server
|
||||||
- speechbrain-service
|
- speechbrain-service
|
||||||
|
|||||||
+56
-8
@@ -1,12 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
Entry point for the Pipecat VoiceCoach agent.
|
Entry point for the Pipecat VoiceCoach agent.
|
||||||
Reads config from environment variables.
|
Runs both the token HTTP server (for web clients) and the voice pipeline.
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
from livekit import api as livekit_api
|
from livekit import api as livekit_api
|
||||||
|
|
||||||
from pipeline import run_voice_coach_pipeline
|
from pipeline import run_voice_coach_pipeline
|
||||||
@@ -14,15 +18,59 @@ from pipeline import run_voice_coach_pipeline
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = structlog.get_logger()
|
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")
|
||||||
|
|
||||||
|
# --- Token HTTP server (serves /token for web clients) ---
|
||||||
|
|
||||||
|
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"}
|
||||||
|
|
||||||
|
|
||||||
|
@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 pipeline ---
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
anthropic_key = os.environ["ANTHROPIC_API_KEY"]
|
anthropic_key = os.environ["ANTHROPIC_API_KEY"]
|
||||||
livekit_api_key = os.environ["LIVEKIT_API_KEY"]
|
|
||||||
livekit_api_secret = os.environ["LIVEKIT_API_SECRET"]
|
|
||||||
livekit_url = os.getenv("LIVEKIT_URL", "ws://livekit-server:7880")
|
|
||||||
|
|
||||||
# Generate LiveKit access token for this agent
|
# Start token server in a background thread
|
||||||
token = livekit_api.AccessToken(livekit_api_key, livekit_api_secret)
|
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.with_grants(livekit_api.VideoGrants(room_join=True, room="voicecoach"))
|
||||||
token.identity = "voicecoach-agent"
|
token.identity = "voicecoach-agent"
|
||||||
livekit_token = token.to_jwt()
|
livekit_token = token.to_jwt()
|
||||||
@@ -33,9 +81,9 @@ async def main():
|
|||||||
"current_mode": "coaching mode",
|
"current_mode": "coaching mode",
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Starting VoiceCoach pipeline", livekit_url=livekit_url)
|
logger.info("Starting VoiceCoach pipeline", livekit_url=LIVEKIT_WS_URL)
|
||||||
await run_voice_coach_pipeline(
|
await run_voice_coach_pipeline(
|
||||||
livekit_url=livekit_url,
|
livekit_url=LIVEKIT_WS_URL,
|
||||||
livekit_token=livekit_token,
|
livekit_token=livekit_token,
|
||||||
anthropic_key=anthropic_key,
|
anthropic_key=anthropic_key,
|
||||||
session_context=session_context,
|
session_context=session_context,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
pipecat-ai[livekit,silero,openai]==0.0.48
|
pipecat-ai[livekit,silero,openai]==0.0.48
|
||||||
|
fastapi==0.115.0
|
||||||
|
uvicorn[standard]==0.30.0
|
||||||
anthropic==0.34.0
|
anthropic==0.34.0
|
||||||
faster-whisper==1.0.3
|
faster-whisper==1.0.3
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
|
|||||||
Reference in New Issue
Block a user