跳到主内容
快讯直播
AI智模界
教程

ElevenLabs 会议笔记部署:接入、模板与会后同步

适用场景

团队每周例会、客户访谈、面试记录这类"开完就散、纪要没人写"的场景,最需要自动化。这套方案把会议录音上传后自动转写成带说话人标记的文稿,再按固定模板渲染成结构化纪要,最后推送到团队的文档或群聊里。

适合三类人:一是没有专职行政、纪要靠轮值的 5~50 人小团队;二是访谈、调研、销售类岗位,需要把大量录音变成可检索的文字资产;三是已经用 ElevenLabs 做语音相关工作、想顺手把听写能力接进内部流程的开发者。整套流程不涉及本地大模型推理,普通云主机就能跑。

环境与前置条件

  • 操作系统:Linux(Ubuntu 22.04 及以上的 LTS 版本)或 macOS;Windows 建议用 WSL2,避免路径和编码问题。
  • 运行时:Python 3.10 及以上,具体最低版本以 ElevenLabs 官方 SDK 文档当前版本为准。Node.js 也可以,本文以 Python 为例。
  • 硬件:转写走云端接口,本机只做文件传输和文本处理,2 核 4G 内存足够。真正吃资源的是磁盘,按单声道 64kbps 估算,1 小时音频约 25~30 MB,建议预留 50 GB 以上用于原始音频与转写稿归档。
  • 账号与凭证:ElevenLabs 账号、一个团队工作区(Workspace),以及在该工作区内生成的 API Key。Key 只在服务端使用,不要写进前端页面或提交到代码仓库。
  • 网络:需要稳定的出网带宽,长音频上传对上行速率敏感。若要用回调方式接收结果,还需要一个公网可达的地址,本地开发可用 cloudflared 或 ngrok 临时隧道。
  • 可选组件:对象存储(S3 / OSS / MinIO)用于长期保存录音;飞书、Notion、Slack 或任意支持 Incoming Webhook 的群机器人作为同步出口。

分步骤部署

整体数据流是:录音文件 → 转写接口(开启说话人分离)→ 转写稿 → 模板渲染 → 同步到文档或群。下面逐步落地。

步骤一:准备 API Key 与团队工作区

登录 ElevenLabs 控制台,在团队工作区里创建一个专用 API Key,命名成 meeting-notes-prod 之类能看出用途的名字,权限按最小化原则只勾选语音转文字相关能力。团队成员通过工作区邀请加入,共享音频与模板资产,而不是各自拿一份密钥——菜单路径和权限项名称请以官方页面当前版本为准。

创建好后先放本地环境变量文件,不要硬编码:

```bash

mkdir -p ~/meeting-notes && cd ~/meeting-notes

cat > .env <<'EOF'

ELEVENLABS_API_KEY=替换成你的key

ELEVENLABS_STT_URL=https://api.elevenlabs.io/v1/speech-to-text

ELEVENLABS_MODEL_ID=替换成官方文档当前推荐的转写模型ID

SYNC_WEBHOOK_URL=替换成你的群机器人或文档接口地址

PUBLIC_BASE_URL=替换成你的公网回调地址

EOF

chmod 600 .env

```

步骤二:初始化项目骨架

```bash

python3 -m venv .venv

source .venv/bin/activate

pip install --upgrade pip

pip install fastapi "uvicorn[standard]" python-multipart httpx python-dotenv

```

这几个包各司其职:fastapi + uvicorn 起一个轻量 HTTP 服务,httpx 负责调用外部接口,python-dotenv 读取 .env,python-multipart 处理文件上传表单。版本不锁定具体号,按官方文档当前版本装即可,生产环境建议用 pip freeze > requirements.txt 固定下来。

建好目录:

```bash

mkdir -p app templates output

```

步骤三:封装转写调用,开启说话人分离

新建 app/transcribe.py。核心是在请求里打开说话人分离(diarization)开关,让返回结果里每个词都带上说话人编号:

```python

import os

import httpx

from dotenv import load_dotenv

load_dotenv()

API_KEY = os.environ["ELEVENLABS_API_KEY"]

STT_URL = os.environ["ELEVENLABS_STT_URL"]

MODEL_ID = os.environ["ELEVENLABS_MODEL_ID"]

def transcribe(path: str, num_speakers: int | None = None) -> dict:

data = {

"model_id": MODEL_ID,

"diarize": "true",

"tag_audio_events": "true",

}

if num_speakers:

data["num_speakers"] = str(num_speakers)

with open(path, "rb") as f:

files = {"file": (os.path.basename(path), f, "audio/mpeg")}

resp = httpx.post(

STT_URL,

headers={"xi-api-key": API_KEY},

data=data,

files=files,

timeout=900,

)

resp.raise_for_status()

return resp.json()

```

