# API接入协议

# 接口说明

1、构建成功并发布上线的对话流,可以通过API实现人机之间按照设计好的交互逻辑进行持续,双向,自然地沟通。

2、接口调用所需参数chatflow_id(对话流唯一标识)和apiKey(接口秘钥)由iFLYOS 对话流平台提供,调用方管理。

3、调用方可以根据需要重置apiKey,以确保秘钥安全。

注意

  • 在iFLYOS 对话流平台创建对话流并构建成功后即可通过此API协议进行接入测试,测试期间每天限制500次调用,如果要获得更多的调用次数,需提交上线发布申请,并由后台管理员审核通过。

# 接口要求

集成对话流API时,需按照以下要求

参数 说明
请求协议 http[s](为提高安全性,强烈推荐https)
请求地址 http[s]: //chatflow.iflyos.cn/app/
请求方式 POST
接口鉴权 详情请参照下方接口鉴权
字符编码 UTF-8
响应格式 统一采用JSON格式
开发语言 任意,只要可以向对话流服务发起HTTP请求的均可

# 接口鉴权

对话流API根据以下参数进行鉴权验证:

参数 说明
chatflow_id 对话流唯一标识
ts 当前时间戳,从1970年1月1日0点0分0秒开始到现在的秒数
apiKey 接口密钥,由iFLYOS 对话流平台提供,调用方管理
signature 加密数字签名,计算方法:HmacSHA1(MD5(chatflow_id+ ts),apiKey)

# signature生成示例

① 获取baseString

baseString由chatflow_id和当前时间戳ts拼接而成;
假如chatflow_id = 202988d20e5d4c7aa7ba1a4a64ab9d8f,ts = 1502607694,则baseString = 202988d20e5d4c7aa7ba1a4a64ab9d8f1502607694

② 对baseString进行MD5

假如baseString为上一步生成的202988d20e5d4c7aa7ba1a4a64ab9d8f1502607694,MD5之后则为 0829d4012497c14a30e7e72aeebe565e

③ 以apiKey为key对MD5之后的baseString进行HmacSHA1加密,然后再对加密后的字符串进行base64编码。

假如apiKey = d9f4aa7ea6d94faca62cd88a28fd5234,MD5之后的baseString为上一步生成的0829d4012497c14a30e7e72aeebe565e,则HmacSHA1加密之后再进行base64编码得到的signature为: IrrzsJeOFk1NGfJHW6SkHUoN9CU=

# 备注:

  • signature 有效期:出于安全性考虑,每个 signature 的有效期为 5 分钟(用 ts 计算),同时 ts 要与标准时间同步,否则,时间相差太大,服务端会直接认为 ts 无效;
  • BASE64 编码采用 MIME 格式,字符包括大小写字母各26个,加上10个数字,和加号 + ,斜杠 / ,一共64个字符。

# 请求头

Content-Type: application/json; charset=utf-8

# 请求体

请求参数构造成json对象放入请求体,参数说明如下:

参数 类型 必须 说明 示例
chatflow_id string 对话流唯一标识 202988d20e5d4c7aa7ba1a4a64ab9d8f
signature string 加密数字签名(基于HMACSHA1算法,生成方式见【授权认证】) BFQEcN3SgZNC4eECvq0LFUPVHvI=
ts string 当前时间戳,从1970年1月1日0点0分0秒开始到现在的秒数 1502607694
auth_id string 用户唯一ID(32位字符串,包括英文小写字母与数字,开发者需保证该值与终端用户一一对应) 2049a1b2fdedae553bd03ce6f4820ac4
data_type string 数据类型,可选值:text(文本),audio(音频) text
data string 数据内容(文本内容或音频二进制字节数组),采用base64编码后的值 北京的天气
test boolean 是否为测试调用,如果不传则默认为正式调用 true
asr boolean 音频请求时是否需要返回语音识别结果,如果不传则默认不返回 true
sample_rate string 音频采样率,可选值:16000(16k采样率)、8000(8k采样率),默认为16000 16000
aue string 音频编码,可选值:raw(未压缩的pcm或wav格式)、speex(speex格式,即sample_rate=8000的speex音频)、speex-wb(宽频speex格式,即sample_rate=16000的speex音频),默认为 raw raw
speex_size string speex音频帧大小,speex音频必传。详见speex_size与speex库压缩等级关系表 60

