"""transcribe_audio: 用户上传的录音文件 → 文字稿(讯飞 LFASR 录音文件转写)。 主模型听不见音频 —— 用户上传录音(会议 / 访谈 / 语音备忘)要"转成文字 / 总结"时调这个。 host-side tool:XFYUN_* 凭据在宿主(沙箱内没有),与 look_at_image 同形态:workspace 文件路径进、文本出。路径解析复用 tools/image_ref.resolve_in_root(同一套三形态解析 + user_root 越界校验)。 底座是 `core.asr_lfasr`(讯飞录音文件转写,单文件 5 小时 / 500MB,服务端解码 + 说话人 分离),原始文件字节直传,本地不需要 ffmpeg。转写全文落 `<音频名>.transcript.txt` (与音频同目录),tool result 只带预览 + 路径 —— 长文进文件不进 context,后续总结 / 检索由主模型 read 文件自取。 """ from __future__ import annotations from pathlib import Path from typing import Callable, Optional from core import asr_lfasr from .base import Tool from .image_ref import resolve_in_root # LFASR 服务端支持的音频格式(文档列的常见项)。视频容器(mp4 等)不支持 —— # 拒绝并提示先抽音轨,而不是静默失败在讯飞侧。 AUDIO_EXTS = { ".mp3", ".wav", ".pcm", ".aac", ".opus", ".flac", ".ogg", ".m4a", ".amr", } _MAX_RAW_MB = 500 # LFASR 单文件上限 _PREVIEW_CHARS = 500 def _fmt_dur(seconds: float) -> str: m, s = divmod(int(seconds + 0.5), 60) return f"{m}m{s:02d}s" if m else f"{s}s" class TranscribeAudioTool(Tool): name = "transcribe_audio" description = ( "Transcribe a speech recording file (meeting / interview / voice memo) in the workspace " "to text via Xfyun long-form ASR, with automatic speaker separation. You (the main model) " "cannot hear audio — call this when the user uploads an audio file (look for a " "`[用户上传的文件] ` line with an audio extension) and asks to 转文字/转写/整理/总结" "录音. Supports mp3/wav/m4a/aac/opus/flac/ogg/amr/pcm, up to 5 hours / 500MB per file " "(video files are rejected — extract the audio track first). Transcription is asynchronous " "on Xfyun's side: expect roughly 10-30s for short clips and up to several minutes for " "hour-long recordings. The FULL transcript is saved to `