两点说明:diarize 打开后返回体里会出现 speaker_id 字段,这是后面按人分段的基础;num_speakers 是可选提示,已知参会人数时填上能明显提升分离准确度,不填则由服务端自行判断。参数名与取值请以官方文档当前版本为准,不同接口版本可能略有差异。

再把逐词结果拼成带人名标记的文稿:

```python

def to_speaker_text(result: dict) -> str:

parts, last = [], None

for w in result.get("words", []):

spk = w.get("speaker_id") or "speaker_0"

if spk != last:

parts.append(f"\n\n{spk}:")

last = spk

parts.append(w.get("text", ""))

return "".join(parts).strip()

```

中文场景直接拼接不需要空格;如果会议以英文为主,改成 " ".join() 更自然。

这一步成功的标志是:拿一段两人对话的测试音频跑一遍,控制台打印出的文稿里至少出现两个不同的 speaker_id 段落。

步骤四:设计纪要模板

模板的作用是把"自由发挥的总结"变成"结构稳定的表格"。在 templates/weekly_review.md 里写死骨架:

```

{{title}}

  • 会议时间:{{date}}
  • 参会人:{{speakers}}

一句话结论

决议事项

事项结论决策人

待办清单

负责人动作截止时间

风险与待确认

原始时间轴

{{transcript}}

```

关键在提示词约束:要求模型填充待办表时必须是"负责人 + 动作 + 截止时间"三要素,任何一项在原文里没提到就写"未指定",不允许推测。这样输出的纪要可以直接派活,而不是一堆"后续跟进"的空话。模板渲染用标准库就够:

```python

from string import Template

def render(tpl_path: str, **kwargs) -> str:

with open(tpl_path, encoding="utf-8") as f:

return Template(f.read()).safe_substitute(**kwargs)

```

生成摘要这一步可以走团队应用内的模板/提示词能力,也可以把转写稿交给任意大模型,只要模板文件保持一致,输出格式就稳定。具体调用方式以官方文档当前版本为准。

步骤五:配置回调与异步处理

长会议的上传和转写耗时较长,同步等待容易超时,建议走异步 + 回调。新建 app/main.py:

```python

import json, os, datetime

from fastapi import FastAPI, Request, BackgroundTasks

from app.transcribe import transcribe, to_speaker_text

from app.render import render

from app.sync import sync

app = FastAPI()

@app.get("/healthz")

def healthz():

return {"status": "ok"}

@app.post("/webhook/elevenlabs")

async def webhook(request: Request, bg: BackgroundTasks):

payload = await request.json()

bg.add_task(handle, payload)

return {"received": True}

def handle(payload: dict):

text = to_speaker_text(payload)

stamp = datetime.date.today().isoformat()

md = render(

"templates/weekly_review.md",

title=payload.get("title", "会议纪要"),

date=stamp,

speakers="待补充",

transcript=text,

)

out = f"output/{stamp}-{payload.get('id','meeting')}.md"

with open(out, "w", encoding="utf-8") as f:

f.write(md)

sync(md, title=payload.get("title", "会议纪要"))

```

回调地址需要在 ElevenLabs 控制台登记,PUBLIC_BASE_URL 要能被公网访问。本地调试时起隧道:

```bash

cloudflared tunnel --url http://localhost:8000

```

把输出的公网域名填进控制台。若官方回调带签名校验,务必在 webhook 里先验证再处理,校验字段以官方文档为准。

步骤六:接上团队同步出口

新建 app/sync.py,用一个通用 Webhook 出口把纪要推到群或文档系统:

```python

import os, httpx

def sync(md: str, title: str):

url = os.environ.get("SYNC_WEBHOOK_URL")

if not url:

return

resp = httpx.post(url, json={"title": title, "text": md}, timeout=30)

resp.raise_for_status()

```

如果目标平台要求特定消息体格式(比如群机器人常用 msg_type + content 结构),在 sync 里做一层转换即可。要落到 Notion、语雀这类文档系统,把 json 换成对应平台的页面创建接口,鉴权令牌同样放环境变量。接口字段以各平台官方文档当前版本为准。

步骤七:加一个上传入口

服务端跑起来:

```bash

uvicorn app.main:app --host 0.0.0.0 --port 8000

```

先用命令行验证整条链路,避免前端干扰排查:

```bash

curl -s -X POST http://localhost:8000/upload \

-F "file=@sample_meeting.mp3" \

-F "title=产品周会" \

-F "num_speakers=3"

```