# 返回值

返回结果为 JSON 对象,字段说明如下:

字段名称 类型 说明
code string 结果码(具体见错误码)
desc string 描述
sid string 会话ID
data array 结果数据

其中 sid 字段主要用于追查问题,如果出现问题,可以提供 sid 给讯飞技术人员帮助确认问题。

# data 字段说明

data为一个对象数组,包含语音识别、语义理解、回复内容等结果,字段说明如下:

字段名称 类型 说明
type String 结果类型
content object 结果内容

语义理解结果说明:

语义理解结果的type值为semantic,content字段说明如下:

字段 类型 说明
qa boolean 是否为qa,保留字段
template string 语料模板
rc int 语义理解服务返回码
slots object 语义理解的槽位信息
text string 语义理解的输入文本
intent string 意图
version string 语义理解的版本
sid string 语义理解服务返回的sid,用于追踪语义理解服务的调用链路

识别结果说明:

语音识别结果的的type值为asr,content字段说明如下:

字段 类型 说明
sn number 第几句
ls boolean 是否最后一句
bg number 开始
ed number 结束
ws array
cw array 中文分词
w string 单字
sc number 分数

回复结果说明:

回复结果的的type值为answer,content字段说明如下:

字段 类型 说明
type string 回复类型:文本(text),音频(audio)
text string 回复的文本内容
data string type为audio时,回复的音频内容经过base64压缩后的结果
chatStop boolean 此次回复以后,对话流是否结束

结果示例

{
    "code": "0",
    "data": [
        {
            "content": {
                "qa": false,
                "template": "{chinacity}的天气",
                "rc": 0,
                "slots": [
                    {
                        "name": "chinacity",
                        "value": "深圳",
                        "normValue": "深圳市"
                    }
                ],
                "text": "深圳的天气",
                "intent": "weather",
                "version": "2.0",
                "sid": "atn003aeb55@dx00071125b3f7a11201"
            },
            "type": "semantic"
        },
        {
            "content": {
                "chatStop": false,
                "text": "天气很好",
                "type": "text"
            },
            "type": "answer"
        }
    ],
    "desc": "success",
    "sid": "072fbe87f4014e0dad0ceb230bc62ada"
}

# 错误码

错误码 描述 说明 处理方式
0 success 成功
10100 bad_request 通用错误请求 根据错误描述进行判断处理
10105 illegal_access 没有权限 检查apiKey,ip,checkSum等授权参数是否正确
10106 invalid_parameter 无效参数或参数值为空 上传必要的参数, 检查参数格式以及编码
10107 illegal_arameter 非法参数值 检查参数值是否超过范围或不符合要求
10108 not_published 对话流未发布 申请发布并等待审核通过
10109 illegal_data_length 数据长度非法 检查传递的音频和文本是否超过长度
10110 no_license 无授权许可
10111 exceed_free_count 超过免费调用次数
10112 chatFlow_not_existed 对话流不存在 检查传入的chatflow_id是否有误
10114 time_out 超时
10301 parse_data_error 解析数据错误
10302 request_tts_error 请求tts服务错误 提供接口返回值,向服务商反馈
10303 parse_tts_error 解析tts结果数据错误 提供接口返回值,向服务商反馈
20001 flow_json_parse_error flow配置json解析错误
20002 load_chatflow_config_error 加载chatFlow配置信息错误
30001 no_node_error 配置节点未发现
30002 no_enter_node_error 未发现Enter节点
30005 execute_script_error 执行脚本错误 检查脚本是否正确
30008 no_match_rule 未匹配上任何规则 检查判断条件判断配置
30012 http_request_error 调用第三方接口错误 检查http节点的配置是否正确
40003 request_nlu_timeout 请求语义理解服务超时
40004 request_asr_timeout 请求识别服务超时

