1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
# Configuración del asistente de voz.
#
# Las rutas relativas se resuelven contra la carpeta de este fichero, no contra
# el directorio desde el que se lanza el binario.
#
# Los valores que llevan un comentario con una medición vienen de
# docs/RENDIMIENTO.md: son los que se midieron en esta máquina, no adivinanzas.
[general]
language = "es"
history_turns = 8
report_latency = true
system_prompt = """
Eres un asistente de voz en español. Tus respuestas se leen en voz alta, así \
que responde en una o dos frases cortas, en texto plano corrido. No uses \
markdown, ni listas, ni asteriscos, ni emojis, ni encabezados. No escribas \
URLs ni código salvo que te lo pidan explícitamente. Si no sabes algo, dilo \
en una frase.
"""
# SUSTITUYE a system_prompt en la pasada en que el modelo decide si llamar a
# una herramienta. Va sola a propósito: medido con Qwen3.5-2B, añadirle
# cualquier indicación de estilo —dos palabras bastan— hace que deje de llamar
# a las herramientas y se invente el dato. La guía sola acierta 8 de 8; con
# «Responde breve.» detrás, 1 de 8; con la persona de asistente de voz, 0 de 8.
# Ver docs/RENDIMIENTO.md. Se desactiva con tools.dedicated_prompt = false.
tools_prompt = """
Antes de responder, comprueba si alguna de tus herramientas te da el dato. Si \
es así, llámala primero y espera su resultado; no contestes de memoria. Sólo \
cuando tengas el resultado, resúmelo en una frase.
"""
[audio]
# Vacío = el dispositivo por defecto. `asistente devices` los lista.
input_device = ""
output_device = ""
playback_prebuffer = 0.20
output_gain = 1.0
[vad]
frame_seconds = 0.1
preroll_seconds = 0.3
silence_hold = 0.8 # silencio que cierra una intervención
min_utterance = 0.3 # se mide sobre la voz, sin contar el preroll
max_utterance = 20.0
threshold_factor = 3.0
min_threshold = 0.0008
max_threshold = 0.02
# Cortar al asistente hablando encima. Apagado por defecto: con altavoces
# abiertos el micrófono se oye a sí mismo y el asistente se interrumpe solo.
# Enciéndelo con auriculares, o con --barge-in.
barge_in = false
barge_in_factor = 4.0
[asr]
model_dir = "../vendor/canary-rs/models/canary-180m-flash-onnx"
source_lang = "es"
target_lang = "es"
window = 6.0
step = 0.4
stability = 2
partials = true
dedicated_final_model = false
# CPU a propósito: la GPU de 4 GB la ocupa el hablante del TTS y disputársela
# sale más caro que decodificar aquí. Ver docs/RENDIMIENTO.md.
execution_provider = "cpu"
inter_threads = 2
intra_threads = 4
[llm]
host = "127.0.0.1"
port = 8012
model = ""
temperature = 0.7
top_p = 0.9
top_k = 40
min_p = 0.1
repeat_penalty = 1.1
max_tokens = 300 # una respuesta hablada larga cansa, y la síntesis es lo caro
max_tool_rounds = 4
request_timeout_secs = 120
[tts]
host = "127.0.0.1"
port = 8013
voice = "asistente"
language = "spanish"
temperature = 0.9
top_k = 50
top_p = 1.0
repetition_penalty = 1.05
max_new_tokens = 2048
warmup = true # la primera síntesis cuesta ~3,5 s más que el resto
request_timeout_secs = 180
# Voz clonada, registrada en el servidor al arrancar. Los tres ficheros salen
# de `qwen-codec --talker` sobre una grabación de referencia; hay un guion en
# scripts/clonar-voz.sh.
[tts.reference]
name = "asistente"
speaker = "../assets/voices/asistente.spk"
codes = "../assets/voices/asistente.rvq"
transcript = "../assets/voices/asistente.txt"
[tools]
enabled = true
# Alterna entre tools_prompt (para decidir) y system_prompt (para redactar).
# Es lo único que hace que las herramientas funcionen con este modelo; a
# cambio, las respuestas que no usan herramienta pierden la guía de estilo.
# Ponlo a false para priorizar el estilo sobre las herramientas.
dedicated_prompt = true
# Ejecución de órdenes del sistema. Apagada por defecto a conciencia: darle
# una shell a un modelo que obedece a lo que oye por el micrófono es un cambio
# de postura de seguridad, no una comodidad. Actívala aquí o con --shell.
shell = false
shell_allowlist = ["date", "uptime", "free", "df", "ls"]
shell_timeout_secs = 10
shell_dry_run = false
shell_working_dir = ""
[supervisor]
manage = true # lanzar los servidores; --no-manage los supone arriba
startup_timeout_secs = 180
[supervisor.llama]
binary = "../vendor/llama.cpp/build/bin/llama-server"
model = "../models/Qwen3.5-2B.Q5_K_M.gguf"
mmproj = "../models/mmproj-BF16.gguf"
# Copia de la plantilla del modelo con el bloque <think> cerrado de entrada.
# Sin esto el modelo razona entre 7 y 9 s antes de la primera palabra audible.
chat_template = "qwen35-no-think.jinja"
extra_args = [
"--threads", "10", "--threads-batch", "10",
"--batch-size", "512", "--ubatch-size", "256",
"--gpu-layers", "10", "--split-mode", "layer",
"--tensor-split", "1", "--main-gpu", "0",
"--no-mmap",
# 8192 en vez del contexto completo del modelo (262144): la caché KV de ese
# tamaño no cabe en 4 GB junto al TTS. Ver docs/RENDIMIENTO.md.
"--ctx-size", "8192", "--parallel", "2",
"--cache-ram", "6144",
"--rope-freq-base", "1000000", "--rope-freq-scale", "0.25",
]
[supervisor.tts]
binary = "../vendor/qwentts.cpp/build/tts-server"
model = "../models/qwen-talker-1.7b-base-Q8_0.gguf"
codec = "../models/qwen-tokenizer-12hz-Q8_0.gguf"
# El ajuste con más efecto de todo el sistema: de fábrica son 24 s, y con eso
# el servidor no devuelve nada hasta terminar la frase entera. Medido, baja el
# primer audio de 4948 ms a 585 ms.
codec_chunk_dur = 1.0
extra_args = []
|