//! Aufrufe an die Server auf der DGX: Sprache erzeugen (Qwen3-TTS), //! transkribieren (Whisper), Erreichbarkeit pruefen. use std::time::Duration; fn agent(timeout_secs: u64) -> ureq::Agent { ureq::Agent::config_builder() .timeout_global(Some(Duration::from_secs(timeout_secs))) // Fehlerantworten selbst auswerten, der Server erklaert im Body, was nicht passt .http_status_as_error(false) .build() .into() } fn net_err(url: &str, e: ureq::Error) -> String { format!("{url} nicht erreichbar: {e}") } /// Liest die Antwort; bei Fehlerstatus wird die Meldung des Servers zurueckgegeben. fn read_body(url: &str, mut resp: ureq::http::Response) -> Result, String> { let status = resp.status(); let body = resp.body_mut().with_config().limit(100 * 1024 * 1024).read_to_vec().map_err(|e| net_err(url, e))?; if status.is_success() { return Ok(body); } let text = String::from_utf8_lossy(&body); // vLLM: {"error": {"message": "..."}} let msg = serde_json::from_str::(&text) .ok() .and_then(|v| v["error"]["message"].as_str().map(String::from)) .unwrap_or_else(|| text.trim().to_string()); Err(format!("Server meldet {status}: {msg}")) } /// POST /v1/audio/speech -> WAV-Bytes. Erster Aufruf nach dem Serverstart dauert ~20 s. pub fn speech(url: &str, body: &serde_json::Value) -> Result, String> { let resp = agent(300).post(url).send_json(body).map_err(|e| net_err(url, e))?; read_body(url, resp) } /// POST /v1/audio/transcriptions (multipart) -> erkannter Text. pub fn transcribe(url: &str, model: &str, wav: &[u8]) -> Result { let boundary = format!("----tts-studio-{}", std::process::id()); let mut body = Vec::with_capacity(wav.len() + 512); for (name, value) in [("model", model), ("language", "de"), ("response_format", "json")] { body.extend(format!("--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\n\r\n{value}\r\n").as_bytes()); } body.extend( format!("--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"stimme.wav\"\r\nContent-Type: audio/wav\r\n\r\n") .as_bytes(), ); body.extend(wav); body.extend(format!("\r\n--{boundary}--\r\n").as_bytes()); let resp = agent(180) .post(url) .header("Content-Type", format!("multipart/form-data; boundary={boundary}")) .send(&body[..]) .map_err(|e| net_err(url, e))?; let json: serde_json::Value = serde_json::from_slice(&read_body(url, resp)?).map_err(|e| format!("Whisper-Antwort unlesbar: {e}"))?; json["text"].as_str().map(|t| t.trim().to_string()).ok_or_else(|| "Whisper hat keinen Text geliefert.".into()) } /// Kurzer Check, ob ein Server antwortet (GET .../v1/models). pub fn reachable(models_url: &str) -> bool { agent(3).get(models_url).call().is_ok_and(|r| r.status().is_success()) }