DSK接入协议

一. DSK协议

1.1 概述

DSK协议为自定义开发过程中需要依赖的协议,本质上规定了开发过程中客户端与服务端数据交互的格式。

1.2 使用场景

有代码能力的开发者,或者第三方平台想要接入技能,可以在DUI平台创建自定义技能,定制识别或者语义,参照DSK协议,通过接入代理服务来接管对话,完成自定义技能的定制。识别和语义的定制同任务型技能的定制过程一致。

二. 协议接入

2.1 接入流程

1)学习DSK协议,详细了解DSK协议各字段所表达的意思。
2)开发者开发自有服务,根据DSK协议,接收来自DUI技能平台(相当于客户端)发送过来的请求,服务端的主要功能就是解析请求的JSON数据,根据相同字段的不同内容,决定服务端返回的数据。
3)服务端开发完毕并启动,在DUI技能平台填写正确的服务地址,进行测试。参考文档《定制自定义技能》来完成控制台上的配置。

2.2 请求格式

2.2.1 请求参数

JSON格式的请求体参数如下所示,主要包括三部分: request、session、context。
request是与当前请求相关的数据, session是与当前技能会话相关的数据, context是全局共享的数据. 祥见《DSK协议基本概念

参数 必填(Y/N) 类型 说明
version Y string 版本号
session Y object 与该skill的会话
session.sessionId Y string 会话唯一标识
session.new Y boolean 是否新创建的session
context Y object 全局共享的数据
context.skill Y object 技能信息
context.user N object 用户信息
context.device N object 设备信息
context.product N object 产品信息
request Y object 当前请求相关的信息, 分三种类型: start, continue, end


Start Request

参数 必填(Y/N) 类型 说明
request.type Y string 这里取值start
request.requestId Y string 请求标识
request.task Y string 任务名
request.slots Y array 合并后的slots
request.audio Y object 音频格式, 只有配置了"透传音频"(内部开关)时才有此键
request.inputs Y object 用户语义解析记录列表,按照从久到近排序
request.inputs[].input Y string 用户输入的文本, 一般是语音识别结果
request.inputs[].audio Y object 音频内容, 只有配置了"透传音频"(内部开关)时才有此键
request.inputs[].audio.content Y base64 string 音频内容, 只有配置了"透传音频"(内部开关)时才有此键
request.inputs[].slots[].name N string 语义槽名称, 其中intent是一种特殊的slot
request.inputs[].slots[].value N string 语义槽取值
request.inputs[].slots[].rawvalue N string 原始value
request.inputs[].slots[].rawpinyin N string 原始value的拼音
request.inputs[].slots[].pos N array value在文本中的位置


Continue Request
与Start Request基本一样, 区别是Continue Request不是第一个请求

参数 必填(Y/N) 类型 说明
request.type Y string 这里取值continue
request.requestId Y string 请求标识
request.task Y string 任务名
request.slots Y array 合并后的slots
request.inputs[].input Y string 用户输入的文本, 一般是语音识别结果
request.inputs[].slots[].name N string 语义槽名称, 其中intent是一种特殊的slot
request.inputs[].slots[].value N string 语义槽取值
request.inputs[].slots[].rawvalue N string 原始value
request.inputs[].slots[].rawpinyin N string 原始value的拼音
request.inputs[].slots[].pos N array value在文本中的位置


End Request
用于强制结束当前session的请求, 一般是发生在用户结束对话、redispatch或错误时。

参数 必填(Y/N) 类型 说明
request.type Y string 这里取值end
request.requestId Y string 请求标识
request.reason Y string 强制结束session的原因, 分为user_initiated, quit, redispatch, error
request.error N object 当reason为error时的描述
request.error.type Y string 错误类型, 分为 invalid_response, device_communication_error, internal_error
request.error.message Y string 错误描述

2.2.2 HTTP请求头

POST /your-skill-path HTTP/1.1
Content-Type : application/json;charset=UTF-8
Host : your.skill.host.com
Accept : application/json
Accept-Charset : utf-8
Authorization : Bearer %BEARER TOKEN%
Content-Length : N

2.2.3 HTTP 请求体

JSON格式的请求体格式如下所示:

{
    "version": "1.0",
    "session": {
        "new": true,
        "sessionId": "this-is-session-id",
        "attributes": {
            "key": "this-is-value"
        }
    },
    "context": {
        "skill": {
            "skillId": "this-is-skill-id",
        },
        "user": {
            "userId": "this-is-user-id"
        },
        "device": {
            "deviceName": "this-is-device-name",
            "deviceInfo":{} // 即将废弃
        },
        "product": {
            "productId": "this-is-product-id",
            "productVersion":"1"
            "profileId": "xxx" //内部使用字段,产品形象Id
        }
    },
    "request": {}
}

