You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

8.6 KiB

EMAI 助手:端上听写 + 对话 + 合成 —— 设计

版本:v0.2  日期:2026-10-18 状态:设计稿,未写代码。 范围:耳机唤醒和 App 里按住说话,两条入口共用这一套。通话 / 同声 / 面对面 / 音视频不在本文。 对照:科大讯飞SparkChain-全面替换第三方-落地文档 第 5.5 节是「三段接上、先问通」;本文把时间、地点、天气、新闻、个人工具的分工定死。


零、结论

每轮对话都带上手机本地时间和缓存地址,只解决「现在几点、今天星期几、我在哪」。

天气、新闻和其它要联网才知道的问题,模型手里只有这两样事实,答不出来。这两类仍由手机执行工具,把结果喂回模型,再合成。

个人数据(纪要、拾忆、待办)同样是手机拿登录身份去调已有 MCP。对话本身不经过业务接口。


一、时间地址怎么带(相对「原样塞进每句话」改过的地方)

做法 原因
时间 每一轮用户说完,用手机本地钟重新拼一次 会话可能持续十几分钟。「现在几点」不能用开场那一刻
地址 只读 DeviceLocationService 的缓存,这一轮不等 GPS 定位会拖住开口。缓存规则维持现状:回前台刷新,10 分钟内不重复
放哪 放进本轮系统提示,不拼进用户原话,不写入聊天历史 写进原话,模型会当成用户自己说的;写进历史,旧时间会把「现在」答错
经纬度 不进模型。只有天气工具要用时,手机自己附上 模型念出坐标没有用,还容易被读出来
没有位置 系统提示写「当前没有位置」,不要写「未知」 「未知」会被当成地名
专问「我在哪」且缓存空 只这一问允许再等一次定位,上限 4 秒,到点仍没有就按「当前没有位置」答 别的问题(含天气)不为这一问阻塞

时间文案用手机时区,带星期和偏移,例如:2026年10月18日 星期日 00:59,东八区(UTC+8)。

地址文案沿用现在能念出来的那串(完整地址,没有再退省市区街道)。念「我在哪」用这串。查天气只用其中的城市名。


二、整轮怎么走

SDK 没有百炼那种边听边说。一轮是说完再答。耳机协议、问候语、打断、后台保活维持第 5.5 节,本文只换「听完之后谁来答」。

麦克风 PCM
  → 讯飞 SDK 实时转写,区域 RegionMSFT,语种 = 下面第三节
  → 一句最终文本
  → 本轮系统提示(新时间 + 缓存地址 + 回答规矩)+ 最近对话 + 工具清单
  → 讯飞 SDK 文本对话 LLM(端上,不经业务服务器)
        ├─ 直接有文字 → 讯飞 SDK 在线合成,区域 RegionMSFT,音色 = 第三节
        └─ 返回 functionCall → 手机执行工具(最多两轮)→ 结果喂回 LLM → 再合成

两条入口(DeviceAiSessionService 耳机唤醒、EmaiController 按住说话)共用这一个编排。PCM 从哪来、喇叭往哪放,仍由各自的会话服务决定。

空识别不调用对话模型。工具失败时合成「暂时查不到」,不让模型编一个天气或一条新闻。工具来回超过两轮就停,用已经拿到的结果回答;还什么都没有就说查不到。

对话模型用 SDK 的文本 LLM(海外 domain=openai,文档点名 gpt-5.3-chat / gpt-5.4)。听写和合成才是 SDK 里的微软区域(RegionMSFT)。会议总结走同一套 OpenAI 文本模型,不走星火,也不拿来做 EMAI 的实时对话。


三、识别语种和合成音色

跟设置里的界面语言走,不跟用户刚说的那一句走。

设置页切换语言时,settings_controller 已经把该语言收成一条 ASR 区域码(跟随系统则用匹配后的系统语言,兜底 en-US)。EMAI 直接用这一条:

取值
实时转写 lang 上面那条区域码,例如界面中文 → zh-CN
合成发音人 同一条语言在音色表里的神经语音。性别只有 1 用男声;未设置 0 和女 2 用女声。用户对象还没就绪时用女声
回答语言 与这条界面语言一致,写进系统提示

