Article

我把本地语音识别接进了 AI 编程助手,全程踩坑实录

trial

10 个视频,手动上传转录工具。

等待、下载、再上传——半小时过去了,内容还没到手。

更麻烦的是:敏感内容不想过云端。

所以我做了一件事:把 SenseVoice 这个本地语音识别模型,直接接进 AI 编程助手 pi 的工具系统里。

说一句话,AI 自动调用,转录结果直接回到对话,后续接着做总结、翻译、写文章——全程不离开工作流。

过程踩了不少坑,一起记下来。


pi 是什么?不只是”另一个 Cursor”

很多人用过 Claude Code、Cursor、Windsurf。

pi 看起来是同类——但有一个根本差异:

它是完全开放的 AI agent 平台,不是封闭的产品。

具体说四件事:

▎支持接入任意模型 Claude、GPT、Gemini、本地开源模型,配置文件一行切换。不被任何厂商锁定,今天用 Claude 做推理,明天换 GPT-5 跑代码,随时迁移。

▎支持注册自定义工具 放一个 TypeScript 文件到 ~/.pi/agent/extensions/,pi 启动时自动加载。AI 就能在对话中直接调用这个工具——调本地脚本、请求 API、操作文件系统、执行 shell 命令,都行。

▎支持非交互批处理 pi --print 模式,可以直接在终端把 pi 当命令行工具用。嵌进 shell 脚本、CI 流程、定时任务——AI 能力变成可编排的管道。

▎完全本地运行 没有强制云同步,没有数据上传。模型和工具全在自己机器上跑。

这次要用的,就是「自定义工具」这个能力。


SenseVoice:最值得本地部署的语音识别模型之一

阿里达摩院开源,Small 版本几百 MB,支持中英日韩粤五种语言,识别速度极快。

目标很简单:把它包装成 pi 的一个工具,让 AI 直接说”帮我转录这个视频”,工具自动跑,结果返回上下文。

三步搞定。


Step 1|建独立 Python 环境

SenseVoice 依赖 funasr,要求 Python 3.10+。

我没有复用已有项目的虚拟环境,而是单独给 pi 建了一个干净的环境。

原因很简单:项目的依赖会变,工具的依赖要稳定。

用 uv 创建(自带 Python 版本管理,比 venv 快一个数量级):

uv venv ~/.pi/agent/sensevoice/venv --python 3.11
uv pip install funasr torch torchaudio \
  --python ~/.pi/agent/sensevoice/venv/bin/python

装完 81 个包,核心只有 funasr + torch,干净。


Step 2|写转录脚本

新建 ~/.pi/agent/sensevoice/transcribe.py,接收音频路径和语言参数,输出 JSON:

from funasr import AutoModel

model = AutoModel(
    model="iic/SenseVoiceSmall",
    disable_update=True,
    log_level="ERROR",
)
res = model.generate(input=audio_path, language=language, use_itn=True)
text = res[0]["text"].split("|>")[-1].strip()
print(json.dumps({"text": text, "language": lang}))

SenseVoice 返回格式是 <语言标记>|>实际文字,取最后一段即可。

💥 坑一:stdout 被 funasr 污染

funasr 在 import 时会往 stdout 打印版本信息,导致 JSON.parse 直接失败,工具崩溃。

修复:import 之前把 stdout 临时重定向到 stderr:

_real_stdout = sys.stdout
sys.stdout = sys.stderr  # 把噪音导走

from funasr import AutoModel
# ... 跑模型 ...

sys.stdout = _real_stdout
print(json.dumps(result))  # 干净输出

Step 3|写 pi Extension

新建 ~/.pi/agent/extensions/sensevoice.ts,注册工具:

pi.registerTool({
  name: "transcribe_audio",
  label: "Transcribe Audio",
  description: "Transcribe speech from audio/video using SenseVoice...",
  parameters: Type.Object({
    path: Type.String({ description: "Absolute path to audio/video file" }),
    language: Type.Optional(Type.String({ default: "auto" })),
  }),
  async execute(_toolCallId, params, _signal, onUpdate) {
    onUpdate?.({ content: [{ type: "text", text: "Transcribing..." }] });
    const { stdout } = await execFileAsync(PYTHON, [SCRIPT, params.path, lang]);
    const result = JSON.parse(stdout.trim());
    return {
      content: [{ type: "text", text: `Transcription (${result.language}):\n\n${result.text}` }],
      details: { text: result.text, language: result.language },
    };
  },
});

