summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorntzyz <i@ntzyz.io>2026-07-05 18:40:44 +0800
committerntzyz <i@ntzyz.io>2026-07-05 18:40:44 +0800
commitee2db04cf4582dc3297fc54f7e15ce516b8fdf10 (patch)
tree6af82918de4f394bff3a1a8819717901cfd5c665
initial commit
-rw-r--r--INSTALL.md82
-rw-r--r--xiaomi-asr.sh107
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"