用户用别的语言插一句,本轮仍按界面语言识别和回答。要换语种,去设置里改,不在对话中途猜。


四、问题分成四类

系统提示里写明:时间、星期、日期、地点只用本轮给出的事实,不要调用工具,也不要改口。

用户在问 谁处理 落地
几点、星期几、今天几号 模型直接答 只用本轮时间。不调工具
我在哪 模型直接答 有地址就念地址。没有就说还没有位置,不要编一个城市
天气 手机执行 hefeng_weather 城市用缓存里的城市名,日期用本轮日期。有城市就不要反问「哪个城市」。没有位置时才问一次城市,问到了再查
新闻、实时信息 手机执行联网搜索 搜索词带上本轮日期,避免「今天」落到模型的训练截止日期。海外节点用 tavily_web_search,国内节点用 bocha_web_search,和现有 MCP 分组一致
我的会议、记忆、待办 手机执行个人 MCP 见第五节。没有的就说没有,不要编

天气和搜索的密钥留在现有 MCP 进程里。手机只发起这一次工具调用,不把天气接口嵌进对话链路,也不把密钥放进安装包。

和风天气的入参是城市名加可选日期,不是经纬度。所以坐标只在缓存里,给不了城市名时不能拿坐标假装能查。


五、个人 MCP

手机调用,数据仍在用户自己的服务器上。这一跳是工具,不是把整段对话交给业务接口。

沿用现在这 7 个、且要身份的工具:

search_meeting_notes、get_memory_items、get_memory_stats、get_memory_report、get_user_tasks、add_user_task、cancel_user_task。

身份仍用 user_getmcptoken 的短时令牌,按工具名附在调用上。令牌不写进系统提示,不进历史,不出现在给模型的工具参数里。模型只看到工具名和参数说明。

百炼时期令牌要按工具名嵌进 user_defined_params,是因为第三方云在代调。换成手机自己调之后,手机在发 MCP 请求时带上令牌即可,形状不再跟百炼的嵌套走。

hefeng_weather 不在这 7 个里面。它和联网搜索不需要用户身份,手机调用时不要附登录令牌。


六、和现在百炼的对应

百炼现在 换成
会话开始时把日期、地址放进 user_prompt_params,整场不变 每轮重算时间;地址仍只读缓存
client_info.location 把经纬度交给云端 经纬度不进模型
云端插件自己搜网页、自己查天气 模型只返回要调哪个工具;手机执行,再把结果喂回去
个人 MCP 由云端代调,令牌按工具名塞进会话 手机自己调,令牌只附在那一次请求上
边听边说 做不到。说完一句再答

测试服 aiOnlyIflytek 已经让百炼 start 直接返回失败。本文落地前 EMAI 在测试服起不来。不要为了对照把百炼重新打开。


七、明确不做

  • 不把对话转发到 user_* 业务接口,也不在服务端再包一层「代答」。
  • 不把会议总结的星火接口拿来当 EMAI 的对话模型。
  • 不在每一轮阻塞等 GPS。
  • 不把经纬度、令牌、天气密钥写进提示词。
  • 不改耳机协议字节,不改通话 / 同声 / 面对面 / 音视频的选路。
  • 第一版不恢复百炼的全双工。工具调用失败不影响「问一句有回答」。

八、验收

  1. 问「现在几点」「今天星期几」:答的是手机本地时间,和会话开始时的时间可以不同。
  2. 定位开着时问「我在哪」:念缓存地址,不念经纬度。关掉定位再问:说没有位置,不编城市。
  3. 有城市缓存时问天气:不反问城市,答案来自工具。把工具结果改成失败时,嘴里是「暂时查不到」,不是编出来的温度。
  4. 问「今天新闻」:搜索词里带当天日期,回答来自搜索结果。
  5. 问「我最近的会议 / 待办」:走个人 MCP,换一个账号看不到上一个账号的数据。抓对话请求里看不到令牌。
  6. 设置改成英语后,识别和回答都是英语,音色是该语言的女声;性别设为男之后换成男声。
  7. 耳机按键和 App 按住说话,问同一句,走的是同一套编排。