aboutsummaryrefslogtreecommitdiffstats
path: root/crates/asist-core
diff options
context:
space:
mode:
Diffstat (limited to 'crates/asist-core')
-rw-r--r--crates/asist-core/src/config.rs271
-rw-r--r--crates/asist-core/src/error.rs30
-rw-r--r--crates/asist-core/src/event.rs52
-rw-r--r--crates/asist-core/src/http.rs66
-rw-r--r--crates/asist-core/src/lib.rs10
-rw-r--r--crates/asist-core/src/proc.rs80
-rw-r--r--crates/asist-core/src/telemetry.rs88
-rw-r--r--crates/asist-core/src/text.rs124
-rw-r--r--crates/asist-core/src/tools.rs158
9 files changed, 440 insertions, 439 deletions
diff --git a/crates/asist-core/src/config.rs b/crates/asist-core/src/config.rs
index c2a9d55..0a8cc04 100644
--- a/crates/asist-core/src/config.rs
+++ b/crates/asist-core/src/config.rs
@@ -1,5 +1,5 @@
-//! Configuración del asistente: un TOML con valores por defecto que ya
-//! incorporan lo aprendido midiendo el pipeline (ver `docs/RENDIMIENTO.md`).
+//! Assistant configuration: a TOML file whose defaults already include what
+//! was learned by measuring the pipeline (see `docs/RENDIMIENTO.md`).
use std::path::{Path, PathBuf};
@@ -26,32 +26,31 @@ pub struct Config {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct General {
- /// Idioma de la conversación; se propaga a ASR y TTS.
+ /// Conversation language; propagated to ASR and TTS.
pub language: String,
- /// Instrucción de sistema. Pide frases cortas y sin markdown a propósito:
- /// el TTS lee literalmente los asteriscos y las viñetas.
+ /// System prompt. It asks for short sentences without markdown on purpose:
+ /// the TTS reads asterisks and bullets literally.
pub system_prompt: String,
- /// Instrucción de sistema de la pasada en que el modelo decide si usar
- /// una herramienta.
+ /// System prompt for the pass where the model decides whether to use a
+ /// tool.
///
- /// Va aparte, y **sustituye** a `system_prompt` en esa pasada, por una
- /// razón medida y no por gusto: con Qwen3.5-2B, añadir cualquier
- /// indicación de estilo a esta guía —en cualquier posición, incluso dos
- /// palabras— hace que el modelo deje de llamar a las herramientas y se
- /// invente el dato. Medido sobre 8 intentos: la guía sola acierta 8/8;
- /// con «Responde breve.» detrás, 1/8; con la persona de asistente de voz,
- /// 0/8. Ver docs/RENDIMIENTO.md.
+ /// It is separate, and **replaces** `system_prompt` in that pass, for a
+ /// measured reason rather than taste: with Qwen3.5-2B, adding any style
+ /// instruction to this guide (in any position, even two words) makes the
+ /// model stop calling tools and make the data up. Measured over 8 tries:
+ /// the guide alone gets 8/8; with «Responde breve.» after it, 1/8; with the
+ /// voice-assistant persona, 0/8. See docs/RENDIMIENTO.md.
pub tools_prompt: String,
- /// Se añade a `system_prompt` para redactar la respuesta cuando una
- /// herramienta ya ha devuelto su resultado.
+ /// Appended to `system_prompt` to write the answer once a tool has
+ /// returned its result.
///
- /// Aquí sí se puede añadir estilo sin miedo —la llamada ya ocurrió—, y
- /// hace falta: sin esta orden el modelo anuncia lo que acaba de hacer en
- /// vez de contar lo que averiguó.
+ /// Here style can be added safely (the call already happened), and it is
+ /// needed: without this instruction the model announces what it just did
+ /// instead of telling what it found out.
pub tool_result_prompt: String,
- /// Turnos de historial que se envían al modelo (0 = sin memoria).
+ /// History turns sent to the model (0 = no memory).
pub history_turns: usize,
- /// Imprime un resumen de latencias por turno al terminar cada respuesta.
+ /// Prints a per-turn latency summary at the end of each answer.
pub report_latency: bool,
}
@@ -89,14 +88,14 @@ impl Default for General {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct AudioConfig {
- /// Nombre (o subcadena) del dispositivo de entrada; vacío = el de por defecto.
+ /// Name (or substring) of the input device; empty = the default one.
pub input_device: String,
- /// Ídem para la salida.
+ /// Same for the output.
pub output_device: String,
- /// Segundos de audio que la reproducción mantiene en cola antes de
- /// arrancar. Amortigua los baches del TTS sin añadir latencia perceptible.
+ /// Seconds of audio playback keeps queued before starting. It absorbs
+ /// TTS hiccups without adding noticeable latency.
pub playback_prebuffer: f32,
- /// Ganancia aplicada a la reproducción.
+ /// Gain applied to playback.
pub output_gain: f32,
}
@@ -114,28 +113,28 @@ impl Default for AudioConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct VadConfig {
- /// Ventana de audio por decisión del detector.
+ /// Audio window per detector decision.
pub frame_seconds: f32,
- /// Audio conservado antes del inicio de voz para no cortar la primera sílaba.
+ /// Audio kept before speech onset so the first syllable is not cut.
pub preroll_seconds: f32,
- /// Silencio que cierra una intervención.
+ /// Silence that ends an utterance.
pub silence_hold: f32,
- /// Intervención más corta que merezca una transcripción final.
+ /// Shortest utterance worth a final transcription.
pub min_utterance: f32,
- /// Corte forzado, que acota el coste de la decodificación final.
+ /// Forced cut, which bounds the cost of the final decode.
pub max_utterance: f32,
- /// Múltiplo del suelo de ruido a partir del cual se considera voz.
+ /// Multiple of the noise floor above which audio counts as speech.
pub threshold_factor: f32,
pub min_threshold: f32,
pub max_threshold: f32,
- /// Permite hablar encima del asistente para cortarlo.
+ /// Lets the user talk over the assistant to interrupt it.
///
- /// Desactivado por defecto: con altavoces abiertos el micrófono se oye a sí
- /// mismo y el asistente se interrumpe solo. Actívalo con auriculares o con
- /// cancelación de eco del sistema.
+ /// Off by default: with open speakers the microphone hears itself and the
+ /// assistant interrupts itself. Turn it on with headphones or with system
+ /// echo cancellation.
pub barge_in: bool,
- /// Con barge-in activo, cuánto más fuerte que el umbral normal debe sonar
- /// la voz para cortar. Sube el listón frente al eco del altavoz.
+ /// With barge-in on, how much louder than the normal threshold the voice
+ /// must be to interrupt. Raises the bar against speaker echo.
pub barge_in_factor: f32,
}
@@ -159,23 +158,23 @@ impl Default for VadConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct AsrConfig {
- /// Carpeta del modelo Canary en ONNX.
+ /// Directory of the Canary ONNX model.
pub model_dir: PathBuf,
pub source_lang: String,
pub target_lang: String,
- /// Ventana deslizante de las transcripciones provisionales.
+ /// Sliding window for the partial transcriptions.
pub window: f32,
- /// Avance de la ventana entre decodificaciones.
+ /// Window step between decodes.
pub step: f32,
- /// Ventanas que deben coincidir para dar una palabra por estable.
+ /// Windows that must agree for a word to be considered stable.
pub stability: usize,
- /// Emitir transcripciones provisionales. Cuestan CPU y sólo sirven para
- /// verlas en pantalla: el turno se dispara con la final.
+ /// Emit partial transcriptions. They cost CPU and are only there to be
+ /// shown on screen: the turn is triggered by the final one.
pub partials: bool,
- /// Carga una segunda instancia del modelo para que las decodificaciones
- /// finales no bloqueen a las provisionales. Duplica la memoria.
+ /// Loads a second model instance so final decodes do not block the
+ /// partial ones. Doubles the memory.
pub dedicated_final_model: bool,
- /// Proveedor de ejecución de ONNX Runtime: `cpu`, `cuda`, ...
+ /// ONNX Runtime execution provider: `cpu`, `cuda`, ...
pub execution_provider: String,
pub inter_threads: usize,
pub intra_threads: usize,
@@ -192,8 +191,8 @@ impl Default for AsrConfig {
stability: 2,
partials: true,
dedicated_final_model: false,
- // CPU a propósito: la GPU de 4 GB está ocupada por el hablante del
- // TTS, y disputársela sale más caro que decodificar en CPU.
+ // CPU on purpose: the 4 GB GPU is taken by the TTS talker, and fighting
+ // over it costs more than decoding on the CPU.
execution_provider: "cpu".into(),
inter_threads: 2,
intra_threads: 4,
@@ -213,7 +212,7 @@ pub struct LlmConfig {
pub min_p: f32,
pub repeat_penalty: f32,
pub max_tokens: u32,
- /// Vueltas máximas del bucle de herramientas antes de rendirse.
+ /// Maximum rounds of the tool loop before giving up.
pub max_tool_rounds: usize,
pub request_timeout_secs: u64,
}
@@ -229,8 +228,8 @@ impl Default for LlmConfig {
top_k: 40,
min_p: 0.1,
repeat_penalty: 1.1,
- // Una respuesta hablada larga cansa; el recorte también acota el
- // coste de la síntesis, que es la etapa lenta.
+ // A long spoken answer is tiring; the cap also bounds the cost of
+ // synthesis, which is the slow stage.
max_tokens: 300,
max_tool_rounds: 4,
request_timeout_secs: 120,
@@ -243,9 +242,9 @@ impl Default for LlmConfig {
pub struct TtsConfig {
pub host: String,
pub port: u16,
- /// Voz registrada en el servidor, o una del modelo.
+ /// Voice registered on the server, or one of the model's.
pub voice: String,
- /// Voz clonada que se registra al arrancar, si se define.
+ /// Cloned voice registered at startup, if defined.
pub reference: Option<ReferenceVoice>,
pub language: String,
pub temperature: f32,
@@ -253,9 +252,8 @@ pub struct TtsConfig {
pub top_p: f32,
pub repetition_penalty: f32,
pub max_new_tokens: u32,
- /// Sintetiza una frase corta al arrancar. La primera petición paga la
- /// construcción de los grafos: ~3,5 s que conviene no gastar en el
- /// primer turno real.
+ /// Synthesizes a short sentence at startup. The first request pays for
+ /// building the graphs: ~3.5 s better not spent on the first real turn.
pub warmup: bool,
pub request_timeout_secs: u64,
}
@@ -279,52 +277,51 @@ impl Default for TtsConfig {
}
}
-/// Latentes de una voz clonada, tal y como los produce `qwen-codec --talker`.
+/// Latents of a cloned voice, as produced by `qwen-codec --talker`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReferenceVoice {
- /// Nombre con el que se registra en el servidor.
+ /// Name it is registered under on the server.
pub name: String,
- /// Embedding del hablante (`.spk`).
+ /// Speaker embedding (`.spk`).
pub speaker: PathBuf,
- /// Códigos de referencia (`.rvq`), que activan el clonado ICL.
+ /// Reference codes (`.rvq`), which enable ICL cloning.
pub codes: PathBuf,
- /// Transcripción de la referencia (`.txt`).
+ /// Transcript of the reference (`.txt`).
pub transcript: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct ToolsConfig {
- /// Deja que el modelo llame a herramientas.
+ /// Lets the model call tools.
pub enabled: bool,
- /// Usa `general.tools_prompt` en solitario para la pasada en que el
- /// modelo decide si llamar a una herramienta, y `general.system_prompt`
- /// sólo para redactar la respuesta hablada.
+ /// Uses `general.tools_prompt` alone for the pass where the model decides
+ /// whether to call a tool, and `general.system_prompt` only to write the
+ /// spoken answer.
///
- /// Es lo único que hace que las herramientas funcionen de verdad con este
- /// modelo (ver `tools_prompt`), a cambio de que las respuestas que no
- /// usan herramienta pierdan la guía de estilo. Se compensa en parte
- /// porque el limpiador de texto quita el markdown antes de hablar.
- /// Ponlo a `false` para priorizar el estilo sobre las herramientas.
+ /// It is the only thing that makes tools really work with this model (see
+ /// `tools_prompt`), at the cost of answers that use no tool losing the
+ /// style guide. That is partly offset by the text cleaner removing
+ /// markdown before speaking. Set it to `false` to favour style over tools.
pub dedicated_prompt: bool,
- /// Pronuncia una frase corta al empezar una herramienta lenta, para que la
- /// espera no se confunda con un cuelgue.
+ /// Says a short sentence when a slow tool starts, so the wait is not
+ /// mistaken for a hang.
pub spoken_ack: bool,
- /// Habilita la herramienta de ejecución de órdenes del sistema.
+ /// Enables the tool that runs system commands.
///
- /// 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 opción de comodidad.
+ /// Off by default on purpose: giving a shell to a model that obeys what
+ /// it hears through the microphone is a change of security posture, not a
+ /// convenience option.
pub shell: bool,
- /// Órdenes admitidas, comparadas contra el ejecutable (argv[0]).
- /// Una lista vacía deniega todo aunque `shell` esté activo.
+ /// Allowed commands, matched against the executable (argv[0]).
+ /// An empty list denies everything even if `shell` is on.
pub shell_allowlist: Vec<String>,
- /// Segundos que puede durar una orden antes de que se la mate.
+ /// Seconds a command may run before it is killed.
pub shell_timeout_secs: u64,
- /// Registra la orden pero no la ejecuta. Útil para estrenar la lista blanca.
+ /// Logs the command but does not run it. Useful to break in the allowlist.
pub shell_dry_run: bool,
- /// Directorio de trabajo de las órdenes; vacío = el del proceso.
+ /// Working directory of the commands; empty = the process one.
pub shell_working_dir: String,
}
@@ -349,29 +346,28 @@ impl Default for ToolsConfig {
}
}
-/// Búsqueda en internet.
+/// Web search.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct SearchConfig {
pub enabled: bool,
- /// `tavily` (necesita clave), `ddgs` (sin clave) o `searxng` (necesita
- /// una instancia propia).
+ /// `tavily` (needs a key), `ddgs` (no key) or `searxng` (needs your own
+ /// instance).
pub backend: String,
- /// Variable de entorno de la que se lee la clave.
+ /// Environment variable the key is read from.
///
- /// La clave se lee del entorno y no del fichero a propósito: la
- /// configuración se versiona y se comparte, y un secreto ahí dentro acaba
- /// en el historial de git.
+ /// The key comes from the environment and not from the file on purpose:
+ /// the configuration is versioned and shared, and a secret in it ends up in
+ /// the git history.
pub api_key_env: String,
- /// URL base de la instancia de SearXNG.
+ /// Base URL of the SearXNG instance.
pub base_url: String,
- /// Programa que ejecuta el backend `ddgs`, con sus argumentos.
+ /// Program that runs the `ddgs` backend, with its arguments.
///
- /// `{consulta}` y `{max}` se sustituyen antes de ejecutar. Tiene que
- /// escribir JSON por la salida estándar; se admiten tanto la lista suelta
- /// que devuelve ddgs como la forma `{answer, results}`. Cualquier otro
- /// programa que respete eso sirve igual, incluido un puente a un servidor
- /// MCP.
+ /// `{query}` and `{max}` are substituted before running. It must write
+ /// JSON to standard output; both the bare list ddgs returns and the
+ /// `{answer, results}` shape are accepted. Any other program that honours
+ /// that works too, including a bridge to an MCP server.
pub command: Vec<String>,
pub max_results: usize,
pub timeout_secs: u64,
@@ -385,35 +381,35 @@ impl Default for SearchConfig {
api_key_env: "TAVILY_API_KEY".into(),
base_url: String::new(),
command: vec![
- "scripts/buscar-ddgs.sh".into(),
- "{consulta}".into(),
+ "scripts/search-ddgs.sh".into(),
+ "{query}".into(),
"{max}".into(),
],
- // Cinco resultados llenan bien el contexto sin ahogar a un modelo
- // de 2B, que con más se pierde entre fuentes.
+ // Five results fill the context well without drowning a 2B model, which
+ // gets lost between sources with more.
max_results: 5,
timeout_secs: 20,
}
}
}
-/// Mirar por la cámara.
+/// Looking through the camera.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct CameraConfig {
- /// Si se apaga, la herramienta no llega a registrarse y el modelo ni
- /// sabe que existe. También se apaga sola si no hay dispositivo.
+ /// If off, the tool is never registered and the model does not even know
+ /// it exists. It also turns itself off if there is no device.
pub enabled: bool,
pub device: PathBuf,
- /// Resolución de captura. Manda mucho en la latencia: medido, el modelo
- /// tarda 1,3 s a 320x240, 2,9 s a 640x480 y 7,8 s a 1280x720.
+ /// Capture resolution. It weighs heavily on latency: measured, the model
+ /// takes 1.3 s at 320x240, 2.9 s at 640x480 and 7.8 s at 1280x720.
pub width: u32,
pub height: u32,
- /// Fotogramas descartados para que la exposición automática se asiente.
+ /// Frames discarded so auto-exposure can settle.
pub warmup_frames: u32,
pub timeout_secs: u64,
- /// Carpeta donde guardar cada fotograma capturado. Vacío = no se guarda
- /// ninguno, que es lo que corresponde por defecto.
+ /// Directory where each captured frame is saved. Empty = none is saved,
+ /// which is the right default.
pub save_dir: String,
}
@@ -431,28 +427,27 @@ impl Default for CameraConfig {
}
}
-/// Mirar la pantalla.
+/// Looking at the screen.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct ScreenConfig {
pub enabled: bool,
- /// Programa de captura. `{ancho}` y `{salida}` se sustituyen antes de
- /// ejecutar, y tiene que escribir un JPEG por la salida estándar. Está
- /// fuera del binario para que añadir un compositor sea editar un guion.
+ /// Capture program. `{width}` and `{output}` are substituted before
+ /// running, and it must write a JPEG to standard output. It lives outside
+ /// the binary so supporting another compositor means editing a script.
pub command: Vec<String>,
- /// Ancho al que se reduce la captura antes de mandarla al modelo.
+ /// Width the capture is scaled down to before sending it to the model.
///
- /// **No lo bajes a la ligera.** Medido con tipografía de interfaz de 13 px
- /// y preguntando por datos concretos: a 1280 px acierta 3 de 3 en 7,6 s; a
- /// 960, 2 de 3; a 640, 1 de 3. Y cuando falla no dice que no lo lee: se
- /// inventa el contenido con aplomo.
+ /// **Do not lower it lightly.** Measured with 13 px UI type and asking for
+ /// specific details: at 1280 px it gets 3 of 3 in 7.6 s; at 960, 2 of 3;
+ /// at 640, 1 of 3. And when it fails it does not say it cannot read it: it
+ /// confidently makes the content up.
pub width: u32,
- /// Monitor concreto. Vacío = todo lo que haya.
+ /// A specific monitor. Empty = everything there is.
pub output: String,
pub timeout_secs: u64,
- /// Carpeta donde guardar las capturas. Vacío = no se guarda ninguna, que
- /// es lo que corresponde: en una captura caben contraseñas y mensajes
- /// privados.
+ /// Directory where captures are saved. Empty = none is saved, which is
+ /// right: a capture can hold passwords and private messages.
pub save_dir: String,
}
@@ -461,9 +456,9 @@ impl Default for ScreenConfig {
Self {
enabled: true,
command: vec![
- "scripts/capturar-pantalla.sh".into(),
- "{ancho}".into(),
- "{salida}".into(),
+ "scripts/capture-screen.sh".into(),
+ "{width}".into(),
+ "{output}".into(),
],
width: 1280,
output: String::new(),
@@ -476,9 +471,9 @@ impl Default for ScreenConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct SupervisorConfig {
- /// Lanza los servidores en lugar de suponer que ya están arriba.
+ /// Starts the servers instead of assuming they are already up.
pub manage: bool,
- /// Segundos de espera a que un servidor conteste a `/health`.
+ /// Seconds to wait for a server to answer `/health`.
pub startup_timeout_secs: u64,
pub llama: LlamaProcess,
pub tts: TtsProcess,
@@ -501,11 +496,11 @@ pub struct LlamaProcess {
pub binary: PathBuf,
pub model: PathBuf,
pub mmproj: PathBuf,
- /// Plantilla de chat que se pasa con `--chat-template-file`.
+ /// Chat template passed with `--chat-template-file`.
///
- /// La del modelo abre `<think>` sin cerrarlo nunca, y el asistente se pasa
- /// entonces varios segundos razonando antes de decir la primera palabra.
- /// Esta copia deja el bloque cerrado de entrada.
+ /// The model's own template opens `<think>` and never closes it, so the
+ /// assistant spends several seconds reasoning before saying the first
+ /// word. This copy starts with the block already closed.
pub chat_template: PathBuf,
pub extra_args: Vec<String>,
}
@@ -560,11 +555,11 @@ pub struct TtsProcess {
pub binary: PathBuf,
pub model: PathBuf,
pub codec: PathBuf,
- /// Segundos de audio que el codec acumula antes de decodificar un bloque.
+ /// Seconds of audio the codec accumulates before decoding a block.
///
- /// El valor de fábrica son 24 s, que en la práctica significa «no
- /// devuelvas nada hasta terminar la frase entera»: medido, baja el primer
- /// audio de 4,9 s a 0,6 s. Es el ajuste con más efecto de todo el sistema.
+ /// The stock value is 24 s, which in practice means «return nothing until
+ /// the whole sentence is done»: measured, it lowers the first audio from
+ /// 4.9 s to 0.6 s. It is the single most effective setting in the system.
pub codec_chunk_dur: f32,
pub extra_args: Vec<String>,
}
@@ -588,8 +583,8 @@ impl Config {
.map_err(|e| Error::Config(format!("no se pudo leer {}: {e}", path.display())))?;
let mut config: Config =
toml::from_str(&raw).map_err(|e| Error::Config(format!("{}: {e}", path.display())))?;
- // Las rutas relativas se resuelven contra la carpeta del TOML, no
- // contra el directorio desde el que se lanza el binario.
+ // Relative paths are resolved against the TOML directory, not against
+ // the directory the binary is launched from.
if let Some(base) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
config.rebase(base);
}
@@ -597,7 +592,7 @@ impl Config {
Ok(config)
}
- /// Reinterpreta las rutas relativas respecto de `base`.
+ /// Reinterprets relative paths relative to `base`.
pub fn rebase(&mut self, base: &Path) {
let fix = |p: &mut PathBuf| {
if p.is_relative() {
@@ -667,7 +662,7 @@ impl Config {
));
}
}
- "ddgs" | "comando" => {
+ "ddgs" | "command" | "comando" => {
if self.search.command.is_empty() {
return Err(Error::Config(
"search.backend = «ddgs» necesita search.command".into(),
diff --git a/crates/asist-core/src/error.rs b/crates/asist-core/src/error.rs
index b559bee..f0dfbe2 100644
--- a/crates/asist-core/src/error.rs
+++ b/crates/asist-core/src/error.rs
@@ -2,12 +2,12 @@ use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
-/// Un fallo de una etapa del pipeline. El orquestador decide si es fatal o si
-/// basta con abortar el turno en curso, así que el error lleva esa distinción
-/// en lugar de dejarla al criterio de quien lo recibe.
+/// A failure in a pipeline stage. The orchestrator decides whether it is
+/// fatal or whether aborting the current turn is enough, so the error carries
+/// that distinction instead of leaving it to whoever receives it.
#[derive(Debug, thiserror::Error)]
pub enum Error {
- #[error("configuración inválida: {0}")]
+ #[error("invalid configuration: {0}")]
Config(String),
#[error("audio: {0}")]
@@ -22,24 +22,24 @@ pub enum Error {
#[error("TTS: {0}")]
Tts(String),
- #[error("HTTP {status} en {url}: {body}")]
+ #[error("HTTP {status} at {url}: {body}")]
Http {
status: u16,
url: String,
body: String,
},
- #[error("transporte hacia {url}: {source}")]
+ #[error("transport to {url}: {source}")]
Transport {
url: String,
#[source]
source: std::io::Error,
},
- #[error("herramienta «{tool}»: {message}")]
+ #[error("tool «{tool}»: {message}")]
Tool { tool: String, message: String },
- #[error("operación cancelada")]
+ #[error("operation cancelled")]
Cancelled,
#[error(transparent)]
@@ -50,17 +50,17 @@ pub enum Error {
}
impl Error {
- /// Un error fatal tumba el asistente; el resto sólo aborta el turno.
+ /// A fatal error brings the assistant down; the rest only abort the turn.
///
- /// Los servidores locales se caen y se reinician, y el usuario prefiere
- /// oír «no te he entendido» a que el proceso muera, así que sólo la
- /// configuración y el audio (que no se pueden reintentar) son fatales.
+ /// The local servers crash and restart, and the user would rather hear «no
+ /// te he entendido» than have the process die, so only configuration and
+ /// audio (which cannot be retried) are fatal.
pub fn is_fatal(&self) -> bool {
matches!(self, Error::Config(_) | Error::Audio(_))
}
- /// `true` cuando reintentar tiene sentido: el servidor aún está cargando
- /// el modelo, o se ha caído la conexión a mitad de una petición.
+ /// `true` when retrying makes sense: the server is still loading the
+ /// model, or the connection dropped mid-request.
pub fn is_retryable(&self) -> bool {
match self {
Error::Transport { .. } => true,
@@ -70,7 +70,7 @@ impl Error {
}
}
-/// Contexto legible para los `Result` que cruzan una frontera de crate.
+/// Readable context for `Result`s that cross a crate boundary.
pub trait Context<T> {
fn ctx(self, f: impl FnOnce() -> String) -> Result<T>;
}
diff --git a/crates/asist-core/src/event.rs b/crates/asist-core/src/event.rs
index 549bec0..bf9a6e6 100644
--- a/crates/asist-core/src/event.rs
+++ b/crates/asist-core/src/event.rs
@@ -1,9 +1,9 @@
use std::time::{Duration, Instant};
-/// Identifica un turno de conversación (una intervención del usuario y la
-/// respuesta que provoca). Todo lo que viaja por el bus lo lleva, para que un
-/// resultado que llega tarde no se confunda con el turno que ya está en curso
-/// —el caso típico cuando el usuario interrumpe al asistente.
+/// Identifies a conversation turn (one user utterance and the answer it
+/// triggers). Everything travelling on the bus carries it, so a result that
+/// arrives late is not mistaken for the turn in progress, the typical case
+/// being the user interrupting the assistant.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct TurnId(pub u64);
@@ -13,32 +13,32 @@ impl std::fmt::Display for TurnId {
}
}
-/// Motivo por el que se corta la reproducción en curso.
+/// Why the current playback is cut.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterruptReason {
- /// El usuario ha empezado a hablar encima del asistente (barge-in).
+ /// The user started talking over the assistant (barge-in).
UserSpoke,
- /// Petición explícita: tecla, señal o cierre.
+ /// Explicit request: key, signal or shutdown.
Requested,
}
-/// Todo lo que las etapas se cuentan entre sí. Un único enum mantiene el bus
-/// observable: el renderizador y la telemetría ven exactamente los mismos
-/// eventos que el orquestador, sin canales paralelos que se desincronicen.
+/// Everything the stages tell each other. A single enum keeps the bus
+/// observable: the renderer and the telemetry see exactly the same events as
+/// the orchestrator, with no parallel channels that fall out of sync.
#[derive(Debug, Clone)]
pub enum Event {
- /// El VAD ha detectado el arranque de una intervención.
+ /// The VAD detected the start of an utterance.
SpeechStarted { turn: TurnId, at: Instant },
- /// Transcripción provisional de la ventana deslizante: `committed` ya es
- /// estable, `volatile` todavía puede cambiar.
+ /// Partial transcription of the sliding window: `committed` is already
+ /// stable, `volatile` may still change.
Partial {
turn: TurnId,
committed: String,
volatile: String,
},
- /// Transcripción definitiva de la intervención completa.
+ /// Final transcription of the whole utterance.
Transcript {
turn: TurnId,
text: String,
@@ -46,33 +46,33 @@ pub enum Event {
decode: Duration,
},
- /// La intervención no contenía nada transcribible.
+ /// The utterance had nothing transcribable.
Discarded { turn: TurnId },
- /// Primer fragmento de texto que devuelve el modelo.
+ /// First text fragment returned by the model.
ReplyStarted { turn: TurnId, ttft: Duration },
- /// Un trozo de respuesta según llega del modelo.
+ /// A piece of the answer as it arrives from the model.
ReplyDelta { turn: TurnId, text: String },
- /// Una frase completa lista para sintetizar.
+ /// A complete sentence ready to be synthesized.
Sentence {
turn: TurnId,
index: usize,
text: String,
},
- /// Respuesta completa del modelo para este turno.
+ /// Complete model answer for this turn.
ReplyDone { turn: TurnId, text: String },
- /// El modelo ha pedido ejecutar una herramienta.
+ /// The model asked to run a tool.
ToolRequested {
turn: TurnId,
name: String,
arguments: String,
},
- /// Resultado de esa ejecución.
+ /// Result of that run.
ToolFinished {
turn: TurnId,
name: String,
@@ -81,22 +81,22 @@ pub enum Event {
took: Duration,
},
- /// Primer audio audible del turno: la métrica que de verdad percibe quien habla.
+ /// First audible audio of the turn: the metric the speaker really perceives.
AudioStarted { turn: TurnId, latency: Duration },
- /// Se ha terminado de reproducir todo lo del turno.
+ /// Everything of the turn has finished playing.
AudioFinished { turn: TurnId },
- /// Se ha cortado la reproducción.
+ /// Playback was cut.
Interrupted {
turn: TurnId,
reason: InterruptReason,
},
- /// Aviso no fatal; el turno continúa.
+ /// Non-fatal warning; the turn continues.
Warning { turn: TurnId, message: String },
- /// Fallo que aborta el turno.
+ /// Failure that aborts the turn.
Failed { turn: TurnId, message: String },
/// Cierre ordenado.
diff --git a/crates/asist-core/src/http.rs b/crates/asist-core/src/http.rs
index 32b6680..3273bb8 100644
--- a/crates/asist-core/src/http.rs
+++ b/crates/asist-core/src/http.rs
@@ -1,12 +1,12 @@
-//! Cliente HTTP/1.1 mínimo para hablar con los servidores locales.
+//! Minimal HTTP/1.1 client to talk to the local servers.
//!
-//! Los tres motores (llama-server, tts-server) escuchan en localhost sobre HTTP
-//! plano, así que no hace falta TLS ni un runtime asíncrono. A cambio de las
-//! ~300 líneas de aquí se gana lo único que ninguna librería genérica da
-//! cómodo y que este pipeline necesita de verdad: **abortar una respuesta a
-//! media descarga**. Cuando el usuario interrumpe al asistente hay que dejar de
-//! leer audio ya generado y cerrar el socket en ese mismo instante; con un
-//! cliente que sólo expone `read_to_end` eso no se puede hacer.
+//! The engines (llama-server, tts-server) listen on localhost over plain
+//! HTTP, so neither TLS nor an async runtime is needed. In exchange for the
+//! ~300 lines here we get the one thing no generic library makes easy and
+//! this pipeline really needs: **aborting a response mid-download**. When the
+//! user interrupts the assistant, reading already-generated audio must stop
+//! and the socket must close that very instant; a client that only exposes
+//! `read_to_end` cannot do that.
use std::io::{BufRead, BufReader, Read, Write};
use std::net::TcpStream;
@@ -16,11 +16,11 @@ use std::time::Duration;
use crate::error::{Error, Result};
-/// Bandera compartida que aborta una lectura en curso.
+/// Shared flag that aborts a read in progress.
///
-/// Se comprueba entre bloques, así que el corte ocurre como muy tarde un
-/// `read` después de activarla —del orden de milisegundos con los tamaños de
-/// bloque que usan los servidores.
+/// It is checked between blocks, so the cut happens at most one `read` after
+/// it is set, in the order of milliseconds with the block sizes the servers
+/// use.
#[derive(Clone, Default)]
pub struct Cancel(Arc<AtomicBool>);
@@ -44,11 +44,11 @@ impl Cancel {
#[derive(Clone, Debug)]
pub struct HttpClient {
- /// `host:puerto` del servidor local.
+ /// `host:port` of the local server.
pub authority: String,
pub connect_timeout: Duration,
- /// Tiempo máximo sin recibir un solo byte. No es el tiempo total: una
- /// síntesis larga puede tardar minutos siempre que siga fluyendo.
+ /// Maximum time without receiving a single byte. It is not the total time:
+ /// a long synthesis can take minutes as long as it keeps flowing.
pub read_timeout: Duration,
}
@@ -75,7 +75,7 @@ impl HttpClient {
}
})?;
stream.set_read_timeout(Some(self.read_timeout))?;
- // Los cuerpos son pequeños y la latencia manda: nada de Nagle.
+ // Bodies are small and latency rules: no Nagle.
let _ = stream.set_nodelay(true);
Ok(stream)
}
@@ -106,22 +106,22 @@ impl HttpClient {
Response::read_head(BufReader::new(stream), url)
}
- /// GET que devuelve el cuerpo completo ya decodificado.
+ /// GET returning the whole, already decoded body.
pub fn get(&self, path: &str) -> Result<Vec<u8>> {
self.send("GET", path, None)?.into_body()
}
- /// POST de JSON que devuelve el cuerpo completo.
+ /// JSON POST returning the whole body.
pub fn post_json(&self, path: &str, body: &serde_json::Value) -> Result<Vec<u8>> {
let payload = serde_json::to_vec(body)?;
self.send("POST", path, Some(&payload))?.into_body()
}
- /// POST de JSON cuya respuesta se consume a trozos según llega.
+ /// JSON POST whose response is consumed in chunks as it arrives.
///
- /// `on_chunk` recibe cada bloque en cuanto está disponible y devuelve
- /// `false` para cortar; junto a `cancel` son las dos vías por las que el
- /// barge-in detiene una síntesis a medias.
+ /// `on_chunk` gets each block as soon as it is available and returns
+ /// `false` to stop; together with `cancel` they are the two ways barge-in
+ /// stops a synthesis midway.
pub fn post_json_streaming(
&self,
path: &str,
@@ -135,7 +135,7 @@ impl HttpClient {
response.stream(cancel, &mut on_chunk)
}
- /// POST de JSON que entrega la respuesta línea a línea (SSE de llama-server).
+ /// JSON POST that delivers the response line by line (llama-server SSE).
pub fn post_json_lines(
&self,
path: &str,
@@ -157,7 +157,7 @@ impl HttpClient {
})
}
- /// Sondea `/health` y devuelve `true` si el servidor está listo.
+ /// Polls `/health` and returns `true` if the server is ready.
pub fn healthy(&self, path: &str) -> bool {
self.get(path).is_ok()
}
@@ -172,14 +172,14 @@ fn resolve(authority: &str, path: &str) -> Result<std::net::SocketAddr> {
source: e,
})?
.next()
- .ok_or_else(|| Error::Config(format!("no se pudo resolver «{authority}»")))
+ .ok_or_else(|| Error::Config(format!("could not resolve «{authority}»")))
}
-/// Codificación del cuerpo anunciada por el servidor.
+/// Body encoding announced by the server.
enum Body {