💥 坑二:onUpdate 格式错了,pi 直接 crash

第一版写成了 onUpdate("Transcribing..."),直接传字符串。

运行后 pi 崩溃,报错:

TypeError: Cannot read properties of undefined (reading 'filter')

翻源码才发现——pi 的 onUpdate 不接受字符串,要传和返回值一样格式的对象:

// ❌ 错误写法,直接崩
onUpdate("Transcribing...");

// ✅ 正确写法
onUpdate?.({ content: [{ type: "text", text: "Transcribing..." }] });

验证:跑起来了

用 pi CLI 非交互模式测试:

pi --print \
  --provider freemodel --model gpt-5.4-mini \
  --tools transcribe_audio --no-builtin-tools \
  "请用 transcribe_audio 工具转录:/path/to/audio.mp4"

第一次调用自动下载模型(约 300MB),之后缓存本地,后续秒级响应。

实测:4 分钟的音频,转录时间不到 10 秒。

跑完测试,用 shell 循环批量处理 10 个视频,每个自动生成对应 Markdown 转录文档,再让 AI 接着做总结——整条链路全打通。


整体架构一眼看清

~/.pi/agent/
├── extensions/
│   └── sensevoice.ts     ← pi 自动加载,注册工具
└── sensevoice/
    ├── venv/              ← 独立 Python 3.11 环境(uv 创建)
    └── transcribe.py      ← SenseVoice 调用脚本

pi 启动时自动扫描 extensions/ 目录,零配置,放进去就生效。


Q&A:踩过的坑,一次说清

Q:工具放进去了,pi 里看不到,怎么排查?

确认路径正确:~/.pi/agent/extensions/sensevoice.ts

pi 在每次打开 /model 菜单时重新扫描 extensions,不需要重启,触发一次刷新即可。如果还没出现,多半是 TypeScript 语法错误——pi 加载报错时会静默失败,不会提示。


Q:运行报 TypeError: Cannot read properties of undefined (reading 'filter')

onUpdate 调用姿势不对,传了字符串而不是对象。正确写法:

onUpdate?.({ content: [{ type: "text", text: "处理中..." }] });

记得加 ?. 可选链,避免 onUpdate 未定义时再次报错。


Q:工具返回 Unexpected token 'u', "funasr vers"... is not valid JSON

funasr import 时往 stdout 打印了版本信息,污染了 JSON 输出。

在 import funasr 之前把 stdout 重定向到 stderr(见 Step 2 代码),问题消失。


Q:系统 Python 是 3.9,funasr 要求 3.10+,怎么办?

用 uv,一条命令解决,不需要手动装 Python:

uv venv ~/.pi/agent/sensevoice/venv --python 3.11

uv 自动下载并管理指定版本,不污染系统环境。


Q:首次运行很慢,卡在哪里?

第一次调用时 funasr 自动从 ModelScope 下载 iic/SenseVoiceSmall(约 300MB)。

下载后缓存本地,后续不再下载。网络访问 ModelScope 有问题的话,可以手动下载后用本地路径指定模型。


Q:只能转录音频?视频怎么处理?

视频直接传,不需要先提取音频。funasr 底层自动处理音轨,mp4、mov、avi 全支持。


Q:工具的返回值格式有什么要求?

execute 必须返回包含 content 数组的对象,格式:

{ content: [{ type: "text", text: "..." }], details: {} }

content 缺失或为 undefined,pi 渲染层直接崩。details 是可选的结构化数据,不显示给用户,可以存转录原文方便后续处理。


最后说一句

接好之后的体验是:

在 AI 对话里说”帮我转录这 10 个视频,每个生成一份 Markdown”——然后去喝杯水。

回来全好了。

本地跑,不过云,隐私安全,速度快。

这才是 AI 工具应该有的样子。


文中涉及的 pi 为 @earendil-works/pi-coding-agent,SenseVoice 为阿里达摩院开源项目。

有问题欢迎留言,或直接在评论区说你想接入的下一个工具。