feat(talkbot): add optional llama.cpp API key and stop double-posting text after audio

Tento commit je obsažen v:
2026-09-10 17:14:06 +02:00
rodič f15443df7a
revize d1fb392ba5
12 změnil soubory, kde provedl 50 přidání a 573 odebrání
-39
Zobrazit soubor
@@ -1,39 +0,0 @@
[Unit]
Description=Qwen3-TTS Server (CPU)
After=network-online.target
Wants=network-online.target
[Container]
Image=localhost/qwen-tts:cpu
ContainerName=qwen-tts
# HTTP API
PublishPort=8000:8000
# Persist downloaded model weights across restarts
# Adjust the host path to wherever you want the models cached
Volume=/srv/containers/qwen-tts/models:/app/models:Z
# Which model(s) to load at startup: "customvoice", "voiceclone", or both
# (default, comma-separated). Loading both lets a single instance serve
# /speech (preset voices) and /speech/clone (voice cloning) at once.
#Environment=QWEN_TTS_LOAD=customvoice,voiceclone
# Optional: override the default checkpoints
#Environment=QWEN_TTS_CUSTOMVOICE_MODEL=Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice
#Environment=QWEN_TTS_VOICECLONE_MODEL=Qwen/Qwen3-TTS-12Hz-1.7B-Base
# CPU-only container: no GPU devices needed
PodmanArgs=--pids-limit=-1
# Resource limits (tune to your host; a 1.7B model on CPU needs headroom)
#Memory=8g
#CPUs=4
[Service]
Restart=on-failure
RestartSec=5
TimeoutStartSec=300
[Install]
WantedBy=default.target
-83
Zobrazit soubor
@@ -1,83 +0,0 @@
#!/bin/bash
set -e
BUILD_DIR="$(pwd)/qwentts-src"
BIN_DIR="$(pwd)/bin-vulkan"
echo "=== Qwentts.cpp Build Script for Vulkan ==="
echo ""
# Step 1: Install dependencies
echo "[1/5] Installing build dependencies..."
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
libvulkan-dev \
vulkan-tools \
glslc \
spirv-tools \
pkg-config
# Step 2: Clone qwentts.cpp with submodules
echo ""
echo "[2/5] Cloning qwentts.cpp repository with submodules..."
if [ -d "$BUILD_DIR" ]; then
rm -rf "$BUILD_DIR"
fi
git clone --recurse-submodules https://github.com/ServeurpersoCom/qwentts.cpp "$BUILD_DIR"
cd "$BUILD_DIR"
# Step 3: Build with Vulkan backend
echo ""
echo "[3/5] Building qwentts with Vulkan support..."
./buildvulkan.sh
# Step 4: Copy binaries to bin-vulkan/
echo ""
echo "[4/5] Copying binaries to bin-vulkan/..."
if [ -d "$BIN_DIR" ]; then
rm -rf "$BIN_DIR"
fi
mkdir -p "$BIN_DIR"
# Copy executables
cp build/qwen-tts "$BIN_DIR/"
cp build/qwen-codec "$BIN_DIR/"
# Copy any shared libraries if they exist
if [ -d "build/lib" ]; then
cp -r build/lib "$BIN_DIR/"
fi
# Step 5: Create entrypoint script
echo ""
echo "[5/5] Creating entrypoint script..."
cat > "$(pwd)/../entrypoint.sh" <<'ENTRYPOINT_EOF'
#!/bin/bash
set -e
# Export Vulkan driver path
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json
export LD_LIBRARY_PATH=/app/lib:$LD_LIBRARY_PATH
# Default to help if no arguments
if [ $# -eq 0 ]; then
/app/bin/qwen-tts --help
else
exec "$@"
fi
ENTRYPOINT_EOF
chmod +x "$(pwd)/../entrypoint.sh"
echo ""
echo "=== Build Complete ==="
echo "Binaries are in: $BIN_DIR"
echo "Models should be placed in: $(pwd)/../models/"
echo ""
echo "Next steps:"
echo "1. Download models: https://huggingface.co/Serveurperso/Qwen3-TTS-GGUF"
echo "2. Build Podman image: podman build -t qwentts:vulkan-amd64 -f qwentts-vulkan.Containerfile ."
echo "3. Test: podman run --rm qwentts:vulkan-amd64"
+1 -1
Zobrazit soubor
@@ -1,5 +1,5 @@
#!/bin/bash
# Qwen3-TTS entrypoint script
# Qwen3 TTS entrypoint script
# Starts the FastAPI server; model weights are downloaded automatically
# on first request via from_pretrained() and cached under /app/models
set -e
-31
Zobrazit soubor
@@ -1,31 +0,0 @@
FROM debian:13-slim
# Install Vulkan runtime libraries
RUN apt-get update && apt-get install -y \
libvulkan1 \
vulkan-tools \
mesa-vulkan-drivers \
libdrm-amdgpu1 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Create app directory
WORKDIR /app
# Copy pre-compiled binaries and models
COPY bin-vulkan/ /app/bin/
COPY entrypoint.sh /app/
# Create directories for models and output
RUN mkdir -p /app/models /app/output
# Set library path for Vulkan
ENV LD_LIBRARY_PATH=/app/lib:$LD_LIBRARY_PATH
ENV VK_DRIVER_FILES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json
# Expose port for API server (if used)
EXPOSE 8080
# Default command
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["--help"]
@@ -1,8 +1,8 @@
### Qwen3-TTS Container - CPU only
### Qwen3 TTS Container
### Text-to-Speech using Qwen3-TTS: https://github.com/QwenLM/Qwen3-TTS
###
### BUILD: podman build -t qwen-tts:cpu -f qwen-tts-cpu.Containerfile .
### RUN: podman run --rm -p 8000:8000 -v /path/to/models:/app/models qwen-tts:cpu
### BUILD: podman build -t qwentts:latest -f qwentts.Containerfile .
### RUN: podman run --rm -p 8000:8000 -v /path/to/models:/app/models qwentts:latest
FROM python:3.12-slim
USER root
-343
Zobrazit soubor
@@ -1,343 +0,0 @@
# Qwentts - Text-to-Speech with Voice Cloning and Voice Design
C++17 port di Qwen3-TTS (Alibaba/Qwen team) con supporto per Text-to-Speech, voice cloning zero-shot e voice design. Supporta 10+ lingue con dialetti Mandarin e output 24 kHz mono.
## Requisiti
- Podman rootless
- Network `internal.network` configurata
- Nginx come reverse proxy
- GPU AMD con Vulkan (opzionale, consigliato)
- Spazio disco: almeno 30 GB per i modelli
- RAM: 16+ GB per il modello 1.7B
- Dipendenze di compilazione (cmake, git, gcc, vulkan-dev)
## Build Container
### 1. Compilare qwentts.cpp
```bash
cd /home/badstorm/Source/bdi/bdi_podman_serverconf/containers/qwentts
./build-container.sh
```
Lo script:
- Installa dipendenze (cmake, git, vulkan-dev, glslc, spirv-tools)
- Clona qwentts.cpp con submoduli ricorsivi
- Compila con `./buildvulkan.sh` per supporto Vulkan
- Copia i binari (`qwen-tts`, `qwen-codec`) in `bin-vulkan/`
- Crea l'entrypoint script
### 2. Buildare l'immagine Podman
```bash
podman build -t qwentts:vulkan-amd64 -f qwentts-vulkan.Containerfile .
```
### 3. Verificare l'immagine
```bash
podman images | grep qwentts
podman run --rm qwentts:vulkan-amd64 --help
```
## Setup Runtime
### 1. Creare le directory di dati
```bash
mkdir -p /srv/containers/qwentts/models
mkdir -p /srv/containers/qwentts/output
chmod 755 /srv/containers/qwentts
chmod 755 /srv/containers/qwentts/models
chmod 755 /srv/containers/qwentts/output
```
### 2. Scaricare i modelli
I modelli pre-convertiti sono disponibili su Hugging Face: https://huggingface.co/Serveurperso/Qwen3-TTS-GGUF
Scarica almeno un modello talker e il tokenizer:
```bash
cd /srv/containers/qwentts/models
pip install huggingface-hub
# Download Base Model (1.7B, default voice)
huggingface-cli download Serveurperso/Qwen3-TTS-GGUF \
qwen-talker-1.7b-base-Q8_0.gguf \
qwen-tokenizer-12hz-Q8_0.gguf \
--local-dir .
# Optional: Download CustomVoice Model (named speakers)
huggingface-cli download Serveurperso/Qwen3-TTS-GGUF \
qwen-talker-1.7b-customvoice-Q8_0.gguf \
--local-dir .
# Optional: Download VoiceDesign Model (voice attributes)
huggingface-cli download Serveurperso/Qwen3-TTS-GGUF \
qwen-talker-1.7b-voicedesign-Q8_0.gguf \
--local-dir .
# Optional: Smaller 0.6B models for faster inference
huggingface-cli download Serveurperso/Qwen3-TTS-GGUF \
qwen-talker-0.6b-base-Q8_0.gguf \
--local-dir .
```
**Opzioni di quantizzazione disponibili:**
- `Q8_0` - Nessuna perdita di qualità, ~50% riduzione dimensione
- `Q4_K_M` - Quantizzazione mista, miglior rapporto qualità/dimensione
- `F32` - Massima qualità, dimensione massima
### 3. Copiare il file quadlet
```bash
cp qwentts.container ~/.config/containers/systemd/
```
### 4. Configurare il dominio
Modifica il file `~/.config/containers/systemd/qwentts.container` se necessario:
- Volumi di modelli e output
- Limiti di memoria (attualmente 16GB)
- Limiti CPU
### 5. Copiare la configurazione Nginx
```bash
cp qwentts.nginx /etc/nginx/conf.d/qwentts.conf
```
Modifica il file per sostituire:
- `qwentts.example.com` con il tuo dominio reale
- Percorsi SSL (standard Let's Encrypt)
### 6. Configurare SSL
```bash
sudo certbot certonly --standalone -d qwentts.tuodominio.com
```
### 7. Riavviare Nginx
```bash
sudo systemctl reload nginx
# oppure per container nginx:
systemctl --user restart nginx
```
### 8. Avviare Qwentts
```bash
systemctl --user daemon-reload
systemctl --user start qwentts
systemctl --user enable qwentts
```
## Verifica
Controlla che il container sia in esecuzione:
```bash
podman ps | grep qwentts
podman logs qwentts
```
## Utilizzo CLI
Qwentts fornisce due tool CLI: `qwen-tts` per la sintesi e `qwen-codec` per la gestione codec.
### Text-to-Speech Base (voce predefinita)
```bash
echo "Hello, this is a test." | podman exec qwentts qwen-tts \
--model /app/models/qwen-talker-1.7b-base-Q8_0.gguf \
--codec /app/models/qwen-tokenizer-12hz-Q8_0.gguf \
--lang English \
-o /app/output/test.wav
```
### Voice Cloning (Zero-Shot)
**Opzione 1: Usa WAV + Testo di riferimento**
```bash
echo "I am cloning this voice." | podman exec qwentts qwen-tts \
--model /app/models/qwen-talker-1.7b-base-Q8_0.gguf \
--codec /app/models/qwen-tokenizer-12hz-Q8_0.gguf \
--ref-wav /app/output/reference.wav \
--ref-text "This is my reference voice sample" \
--lang English \
-o /app/output/cloned.wav
```
**Opzione 2: Pre-encode il riferimento (più efficiente)**
```bash
# Estrai speaker embedding e codici
podman exec qwentts qwen-codec \
--model /app/models/qwen-tokenizer-12hz-Q8_0.gguf \
--talker /app/models/qwen-talker-1.7b-base-Q8_0.gguf \
-i /app/output/reference.wav
# Sintetizza usando i file pre-encodati
echo "Now I can synthesize with this voice." | podman exec qwentts qwen-tts \
--model /app/models/qwen-talker-1.7b-base-Q8_0.gguf \
--codec /app/models/qwen-tokenizer-12hz-Q8_0.gguf \
--ref-spk /app/output/reference.spk \
--ref-rvq /app/output/reference.rvq \
--ref-text "This is my reference voice sample" \
--lang English \
-o /app/output/synthesized.wav
```
### Named Speakers (CustomVoice Mode)
Voci predefinite disponibili: serena, vivian, uncle_fu, ryan, aiden, ono_anna, sohee, eric (dialetto sichuan), dylan (dialetto beijing)
```bash
echo "Hello from a named speaker." | podman exec qwentts qwen-tts \
--model /app/models/qwen-talker-1.7b-customvoice-Q8_0.gguf \
--codec /app/models/qwen-tokenizer-12hz-Q8_0.gguf \
--speaker vivian \
--lang English \
-o /app/output/vivian.wav
```
### Voice Design (Attributi di Voce)
Descrivi gli attributi della voce desiderata in testo libero:
```bash
echo "A very friendly and warm conversation starter." | podman exec qwentts qwen-tts \
--model /app/models/qwen-talker-1.7b-voicedesign-Q8_0.gguf \
--codec /app/models/qwen-tokenizer-12hz-Q8_0.gguf \
--instruct "female, young adult, cheerful, moderate pitch" \
--lang English \
-o /app/output/designed.wav
```
Esempi di descrizioni:
- "male, professional, deep voice, authoritative"
- "female, elderly, warm and nurturing"
- "non-binary, young, energetic and upbeat"
- "child, playful, high-pitched, innocent"
## Lingue Supportate
- English
- Mandarin (Standard)
- Mandarin Sichuan (eric speaker)
- Mandarin Beijing (dylan speaker)
- Cantonese
- Japanese
- Korean
- Spanish
- French
- German
- Russian
- And more...
Specifica con il flag `--lang`
## Modelli Disponibili
### Base Mode (Voce predefinita)
- **Size**: 1.7B o 0.6B
- **Quantization**: Q8_0, Q4_K_M, F32
- **Caratteristiche**: Voce singola fissa, voice cloning
- **Use case**: Sintesi rapida con voice cloning da riferimento
### CustomVoice Mode (Altoparlanti nominati)
- **Speakers**: 8 voci predefinite con nomi e dialetti
- **Size**: 1.7B
- **Caratteristiche**: Scelta rapida tra voci note
- **Use case**: Produzione di contenuto con voci consistenti
### VoiceDesign Mode (Attributi di voce)
- **Size**: 1.7B
- **Caratteristiche**: Sintesi controllata via attributi in testo libero
- **Use case**: Creazione di voci custom con prompt descrittivi
### Dimensioni Modelli
| Modello | Dimensione (F32) | Q8_0 | Q4_K_M | RTF (GPU) |
|---------|-----------------|------|--------|-----------|
| 0.6B | 2.4 GB | 1.2 GB | 0.6 GB | < 0.5x |
| 1.7B | 6.8 GB | 3.4 GB | 1.7 GB | < 1.0x |
RTF = Real-Time Factor (< 1.0 significa più veloce del tempo reale)
## Performance
### Benchmark (Estimated)
**CPU (AMD Ryzen 9950X3D):**
- Sintesi 10 secondi: ~2-5 secondi
**GPU (NVIDIA A100 o AMD GPU equivalente con Vulkan):**
- Sintesi 10 secondi: < 1 secondo
### Ottimizzazione
Per migliore performance:
1. Usa modello 0.6B per latenza bassa
2. Usa quantizzazione Q8_0 o superiore
3. Pre-encode i riferimenti con `qwen-codec --talker` per voice cloning
4. Aumenta memoria allocata nel quadlet se disponibile
## Troubleshooting
### Build falls con dipendenze mancanti
```bash
# Esegui con sudo
sudo ./build-container.sh
```
### Vulkan non disponibile su container
```bash
# Verifica driver Vulkan
podman run --rm --device=/dev/dri --device=/dev/kfd ghcr.io/library/debian:13-slim vulkaninfo
```
### Modelli non trovati
```bash
# Verifica volume mounting
podman exec qwentts ls -la /app/models
```
### Memory issues
Se il container crasha per memoria:
```bash
# Aumenta memoria nel quadlet (attualmente 16GB)
# Oppure usa quantizzazione Q4_K_M per ridurre consumo
```
### Sintesi lenta
- Verifica che Vulkan sia attivo
- Usa modello 0.6B
- Aumenta CPUQuota nel quadlet
- Verifica che /dev/dri e /dev/kfd siano accessibili
## Aggiornamento
Per aggiornare a una versione più recente di qwentts.cpp:
```bash
# Ricompila
./build-container.sh
# Rebuild immagine
podman build -t qwentts:vulkan-amd64 -f qwentts-vulkan.Containerfile .
# Riavvia container
systemctl --user restart qwentts
```
## References
- [Qwentts.cpp GitHub](https://github.com/ServeurpersoCom/qwentts.cpp)
- [Models on Hugging Face](https://huggingface.co/Serveurperso/Qwen3-TTS-GGUF)
- [Qwen3-TTS Documentation](https://docs.qwenlm.ai/)
- [Architecture & API Reference](https://github.com/ServeurpersoCom/qwentts.cpp/blob/master/docs/ARCHITECTURE.md)
+34 -20
Zobrazit soubor
@@ -1,31 +1,45 @@
[Unit]
Description=Qwentts (Text-to-Speech with Voice Cloning)
After=internal.network
Wants=internal.network
Description=Qwen3 TTS Server
After=network-online.target
Wants=network-online.target
[Container]
Image=localhost/qwentts:vulkan-amd64
Image=localhost/qwentts:latest
ContainerName=qwentts
Hostname=qwentts
Network=internal.network
PublishPort=127.0.0.1:8080:8080
Volume=/srv/containers/qwentts/models:/app/models:ro
Volume=/srv/containers/qwentts/output:/app/output:rw
# ROCm
AddDevice=/dev/kfd
AddDevice=/dev/dri/renderD128
PodmanArgs=--group-add=keep-groups --ipc=host --pids-limit=-1 --security-opt label=disable
SecurityLabelType=container_runtime_t
# GPU access - AMD Vulkan
Device=/dev/dri:/dev/dri:rw
Device=/dev/kfd:/dev/kfd:rw
# HTTP API
PublishPort=8000:8000
# Resource limits (adjust as needed)
Memory=16g
MemorySwap=24g
CPUQuota=50%
# Persist downloaded model weights across restarts
# Adjust the host path to wherever you want the models cached
Volume=/srv/containers/qwen-tts/models:/app/models:Z
# Restart policy
# Which model(s) to load at startup: "customvoice", "voiceclone", or both
# (default, comma-separated). Loading both lets a single instance serve
# /speech (preset voices) and /speech/clone (voice cloning) at once.
#Environment=QWEN_TTS_LOAD=customvoice,voiceclone
# Optional: override the default checkpoints
#Environment=QWEN_TTS_CUSTOMVOICE_MODEL=Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice
#Environment=QWEN_TTS_VOICECLONE_MODEL=Qwen/Qwen3-TTS-12Hz-1.7B-Base
# CPU-only container: no GPU devices needed
PodmanArgs=--pids-limit=-1
# Resource limits (tune to your host; a 1.7B model on CPU needs headroom)
#Memory=8g
#CPUs=4
[Service]
Restart=on-failure
RestartMaxAttempts=5
RestartSec=10s
RestartSec=5
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
WantedBy=default.target
-53
Zobrazit soubor
@@ -1,53 +0,0 @@
server {
listen 80;
server_name qwentts.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name qwentts.example.com;
ssl_certificate /etc/letsencrypt/live/qwentts.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/qwentts.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
client_max_body_size 100m;
# Re-resolve qwentts's DNS name at request time so a container restart
# (new IP on internal.network) doesn't leave nginx stuck on a stale
# address until nginx itself is restarted.
resolver 10.10.0.1 valid=10s;
resolver_timeout 5s;
# Logging
access_log /var/log/nginx/qwentts_access.log;
error_log /var/log/nginx/qwentts_error.log;
location / {
set $qwentts_upstream http://qwentts:8080;
proxy_pass $qwentts_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts for long-running synthesis requests
proxy_connect_timeout 120s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
send_timeout 300s;
# Buffering for large audio files
proxy_buffering on;
proxy_buffer_size 64k;
proxy_buffers 8 64k;
proxy_busy_buffers_size 128k;
}
}
+1
Zobrazit soubor
@@ -129,6 +129,7 @@ conversazione Talk dove il bot è abilitato: vedi sezione "Stato dei test".
| `WHISPER_URL` | no | `http://whisper:8080` | Endpoint whisper-server |
| `LLAMACPP_URL` | no | `http://llamacpp:7000` | Endpoint llama.cpp (OpenAI-compatible chat) |
| `LLAMACPP_MODEL` | no | (vuoto) | Nome modello, se il tuo router llama.cpp lo richiede |
| `LLAMACPP_API_KEY` | no | (vuoto) | API key per llama.cpp (header `Authorization: Bearer ...`); se vuoto, nessun header è inviato |
| `LLAMACPP_TIMEOUT` | no | `600` | Timeout (secondi) per la chiamata a llama.cpp |
| `QWEN_TTS_URL` | no | `http://qwen-tts:8000` | Endpoint qwen-tts (deve girare con `QWEN_TTS_LOAD` includendo sia `customvoice` che `voiceclone`, default) |
| `QWEN_TTS_TIMEOUT` | no | `1800` | Timeout (secondi) per la chiamata a qwen-tts |
+10
Zobrazit soubor
@@ -26,6 +26,7 @@ NC_PASSWORD = os.environ["NC_PASSWORD"]
WHISPER_URL = os.environ.get("WHISPER_URL", "http://whisper:8080").rstrip("/")
LLAMACPP_URL = os.environ.get("LLAMACPP_URL", "http://llamacpp:7000").rstrip("/")
LLAMACPP_MODEL = os.environ.get("LLAMACPP_MODEL", "").strip()
LLAMACPP_API_KEY = os.environ.get("LLAMACPP_API_KEY", "").strip()
LLAMACPP_TIMEOUT = int(os.environ.get("LLAMACPP_TIMEOUT") or "600")
LLAMACPP_MAX_TOKENS = int(os.environ.get("LLAMACPP_MAX_TOKENS") or "8192")
LLAMACPP_ENABLE_THINKING = (
@@ -676,9 +677,14 @@ def translate(
if LLAMACPP_MODEL:
payload["model"] = LLAMACPP_MODEL
headers = {}
if LLAMACPP_API_KEY:
headers["Authorization"] = f"Bearer {LLAMACPP_API_KEY}"
response = httpx.post(
f"{LLAMACPP_URL}/v1/chat/completions",
json=payload,
headers=headers,
timeout=LLAMACPP_TIMEOUT,
)
@@ -1280,6 +1286,10 @@ def process_event(job: dict) -> None:
# the worker considers the event successful and removes it.
raise
# Audio messages are translated and posted as audio only; don't also
# post the translated text (e.g. a caption sent alongside the file).
return
if message_text:
log.info(
"Text message %s from %s",
+1
Zobrazit soubor
@@ -35,6 +35,7 @@ Environment=NC_PASSWORD=changeme
Environment=WHISPER_URL=http://whisper:8080
Environment=LLAMACPP_URL=http://llamacpp:8090
#Environment=LLAMACPP_MODEL=
#Environment=LLAMACPP_API_KEY=
Environment=LLAMACPP_MAX_TOKENS=8192
Environment=LLAMACPP_ENABLE_THINKING=false
#Environment=LOG_WEBHOOK_PAYLOAD=false