# 调用示例

java程序调用示例

说明:需要将代码中的 CHATFLOW_ID、API_KEY参数替换成平台已发布对话流的 chatflow_id 及对应的 apiKey。


package com.iflytek.flow.demo;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;

/**
 * iFLYOS 对话流平台 API调用示例
 * <p>
 * 运行方法:直接运行 main()
 * <p>
 * 结果: 控制台输出接口返回值信息
 *
 * @author iflyos_chatflow
 */
public class WebApiDemo {
    private static final String URL = "https://chatflow.iflyos.cn/app/";
    private static final String CHATFLOW_ID = "";
    private static final String API_KEY = "";
    private static final String AUTH_ID = "12345";
    private static final String QUERY_TEXT = "帮我订下酒店";

    public static void main(String[] args) throws SignatureException {

        Map<String, String> header = new HashMap<String, String>();
        header.put("Content-Type", "application/json; charset=utf-8");

        JSONObject body = new JSONObject();
        body.put("auth_id", AUTH_ID);
        body.put("data_type", "text");
        body.put("chatflow_id", CHATFLOW_ID);
        body.put("test", true);//测试调用需加此参数,发布后正式调用去掉
        String ts = String.valueOf(System.currentTimeMillis() / 1000);
        body.put("ts", ts);
        body.put("data", Base64.encodeBase64String(QUERY_TEXT.getBytes()));

        String signature = HmacSHA1Encrypt(MD5(CHATFLOW_ID + ts), API_KEY);
        body.put("signature", signature);

        String result = httpPost(URL, header, body.toJSONString());

        System.out.println(result);
    }

    /**
     * 加密数字签名(基于HMACSHA1算法)
     *
     * @param encryptText
     * @param encryptKey
     * @return
     * @throws SignatureException
     */
    private static String HmacSHA1Encrypt(String encryptText, String encryptKey) throws SignatureException {
        byte[] rawHmac = null;
        try {
            byte[] data = encryptKey.getBytes("UTF-8");
            SecretKeySpec secretKey = new SecretKeySpec(data, "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(secretKey);
            byte[] text = encryptText.getBytes("UTF-8");
            rawHmac = mac.doFinal(text);
        } catch (InvalidKeyException e) {
            throw new SignatureException("InvalidKeyException:" + e.getMessage());
        } catch (NoSuchAlgorithmException e) {
            throw new SignatureException("NoSuchAlgorithmException:" + e.getMessage());
        } catch (UnsupportedEncodingException e) {
            throw new SignatureException("UnsupportedEncodingException:" + e.getMessage());
        }
        String oauth = new String(Base64.encodeBase64(rawHmac));

        return oauth;
    }

    private final static String MD5(String pstr) {
        char md5String[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            byte[] btInput = pstr.getBytes();
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) { // i = 0
                byte byte0 = md[i]; // 95
                str[k++] = md5String[byte0 >>> 4 & 0xf]; // 5
                str[k++] = md5String[byte0 & 0xf]; // F
            }

            return new String(str);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * post请求
     *
     * @param url
     * @param header
     * @param body
     * @return
     */
    private static String httpPost(String url, Map<String, String> header, String body) {
        String result = "";
        BufferedReader in = null;
        OutputStream out = null;
        try {
            URL realUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
            for (String key : header.keySet()) {
                connection.setRequestProperty(key, header.get(key));
            }
            connection.setDoOutput(true);
            connection.setDoInput(true);

            //connection.setConnectTimeout(20000);
            //connection.setReadTimeout(20000);
            try {
                out = connection.getOutputStream();
                out.write(body.getBytes());
                out.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line;
                while ((line = in.readLine()) != null) {
                    result += line;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}