diff options
| author | ntzyz <i@ntzyz.io> | 2026-07-05 18:40:44 +0800 |
|---|---|---|
| committer | ntzyz <i@ntzyz.io> | 2026-07-05 18:40:44 +0800 |
| commit | ee2db04cf4582dc3297fc54f7e15ce516b8fdf10 (patch) | |
| tree | 6af82918de4f394bff3a1a8819717901cfd5c665 | |
initial commit
| -rw-r--r-- | INSTALL.md | 82 | ||||
| -rw-r--r-- | xiaomi-asr.sh | 107 |
2 files changed, 189 insertions, 0 deletions
diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..f090991 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,82 @@ +# Xiaomi MiMo ASR (mimo-v2.5-asr) — Hermes STT 安装说明 + +将小米 MiMo ASR 语音识别模型接入 Hermes 的语音转文字功能。 + +## 前置条件 + +- Hermes Agent 已安装 +- Xiaomi MiMo API Key(https://platform.xiaomimimo.com 获取) +- ffmpeg(用于音频格式转换):`brew install ffmpeg` + +## 工作原理 + +Xiaomi ASR **不使用**标准的 `/v1/audio/transcriptions` 端点,而是通过 +`/v1/chat/completions` 接口 + `input_audio` 格式工作。因此需要一个包装脚本来处理: +- 音频格式转换(非 wav/mp3 文件通过 ffmpeg 转为 16kHz 单声道 WAV) +- Base64 编码音频数据 +- 构造 chat completions JSON payload +- 解析响应提取转录文本 + +Hermes 的 **command provider** 机制允许通过 shell 命令接入任意 STT 后端, +无需编写 Python 插件。 + +## 安装步骤 + +### 1. 复制脚本 + +```bash +cp xiaomi-asr.sh ~/.hermes/scripts/xiaomi-asr.sh +chmod +x ~/.hermes/scripts/xiaomi-asr.sh +``` + +### 2. 配置环境变量 + +在 `~/.hermes/.env` 中添加(如果还没有): + +``` +XIAOMI_API_KEY=your_api_key_here +XIAOMI_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1 +``` + +> 如果你的 API Key 绑定的是国际站,用 `https://api.xiaomimimo.com/v1` + +### 3. 修改 Hermes 配置 + +在 `~/.hermes/config.yaml` 的 `stt` 部分: + +```yaml +stt: + enabled: true + provider: xiaomi-asr + providers: + xiaomi-asr: + type: command + command: ~/.hermes/scripts/xiaomi-asr.sh {input_path} {output_path} mimo-v2.5-asr {language} + language: zh + format: txt + timeout: 120 +``` + +### 4. 重启 Hermes + +- Desktop / TUI:关闭并重新打开 +- Gateway:运行 `/restart` + +## 验证 + +```bash +# 手动测试脚本 +export XIAOMI_API_KEY=your_key +export XIAOMI_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1 +~/.hermes/scripts/xiaomi-asr.sh /path/to/audio.wav /tmp/output.txt mimo-v2.5-asr zh +cat /tmp/output.txt +``` + +## 注意事项 + +| 问题 | 说明 | +|------|------| +| 音频格式 | 只支持 wav 和 mp3,其他格式自动通过 ffmpeg 转换 | +| 纯音乐 | 无法识别纯音乐内容,会返回空结果 | +| 语言 | 主要针对中文优化,其他语言效果较差 | +| ffmpeg | 非 wav/mp3 格式必须安装 ffmpeg | diff --git a/xiaomi-asr.sh b/xiaomi-asr.sh new file mode 100644 index 0000000..532233d --- /dev/null +++ b/xiaomi-asr.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Xiaomi MiMo ASR wrapper for Hermes command STT provider. +# +# Usage (called by Hermes automatically via placeholders): +# xiaomi-asr.sh <input_audio> <output_txt> [model] [language] +# +# Xiaomi ASR uses chat/completions API with input_audio format, +# NOT the standard /v1/audio/transcriptions endpoint. +# Only wav and mp3 are accepted — other formats are auto-converted +# to 16kHz mono WAV via ffmpeg. + +set -euo pipefail + +INPUT="${1:?usage: xiaomi-asr.sh <input> <output> [model] [language]}" +OUTPUT="${2:?usage: xiaomi-asr.sh <input> <output> [model] [language]}" +MODEL="${3:-mimo-v2.5-asr}" +LANGUAGE="${4:-zh}" + +API_KEY="${XIAOMI_API_KEY:?XIAOMI_API_KEY is not set}" +BASE_URL="${XIAOMI_BASE_URL:-https://api.xiaomimimo.com/v1}" + +# Determine audio format from extension +EXT="${INPUT##*.}" +case "$EXT" in + wav|WAV) FORMAT="wav" ;; + mp3|MP3) FORMAT="mp3" ;; + *) FORMAT="wav" ;; # Will convert to WAV via ffmpeg +esac + +# If the file is not already wav/mp3, convert to wav via ffmpeg +AUDIO_INPUT="$INPUT" +if [ "$FORMAT" = "wav" ] && [[ ! "$EXT" =~ ^[Ww][Aa][Vv]$ ]] && [[ ! "$EXT" =~ ^[Mm][Pp]3$ ]]; then + if command -v ffmpeg &>/dev/null; then + AUDIO_INPUT=$(mktemp /tmp/hermes-asr-XXXXXX.wav) + ffmpeg -y -i "$INPUT" -ar 16000 -ac 1 -f wav "$AUDIO_INPUT" 2>/dev/null + trap 'rm -f "$AUDIO_INPUT"' EXIT + else + echo "ffmpeg is required to convert .$EXT to wav/mp3 for Xiaomi ASR" >&2 + exit 1 + fi +fi + +# Build JSON payload and call API using Python to avoid arg-length limits +RESPONSE=$(python3 - "$AUDIO_INPUT" "$MODEL" "$FORMAT" "$API_KEY" "$BASE_URL" << 'PYEOF' +import base64, json, sys, urllib.request, urllib.error, ssl + +input_path = sys.argv[1] +model = sys.argv[2] +fmt = sys.argv[3] +api_key = sys.argv[4] +base_url = sys.argv[5] + +with open(input_path, "rb") as f: + audio_b64 = base64.b64encode(f.read()).decode() + +payload = json.dumps({ + "model": model, + "messages": [{ + "role": "user", + "content": [ + {"type": "input_audio", "input_audio": {"data": audio_b64, "format": fmt}}, + ] + }] +}).encode() + +req = urllib.request.Request( + f"{base_url}/chat/completions", + data=payload, + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + }, +) +ctx = ssl.create_default_context() +try: + resp = urllib.request.urlopen(req, context=ctx, timeout=300) + print(resp.read().decode()) +except urllib.error.HTTPError as e: + body = e.read().decode() + print(f"HTTP {e.code}: {body}", file=sys.stderr) + sys.exit(1) +PYEOF +) + +# Extract the transcribed text from the response +TRANSCRIPT=$(python3 -c " +import json, sys +resp = json.loads(sys.stdin.read()) +choices = resp.get('choices', []) +if choices: + msg = choices[0].get('message', {}) + content = msg.get('content', '') + print(content.strip()) +else: + err = resp.get('error', {}) + print(err.get('message', 'No transcription result'), file=sys.stderr) + sys.exit(1) +" <<< "$RESPONSE") + +if [ -z "$TRANSCRIPT" ]; then + echo "Xiaomi ASR returned empty transcript" >&2 + exit 1 +fi + +# Write the transcript to the output file +echo "$TRANSCRIPT" > "$OUTPUT" +echo "$TRANSCRIPT" |
