Uma voz própria no Vapi: seu servidor de TTS em 30 linhas
O Vapi roda agentes de voz com IA para telefone e web e deixa você trazer seu próprio texto para fala: com o provedor custom-voice ele envia cada fala do assistente a um servidor seu e toca o áudio que volta.
Este guia monta esse servidor em Python. Ele gera a voz com o motor gratuito da SuperClonica na sua própria máquina, então a fala em si não custa nada, ou pela API de voz da SuperClonica. De qualquer forma seu agente pode usar qualquer voz do catálogo, de narradores calmos a personagens de jogo, ou uma voz treinada a partir da sua gravação.
O que o Vapi envia e espera
Segundo o guia de TTS próprio do Vapi, ele envia um POST com JSON cujo objeto message traz type voice-request, o texto e o sampleRate desejado. A resposta deve ser PCM bruto sem cabeçalho: mono, 16 bits com sinal, little-endian, exatamente nessa taxa, com o tipo de conteúdo application/octet-stream. Com um secret embutido, o Vapi o envia no cabeçalho X-VAPI-SECRET.
O servidor
Instale os pacotes e salve isto como vapi_voice.py:
pip install fastapi uvicorn scipy superclonica-cpu
import math, os
import numpy as np
from fastapi import FastAPI, Header, HTTPException, Request, Response
from fastapi.concurrency import run_in_threadpool
from scipy.signal import resample_poly
from superclonica import Superclonica
VOICE = os.environ.get("TTS_VOICE", "F1") # M1..M5, F1..F5 or a voice file from the catalog
LANGUAGE = os.environ.get("TTS_LANGUAGE", "en")
SECRET = os.environ["VAPI_SECRET"]
app = FastAPI()
tts = Superclonica()
def voice_audio(text):
audio, rate = tts.synthesize(text, voice=VOICE, lang=LANGUAGE)
return np.asarray(audio, dtype=np.float32).reshape(-1), rate
@app.post("/synthesize")
async def synthesize(request: Request, x_vapi_secret: str = Header(None)):
if x_vapi_secret != SECRET:
raise HTTPException(401)
message = (await request.json())["message"]
audio, rate = await run_in_threadpool(voice_audio, message["text"])
target = int(message["sampleRate"])
if target != rate:
divisor = math.gcd(target, rate)
audio = resample_poly(audio, target // divisor, rate // divisor)
pcm = (np.clip(audio, -1, 1) * 32767).astype(np.int16).tobytes()
return Response(pcm, media_type="application/octet-stream")O motor gera a voz a 44,1 kHz numa thread de trabalho, o scipy reamostra para a taxa que o Vapi pediu, e o servidor devolve PCM de 16 bits. Requisições sem o segredo certo recebem 401. Inicie com uvicorn:
uvicorn vapi_voice:app --host 0.0.0.0 --port 8000
Conecte ao seu assistente
O Vapi precisa alcançar o servidor por HTTPS público: coloque-o atrás do seu proxy reverso habitual ou, para testar, de um túnel. Depois defina a voz do assistente, por exemplo ao atualizar o assistente pela API do Vapi:
{
"voice": {
"provider": "custom-voice",
"server": {
"url": "https://your-server.example.com/synthesize",
"secret": "the value of VAPI_SECRET",
"timeoutSeconds": 30
}
}
}No servidor, defina VAPI_SECRET com o mesmo valor. O guia do Vapi também descreve credenciais criadas no painel e uma voz reserva, que vale adicionar para a ligação continuar falando se seu servidor cair.
O mesmo servidor pela API
Se o servidor for pequeno demais para o modelo, gere a voz pela API da SuperClonica: troque voice_audio por esta função e defina SUPERCLONICA_KEY e VOICE_ID. A reamostragem e a resposta continuam iguais.
import io, wave
import requests
API = "https://superclonica.com/api/v1/speech"
VOICE_ID = os.environ["VOICE_ID"] # the ID on the page of a catalog voice
def voice_audio(text):
answer = requests.post(API, timeout=60,
headers={"Authorization": "Bearer " + os.environ["SUPERCLONICA_KEY"]},
json={"text": text, "voice": VOICE_ID, "language": LANGUAGE, "format": "wav"})
answer.raise_for_status()
with wave.open(io.BytesIO(answer.content)) as file:
rate = file.getframerate()
pcm = np.frombuffer(file.readframes(file.getnframes()), dtype=np.int16)
return pcm.astype(np.float32) / 32768, rateToda voz do catálogo funciona pelo ID da página dela, em qualquer plano. O Inicial custa US$ 5 por mês por até 3 minutos de fala em cada janela de 10 minutos para todas as chamadas juntas; o Pro, 20 minutos.
Velocidade e vozes
O servidor gera cada requisição inteira antes de responder. Nas nossas medições, uma frase curta levou em mediana 0,43 s num Ryzen de mesa com quatro threads e 0,12 s numa RTX 5070 Ti, então mantenha curtas as frases do assistente. TTS_VOICE aceita uma das dez vozes padrão, M1 a M5 e F1 a F5, ou um arquivo de voz do catálogo; o Supertonic 3 fala 31 idiomas, incluindo português (TTS_LANGUAGE=pt), mas ainda não chinês.