# EMAI 助手:端上听写 + 对话 + 合成 —— 设计 > 版本:v0.2  日期:2026-10-18 > 状态:**设计稿,未写代码。** > 范围:耳机唤醒和 App 里按住说话,两条入口共用这一套。通话 / 同声 / 面对面 / 音视频不在本文。 > 对照:[科大讯飞SparkChain-全面替换第三方-落地文档](科大讯飞SparkChain-全面替换第三方-落地文档.md) 第 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 按住说话,问同一句,走的是同一套编排。