小爱音箱与外部 AI 服务(小智 AI、OpenClaw)的桥接器
打破小爱音箱的封闭生态,灵活接入多种 AI 服务,提供 HTTP API 实现远程控制。
📖 快速开始 · 🦞 OpenClaw 集成 · 🔧 API 文档 · 🐛 常见问题
本项目由 Open-XiaoAI 的
examples/xiaozhi/演进而来,已成为独立项目。
| 功能 | 说明 |
|---|---|
| 🦞 OpenClaw 集成 | 接入 OpenClaw,支持连续对话,可选豆包 TTS 或小爱原生 TTS |
| 🤖 小智 AI 集成 | 接入 xiaozhi-esp32-server 实时音频流 |
| 🎙️ 自定义唤醒词 | 支持中英文,不同唤醒词可路由到不同 AI 服务或不同 OpenClaw Agent |
| 🧠 多 Agent 路由 | 一台音箱,多个唤醒词,每个唤醒词对应不同的 OpenClaw Agent Session,动态切换零开销 |
| 💬 连续对话 | 多轮对话无需反复唤醒,喊"小爱同学"可随时打断 |
| ⚡ VAD + KWS | 语音活动检测前置,减少无效识别,更省电 |
| 🌐 HTTP API | 远程播放文字/音频、控制音箱 |
| 🧩 模块化 | 各功能独立开关,按需启用 |
⚠️ 本项目仅包含服务端,需要先在小爱音箱上安装 Client 端。
如果你启用小智 AI 或 OpenClaw 连续对话功能,需要下载 VAD + KWS + ASR 模型文件:
- 从 releases 下载模型压缩包
- 解压模型文件(路径见下方具体部署方式)
模型文件解压到 ./models 目录,然后下载配置并启动:
# 下载配置文件
curl -O https://raw.githubusercontent.com/coderzc/open-xiaoai-bridge/main/config.py
curl -O https://raw.githubusercontent.com/coderzc/open-xiaoai-bridge/main/docker-compose.yml
# 按需修改 config.py 和 docker-compose.yml,然后启动
docker compose up -ddocker-compose.yml 已包含模型目录挂载:
volumes:
- ./models:/app/core/models模型文件解压到 core/models/ 目录,然后克隆仓库并启动:
git clone https://github.com/coderzc/open-xiaoai-bridge.git
cd open-xiaoai-bridge
# 依赖: uv, Rust
# Linux 还需要: pkg-config, patchelf
# 启动(按需设置环境变量)
API_SERVER_ENABLE=1 XIAOZHI_ENABLE=1 OPENCLAW_ENABLED=1 ./scripts/start.sh| 变量 | 说明 | 默认值 |
|---|---|---|
XIAOZHI_ENABLE |
启用小智 AI | 禁用 |
OPENCLAW_ENABLED |
启用 OpenClaw | 禁用 |
API_SERVER_ENABLE |
启用 HTTP API | 禁用 |
API_SERVER_HOST |
API 监听地址 | 127.0.0.1 |
API_SERVER_PORT |
API 监听端口 | 9092 |
CONFIG_PATH |
自定义配置文件路径 | ./config.py |
LOGLEVEL |
日志级别 | INFO |
flowchart TB
subgraph XiaoaiDevice["📱 小爱音箱"]
direction LR
Mic["麦克风"] -->|"PCM"| AudioCapture["open-xiaoai-client<br/>音频采集 / 播放"]
AudioCapture -->|"播放"| Speaker["扬声器"]
XiaoaiOS["小爱音箱系统"] <-->|"ASR / TTS / 控制"| AudioCapture
end
subgraph OpenXiaoAI["🧠 Open-XiaoAI Bridge"]
direction TB
WSServer["open_xiaoai_server<br/>WebSocket :4399"]
XiaoaiPy["XiaoAI<br/>设备接入 / 事件桥接"]
GlobalStream["GlobalStream<br/>全局音频流"]
subgraph AudioPipeline["音频处理"]
direction LR
VAD["VAD<br/>语音起止检测"]
KWS["KWS<br/>唤醒词检测"]
ASR["SherpaASR<br/>离线语音识别"]
Codec["AudioCodec<br/>编码 / 播放"]
end
subgraph Runtime["运行时控制"]
direction LR
MainApp["MainApp<br/>主循环 / device_state"]
WakeupMgr["WakeupSessionManager<br/>唤醒会话状态机"]
XiaoAIConv["XiaoAIConversationController<br/>小爱连续对话"]
SpeakerMgr["SpeakerManager"]
Config["config.py<br/>before/after_wakeup"]
end
subgraph AIConnectors["AI 连接器(可选)"]
direction LR
Xiaozhi["XiaoZhi<br/>小智协议客户端"]
OpenclawMgr["OpenClawManager<br/>OpenClaw 网关客户端"]
OpenclawConv["OpenClawConversation<br/>连续对话控制器"]
end
subgraph ServicesLayer["服务层(可选)"]
direction LR
APIServer["API Server<br/>HTTP :9092"]
TTSModule["Doubao TTS"]
end
end
subgraph ExternalServices["☁️ 外部服务"]
direction TB
XiaozhiServer["xiaozhi-esp32-server"]
OpenclawGW["OpenClaw Gateway"]
DoubaoTTS["豆包语音服务"]
XiaozhiServer ~~~ OpenclawGW ~~~ DoubaoTTS
end
subgraph APIClients["🌐 API 客户端"]
direction TB
Curl["curl / HTTP 客户端"]
XiaoaiTTS["skills/xiaoai-tts"]
Curl ~~~ XiaoaiTTS
end
%% ===== 设备接入 =====
AudioCapture <-->|"WebSocket"| WSServer
WSServer -->|"音频帧 / 设备事件"| XiaoaiPy
XiaoaiPy -->|"播放 / 控制"| WSServer
%% ===== 音频流 =====
XiaoaiPy -->|"输入音频"| GlobalStream
GlobalStream --> KWS
GlobalStream --> VAD
GlobalStream --> Codec
%% ===== 控制流 =====
Config -->|"before/after_wakeup"| WakeupMgr
MainApp -->|"初始化 / 主 loop"| WakeupMgr
MainApp -->|"device_state"| Codec
MainApp -->|"device_state"| SpeakerMgr
XiaoaiPy -->|"ASR / playing / AudioPlayer"| WakeupMgr
XiaoaiPy -->|"AudioPlayer / playing"| XiaoAIConv
KWS -->|"唤醒词"| WakeupMgr
VAD -->|"speech / silence"| WakeupMgr
%% ===== 小智对话链路(可选) =====
WakeupMgr -->|"listen start / stop"| Xiaozhi
MainApp -->|"启动 / 回调接线"| Xiaozhi
Codec -->|"编码音频"| Xiaozhi
Xiaozhi -->|"TTS / STT / LLM"| MainApp
Xiaozhi <-->|"WebSocket"| XiaozhiServer
%% ===== OpenClaw 链路(可选) =====
MainApp -.->|"启动"| OpenclawMgr
MainApp -.->|"send_to_openclaw()"| OpenclawMgr
OpenclawMgr <-->|"WebSocket"| OpenclawGW
WakeupMgr -.->|"唤醒词路由"| OpenclawConv
OpenclawConv -.->|"VAD 监听"| VAD
OpenclawConv -.->|"语音识别"| ASR
OpenclawConv -.->|"发送消息"| OpenclawMgr
OpenclawConv -.->|"播放回复"| TTSModule
%% ===== 播放回路 =====
SpeakerMgr -->|"play()"| XiaoaiPy
Codec -->|"播放音频"| XiaoaiPy
%% ===== 服务层 =====
MainApp -.->|"启动"| APIServer
APIServer -->|"调用"| SpeakerMgr
APIServer -.->|"TTS"| TTSModule
OpenclawMgr -.->|"服务端自动 TTS"| TTSModule
TTSModule -.->|"合成语音"| DoubaoTTS
TTSModule -->|"播放"| SpeakerMgr
%% ===== API 客户端 =====
APIServer <-->|"HTTP"| Curl
OpenclawGW -.->|"Agent 调用"| XiaoaiTTS
XiaoaiTTS -->|"HTTP"| APIServer
%% 样式
classDef hardware fill:#f472b6,stroke:#db2777,stroke-width:1.5px,color:#fff
classDef rust fill:#fb923c,stroke:#ea580c,stroke-width:1.5px,color:#fff
classDef core fill:#60a5fa,stroke:#2563eb,stroke-width:1.5px,color:#fff
classDef audio fill:#4ade80,stroke:#16a34a,stroke-width:1.5px,color:#fff
classDef connector fill:#fbbf24,stroke:#d97706,stroke-width:1.5px,color:#fff
classDef api fill:#a78bfa,stroke:#7c3aed,stroke-width:1.5px,color:#fff
classDef external fill:#f87171,stroke:#dc2626,stroke-width:1.5px,color:#fff
class Mic,Speaker,XiaoaiOS hardware
class AudioCapture,WSServer rust
class MainApp,WakeupMgr,XiaoAIConv,SpeakerMgr,Config,GlobalStream core
class VAD,KWS,ASR,Codec audio
class XiaoaiPy,Xiaozhi,OpenclawMgr,OpenclawConv connector
class APIServer,TTSModule api
class XiaozhiServer,OpenclawGW,DoubaoTTS,Curl,XiaoaiTTS external
🎯 小智唤醒与对话
麦克风 → client → server → XiaoAI → GlobalStream → KWS/小爱 ASR
→ WakeupSessionManager → before_wakeup() → VAD speech/silence
→ XiaoZhi start/stop listening → xiaozhi-esp32-server
🔄 小爱连续对话
小爱 ASR / AudioPlayer 事件 → XiaoAIConversationController
→ 决定继续唤醒或退出
🦞 OpenClaw 单次对话
小爱指令 "让龙虾 xxx" → before_wakeup() → send_to_openclaw()
→ OpenClawManager → Gateway → Agent
→ 自动 TTS 播报 或 Agent 主动调用 xiaoai-tts skill
🦞 OpenClaw 连续对话
唤醒词 "你好龙虾" → WakeupSessionManager → OpenClawConversationController
→ 循环: VAD 检测语音 → SherpaASR 离线识别 → OpenClaw → TTS 播放
→ 说"退出"/"再见"退出
🌐 远程控制
curl POST /api/play/text → API Server → SpeakerManager → 小爱音箱
设置 API_SERVER_ENABLE=1 启用,默认端口 9092。
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/play/text |
播放文字(TTS) |
POST |
/api/play/url |
播放音频链接 |
POST |
/api/play/file |
上传并播放音频文件 |
POST |
/api/tts/doubao |
豆包 TTS 合成并播放 |
GET |
/api/tts/doubao_voices |
获取可用音色列表 |
POST |
/api/wakeup |
唤醒小爱音箱 |
POST |
/api/interrupt |
打断当前播放 |
GET |
/api/status |
获取播放状态 |
GET |
/api/health |
健康检查 |
# 播放文字
curl -X POST http://localhost:9092/api/play/text \
-H "Content-Type: application/json" \
-d '{"text": "你好,我是小爱同学"}'
# 播放音频链接
curl -X POST http://localhost:9092/api/play/url \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/audio.mp3"}'
# 上传音频文件
curl -X POST http://localhost:9092/api/play/file \
-F "file=@/path/to/audio.mp3"
# 豆包 TTS(可指定音色)
curl -X POST http://localhost:9092/api/tts/doubao \
-H "Content-Type: application/json" \
-d '{"text": "你好", "speaker_id": "zh_female_cancan_mars_bigtts"}'
# 打断播放
curl -X POST http://localhost:9092/api/interrupt通过 OpenClaw 将小爱音箱变成你的 AI Agent 终端。
设置 OPENCLAW_ENABLED=1 启用
用自定义唤醒词触发后进入多轮对话循环,全程本地处理,不依赖小爱 ASR:
唤醒词 "你好龙虾" → 提示音 → 你说话 → [VAD检测] → [本地ASR识别] → OpenClaw Agent → [TTS播报] → 提示音 → 你继续说 → ...
- 说"退出"或"再见"退出对话
- 小爱唤醒时自动打断 TTS 并退出
- 退出关键词可自定义
触发方式见下方自定义唤醒词,before_wakeup 返回 "openclaw" 即进入连续对话。
通过小爱语音指令发送一条消息给 Agent,收到回复后自动 TTS 播报:
# config.py 中的 before_wakeup
if "让龙虾" in text:
await speaker.abort_xiaoai()
await app.send_to_openclaw_and_play_reply(text.replace("让龙虾", ""))
return None # 框架不做额外处理用户说"让龙虾查一下明天天气" → 打断小爱 → 发给 Agent → TTS 播报回复。
只发送消息,不自动播报,由 Agent 调用 xiaoai-tts skill 自主播报:
if "告诉龙虾" in text:
await speaker.abort_xiaoai()
await app.send_to_openclaw(text.replace("告诉龙虾", ""))
return Nonesend_to_openclaw() 会自动追加 rule_prompt_for_skill(配置在 config.py 中),告诉 Agent 需要调用 skill 播报。适合 Agent 需要做复杂处理后再决定是否/如何播报的场景。
唤醒词在 config.py 的 wakeup.keywords 中定义,支持中英文混合:
"wakeup": {
"keywords": [
"你好小智", # 中文
"小智小智",
"hi openclaw", # 英文(全小写)
"你好龙虾",
"龙虾你好",
],
},不同唤醒词可以路由到不同 AI 服务,在 before_wakeup 中根据文本内容判断:
async def before_wakeup(speaker, text, source, app):
if source == "kws": # 唤醒词触发
if "龙虾" in text:
await speaker.play(text="龙虾来了")
return "openclaw" # → OpenClaw 连续对话
if "小智" in text:
await speaker.play(text="小智来了")
return "xiaozhi" # → 小智 AI
return None # → 不处理
if source == "xiaoai": # 小爱语音指令
if text == "召唤龙虾":
await speaker.abort_xiaoai()
return "openclaw"
if text == "召唤小智":
await speaker.abort_xiaoai()
return "xiaozhi"
# 返回 None → 交给小爱原生处理返回值含义: "openclaw" → 连续对话,"xiaozhi" → 小智 AI,None → 不处理(用户可自行调用 app.send_to_openclaw() 等方法)
set_openclaw_session_key() 让你在发送消息前动态切换目标 Agent Session,无需重连,无性能开销。结合自定义唤醒词,可以实现:
一台音箱,N 个专属 AI 助手,按名字呼唤谁,谁就来响应。
# config.py
AGENT_SESSIONS = {
"龙虾": "agent:assistant:open-xiaoai-bridge",
"小美": "agent:xiaomei:open-xiaoai-bridge",
"管家": "agent:butler:open-xiaoai-bridge",
}
async def before_wakeup(speaker, text, source, app):
if source == "kws":
for keyword, session_key in AGENT_SESSIONS.items():
if keyword in text:
app.set_openclaw_session_key(session_key) # 切换到对应 Agent
await speaker.play(text=f"{keyword}来了")
return "openclaw" # 进入连续对话
if source == "xiaoai":
for keyword, session_key in AGENT_SESSIONS.items():
if f"召唤{keyword}" in text:
app.set_openclaw_session_key(session_key)
await speaker.abort_xiaoai()
return "openclaw"配合唤醒词配置:
"wakeup": {
"keywords": [
"你好龙虾", "你好小美", "你好管家",
],
},此后你说"你好龙虾",进入的是龙虾 Agent 的上下文;说"你好小美",进入的是小美 Agent 的上下文 —— 同一台音箱,完全隔离的多个 AI 人格。
退出时同样可以区分是哪个 Agent 结束了对话。after_wakeup 在 OpenClaw 退出时会收到 session_key 参数,取第二段即为 agentId:
async def after_wakeup(speaker, source=None, session_key=None):
if source == "openclaw":
# session_key 格式:agent:<agentId>:<rest>,第二段即 agentId
agent_id = session_key.split(":")[1] if session_key else None
if agent_id == "assistant":
await speaker.play(text="龙虾,再见")
elif agent_id == "xiaomei":
await speaker.play(text="小美,再见")
elif agent_id == "butler":
await speaker.play(text="管家,再见")
else:
await speaker.play(text="再见")
if source == "xiaozhi":
await speaker.play(text="小智,再见")有两个 prompt 配置,分别用于不同的播报场景:
| 配置 | 使用场景 | 自动追加位置 |
|---|---|---|
rule_prompt |
自动播放、连续对话 | send_to_openclaw_and_play_reply()、连续对话循环 |
rule_prompt_for_skill |
Agent 自主播报(方式三) | send_to_openclaw() |
为什么需要两个 prompt?
rule_prompt:服务端会自动 TTS 播放,只需告诉 Agent 输出纯文字、控制字数rule_prompt_for_skill:服务端不会自动播放,需要告诉 Agent 主动调用xiaoai-ttsskill 来播报
示例配置:
"openclaw": {
# 自动播放/连续对话用:约束输出格式
"rule_prompt": "注意:将结果处理成纯文字版,不要返回任何 markdown 格式,也不要包含任何代码块,并将字数控制在300字以内",
# Agent 自主播报用:告诉 Agent 需要调用 skill
"rule_prompt_for_skill": "注意:这条消息是主人通过小爱音箱发送的,他看不到你回复的文字,调用 `xiaoai-tts` skill 播报出来。字数控制在300字以内",
}不需要可以留空或不设置。
openclaw.tts_speaker 支持两种值:
| 值 | 效果 | 说明 |
|---|---|---|
"xiaoai" |
小爱原生 TTS | 零配置即可使用,音色由设备决定 |
| 豆包音色 ID | 豆包语音合成 | 需配置 tts.doubao 的 app_id 和 access_key,详见 TTS 章节 |
skills/xiaoai-tts/ — Agent 通过 HTTP API 控制小爱播放语音,支持小爱内置 TTS 和豆包 TTS。
📖 详见 SKILL.md
接入 xiaozhi-esp32-server,使用小智 AI 的对话能力。
设置 XIAOZHI_ENABLE=1 启用
APP_CONFIG = {
"xiaozhi": {
"OTA_URL": "http://127.0.0.1:8003/xiaozhi/ota/",
"WEBSOCKET_URL": "ws://127.0.0.1:8000/xiaozhi/v1/",
"WEBSOCKET_ACCESS_TOKEN": "", # 可选
# "DEVICE_ID": "", # 可选,默认自动生成
},
}唤醒词触发后,before_wakeup 返回 "xiaozhi" 即进入小智对话流程。
详见自定义唤醒词章节。
-
模型文件在哪下载?
启用小智 AI 或 OpenClaw 连续对话功能需要下载 VAD + KWS + ASR 模型文件,详见快速开始 - Docker Compose 或 本地编译 章节。
-
如何打断 AI 的回答?
直接喊"小爱同学"即可打断小智或 OpenClaw 的回答。
-
话没说完 AI 就开始回答?
调大
min_silence_duration:APP_CONFIG = { "vad": { "min_silence_duration": 1000, # 毫秒 }, }
-
唤醒词没反应?
- 调低
vad.threshold(越小越灵敏,如0.05) - 启动后需等约 30s 加载模型
- 英文唤醒词用空格分开(如
"open ai") - 换更易识别的唤醒词
- 调低
-
首次连接出现 pairing required?
正常流程。保持服务在线,到 OpenClaw UI 批准设备:Nodes → Devices → Approve。
-
容器重建后需要重新配对?
Docker 部署时挂载
identity_path目录为持久化卷,否则设备身份丢失需重新配对:# docker-compose.yml volumes: - ./openclaw:/app/openclaw
-
session_key 是什么?
告诉 Gateway 把消息路由到哪个 Agent Session,格式为冒号分隔的层级路径:
agent:<agentId>:<rest>字段 说明 示例 agent固定前缀 agent<agentId>OpenClaw 中配置的 Agent ID(默认为 main)main、assistant<rest>会话标识,可自由命名,用于区分不同来源/场景 home、open-xiaoai-bridge常见格式举例:
agent:main:open-xiaoai-bridge # 默认值(本项目) agent:main:main # OpenClaw 原生默认主会话 agent:assistant:open-xiaoai-bridge # 指定其他 Agent agent:main:direct:alice # 按用户隔离 -
如何在运行时动态切换 session_key?
每次唤醒触发
before_wakeup之前,框架会自动将session_key重置为配置文件中的默认值。因此:- 在
before_wakeup中调用app.set_openclaw_session_key()→ 本次唤醒使用指定的 session - 不调用 → 自动使用配置文件中的
openclaw.session_key,不会沿用上一次的值
这意味着你只需要在需要切换的路径里调用一次,不用担心"忘记重置"的问题。
常见使用场景:
场景一:按唤醒词路由到不同 Agent
说"你好龙虾"唤醒龙虾 Agent,说"你好小美"唤醒小美 Agent:
AGENT_SESSIONS = { "龙虾": "agent:assistant:open-xiaoai-bridge", "小美": "agent:xiaomei:open-xiaoai-bridge", "管家": "agent:butler:open-xiaoai-bridge", } async def before_wakeup(speaker, text, source, app): if source == "kws": for keyword, session_key in AGENT_SESSIONS.items(): if keyword in text: app.set_openclaw_session_key(session_key) await speaker.play(text=f"{keyword}来了") return "openclaw"
场景二:每次唤醒生成独立 Session
每次对话互相隔离,适合以下情况:
- "提问 → 回答"式交互,不需要 Agent 记住上下文
- 长期使用同一 Session 导致 Agent 上下文堆积过长,影响响应质量和速度
import uuid def new_session_key(): return f"agent:main:session-{uuid.uuid4().hex[:8]}" async def before_wakeup(speaker, text, source, app): if source == "kws" and "龙虾" in text: app.set_openclaw_session_key(new_session_key()) await speaker.play(text="龙虾来了") return "openclaw"
- 在
-
send_to_openclaw() 的返回值是什么?
send_to_openclaw(text)→ 成功返回run_id(str),失败返回Nonesend_to_openclaw(text, wait_response=True)→ 成功返回回复文本,超时/失败返回Nonesend_to_openclaw_and_play_reply(text)→ 同上,但会自动 TTS 播放回复
-
第一次运行提示验证码绑定设备?
打开小智 AI 管理后台,根据提示创建 Agent 绑定设备。验证码会在终端打印或写入
config.py:APP_CONFIG = { "xiaozhi": { "VERIFICATION_CODE": "首次登录时,验证码会在这里更新", }, }
绑定成功后可能需要重启应用。
-
怎样使用自己部署的 xiaozhi-esp32-server?
修改
config.py中的接口地址:APP_CONFIG = { "xiaozhi": { "OTA_URL": "https://your-server/xiaozhi/ota/", "WEBSOCKET_URL": "wss://your-server/xiaozhi/v1/", }, }
-
如何配置豆包 TTS?
- 开通火山引擎语音合成服务,获取 App ID 和 Access Key(接入文档)
- 填入配置:
"tts": { "doubao": { "app_id": "你的 App ID", "access_key": "你的 Access Key", "default_speaker": "zh_female_cancan_mars_bigtts", # 默认音色,可选列表见下方 } }
音色列表:火山引擎音色库
-
如何使用声音复刻?
- 在火山引擎声音复刻控制台上传 10-30 秒音频
- 训练完成后到音色库复制音色 ID(格式
S_xxxxxxxx) - 重要:确保复刻音色与
tts.doubao.app_id属于同一个火山引擎项目,否则无法使用 - 填入配置:
"tts": { "doubao": { "default_speaker": "S_xxxxxxxx", } }
-
支持流式播放吗?怎么配置?
支持。推荐配置:
"tts": { "doubao": { "stream": True, # 流式播放,首音延迟更低 "audio_format": "pcm", # 局域网推荐,首音更快 # "audio_format": "auto", # 短文本 PCM,长文本 MP3 } }
pcm:首音快,流式稳定,长文本总耗时可能更高mp3:传输效率高,长文本更早结束auto:折中方案,按文本长度自动选择
冒烟测试(无需音箱,验证 TTS 是否正常):
python3 tests/test_tts_stream.py # 测试流式 TTS 连通性 python3 tests/test_tts_latency.py --formats mp3,pcm --rounds 3 --repeat 8 # 对比 mp3/pcm 延迟
| 资源 | 链接 |
|---|---|
| 🔧 刷机教程 | 刷机教程 |
| 🛠️ Client 端安装 | Client 端安装 |
| 🎙️ 豆包 TTS 音色列表 | 火山引擎文档 |
Made with ❤️ by coderzc
如果这个项目对你有帮助,请给它一颗 ⭐️