当前request 分三种类型: start, continue, end:


1)请求体 : Start Request

{
    "version": 1.0,
    "session": {...},
    "context": {...},
    "request": {
        "type": "start",
        "requestId": "this-is-request-id",
        "task": "查天气",
        "slots": [  // 合并后的slots
            {"name": "intent", "value": "查城市天气"},
            {"name": "city", "value": "北京", "rawvalue": "北京", "rawpinyin": "bei jing", "pos": [1, 2]}
        ],
        "inputs": [
            {
                "input": "我要查天气",
                "audio": { // 限内部使用, 只有配置了"透传音频"时才有此键
                    "audioType": "ogg",
                    "sampleRate": 16000,
                    "channel": 1,
                    "sampleBytes": 2,
                    "content": "base64_encoded_audio_data"
                },
                "task": "查天气",
                "timestamp": 1514882440,
                "slots": [
                    {"name": "intent", "value": "查城市天气"}
                ]
            },
            {
                "input": "北京",
                "audio": { // 限内部使用, 只有配置了"透传音频"时才有此键
                    "audioType": "ogg",
                    "sampleRate": 16000,
                    "channel": 1,
                    "sampleBytes": 2,
                    "content": "base64_encoded_audio_data"
                },
                "task": "查天气",
                "timestamp": 1514882444,
                "slots": [
                    {"name": "intent", "value": "查城市天气"},
                    {"name": "city", "value": "北京", "rawvalue": "北京", "rawpinyin": "bei jing", "pos": [1, 2]}
                ]
             }
        ]
    }
}


2)请求体 : Continue Request

{
    "version": 1.0,
    "session": {...},
    "context": {...},
    "request": {
        "type": "continue",
        "requestId": "this-is-request-id",
        "task": "查天气",
        "slots": [  // 合并后的slots
            {"name": "intent", "value": "查城市天气"},
            {"name": "city", "value": "北京", "rawvalue": "北京", "rawpinyin": "bei jing", "pos": [1, 2]}
        ],
        "inputs": [
            {
                "input": "我要查天气",
                "audio": { // 限内部使用, 只有配置了"透传音频"时才有此键
                    "audioType": "ogg",
                    "sampleRate": 16000,
                    "channel": 1,
                    "sampleBytes": 2,
                    "content": "base64_encoded_audio_data"
                },
                "task": "查天气",
                "timestamp": 1514882440,
                "slots": [
                    {"name": "intent", "value": "查城市天气"}
                ]
            },
            {
                "input": "北京",
                "audio": { // 限内部使用, 只有配置了"透传音频"时才有此键
                    "audioType": "ogg",
                    "sampleRate": 16000,
                    "channel": 1,
                    "sampleBytes": 2,
                    "content": "base64_encoded_audio_data"
                },
                "task": "查天气",
                "timestamp": 1514882444,
                "slots": [
                    {"name": "intent", "value": "查城市天气"},
                    {"name": "city", "value": "北京", "rawvalue": "北京", "rawpinyin": "bei jing", "pos": [1, 2]}
                ]
             }
        ]
    }
}


3)请求体: End Request

{
    "version": 1.0,
    "session": {...},
    "context": {...},
    "request": {
        "type": "end",
        "requestId": "this-is-request-id",
        "reason": "redispatch",
        "error": {
            "type": "string",
            "message": "string"
        }
    }
}

2.3 响应格式

2.3.1 响应参数

第三方平台需要把数据结构化成DUI的数据格式。

参数 必填(Y/N) 类型 说明
version Y string 版本号
session Y object 上下文信息
session.nextIntents N array 希望用户下一句话的意图
response.speak Y object 对话交互中语音播报的内容
response.speak.type Y string 对话输出,音频或者语音合成;"text": 纯文本;"audio": 音频资源; "ssml":合成SSML标记
response.speak.text N string 合成文本,speak.type是"text"时必须有
response.speak.audioUrl N string 对话语音输出音频,speek.type为“audio”时必须有
response.speak.ssml N string 用于合成的ssml标记文本
response.widget N object 对话交互中要显示的内容, DUI控件包含: 文本(text)、内容卡片(content)、列表(list)、多媒体(media)、内嵌网页(web)、自定义。
response.execute N object 执行本地命令或本地查询, nativecmd没有返回值, nativeapi有返回值(nativeapi保留备用)
shouldEndSession Y boolean 对话是否结束,false:继续对话,true:结束对话
confidence N float 置信度


其中widget包括文本、内容卡片、列表、多媒体、内嵌入网页等, 详见文档《DUI控件介绍》 《控件数据格式