/upload 路由里做的就是把文件存到本地、调用 transcribe、再走一遍 handle 的渲染与同步逻辑。跑通后,再按团队习惯包一层网页表单或做成快捷指令,让同事拖个音频文件就能出纪要。

验证部署是否成功

按顺序检查四层,任何一层不通都停下来修:

1. 服务存活:curl -s http://localhost:8000/healthz,预期返回 {"status":"ok"}。

2. 转写可用:用 30 秒左右的双人对话音频调一次接口,预期 HTTP 200,返回 JSON 中 text 字段非空,words 数组里 speaker_id 至少出现两个不同取值。

3. 模板渲染正确:检查 output/ 目录下生成的 .md 文件,预期包含"决议事项""待办清单""原始时间轴"三个小节标题,且待办表里每行都有负责人列。

4. 同步送达:确认目标群或文档里出现了同名纪要,标题与文件一致。

再补一个容错验证:故意传一个不存在的文件路径,预期服务返回明确的 4xx 而不是进程崩溃——后台任务里记得包 try/except 并写日志。

常见报错与解决

1. 401 Unauthorized 或返回体提示 invalid_api_key

原因:Key 写错、已失效,或 .env 没被加载(比如在虚拟环境外启动服务)。

解决:

```bash

source .venv/bin/activate

python -c "from dotenv import load_dotenv; import os; load_dotenv(); print(os.environ['ELEVENLABS_API_KEY'][:6])"

```

能打印出前六位说明变量读到了;仍然 401 就去控制台重新生成 Key。

2. 422 Unprocessable Entity 或提示音频格式不支持

原因:上传的是 m4a、amr、带 DRM 的会议导出文件,或者采样率过高。

解决:统一用 ffmpeg 转成单声道 MP3 再传:

```bash

ffmpeg -i input.m4a -ac 1 -ar 16000 -b:a 64k output.mp3

```

3. 上传大文件时报超时或 413 Request Entity Too Large

原因:单文件过大,或反向代理(Nginx)的 client_max_body_size 太小。

解决:先降码率压缩;若是 Nginx,在 server 段加 client_max_body_size 200m; 后 nginx -s reload;同时确认客户端 httpx 的 timeout 已调到足够长。

4. 说话人分离结果全归到同一个人

原因:会议是用单支麦克风外放录制的,多人声音混在一路里,声学特征接近;或没有给 num_speakers 提示。

解决:优先让每位参会者各自一路音轨(线上会议直接导出分轨录音);无法分轨时传入已知人数并提高录音质量:

```bash

curl -s -X POST "$ELEVENLABS_STT_URL" \

-H "xi-api-key: $ELEVENLABS_API_KEY" \

-F "model_id=$ELEVENLABS_MODEL_ID" \

-F "diarize=true" \

-F "num_speakers=4" \

-F "file=@sample_meeting.mp3"

```

5. 回调一直收不到,output/ 目录没有新文件

原因:登记的回调地址不可达,或隧道已关闭、防火墙拦截了入站。

解决:先本地自测回调端点:

```bash

curl -s -X POST http://localhost:8000/webhook/elevenlabs \

-H "Content-Type: application/json" \

-d '{"id":"test","title":"联通性测试","words":[]}'

```

返回 {"received":true} 说明服务端没问题,问题在公网链路,重新拉起隧道并更新控制台地址。

6. 中英夹杂的会议转写错字多

原因:自动语种判断在频繁切换时抖动,专有名词未被正确识别。

解决:会议以中文为主时显式指定语言参数;把产品名、内部系统代号整理成一份术语清单,在生成纪要的提示词里带上,让摘要环节做一次归一化。

后续维护

备份与留存:原始音频和转写稿分开存放,音频进对象存储并设置生命周期规则(比如 90 天后转低频存储),转写稿和纪要进版本化文档库。会议录音含个人信息,上线前和团队明确告知与留存期限。

升级策略:requirements.txt 固定依赖版本,升级前先在 staging 环境用同一段测试音频跑一遍回归,确认返回结构里的 words、speaker_id 字段没有变化再切生产。模型 ID 随官方迭代更新,变更前对比新旧稿件的标点与分段差异。

日志与监控:每次请求记录 request_id、音频时长、处理耗时、字符数、同步结果,结构化输出成 JSON 便于检索。对 /healthz 做分钟级探活,连续失败告警;同步失败的任务写入重试队列,避免纪要静默丢失。

成本与用量观察:按音频时长统计每日、每周用量,评估是否有可以合并的短会录音,以及是否需要对超长录音设置上限。具体计费口径以官方页面为准,别靠记忆估算。

密钥管理:API Key 按环境分开(开发/生产),定期轮换,轮换时先加新 Key 再删旧 Key,避免服务中断。团队成员离岗时同步回收工作区权限。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。