传统机器人技术架构(传统非大模型方案)
传统机器人整体架构图与流程图
1. 整体架构图(分层框图)
flowchart TB
subgraph 用户侧
A[用户语音]
A2[前端播放]
end
subgraph 前端硬件与SDK["前端硬件 / SDK"]
B[录音 / 降噪 / VAD]
B2[音频推流]
B3[TTS 播放]
end
subgraph 云端服务["云端服务"]
subgraph ASR["ASR 服务"]
C[语音识别]
end
subgraph NLP["NLP 服务"]
D1[分词]
D2[词典 Dict]
D3[文法 Grammar]
D4[意图 / 槽位 / 领域]
end
subgraph 网关["API 网关"]
G[鉴权 / 限流 / 服务发现 / 监控]
end
subgraph DM["DM 对话管理"]
E1[对话状态维护]
E2[多轮 / 填槽 / 兜底]
E3[Lua:调下游技能 + 控对话]
E4[技能分发决策]
end
subgraph SkillRouter["技能路由 Skill Manager"]
F[domain / intent / 设备配置 / 显式指定]
end
subgraph 技能["具体技能(下游技能服务)"]
S1[音乐]
S2[视频]
S3[天气]
S4[有声]
S5[医生 / 百科 …]
end
subgraph TTS["TTS 服务"]
H[语音合成]
end
end
subgraph 第三方["第三方 / 后台服务"]
T[音乐 API / 天气 API / 百科 …]
end
A --> B --> B2 --> C
C --> D1 --> D2 --> D3 --> D4
D4 --> G
G --> E1 --> E2 --> E3 --> E4
E4 --> F
F --> S1 & S2 & S3 & S4 & S5
S1 & S2 & S3 & S4 & S5 --> T
T --> E1
E1 --> H
H --> B3 --> A2
3. 场景绑定流程图(NLP Launcher 分发)
通过场景绑定,结构化的语义表示和对话流经 NLP Launcher 处理,分发给特定场景/功能。
flowchart LR
subgraph 输入["NLP 输出(结构化语义)"]
IN["domain: music_1<br/>intent: search_music<br/>slot: singer陈粒<br/>对话流: 无"]
end
subgraph Launcher["NLP Launcher"]
L[场景绑定 / 分发]
end
subgraph 场景["场景 / 功能"]
M1[音乐]
M2[视频]
M3[有声]
M4[天气]
M5[…]
end
IN --> L
L -->|实线| M1
L -.->|虚线| M2
L -.->|虚线| M3
L -.->|虚线| M4
L -.->|虚线| M5
NLP Launcher 与场景绑定(本质定义)
- Launcher(桌面)的本质:是调度 App 的工具。传统机器人的 App 同时支持语音和图形界面,因此既能通过触屏调度,又能通过语音调度。
- NLP Launcher 是什么:语音交互的分发机制。NLP Launcher 通过分发语音指令,来调度不同的功能(App)。每个功能(App)在 NLP Launcher 下创建场景,通过场景绑定,NLP Launcher 在收到语音指令后就能把特定的信息分发给对应的场景/功能(如视频、有声、天气)。
- 场景绑定是什么:
- 把文法、对话流绑定在具体的场景/功能里(如视频、有声、天气);
- 再把该功能绑定到负责分发的 NLP Launcher 下。
NLP Launcher 根据这些绑定关系,把语音指令的处理结果(领域 domain、意图 intent、词槽 slot、对话流)分发给对应的场景/功能。
NLP Launcher 是干啥的?如何识别是哪个技能?同时命中多个怎么办?
-
NLP Launcher 是干啥的?
NLP Launcher 是语音指令的分发中枢:接收 NLP 产出的结构化语义(domain、intent、slots)和对话流信息,根据你在平台里配的场景绑定,把当前这句话分发给对应的场景/功能(如音乐、视频、有声、天气、医生等)。
可以理解为:Launcher 不负责理解语义(那是 NLP 的事),只负责「这句话已经识别成 domain=X、intent=Y 了,该交给哪个场景处理」。每个 App 在 Launcher 下挂若干场景,文法 + 对话流绑定到具体场景上,Launcher 按绑定关系把请求丢给对应场景,后续由该场景下的 DM、技能路由、具体技能去执行。 -
如何识别是哪个技能的?
分两步:- Launcher 先认「场景」:根据 NLP 的 domain(和 intent) 查场景绑定表,得到「这条请求属于哪个场景」(如 music → 音乐场景)。
- 该场景内再选「具体技能」:同一场景下可能有多条技能(例如音乐场景下有网易云、QQ 音乐等),由 DM + 技能路由(Skill Router) 按设备配置、用户显式指定、意图优先级/置信度等,在候选里选出一个技能去调。
所以:识别到「哪个技能」= Launcher 识别到场景 + 该场景内技能路由选出唯一技能。
-
如果同时命中多个技能呢?
同一句用户话可能命中多个 domain/多个意图(例如既像音乐又像有声),或同一 domain 下多个技能都满足。处理方式:- 多 domain/多意图:按平台配置的领域/意图优先级对候选排序,只取第一个作为当次请求的结果,后续 DM 和技能只针对这一个结果执行。
- 同一 domain 下多技能:由技能路由按设备/渠道默认、用户显式指定、置信度再排一次序,同样只选一个技能执行。
未配置优先级时,文档写的是「结果随机」或按系统默认顺序取第一个,因此若要行为可预期,需要在平台上把领域意图优先级和技能优先级/默认技能配好。
-
典型歧义示例:音乐 vs 有声/FM
同一句话可能同时命中不同场景的句式和槽位,例如用户说「打开郭德纲相声」:- 音乐技能可能命中:把「郭德纲相声」当成歌单/专辑类内容,走 music 场景;
- 有声/FM 技能可能命中:把「相声」当成有声读物/电台内容,走 fm / 有声场景。
此时 NLP 可能产出多个候选(如 domain=music 与 domain=fm 都命中),最终走哪个场景由 Launcher 的领域/意图优先级决定:平台里若把「有声」或「FM」配得比「音乐」高,就会进有声/FM 技能;反之则进音乐技能。若要固定行为(例如相声统一走 FM),需在领域意图优先级里把对应 domain 的先后顺序配好。
场景绑定 / 分发 是咋实现的?规则是啥?
-
实现方式(平台侧)
在 传统机器人 的场景管理里,每个 NLP Launcher 下会挂多个场景(如音乐、视频、有声、天气、医生等)。每个场景要配置「绑定」关系:把哪些 domain(以及可选的 intent、对话流) 算作属于这个场景。也就是说:- 你先在「文法配置」里定义好 domain/intent(如 music、play_music);
- 再在「场景管理」里给每个场景指定:当 NLP 输出 domain=某某(或 domain+intent=某某)时,这条请求归这个场景处理。
同时,文法和对话流在配置时会关联到某个场景,所以 Launcher 知道「这段文法/这条对话流属于哪个场景」。
-
实现方式(运行时)
Launcher 收到 NLP 的输出(domain、intent、slots、对话流等)后:- 查场景绑定表:看当前 domain(和 intent)被绑到了哪个场景;
- 若当前已在某条对话流中,可能按「对话流归属场景」直接继续交给该场景;
- 把请求(含 domain、intent、slots、对话流)分发给匹配到的场景,由该场景下的 DM、技能路由、具体技能去执行。
-
规则是啥(匹配规则)
- 主键一般是 domain:一条请求先看 domain 属于哪个场景;可细化为 domain + intent(同一 domain 下不同 intent 可绑到不同场景或同场景不同逻辑)。
- 一对一或多对一:一个 domain 通常只绑一个场景(如 music → 音乐场景);若多个场景都声明能处理同一 domain,则靠领域/意图优先级排序,取第一个。
- 对话流:若请求里带了「当前对话流」,可用来决定继续在哪个场景(对话流本身在配置时已挂在某场景下)。
- 未命中:若 domain 没有绑定到任何场景,走兜底逻辑(如默认场景或拒识)。
-
小结
场景绑定 = 在平台里配置「domain(+ intent/对话流)→ 场景」的映射;分发 = Launcher 用 NLP 输出查这张映射,把请求转发给对应场景。规则就是:按 domain(及可选 intent、对话流)匹配绑定表,多命中时按优先级取第一个。
平台配置操作步骤(参考 传统机器人 开发者文档)
以下为在猎户开发者平台中,从创建 App/场景到绑定对话流的典型操作顺序,便于与上文「场景绑定」对应。
| 步骤 | 操作位置 | 说明 |
|---|---|---|
| 1. 新建 APP | 场景管理 → APP 和场景 | 点击右上角「新建」,填写 APP 名称 和 APP ID(ID 可在「发布应用」中生成)。初始状态下组织下无 APP,需先建 APP。 |
| 2. 新建场景 | 同上,每个 APP 后 | APP 建好后默认有一个场景 Main。可点击该 APP 后的「添加场景」新增场景;场景名仅支持英文,同 APP 下不可重名。新增后会生成场景 ID。功能简单的 APP 可只使用 Main。 |
| 3. 配置领域、意图、词槽(文法/词典) | NLP 配置 → 编辑意图 / 文法配置 / 词典配置 | 领域(domain):同一类数据/服务,英文命名且唯一(如 music、music_1)。意图(intent):用户对领域数据的操作,英文动词(如 search_music)。词槽(slot):实现意图所需参数(如歌手、城市、日期);可设必填与追问话术(缺槽时多轮追问)。文法:把用户句匹配为 domain + intent + slots;词典:实体/词表,供文法占位符识别。先配好词典与文法,NLU 才能产出结构化结果。 |
| 4. 配置对话流 | NLP 配置 → 对话流管理 | 新建对话流(如「音量多轮」),在画布上添加 触发单元(domain+intent 条件)、回复单元(话术)、功能单元(可选)、分支(如「再大点」「再小点」)等,定义多轮逻辑。对话流在创建/编辑时可关联到具体场景。 |
| 5. 编辑场景并绑定 | 场景管理 → APP 和场景 → 某场景「编辑」 | 点击场景后的「编辑」。场景编辑即把当前场景与设备、对话(含意图与对话流)做关联。默认无关联,需点击「新增配置」→ 选择 设备组 和 对话(即已配置好的意图+对话流)→ 确认。完成后点「确定」完成编辑。 |
| 6. 搜索与维护 | 同上列表页 | 支持按 APP 名称、场景名称、关联设备组、关联领域 搜索场景和 APP,便于后续维护。 |
顺序小结:新建 APP → 新建场景 → 配置领域/意图/词槽(文法+词典)→ 配置对话流并关联场景 → 编辑场景,绑定「设备组 + 对话」。绑定后,NLP Launcher 才能把语音指令的处理结果(domain、intent、slot、对话流)分发给对应场景/功能。
关键模块拆解
① NLP Launcher
- 作用:整个 NLP 系统的入口分发器,所有用户输入都会先到这里。
- 逻辑:根据用户说的话(NLU 产出的 domain/intent 等),判断应该交给哪个子场景去处理(如 global_command、weather、workflow),实现多技能的路由。
- 特点:本身不处理具体业务,只负责「调度」。
② global_command(全局命令场景)
- 作用:NLP Launcher 下的一个特殊子场景,用来处理全局通用指令,例如:
- 唤醒词、退出、返回上一级(go_back)
- 音量调节、停止播放等设备控制
- 特点:优先级高,无论当前在哪个业务场景,全局命令都能被优先识别和执行。
③ 编辑场景
- 作用:点击「编辑场景」后,在该场景里可以:
- 绑定对应的领域(Domain)(如音乐、天气、导航)
- 配置该场景下支持的意图(Intent)
- 关联对应的对话流(Workflow),定义多轮对话逻辑
- 本质:把 NLU(文法 + 词典) 和 DM(对话流) 绑定到一个场景里,让系统知道「这个场景能处理什么话、怎么处理」。
完整工作流(真实逻辑)
- 用户说一句话 → 先到 NLP Launcher
- Launcher 根据规则,把请求分发到对应的子场景(如 weather、music、global_command)
- 子场景里的文法/词典做 NLU 解析,识别出意图和槽位(若尚未在 Launcher 前统一做 NLU,则按实际架构可能在该场景内或前置完成)
- 触发该场景绑定的对话流(Workflow),执行 Go + Lua 脚本逻辑
- 最后生成回复或执行动作,经 TTS 或技能服务返回用户
即:用户输入 → Launcher 分发 → 子场景 → 该场景 NLU + 绑定的对话流 → Go+Lua 执行 → 回复/动作。
4. 对话流示例:远程医生多轮对话
以「远程医生」为例:用户说「胃疼怎么办」→ 识别意图 → TTS 反问 → 根据用户响应分支结束或转百科。
绘制或引用时请勿截断:NLP 识别须写全 domain=doctor_1 与 intent=ask;TTS 询问须写全整句「身体不舒服?需要帮您呼叫远程医生吗」。
flowchart TB
Start["用户输入<br/>如「胃疼怎么办」"] --> NLP["NLP 识别<br/>domain=doctor_1 且 intent=ask"]
NLP --> TTS["TTS 询问<br/>身体不舒服? 需要帮您呼叫远程医生吗"]
TTS --> Judge{"判断 domain / 用户响应"}
Judge -->|用户确认<br/>domain=general_command_3<br/>intent=confirm 或 点击确认| End1["结束对话,结果回传"]
Judge -->|3s 超时| End2["结束对话"]
Judge -->|用户取消<br/>domain=general_command_3<br/>intent=cancel 或 点击取消| End3["将 query「胃疼怎么办」<br/>传给百科服务,并结束对话"]
End2 --> End2b["结束"]
一、整体语音链路
用户语音
↓
【前端硬件/SDK】(音箱、机器人、APP)
↓
【ASR 服务】(语音识别)
↓
【NLP 服务】(分词、词典、文法解析、意图/槽位)
↓
【网关】(鉴权、限流、环境隔离、服务发现)
↓
【DM 对话管理】(Dialogue Manager,核心调度;多轮时用 session 维护上下文)
↓
【技能路由 / Skill Manager】
↓
【具体技能(下游技能服务,非 Lua 实现)】 ← 多轮且缺槽时 DM 不走到这里,先 TTS 追问,等用户下一句再走整条链路
↓
【第三方服务/后台服务】(音乐、天气、视频、控制等)
↓
【DM 汇总结果】
↓
【TTS 服务】(播报:执行结果 或 追问话术)
↓
前端播放
记忆里的链路 ASR → NLP → 网关 → DM(Lua)→ 技能 完全正确。其中 Lua 在 DM 层,负责调用下游技能服务和控制 DM 对话;具体技能本身是独立的下游服务,不是用 Lua 实现的。下面按每一层的职责、实现、以及你配置的词典/文法如何串进去说明。
多轮对话在整体链路里如何体现?
上面这条链路是每一轮都会走的:用户说一句 → ASR → NLP → 网关 → DM(带 session)→ Lua/对话流判断。
- 若槽位已齐或无需补槽:DM 调技能 → 结果 → TTS → 结束或进入下一话题。
- 若缺槽:DM 不调技能,而是根据 Lua/对话流返回追问话术(如「您想听哪首歌?」)→ TTS 播报 → 本轮结束,但 session 里保留当前 intent 和已填槽位;用户下一句再说话时,重新走一遍 ASR → NLP → … → DM,DM 把新一句的解析结果和 session 里已有内容合并,再判断是继续追问还是槽位齐了去调技能。
所以多轮对话 = 同一套链路多跑几轮,由 DM 的 session + 对话流/Lua(缺槽则追问、槽满则调技能) 决定每一轮是「追问」还是「执行技能」。
先弄清这几件事
domain、intent、slots、对话流 分别指啥?
NLP 输出和 Launcher/DM 用的四个核心概念,统一定义如下:
| 名词 | 英文 | 是啥 | 举例 |
|---|---|---|---|
| Domain | 领域 | 最高层的业务分类:这句话属于哪一大类功能。Launcher 主要靠 domain 决定把请求分发给哪个场景。 | 音乐、天气、音量、视频、医生、通用指令 |
| Intent | 意图 | 在领域下的具体动作:用户在该领域里要做的操作,一个 domain 下可有多个 intent。 | 播放音乐、暂停音乐、搜索音乐、查天气、调大/调小音量 |
| Slot | 槽位 | 执行意图所需的参数:从用户话里抽出来的键值对,缺了就要多轮追问补全。 | 歌手、歌曲名、城市、日期、音量档位 |
| 对话流 | — | 多轮对话的流程配置:当前若处于某条对话流中,会带「对话流」信息,DM 按对话流决定下一轮追问还是执行;对话流在配置时挂在某场景下。 | 无(首句)/ 某条对话流 ID 或名称 |
平台里 domain/intent 常用英文标识(如 music、play_music),便于绑定与分发;上面举例用中文便于理解。Launcher 根据 domain(和 intent) 做场景绑定与分发。
文法配置、词典配置到底是干啥的?
-
一句话:
词典 = 系统要识别的「词都是啥」(歌手名、歌名、城市名…);文法 = 系统要识别的「句子长什么样、对应哪个意图和哪些槽位」。
用户说一句话 → 先用词典认出里面的实体(谁、什么、哪里)→ 再用文法规则匹配句式,得到「领域 + 意图 + 槽位」。 -
举个例:用户说「播放周杰伦的七里香」
- 词典:配置了「周杰伦」属于歌手、「七里香」属于歌曲名,分词/实体识别才能把这两个词标成「歌手」「歌曲」。
-
文法:配置了规则如
播放 <歌手> 的 <歌曲>→ 对应领域=音乐、意图=播放音乐、槽位 singer=xxx, song=xxx。(<歌手>/<歌曲>是文法里的占位符,由词典识别的实体来填。)- 合起来:词典把「周杰伦」「七里香」识别成实体,文法把整句匹配到该规则,输出
domain=music, intent=play_music, slots: singer=周杰伦, song=七里香。 所以:不配词典,系统不知道「周杰伦」是歌手;不配文法,系统不知道这句话是「播放音乐」而不是别的意图。
- 合起来:词典把「周杰伦」「七里香」识别成实体,文法把整句匹配到该规则,输出
示例:文法配置和词典配置长什么样?
下面用「音乐」和「天气」两个领域,给出你在平台里实际会配的大致内容(格式依平台界面可能略有差异,逻辑一致)。
1)词典配置示例
词典 = 一张「词 → 属于哪个槽位/类型」的表。NLP 分词后查表,把词标成对应槽位,供文法规则里的占位符填值。
| 词条 | 槽位/类型 | 说明 |
|---|---|---|
| 周杰伦、陈粒、邓紫棋 | singer | 歌手名,填到「播放/搜索音乐」的歌手槽 |
| 七里香、光年之外、小半 | song | 歌曲名,填到歌曲槽 |
| 北京、上海、杭州 | city | 城市名,填到「查天气」的城市槽 |
| 今天、明天、后天 | date | 日期,填到天气日期槽 |
| 网易云、QQ音乐、酷狗 | music_source | 音乐来源,用于技能路由时「用户显式指定」 |
同义词也可在词典里配,例如「播」→ 同义「播放」,「查」→ 同义「查询」。
不配「周杰伦」进 singer:用户说「播放周杰伦的七里香」时,系统可能识别不出「周杰伦」是歌手,槽位为空或错误。
词典配置具体例子(仿平台,可直接照抄扩展)
在平台「词典配置」里按「槽位/类型」建词表,每条一行「词 → 属于哪个槽」。示例:
# 槽位: singer(歌手)
周杰伦 陈粒 邓紫棋 毛不易 李荣浩
# 槽位: song(歌曲名)
七里香 光年之外 小半 消愁 年少有为
# 槽位: city(城市)
北京 上海 杭州 深圳 广州
# 槽位: date(日期)
今天 明天 后天 周一 周末
# 同义词(动作词,供文法匹配)
播 -> 播放 放 -> 播放 想听 -> 播放
查 -> 查询 查一下 -> 查询
分词得到「周杰伦」时查词典 → 标为 singer;「七里香」→ song;「播」按同义词当「播放」参与文法。
2)文法配置示例
文法 = 一组「句式模板 → 对应哪个 domain、哪个 intent、哪些 slot」的规则。用户说的话和模板匹配上,就得到结构化结果。
| 句式规则(占位符用尖括号) | domain | intent | 槽位 |
|---|---|---|---|
播放 <singer> 的 <song> | music | play_music | singer, song |
我想听 <song> | music | play_music | song |
搜索 <singer> 的歌 | music | search_music | singer |
| 暂停 / 暂停播放 | music | pause_music | (无) |
<city> 今天天气怎么样 | weather | query_weather | city, date(今天) |
查一下 <city> 明天天气 | weather | query_weather | city, date(明天) |
占位符 <singer>、<song>、<city> 等必须和词典里配置的槽位/类型名一致,解析时用词典识别出的实体去填。
例如用户说「播放周杰伦的七里香」→ 匹配第一行规则 → 输出 domain=music, intent=play_music, slots: {singer=周杰伦, song=七里香}。
文法配置具体例子(仿平台规则 + 匹配过程)
在平台「文法配置」里为每个 domain 写多条规则:句式模板 + domain + intent + 要抽取的 slot。下面给几条可照抄的规则,并跟两条「用户说 → 匹配 → 输出」的完整例子。
| 规则 | 句式模板(占位符与词典槽位名一致) | domain | intent | 抽取的 slot |
|---|---|---|---|---|
| G1 | 播放 <singer> 的 <song> | music | play_music | singer, song |
| G2 | 我想听 <song> | music | play_music | song |
| G3 | 放一首 <song> | music | play_music | song |
| G4 | 搜索 <singer> 的歌 | music | search_music | singer |
| G5 | 暂停 / 暂停播放 / 别放了 | music | pause_music | — |
| G6 | <city> 今天天气怎么样 | weather | query_weather | city, date |
| G7 | 查一下 <city> 明天天气 | weather | query_weather | city, date |
例 1:用户说「我想听陈粒的小半」
- 分词:我、想、听、陈粒、的、小半
- 词典:陈粒→singer,小半→song;想听→播放
- 文法:命中 G2「我想听 <song>」→ domain=music, intent=play_music, 抽 slot:song=小半
- 输出:
{ "domain": "music", "intent": "play_music", "slots": { "song": "小半" } }
例 2:用户说「北京今天天气怎么样」
- 分词:北京、今天、天气、怎么样
- 词典:北京→city,今天→date
- 文法:命中 G6「<city> 今天天气怎么样」→ domain=weather, intent=query_weather, slots: city=北京, date=今天
- 输出:
{ "domain": "weather", "intent": "query_weather", "slots": { "city": "北京", "date": "今天" } }
3)二者如何配合(一句话)
- 词典:告诉系统「周杰伦」是 singer、「七里香」是 song、「北京」是 city。
- 文法:告诉系统「播放 <singer> 的 <song>」这句话 → 属于 music 领域、play_music 意图,并抽出 singer、song 两个槽。
- 两者一起,才能把「播放周杰伦的七里香」变成
{domain=music, intent=play_music, slots: {singer=周杰伦, song=七里香}},供后面 DM 和技能使用。
这里的 NLP 用的是机器学习吗?
- 不是。 猎户这条链路里,意图/槽位/领域的解析是规则式 NLU:靠你配置的词典 + 文法做匹配,不用深度学习/大模型来做意图识别。
- 通常会用机器学习的是 ASR(语音转文字)和 TTS(文字转语音);而「这句话是啥意图、槽位填了啥」是在 NLP 里用规则+词典算出来的,所以可解释、可配置、可热更新,但泛化能力不如模型。
网关层在哪儿、干啥?
- 位置:NLP 输出之后,DM / 技能 / 第三方服务调用之前。所有「去调具体技能或第三方 API」的请求都先经过网关。
- 职责:鉴权、限流、环境隔离(测试/预发/线上)、服务发现、日志与监控。
- 下面「每层详细职责」里会单独写一档 4)网关。
二、每层详细职责(传统机器人真实架构)
1)前端硬件 / SDK
- 负责录音、音频采集、降噪、VAD
- 把音频流推给云端 / 本地 ASR
- 接收最终 TTS 音频并播放
2)ASR(语音识别)
- 猎户自研或私有化部署的 ASR:音频 → 文本
- 输出:
text+confidence - 可热更新领域语言模型(你配的词典会影响这里的识别率)
3)网关层(API 网关 / 服务网关)
- 在链路中的位置:NLP 输出(domain / intent / slots)之后,DM 和技能、第三方服务之前。所有对「具体技能」和「第三方 API」的请求都经过网关,再转发到下游。
- 职责:鉴权、限流、环境隔离(测试/预发/线上)、服务发现、日志与监控、链路追踪。
- 所以你说「ASR → NLP → 网关 → DM → 技能」里的网关就是这一层,有独立一层描述。
4)NLP(核心:你配的词典 + 文法就在这一层)
这一层是传统规则式 NLU:用词典 + 文法做意图/槽位解析,不用机器学习做意图识别(ASR/TTS 可能用 ML,但意图解析是规则匹配)。
4.0 NLP 服务内部流程(说清楚顺序与职责)
平台里 NLP 服务 内部是按下面顺序跑的,和流程图一致:
用户文本(ASR 输出)
↓
┌─────────────┐
│ 分词 │ 把整句切成词/ token
└─────────────┘
↓
┌─────────────┐
│ 词典 Dict │ 查词典配置:实体类型、同义词、槽位
└─────────────┘
↓
┌─────────────┐
│ 文法 Grammar │ 用文法配置:匹配句式 → domain / intent / slots
└─────────────┘
↓
输出:domain、intent、slots → 交给 NLP Launcher / DM / 技能路由
每一步做什么、和你配置的「词典」「文法」怎么对应,说清楚如下。
| 步骤 | 名称 | 输入 | 做什么 | 你配置的对应关系 |
|---|---|---|---|---|
| 1 | 分词 | 用户文本(ASR 来的) | 把整句切成词/ token,例如「我想听周杰伦的歌」→ 我、想、听、周杰伦、的、歌 | 不直接配;可选热词/领域词影响 ASR 或分词粒度 |
| 2 | 词典 Dict | 分词结果 | 查词典配置:哪些词是实体、属于哪个槽位/类型(如「周杰伦」→ 歌手)、同义词(如「想听/播放/放」→ 播放)。给每个词打上语义标签,供文法用 | 词典配置:词条 → 槽位/类型/同义词,见下文 4.1 |
| 3 | 文法 Grammar | 带词典标签的文本 | 用文法配置的规则做模式匹配:哪种词序+实体组合对应哪个 domain / intent,并抽槽位(如 [播放] + [歌手] + [歌曲] → domain=music, intent=play_music, slots: singer=周杰伦)。输出结构化结果 | 文法配置:句式规则 → domain、intent、slot,见下文 4.2 |
| 4 | 输出 | 文法结果 | 得到 domain、intent、slots(及对话流等),交给下游 | 无单独配置;即 4.3 的 JSON/层级结构 |
和 Launcher、技能识别的关系:
- NLP 服务 的最终输出(domain、intent、slots)就是 NLP Launcher 的输入:Launcher 根据这些字段做场景绑定,把请求分发给对应场景。
- 识别是哪个技能:先由 Launcher 按 domain/intent 确定场景,再在该场景内由 DM/技能路由按设备配置、用户显式指定、优先级等选出唯一一个技能。
- 同时命中多个:多 domain/多意图时按领域意图优先级取第一个;同场景下多技能时按默认/显式指定/置信度再取一个。详见前文「NLP Launcher 是干啥的?如何识别技能?多命中怎么办?」。
4.1 词典(Dict)
- 领域词表
- 同义词
- 实体:歌手、歌曲、城市、日期、设备名…
- 作用:在「分词」之后、「文法」之前,给词打上类型/槽位标签,让文法规则能命中并填槽。
4.2 文法(Grammar / CFG)
你在平台配的就是这个,句式规则例如:
播放 <歌手> 的 <歌曲>打开 <应用>音量 <数值>
输出:intent 意图、slots 槽位、domain 领域。
4.3 NLP 最终输出结构
平台侧解析后的结果在配置界面中常以层级结构呈现(见下图示意):先按 domain 分领域,再在领域下挂多个 intent,每个 intent 下挂该意图需要的 slot。这正是文法 + 词典产出的结构,也是 DM / 技能路由的输入。
- domain(领域):最高层的业务分类,如音乐、天气、音量、视频、医生。
- intent(意图):在领域下的具体动作,如播放音乐、暂停音乐、搜索音乐、查天气。
- slot(槽位):执行意图所需的参数,如歌手、歌曲名、城市、日期;对应抽取结果如
singer:陈粒、song:七里香。
domain: 音乐 (music)
/ | \
搜索音乐(search) 暂停音乐(pause) 播放音乐(play)
/ | \
singer:陈粒 name:七里香 ...
{
"intent": "play_music",
"slots": {
"singer": "周杰伦",
"song": "七里香"
},
"domain": "music"
}
4.4 和文法/词典、技能选择的关系
- 文法规则:定义 domain + intent 的匹配模板(哪种句式 → 哪个领域、哪个意图)。
- 词典配置:负责填充 slot 的值(歌手、歌曲名、城市等实体)。
- 技能选择:DM 和技能路由用 domain 先锁定大类,再用 intent 和 slots 决定具体调哪个技能、传什么参数。
一句话:NLU = 把用户输入拆解成 domain + intent + slot 的规则引擎(文法定义匹配模板,词典填充槽位)。
5)DM 对话管理(Dialogue Manager)
猎户这套 DM 是真正的中枢。
5.1 DM 做什么
- 维护对话上下文(上一轮说啥、槽位是否完整)
- 多轮对话状态管理
- 反问缺失槽位
- 拒答、兜底、纠错
- 技能分发决策
5.2 猎户特色:Lua 在 DM 层(调下游技能 + 控对话)
具体技能并不是 Lua 插件化:技能是独立的下游服务(音乐、天气、视频等),用各自的后端实现。Lua 只做两件事:在 DM 里调用下游技能服务(根据 intent/slots 选技能、发请求),以及控制 DM 对话(多轮、填槽、反问、兜底)。每个领域/意图对应一段 Lua,负责「该不该追问、调哪个技能、返回什么话术」。优点:Lua 热更新、不用发版即可改对话逻辑和路由,技能服务可独立部署迭代。
Lua 脚本长什么样?下面是一个精简但完整的示例(接口名和字段依实际平台可能略有差异,逻辑一致):
-- 入口:收到 NLP 解析结果后由 DM 调用
function on_intent(domain, intent, slots, session)
-- 按领域分流
if domain == "music" then
return handle_music(intent, slots, session)
end
if domain == "weather" then
return handle_weather(intent, slots, session)
end
return { type = "fallback", text = "暂不支持该功能" }
end
-- 音乐领域:多意图 + 缺槽反问
function handle_music(intent, slots, session)
if intent == "play_music" or intent == "search_music" then
-- 缺歌名时可追问(多轮填槽)
if not slots.song and not slots.singer then
return { type = "ask_slot", slot = "song", text = "您想听哪首歌?" }
end
-- 决定用哪个音乐技能(多音乐服务选择可在这里或 skill_router 里做)
return skill_router.route("music", slots)
end
if intent == "pause_music" then
return skill_router.route("music", { action = "pause" })
end
return { type = "fallback", text = "没听懂,再说一次?" }
end
-- 天气领域
function handle_weather(intent, slots, session)
if intent == "query_weather" then
if not slots.city then
return { type = "ask_slot", slot = "city", text = "您要查哪个城市的天气?" }
end
return skill_router.route("weather", slots)
end
return { type = "fallback", text = "暂不支持该天气指令" }
end
说明:
- 入参:
domain、intent、slots(表)、session(会话),来自 NLP + 网关后的 DM。 - 按 domain / intent 分支:不同意图走不同逻辑,或调不同技能。
- 缺槽时:返回
ask_slot,DM 会做多轮追问(例如「您想听哪首歌?」),下一轮用户补全后再进同一段 Lua。 - 调技能:
skill_router.route("music", slots)表示调用下游的音乐技能服务,并带上槽位;具体用哪个音乐服务(网易云/QQ 音乐等)由技能路由层按设备配置/用户显式指定等决定。技能本身是独立服务,不是 Lua 写的。 - 兜底:无法匹配时返回
fallback,由 DM 播报默认话术。
5.3 对话流管理界面(平台示意)
这是 传统机器人 里配置多轮对话的界面,核心是对话流(Workflow),也就是 DM(对话管理)的可视化配置方式。
下图说明(猎户开发者平台「对话流管理」— 音量多轮)
下图为猎户开发者平台中 NLP 配置 → 对话流管理 的配置界面,示例为一条名为「音量多轮」的对话流(标题:音量的多轮对话)。图中元素对应关系如下:
| 图中元素 | 含义 |
|---|---|
| START | 对话流入口。 |
| 触发单元(蓝) | 触发条件,通常用 domain + intent 限制(如 domain=volume, intent=adjust),即 NLU 输出的结构化结果;用户说「调音量」等会命中此处,进入该对话流。 |
| 回复单元(绿) | 多轮对话中机器的回复,可配置「音量已调大」「还需要再调吗?」等话术。 |
| 「再大点」「再小点」 | 分支上的再次触发:用户在同一对话流中继续说「再大点」「再小点」,会命中对应分支,跳转到新的回复单元,形成多轮调节。 |
左侧「插入对话单元」提供触发单元(蓝)、功能单元(橙)、回复单元(绿);画布上通过拖拽、连线构成 START → 触发单元 → 回复单元 → 分支(再大点/再小点)→ 更多回复单元 的流程图。该流程图即 DM 多轮逻辑的可视化,背后由 Go + Lua 执行。
- 触发单元:定义这条对话流什么时候被激活,通常是绑定一个 domain + intent(例如
domain=volume, intent=adjust),对应的就是 NLU 输出的结构化结果。 - 回复单元:配置机器在不同节点下的回复话术,例如「音量已调大」「还需要再调吗?」。
- 分支节点(如「再大点」「再小点」):根据用户后续输入的意图决定对话跳转方向;这部分逻辑在实现上是用 Go 解析 Lua 脚本驱动的状态机。
一句话:对话流 = 可视化的 Lua 脚本,用来定义多轮对话的跳转逻辑和回复规则。
左侧可插入触发单元(蓝)、功能单元(橙)、回复单元(绿)。典型画布:START → 触发单元(domain+intent)→ 回复单元 → 分支(再大点/再小点)→ 更多回复单元。
5.4 对话流的本质:状态机
DM = 状态机;对话流 = 状态机的可视化配置;Lua 脚本 = 状态机的执行逻辑。传统机器人的 DM 就是用这种状态机模型,通过 Go 框架调度 Lua 脚本,来管理多轮对话的上下文和流转。
以披萨订单流程图为例,和你在平台里配的「音量多轮」对话流是同一类东西:
- Edit Order 相当于对话的核心状态,用户在这个状态下可以执行「Add Pizza」「Add Drink」等操作。
- 每一个箭头和分支,都对应对话流里的一个跳转条件(如用户说「再大点」→ 走某一分支的回复单元)。
一句话:DM = 状态机,对话流 = 状态机的可视化配置,Lua 脚本 = 状态机的执行逻辑。
其他例子:订餐场景 开始→编辑订单↔(加饮料/加披萨/删除)→确认;音量多轮 触发→回复单元→用户「再大点」/「再小点」→再进回复单元,形成循环直到满意或超时。
完整闭环示例(音量调大)
- 用户说:「把音量再调大点」
- NLU:通过词典 + 文法,拆解成
domain=volume, intent=increase, slots={value: +2}(或类似) - DM:根据 NLU 结果触发「音量多轮」对话流,进入对应状态节点,执行 Lua 脚本逻辑
- 输出:DM 调用回复单元,生成「好的,音量已调大」的回复,并等待用户下一轮输入
这就是 NLU → DM → 输出 的完整闭环:NLU 产出结构化结果,DM 用对话流/Lua 决定状态与回复,再经 TTS 播报。
5.5 多轮对话完整流程(在链路中的体现)
多轮对话不是「另一条链路」,而是同一套链路多轮执行,由 DM 用 session 维护上下文,配合 Lua/对话流 的「缺槽追问、槽满执行」逻辑实现。下面用「听音乐」缺歌名为例,把多轮完整走一遍。
第 1 轮
| 步骤 | 说明 |
|---|---|
| 用户 | 说「我想听歌」(没说歌名/歌手) |
| ASR | 转成文本 |
| NLP | 分词 → 词典 → 文法,得到 domain=music, intent=play_music, slots={}(song/singer 都缺) |
| Launcher | 分到「音乐」场景 |
| DM | 带上 session(当前可为空或已有历史)把 (domain, intent, slots) 交给 Lua |
| Lua | 发现 slots.song、slots.singer 都没有 → 返回 { type = "ask_slot", slot = "song", text = "您想听哪首歌?" },不调技能 |
| DM | 把追问话术交给 TTS 播报,并在 session 里记下:当前意图=play_music、已填 slots=空、等待补 song |
| 用户 | 听到「您想听哪首歌?」 |
第 2 轮
| 步骤 | 说明 |
|---|---|
| 用户 | 说「七里香」 |
| ASR → NLP | 得到 domain=music, intent=play_music(或仅识别出 song=七里香,DM 用 session 补全 intent) |
| DM | 从 session 取出上一轮的 intent 和已有 slots,把本轮新解析的 slot(如 song=七里香)合并进去,得到 slots 齐了(或仍缺 singer,可再追问) |
| Lua | 发现 slots 已够 → skill_router.route("music", slots),调下游音乐技能 |
| 技能 | 拉歌、播放;结果回传 DM |
| DM | 汇总回复 → TTS 播报(如「好的,正在播放七里香」),多轮结束 |
流程小结(多轮时怎么循环)
用户第 N 句
→ ASR → NLP → 网关 → DM(带上 session)
→ Lua / 对话流 判断:槽位齐了吗?
→ 否:返回追问话术 → TTS 播报 → 写 session → 等用户第 N+1 句(再走一遍上面)
→ 是:调技能 → 结果 → TTS 播报 → 清/更新 session,本话题结束
和对话流配置的关系:平台里「触发单元」用 domain/intent 选中的对话流,在多轮里会一直生效——同一对话流内的回复单元和分支决定每一轮机器说什么(追问还是执行结果);功能单元可在某一步调接口。session 由 DM 维护,保证「上一轮填了哪些槽、当前在哪个对话流」被下一轮复用。
6)技能路由 Skill Router + 技能是怎么选出来的?
NLP Launcher 负责按 domain/intent 把请求分到哪个场景(见上文「NLP Launcher 是干啥的?」);技能路由负责在同一场景内选出唯一一个要调用的技能(如音乐场景下选网易云还是 QQ 音乐)。
这一层就是你问的:多个音乐服务(或同领域多个技能),怎么选?
选择步骤(顺序可配置,一般如下):
- 按 domain 筛到一大类:例如
domain=music→ 只考虑「音乐」下的所有技能,不会跑到天气、视频去。 - 按设备/渠道配置:这台设备或这个客户绑定了「默认音乐服务 = 网易云」→ 就只在该设备上用网易云技能。
- 按用户显式指定:用户说「用 QQ 音乐播放」→ 槽位或意图里带了「QQ 音乐」→ 直接选 QQ 音乐技能,覆盖默认。
- 按意图与置信度:若同一句命中多个意图/多个技能,按平台配置的意图优先级或置信度排序,取第一个。
输出:唯一确定的「哪个技能 + 版本 + 环境」。
flowchart LR
A[NLP 输出<br/>domain / intent / slots] --> B{domain?}
B -->|music| C[音乐类技能候选]
B -->|video| D[视频类技能候选]
B -->|其他| E[其他场景]
C --> F{设备/渠道默认?}
F --> G{用户显式指定?}
G --> H[按优先级/置信度选一个]
H --> I[选定技能]
路由策略小结:domain 优先 → 设备/渠道配置 → 用户显式指定 → 意图匹配与置信度;最终只选出一个技能去执行。
7)技能 Skill(下游技能服务,非 Lua 实现)
猎户技能体系里,具体技能是独立的下游服务,不是用 Lua 实现的;由 DM 里的 Lua 调用这些技能服务。
- 每个技能是独立服务:音乐技能、天气技能、扫地技能、扫地机器人技能…(各自后端实现,通过网关被 DM/Lua 调用)
- 技能内部:校验槽位、调用第三方 API、拼接返回结果、构造回复话术,结果回传给 DM,再由 DM 控制后续对话或 TTS
7.1 多音乐服务怎么选?(你最关心的)
用户说「播放周杰伦」
↓
NLP 输出 intent: play_music
↓
DM 看:
设备配置 → 默认音乐服务是 A
或 白名单/客户配置 → 只能用 B
或 显式指令「网易云」→ 用 C
↓
SkillRouter 选择对应音乐技能
↓
音乐技能去拉歌、播放、控制
选择不是在 NLP,是在 DM + SkillRouter + 设备配置。
8)TTS 语音合成
- 技能 / DM 返回文本
- TTS 转音频
- 回传给前端播放
文档来源
- 传统机器人 文档中心(对话流、文法、词槽等)
- 语音链路简介(ASR→NLP→DM→Launcher→TTS)
附图与章节对应(结合你提供的几张图)
| 图 | 内容 | 对应文档位置 |
|---|---|---|
| 对话流管理界面 | 传统机器人 对话流管理页:左侧「文法配置」「词典配置」「对话流管理」;画布上 START → 触发单元(domain=XXX, intent=XXX)→ 回复单元,分支「再大点」「再小点」等 | 5.3 对话流管理界面、先弄清:文法/词典 |
| NLP 输出层级(音乐) | domain 音乐 → intents 搜索音乐/暂停音乐/播放音乐 → slots(singer:陈粒, name:七里香, …) | 4.3 / 4.4 NLP 最终输出结构、技能选择用 domain/intent/slots 作输入 |
| 订餐/状态机流程图 | 开始 → 编辑订单 ↔ 加饮料/删除/加披萨(厚底/薄底)→ 确认 | 5.4 对话流的本质:状态机,多轮可回环、状态转移的类比 |
三张图与完整闭环(总结)
第一张图:对话流管理(DM 层)
传统机器人 里配置多轮对话的界面,核心是对话流(Workflow),即 DM 的可视化配置。触发单元绑定 domain+intent(NLU 的结构化结果);回复单元配置各节点话术(如「音量已调大」「还需要再调吗?」);分支节点(如「再大点」「再小点」)根据用户后续意图跳转,逻辑由 Go 解析 Lua 脚本实现的状态机驱动。
→ 对话流 = 可视化的 Lua 脚本,定义多轮对话的跳转逻辑和回复规则。
第二张图:状态机示例(DM 本质)
披萨订单流程图和「音量多轮」对话流是同一类东西:Edit Order = 对话核心状态,箭头与分支 = 对话流里的跳转条件。猎户 DM 用状态机模型,通过 Go 框架调度 Lua 脚本管理多轮对话的上下文和流转。
→ DM = 状态机,对话流 = 状态机的可视化配置,Lua 脚本 = 状态机的执行逻辑。
第三张图:NLU 语义框架(NLU 本质)
Domain(领域)= 最高层业务分类(音乐、天气、音量);Intent(意图)= 领域下的具体动作(播放音乐、暂停音乐);Slot(槽位)= 执行意图所需的参数(歌手、歌曲名)。文法规则定义 domain+intent 的匹配模板,词典配置填充 slot。
→ NLU = 把用户输入拆解成 domain + intent + slot 的规则引擎。
三者关系(完整闭环)
- NLU:词典 + 文法把用户说的「把音量再调大点」拆成
domain=volume, intent=increase, slot={value: +2}。 - DM:根据 NLU 结果触发「音量多轮」对话流,进入对应状态节点,执行 Lua 逻辑。
- 输出:DM 调用回复单元生成「好的,音量已调大」,经 TTS 播报,并等待用户下一轮输入。