2.3.2 正确响应


HTTP 响应头

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Content-Length: N


HTTP 响应体

第三方平台需要把数据结构化成DUI的数据格式。


JSON字符串:

{
    "version": "1.0",
    "session": {
        "nextIntents": ["下一轮意图1","下一轮意图2"],
        "attributes": {...}    // 保留备用      
    },
    "response": {
        "speak": {
            "type": "text",
            "text": "北京晴, 26到32度",
            "ssml": "SSML markup text string to speak"
        },
        "widget": {
            "type": "content",
            "name": "the widget name",
            "title": "the title",
 
            "subTitle": "sub-title",
            "label": "label",
            "imageUrl":"URL of the image to be shown",
            "linkUrl":"URL of the attribute to be associated with the card",
            "extra": {
                "key1": "val1",
                "key2": "val2"
            },
            "recommendations": ["推荐说法1", "推荐说法2"]
        },
        "execute": {                                // 执行本地命令或本地查询, 可以有返回值, 也可以没返回值
            "url": "nativecmd://settings/openwifi", // 命令URL, nativecmd没有返回值, nativeapi有返回值
            "args": {                               // 参数
                "arg1": "val1",
                "arg2": "val2"
            }
        }
    },
    "yield": false,
    "shouldEndSession": false,
    "confidence": 0.9
  }
}

2.3.3 异常

使用标准HTTP状态来表达 API 请求的成功或是错误。以下是有可能遇到的HTTP状态:
200:表示请求成功
非200:表示请求失败

2.4 示例demo

2.4.1 下载Python示例

Python 代码下载:
Minion

2.4.2 下载Java示例

Java demo下载:
Minion

2.4.3 响应

示例demo的响应如下:

1)响应头

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization
Access-Control-Allow-Methods:GET, PUT, POST, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin:*
Access-Control-Allow-Origin:*
Cache-Control:no-store
Connection:keep-alive
Content-Encoding:br
Content-Type:application/json; charset=UTF-8
Date:Mon, 29 Jan 2018 03:15:20 GMT
Etag:W/"20efe7e3b17bdde3fdea093b7c0e642a70319aba"
Expires:0
Prama:no-store
Server:nginx/1.10.2
Transfer-Encoding:chunked
Vary:Accept-Encoding
X-Frame-Options:'ALLOW-FROM https://www.growingio.com'


2)响应体

{
    {
    "status":0,
    "result":{
        "dlg":[
            {
                "recordId":"this-is-request-id",
                "sessionId":"this-is-session-id",
                "dm":{
                    "status":0,
                    "ssml":"SSML markup text string to speak",
                    "shouldEndSession":false,
                    "nlg":"这是第2次helloworld"
                },
                "nlu":{
                    "timestamp":this-is-timestamp,
                    "skillVersion":"latest",
                    "input":"helloworld",
                    "skill":"测试技能",
                    "semantics":{
                        "request":{
                            "slotcount":0,
                            "task":"sys.技能调用"
                        }
                    },
                    "skillId":"this-is-skill-id"
                },
                "contextId":"this-is-context-id"
            }
        ]
    }
}

2.5 其他说明

2.5.1 注意事项

1)对话回复部分为”接入服务代理“,配置时暂只支持填写web服务地址(即http://、https://),且服务地址必须为服务的公网IP地址,否则DUI网站请求将无法到达开发者的服务地址。
2)提供的Java示例是Spring Boot工程,需开发者自己创建一个工程,模仿示例可以写出一个最简单的demo。(Spring Boot基础教程:https://gitee.com/didispace/SpringBoot-Learning
3)Java示例中用到了Redis数据库、fastjson等工具,开发者需要在pom.xml中提供相关依赖:
Minion
4)Java示例中的端口、数据库设置通过application.properties文件设置,开发者可根据实际情况自行设置:
Minion
5)使用python示例需要安装python3和python3-flask模块,重启脚本,计数会清零。

2.5.2 常见问题

Q1:使用Python示例后,在线测试时,输入调用名,回复出错,查看json时,"nlg" 为 "xxx服务有故障,暂时不能为你提供服务" (XXX为技能名称)

A1:请检查下代码中定制的调用名是否和网站上配置的一致。如果改动了调用名,代码中也同样需要改变。


Q2: 使用Java示例后,在线测试时,回复提示SSM错误

A2: 如果开发者使用的是windows系统,当出现SSM错误时,可以查看是否是因为防火防设置导致无法ping通。


Q3: 使用Java示例后,在线测试时,用调用名唤醒技能成功,但是后续回复为“error”。

A3: 请检查eclipse的报错信息,如果报错信息为“Exception thrown :org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool”, 开发者需要检查是否已安装java redis。