上篇写原生 ASR 接入时,我把语音识别的链路走通了:录音、采集 PCM、送进系统识别服务、拿到文本结果上屏。那条链路本身没什么问题,稳定、延迟也低,但项目真正需要的不是“把话说成字”,而是“让设备听懂人话并自动执行”。这个差距,是原生 ASR 再怎么调参也补不上的。所以我开始评估 Copilot SDK,并且在 HarmonyOS 6 上完成了整套对接与重构。
这篇是下篇,重点就三块:为什么必须从原生 ASR 切到 Copilot SDK、Copilot SDK 在 HarmonyOS 6 上的完整对接流程、以及重构过程中真实踩过的坑和最终沉淀下来的架构。文章偏实战,适合那种“已经接完了基础 ASR、正在纠结要不要上更上层智能能力”的团队。
1. 为什么重构:原生 ASR 的瓶颈与 Copilot SDK 的定位
1.1 原生 ASR 在真实产品里的尴尬位置
原生 ASR 解决的是一个很窄的问题:把语音转成一段文本。它不知道“打开空调到26度”是什么意思,不知道“明天早上提醒我开会”需要创建一个日历事件,更不会记住你上一句说的“那换成23度”里的“那”指的是什么。我在上篇末尾提过一句“ASR 只是起点”,当时很多朋友留言问为什么不直接在 ASR 结果后面继续做 NLP,非要引入 SDK。
我的回答是:能,但成本完全不可控。你接完原生 ASR 后,后面实际上还有一长串工作要做,而且每一环都不简单:
- 语义理解:得自己接一套 NLU 服务,自定义意图和实体。
- 多轮上下文:必须自己做会话状态管理,处理指代、省略、纠错。
- 指令执行:要把 NLU 输出的结构化意图映射到底层业务 Action。
- 语音合成:如果产品需要语音回复,还得再对接 TTS。
这些工作一旦全部自己做,本质上就是在造一个不完整的助手框架。而 Copilot SDK 的定位恰好就是把这一整套能力压缩成一个 SDK:音频进、事件出,中间给你透出识别中间态、意图结果、动作指令和回复文本。对于我这次做的智能助手类项目,这才是合适的抽象层级。
1.2 对比一下两者到底差在哪
我整理了一张表,是当时给团队评审用的,直接贴出来:
| 能力维度 | 原生 ASR | Copilot SDK |
|---|---|---|
| 输入方式 | PCM/音频流 | PCM/音频流,同时支持文本 |
| 输出内容 | 识别文本 | 中间文本、最终文本、意图、动作、回复 |
| 多轮上下文 | 无,需自建 | 会话内自动维护 |
| 离线能力 | 取决于系统能力 | 支持基础离线识别和对话配置 |
| 结构化指令 | 不支持 | 支持意图和参数提取 |
| 内置 TTS | 无 | 支持回复播报 |
| 集成成本 | 低 | 中高,需要理解事件模型 |
| 对 HarmonyOS 6 的适配 | 系统级稳定 | 新版本持续演进,需要踩坑 |
这张表的核心信息是:Copilot SDK 不是 ASR 的替代品,而是 ASR 的上一层封装。如果你的产品只是要一个“语音转文字输入框”,原生 ASR 完全够用,别折腾。但如果产品核心是“对话式控制”“语音助手”“智能客服”,那从原生 ASR 往 Copilot SDK 迁移,其实是在买保险——避免自己维护一条脆弱无比的理解链路。
1.3 什么情况下不建议切
我见过一些团队推 Copilot SDK 推得很激进,最后反而翻车。给大家一个反直觉的结论:如果你的应用对时延极度敏感,比如实时字幕、同传场景,那原生 ASR 仍然更好,因为它只管识别,链路短,且系统级服务有专项调度优化。Copilot SDK 由于要跑意图理解和多轮管理,端到端响应时间通常要多出几百毫秒到一秒。另外,如果你们已经有成熟的 NLU 服务,只是缺一个语音入口,那也不必硬塞一个 Copilot SDK 进来。
我把这个判断放在最前面,就是怕有人说“既然 Copilot SDK 这么好,原生 ASR 是不是可以删了”。在我这次重构里,两个能力我保留了开关,后面灰度章节会细说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 对接前的工程测绘:HarmonyOS 6 环境与依赖关系梳理
2.1 先别急着写代码,把工程底数摸清楚
HarmonyOS 6 对工程结构的要求比早期版本严格了不少。接入 Copilot SDK 之前,我先把项目现状盘点了一遍:当前 DevEco Studio 版本、SDK API 级别、模块依赖方式、签名证书类型、是否启用了 Stage 模型。这些信息决定了后续能不能顺利跑通。
我用的是 HarmonyOS 6 配套的 DevEco Studio,API 级别已经升级到比较新的版本。Copilot SDK 的官方文档里要求最低 API 级别,我们工程本来就不低,所以没有卡住。但有一点要注意:SDK 内部如果依赖了较新的系统组件,比如新的音频路由服务、分布式协同能力,那么运行时如果跑在旧版本系统上会直接抛初始化异常。我们应用的最低支持版本和测试机的系统版本不一致,前期没注意,导致模拟器上反复初始化失败。所以动手前,先确认三件事:
- 编译用 SDK 版本(API 级别)
- 最低兼容版本
- 测试真机的系统版本
三者差距越小,后面越省心。
2.2 依赖引入与签名、鉴权的前置条件
Copilot SDK 的引入方式是通过 ohpm 安装,编辑 oh-package.json5,加入依赖声明,然后 sync。这一步很常规,但有一个隐藏的坑:SDK 包体积不小,而且它会有自己的传递依赖,sync 时如果网络环境不稳定,很容易出现依赖下载不完整。我后来在 CI 上单独缓存了 ohpm 仓库,避免每次构建都去拉。
更关键的是鉴权信息。Copilot SDK 初始化时需要的不是简单的一个 API Key,而是一套 appId + productId + deviceId 的组合。其中 deviceId 在配对调试设备时需要做一套签名校验。HarmonyOS 6 对设备和应用的绑定关系校验比以前更严,如果调试机的 UDID 没有添加到后台白名单,SDK 会初始化成功,但第一个请求会返回 AUTH_DEVICE_NOT_BOUND。这个错误特别迷惑人,因为它不是出现在初始化阶段,而是出现在第一次建会话时。
所以建议流程是:
- 在 AppGallery Connect 后台创建应用,拿到 appId。
- 在 AI 服务后台开通 Copilot 能力,创建产品并绑定应用。
- 把测试真机的 UDID 加进设备白名单。
- 生成并下载签名证书,配置到工程里。
2.3 module.json5 的权限配置与隐私声明
HarmonyOS 6 的权限模型是一事一申请,但麦克风权限和网络权限还是要先在 module.json5 里做静态声明。我直接贴出配置:
json复制{
"module": {
"name": "entry",
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:microphone_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "foreground"
}
},
{
"name": "ohos.permission.INTERNET",
"reason": "$string:internet_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
]
}
}
ohos.permission.INTERNET 在普通调试包上默认有,但正式打包或上架审核时,如果没声明会导致网络请求直接失败。另外还有一个容易漏的:如果 Copilot SDK 需要读取设备信息用于生成设备标识,可能还需要声明 ohos.permission.READ_DEVICE_INFO,但这个权限比较敏感,能用 deviceId 接口的轻量方案就不要申请它。
2.4 与原生 ASR 模块的共存策略
我在工程里保留了原生 ASR 模块,没有直接删除。原因很简单:Copilot SDK 处于灰度阶段,不能保证在所有机型上都稳定。工程里同时存在 speech-native-asr 和 speech-copilot 两个 feature 模块,通过编译开关和运行时开关做切换。这样做的好处是重构过程中始终有一条可回退的稳定链路,坏处是多了一部分维护成本,但相比一夜之间翻车,这个成本完全可以接受。
3. 核心对接链路:初始化、鉴权、会话与流式回调
3.1 初始化 Copilot SDK
HarmonyOS 6 上初始化 Copilot SDK 的整体思路和主流 SDK 一致:先创建配置,再调 initialize。但这里有个细节:初始化过程异步且可能耗时,不能卡在页面加载里。我放在应用启动后的闪屏阶段做,并行拉取远端配置,等首页加载完成时,SDK 基本已经就绪。
初始化代码大致长这样:
typescript复制import { CopilotSDK, CopilotConfig, LogLevel } from '@kit/CopilotSDK';
import { common } from '@kit.AbilityKit';
import { deviceInfo } from '@kit.BasicServicesKit';
export class CopilotManager {
private static instance: CopilotManager;
private sdk?: CopilotSDK;
static getInstance(): CopilotManager {
if (!CopilotManager.instance) {
CopilotManager.instance = new CopilotManager();
}
return CopilotManager.instance;
}
async init(context: common.UIAbilityContext): Promise<void> {
const config: CopilotConfig = {
context,
appId: 'your_app_id',
productId: 'your_product_id',
deviceId: deviceInfo.deviceId,
region: 'cn',
logLevel: LogLevel.WARN,
};
this.sdk = await CopilotSDK.create(config);
await this.sdk.initialize();
}
}
注意 context 的传递。我在第一版里传的是 Page 的 UIContext,结果页面一跳转,SDK 内部持有的 context 就失效了,后续回调全无。改成 UIAbilityContext 之后才稳定。这是 HarmonyOS 生命周期模型的特性,玩过 Android 的朋友应该秒懂——尽量持有 Application 级 Context,不要持有 Activity/Page 级。
3.2 鉴权 Token 的获取与自动刷新
Copilot SDK 初始化完成后并不会立刻鉴权,而是等到创建会话或发起第一次请求时才去换取 token。SDK 提供了 tokenProvider 字段,让我把自己的 token 获取逻辑注入进去。这个设计比较友好,尤其适合我们已经有自己账号体系的情况,可以在后端统一签发 token,由客户端透传。
核心代码如下:
typescript复制const config: CopilotConfig = {
// ... 其他配置
tokenProvider: async () => {
// 从后端换取临时 token,注意这里要处理过期刷新
return await this.fetchTokenFromServer();
},
};
token 过期是必现问题。SDK 内部一般会在收到 TOKEN_EXPIRED 错误时回调,我建议在这里做一个 token 失效后的自动重试队列,而不是直接抛错误给用户:
typescript复制this.sdk.on('tokenExpired', async () => {
await this.refreshToken();
this.session?.resume(); // 恢复当前会话
});
3.3 创建会话与事件监听
会话是 Copilot SDK 的核心概念。语音交互、文本交互、多轮上下文都存在会话里。官方示例通常把创建会话和事件监听写在同一个方法里,我重构之后拆成了两个方法,因为事件监听的生命周期和 UI 组件生命周期要对应起来。
创建会话时有两个参数要特别留意:enableVoice 和 wakeupMode。如果只想走文本问答,enableVoice 可以关掉;如果要做语音助手,就要打开,并且要决定是用关键词唤醒还是手动按键唤醒。
typescript复制import { SessionEvent, WakeupMode } from '@kit/CopilotSDK';
async createSession(): Promise<void> {
const session = await this.sdk.createSession({
enableVoice: true,
wakeupMode: WakeupMode.MANUAL,
language: 'zh-CN',
});
session.on(SessionEvent.SPEECH_STATE_CHANGED, (state) => {
// 用户开始说话、结束说话、识别中、播报中等状态切换
this.handleSpeechState(state);
});
session.on(SessionEvent.PARTIAL_TEXT, (payload) => {
// 实时上屏文本,类似 ASR 的中途结果
this.partialText = payload.text;
});
session.on(SessionEvent.FINAL_TEXT, (payload) => {
this.finalText = payload.text;
});
session.on(SessionEvent.INTENT_RESULT, (intent) => {
// 解析后的意图,包含意图名和槽位
this.handleIntent(intent);
});
session.on(SessionEvent.ACTION_EXECUTE, (action) => {
// 需要业务侧执行的动作
this.handleAction(action);
});
session.on(SessionEvent.REPLY_TEXT, (reply) => {
// 助手回复的文本,可用于展示或 TTS
this.replyText = reply.text;
});
this.session = session;
}
这套事件模型和原生 ASR 最大的差异是:原生 ASR 只给你最终文本,Copilot SDK 从说话状态开始就持续通知你。UI 层可以做出更自然的过渡,比如先显示“正在聆听”,再显示“正在理解”,最后才出现回复卡片。这对用户体感是质的提升。
3.4 音频数据喂入与手动结束说话
Copilot SDK 在启用语音后,内部有自己的录音器。但如果你之前已经有一套成熟的录音链路,比如在原生 ASR 里做了回声消除、降噪、静音检测,那么你可以关闭 SDK 自带录音,通过 feedAudio 接口手动喂 PCM 数据。
我的做法是保留原生 ASR 的 AudioCapturer 采集链路,只把数据转发给 Copilot SDK:
typescript复制session.startListening();
// 在音频采集回调里持续写入 PCM
audioCapturer.on('data', (pcmBuffer: ArrayBuffer) => {
session.feedAudio(pcmBuffer);
});
// 用户说完或者静音超时后调用
session.stopListening();
这里有一个采样率匹配的坑。原生 ASR 默认采集 16kHz 单声道,而 Copilot SDK 在某些配置下要求 48kHz。如果没对齐,识别率和意图识别准确率会肉眼可见地下降。我在这里卡了一个下午,最后在 SDK 配置里显式指定 audioSampleRate 为 16000,并把 AudioCapturer 的编码格式调整为 AudioEncodingType.ENCODING_PCM,才完全稳定。
4. 重构落地的关键手术:从 ASR 回调到 Copilot 统一事件模型
4.1 界面状态机重构
原生 ASR 对接时,我的页面状态只有三个:IDLE、RECORDING、RECOGNIZING、RESULT。Copilot SDK 接入后,状态明显不够用了。有一次用户问完问题,界面还停留在“识别中”,实际上助手已经在执行开灯动作了,UI 完全错位。
我重新设计了一套状态机:
typescript复制export type AssistantState =
| 'Idle'
| 'Listening'
| 'Understanding'
| 'Responding'
| 'Executing'
| 'Error';
对应关系如下:
| Copilot SDK 事件 | UI 状态 |
|---|---|
SPEECH_STATE_CHANGED -> START |
Listening |
PARTIAL_TEXT |
Listening(同时显示中间文本) |
FINAL_TEXT / INTENT_RESULT |
Understanding |
ACTION_EXECUTE |
Executing |
REPLY_TEXT |
Responding |
| 错误码回调 | Error |
状态切换全部收敛到一个单独的状态管理类里,不直接散落在页面回调中:
typescript复制class AssistantStateMachine {
private current: AssistantState = 'Idle';
private listeners: Array<(state: AssistantState) => void> = [];
transition(next: AssistantState) {
if (this.current === next) return;
this.current = next;
this.listeners.forEach((listener) => listener(this.current));
}
}
这套状态机的好处是:以后哪怕从 Copilot SDK 再换到另一个 SDK,UI 层代码不需要动,只需要把 SDK 事件翻译成状态迁移。
4.2 定义一个统一的语音服务接口
我不希望业务代码分散地调用 CopilotSDK 或 NativeASR,所以加了一个薄薄的抽象层:
typescript复制export interface SpeechAssistant {
start(): Promise<void>;
stop(): Promise<string>;
sendText(text: string): Promise<void>;
onEvent(callback: (event: AssistantEvent) => void): void;
destroy(): Promise<void>;
}
CopilotSDK 实现这个接口时,把它的 INTENT_RESULT、ACTION_EXECUTE 等事件包装成统一的 AssistantEvent:
typescript复制export type AssistantEvent =
| { type: 'partialText'; text: string }
| { type: 'finalText'; text: string }
| { type: 'intent'; intentName: string; slots: Record<string, string> }
| { type: 'action'; actionName: string; params: Record<string, string> }
| { type: 'reply'; text: string }
| { type: 'error'; code: number; message: string };
原生 ASR 也实现同一个接口,但只产出 finalText 事件。这样做的直接收益是:UI 层只需要面向 AssistantEvent 编程,切换底层实现就是一个工厂模式的配置项。团队里新来的同事也不用理解 Copilot SDK 的大全套,只需要看我们的接口文档。
4.3 把原来散落的业务逻辑收敛成 ActionExecutor
原生 ASR 阶段,我是在拿到识别文本后写一堆 if/else 去匹配关键词。Copilot SDK 接入后,意图识别出来了,动作参数也给你抽好了,剩下的事就很纯粹:执行动作。
我建了一个 ActionExecutor 注册表:
typescript复制export class ActionExecutor {
private actions = new Map<string, ActionHandler>();
register(actionName: string, handler: ActionHandler) {
this.actions.set(actionName, handler);
}
execute(action: ActionDescriptor): Promise<void> {
const handler = this.actions.get(action.actionName);
if (!handler) {
console.warn(`Unhandled action: ${action.actionName}`);
return Promise.resolve();
}
return handler(action.params);
}
}
比如“控制空调”就是一个 ActionHandler:
typescript复制actionExecutor.register('air_conditioner.set_temperature', async (params) => {
const temperature = Number(params.temperature);
await deviceManager.airConditioner.setTemperature(temperature);
// 返回执行结果给 SDK,让助手播报“好的,已调到26度”
await this.session.notifyActionResult(true, { temperature });
});
这一步是整个重构里最值钱的部分。以前写关键词匹配,用户换个说法就废了;现在 Copilot SDK 负责把不同说法映射到同一个 actionName,业务侧只维护一套动作逻辑。
4.4 音频采集链路的复用与改造
音频链路是重构中改动最大、最容易被忽略的。原生 ASR 我用的 AudioCapturer 采集 16kHz/16bit/单声道 PCM。Copilot SDK 虽然自带录音,但我在上一节说过,为了复用降噪和回声消除,我选择手动喂数据。
具体改造点有三个:
- 原来的音频回调直接给原生 ASR,现在改成同时分发给 Copilot SDK。
- 增加采集状态和识别状态的联动,避免出现“SDK 还在识别,录音器已经在写下一段数据”的错乱。
- 增加静音检测阈值,用户停顿超过 800ms 自动触发
stopListening,减少用户等待。
这里给个经验值:静音检测 800ms 在安静环境下很合适,但在嘈杂环境中要放宽到 1200ms 左右,否则用户话说到一半被切断,意图识别会收到不完整的音频,准确率掉得很明显。
5. 踩坑实录:权限、线程与生命周期里的那些坑
5.1 动态权限申请与 UI 的无感衔接
HarmonyOS 6 的麦克风权限机制要求在使用前动态申请。如果用户首次启动直接点“允许”倒还好,但很多用户会在权限弹窗上犹豫几秒,我们的页面流程就会卡住。
我最终的处理是:在用户点击语音按钮时才申请权限,而不是在 onPageShow 里提前申请。否则用户还没意识到要干什么,就被一个权限弹窗打断,体验很差。申请权限的代码如下:
typescript复制import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
export async function requestMicrophonePermission(context: common.UIAbilityContext): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
const result = await atManager.requestPermissionsFromUser(context, permissions);
return result.authResults[0] === 0;
}
5.2 回调线程模型:别在回调里碰 UI
Copilot SDK 的回调线程根据事件类型会有差异,语音相关事件可能走音频线程,网络相关事件可能走异步线程。我第一版直接把 this.replyText = reply.text 写在回调里,然后立刻更新 Text 组件,结果偶尔闪退,报错是“非 UI 线程更新组件”。
解决办法是统一切回到主线程再更新 UI。HarmonyOS 里最稳妥的方式是用 emitter 或者页面级 PostTask。我用的是全局 EventHub 做事件转发:
typescript复制import { emitter } from '@kit.BasicServicesKit';
session.on(SessionEvent.REPLY_TEXT, (reply) => {
const eventData = {
eventId: 'assistant.reply',
data: { text: reply.text },
};
emitter.emit(eventData);
});
页面在 onPageShow 里订阅,在 onPageHide 里取消,基本杜绝了跨线程问题。
5.3 SDK 生命周期与页面生命周期不一致
Copilot SDK 的实例一旦创建,会长期持有会话资源。我把 SDK 实例放在 Application 级别的单例里,但会话不能一直挂在页面上。页面从 A 跳转到 B 时,如果不销毁会话,麦克风会一直被占着,其他页面录音会失败。
我的规则是:
- 页面级会话:进入语音页面时创建,离开时
stop()并销毁。 - 全局级 SDK:应用启动时创建,应用退出时
release()。 - 前后台切换:退后台时强制
stopListening(),回前台时恢复状态机。
这套生命周期管理规则实际上比 Android 的 Activity 生命周期还要严格一点,因为 HarmonyOS 的 Page 栈管理更复杂。我在 onPageHide 里忘了回收麦克风,导致从语音页返回后其他功能音频播放变成手机听筒出声,查了半天才发现是音频焦点被 SDK 占住没释放。
5.4 音频焦点与系统音量冲突
这是最后一个也是最隐蔽的坑。当 Copilot SDK 进入播报状态(TTS 回复)时,它默认会请求音频焦点。如果此时用户正在听音乐,音乐会被打断。我在会议室里演示时,手机连了蓝牙音箱,助手一开口,前一句音乐戛然而止,然后助手的声音从蓝牙音箱传出来,总经理脸色微妙。
处理方案是:在创建会话时配置音频焦点策略为“混音或短暂闪避”:
typescript复制const session = await this.sdk.createSession({
enableVoice: true,
audioFocusMode: 'DUCK_OTHERS',
});
这样助手播报时,音乐不会被完全暂停,而是降低音量,播报结束后自动恢复。同样重要的是,在 destroy() 之前一定要主动释放音频焦点,否则系统会认为应用仍在占用焦点。
5.5 真机与模拟器的巨大差异
我用模拟器调试时发现,Copilot SDK 的音频事件经常延迟 2 到 3 秒,甚至收不到 FINAL_TEXT。一开始以为代码问题,后来换到真机测试,一切正常。模拟器的音频虚拟设备对实时流支持很差,特别是流式识别和流式应答这种对时序敏感的功能,模拟器基本不可用。
所以强烈建议:涉及语音和音频的 HarmonyOS 6 开发,从一开始就用真机调试。模拟器只用来验证 UI 布局和普通逻辑。别在模拟器上花时间排查音频问题,那是死路。
我也遇到过一次真机上 deviceInfo.deviceId 在无网络时获取为空,导致 tokenProvider 拿不到合法设备标识,鉴权失败。加了兜底逻辑,为空时从本地持久化缓存取上一次的值,并提示用户检查网络。
6. 验证、灰度与后续演进建议
6.1 回归测试集与自动化脚本
重构完成后,我先录了一批测试音频,覆盖了各种说法变体,存成测试资源。这些音频同时也是回归测试的输入。自动化脚本遍历每条测试音频,把识别结果、意图命中率、动作执行准确率、端到端耗时落成指标。
重点看的指标有三个:
- 意图识别准确率:目标 95% 以上,低于 90% 直接阻塞发布。
- 端到端延迟:从用户停止说话到界面展示最终结果,目标 1.5 秒内。
- 动作执行成功率:业务 Action 无异常回执的比例。
实测下来,Copilot SDK 的意图识别准确率确实比我在原生 ASR 后面硬接关键词的方式高,尤其是在用户换语序、说半截话的情况下。端到端延迟比原生 ASR 慢 400ms 左右,但这个差距换来了“能理解意图”和“能执行动作”,对产品价值来说是划算的。
6.2 灰度发布:用开关决定走哪条链路
我前面提到保留了原生 ASR 链路,就是为了灰度。具体做法是在服务端下发一个配置项 assistant_engine,值可以是 native_asr 或 copilot。客户端启动时拉取配置,选择对应的 SpeechAssistant 实现。这样一旦 Copilot SDK 在某个机型上出现问题,可以远程切回原生 ASR,而不是紧急发版。
灰度顺序:先内部测试机 -> 种子用户 -> 5% 流量 -> 50% 流量 -> 全量。每个阶段观察 crash 率、音频上报异常、用户主动关闭语音功能的比率。
6.3 日志与监控的沉淀
SDK 的日志级别上线后我调成了 WARN,避免刷屏。但自己业务侧的事件日志要打全,尤其是状态机切换、Action 执行结果、错误码。我把这些日志统一上报到日志平台,后面排查问题时可以按用户维度还原当时发生了什么。
给一个建议:日志一定要带 sessionId 和 requestId。Copilot SDK 的事件里通常带这两个字段,把它们原样透传到日志里,后面找问题能省一半时间。
6.4 后续演进:离线兜底、多语言与联调生态
当前版本 Copilot SDK 在断网时只能依赖本地离线指令集,覆盖不了复杂意图。我们的下一步是把高频指令(开关灯、调温度、设置提醒)压到离线可识别,网络不可用时至少保住基础体验。
多语言则更简单,SDK 支持多语言配置,切换 language 字段就能覆盖英语场景。真正难的还是不同语言的意图模板覆盖率,需要靠运营侧持续补充语料。
最后说一点个人体会:我这套架构里,最满意的不是 Copilot SDK 本身,而是那一层 SpeechAssistant 接口。一开始同事觉得多此一举,等我们后来真的从 Copilot SDK 新版本换到另一个云厂商的 SDK 时,UI 层几乎零改动,只重写了那个实现类。做技术对接,永远是抽象先行。如果你也正在从原生 ASR 往 Copilot SDK 迁移,建议第一步就把接口设计好,而不是先把 SDK 接进来再想着抽层。
