notes(asr): 切片串行 ASR 绕单文件大小限制
deploy notes / build-and-deploy (push) Successful in 3m40s

ASR server 直接 500 拒绝大文件 (15MB / ~15min 4.7s 即返回 500),不是
处理超时。改成:sidecar 装 ffmpeg → /transcribe endpoint 把音频切 60s
段 → 串行调外部 ASR → 拼接 transcript。notes 主容器 call_asr 改成 POST
到 sidecar /transcribe(timeout 1h 给长录音留余地)。

- feishu sidecar Dockerfile + ffmpeg + requests
- server.py 加 TranscribeReq;fallback -c copy 失败时 re-encode AAC
- main.rs 删除 asr_url/asr_token 字段(now sidecar concern)
- k8s manifest: ASR_URL/ASR_TOKEN 从主容器移到 feishu sidecar env
This commit is contained in:
Fam Zheng
2026-05-17 22:38:05 +01:00
parent e5a87cc65f
commit 688ccdc76f
4 changed files with 104 additions and 39 deletions
+11 -25
View File
@@ -31,8 +31,6 @@ struct AppState {
db: Arc<Mutex<Connection>>,
blobs_dir: PathBuf,
passphrase: String,
asr_url: String,
asr_token: String,
llm_gateway: String,
llm_token: String,
llm_model: String,
@@ -53,9 +51,7 @@ async fn main() -> std::io::Result<()> {
if passphrase.is_empty() {
tracing::warn!("PASSPHRASE not set — all /api/* will return 401");
}
let asr_url = std::env::var("ASR_URL")
.unwrap_or_else(|_| "http://18.159.112.195:8848/v1/audio/transcriptions".into());
let asr_token = std::env::var("ASR_TOKEN").unwrap_or_default();
// ASR 现在由 sidecar 调(切片串行),主容器不再直接调外部 ASR
let llm_gateway =
std::env::var("LLM_GATEWAY").unwrap_or_else(|_| "http://3.135.65.204:8848/v1".into());
let llm_token = std::env::var("LLM_TOKEN").unwrap_or_default();
@@ -95,8 +91,6 @@ async fn main() -> std::io::Result<()> {
db: Arc::new(Mutex::new(conn)),
blobs_dir,
passphrase,
asr_url,
asr_token,
llm_gateway,
llm_token,
llm_model,
@@ -465,38 +459,30 @@ fn set_status(s: &AppState, id: i64, status: &str, transcript: Option<&str>, err
async fn call_asr(
s: &AppState,
path: &std::path::Path,
filename: &str,
_filename: &str,
) -> Result<String, String> {
let bytes = tokio::fs::read(path).await.map_err(|e| e.to_string())?;
let part = reqwest::multipart::Part::bytes(bytes)
.file_name(filename.to_string())
.mime_str("audio/mpeg")
.map_err(|e| e.to_string())?;
let form = reqwest::multipart::Form::new()
.text("model", "qwen3-asr")
.text("response_format", "json")
.part("file", part);
// 走 sidecar /transcribesidecar 用 ffmpeg 切片 + 串行调外部 ASR,绕过 ASR server 单文件大小限制
let url = format!("{}/transcribe", s.feishu_url.trim_end_matches('/'));
let payload = json!({ "audio_path": path.to_string_lossy() });
let resp = s
.http
.post(&s.asr_url)
.bearer_auth(&s.asr_token)
.multipart(form)
.timeout(std::time::Duration::from_secs(600))
.post(&url)
.json(&payload)
.timeout(std::time::Duration::from_secs(3600))
.send()
.await
.map_err(|e| format!("connect: {e}"))?;
.map_err(|e| format!("connect sidecar: {e}"))?;
if !resp.status().is_success() {
let st = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(format!("ASR {st}: {body}"));
return Err(format!("sidecar /transcribe {st}: {body}"));
}
let v: Value = resp.json().await.map_err(|e| format!("decode: {e}"))?;
let text = v
.get("text")
.and_then(|x| x.as_str())
.map(|s| s.to_string())
.ok_or_else(|| format!("ASR response no 'text': {v}"))?;
.ok_or_else(|| format!("no 'text' in response: {v}"))?;
Ok(text)
}