阅读本文,你可以了解到以下信息:
host
headers
Content-Type: application/json
url
POST /v1/auth/tokens
请求参数
名称 | 类型 | 说明 | 是否必填 | 默认值 | 备注 |
---|---|---|---|---|---|
productId | string | 产品id | 是 | - | - |
deviceId | string | 设备id | 是 | - | 一个代表你的终端的唯一标识符 |
curtime | number | 请求授权时间 | 是 | - | 当前UTC时间戳,从 1970年1月1日0点0分0秒开始到现在的秒数 |
checksum | string | 令牌 | 是 | - | 计算方法: md5(secret + deviceId + curtime),两个值拼接的字符串,并通过md5进行加密。 |
接口错误码
错误码 | 错误信息 |
---|---|
20101 | 请求参数错误 |
20102 | 授权时间戳有误 |
20103 | productId无效 |
20104 | Checksum鉴权失败 |
20105 | 其他错误(包括deviceId不在设备白名单内/设备配额已耗尽/deviceId为空/deviceId格式有误) |
20199 | 服务异常 |
请求示例
static char * aiui_generate_token(const char *product_id, const char *secret_id, const char *device_id)
{
...
char *origin_product_ntp = csk_calloc(1, strlen(secret_id)+strlen(current_time) + 1);
if (origin_product_ntp == NULL) {
LOG_ERR("malloc failed");
return NULL;
}
strcat(origin_product_ntp, secret_id);
strcat(origin_product_ntp, current_time);
char md5_hex_string[80] = {'\0'};
unsigned char output[16] = {'\0'};
mbedtls_md5_context ctx;
mbedtls_md5_init(&ctx);
mbedtls_md5_starts(&ctx);
mbedtls_md5_update(&ctx, origin_product_ntp, strlen(origin_product_ntp)); // 更新有效数据部分的MD5值
mbedtls_md5_finish(&ctx, output);
mbedtls_md5_free(&ctx);
for (int i = 0; i < 16; ++i) {
sprintf(md5_hex_string + i * 2, "%02x", output[i]);
}
const char *http_req_headers[] = {
"Content-Type: application/json\r\n",
}
...
req.method = HTTP_POST;
req.url = AIUI_TOKEN_URL;
req.host = AIUI_HOST;
req.protocol = "HTTP/1.1";
req.header_fields = http_req_headers;
req.payload = req_body;
req.payload_len = strlen(req.payload);
req.response = response_cb;
req.recv_buf = recv_buf_ipv4;
req.recv_buf_len = sizeof(recv_buf_ipv4);
ret = http_client_req(sock4, &req, timeout, "IPv4 GET");
close(sock4);
csk_free(origin_product_ntp);
char *aiui_token = NULL;
ret = k_msgq_get(&token_msgq, &aiui_token, K_SECONDS(5));
if (ret != 0) {
LOG_ERR("get token timeout");
return NULL;
}
return aiui_token;
}
postman请求示例
聆思大模型应用设备端授权.postman_collection.json
示例代码
详细内容请参考CSK6接入大模型示例代码:
duomotai_ap\components\aiui_inter_conn\aiui_conn.c
响应示例
{
"token": "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJVQUp3dE9hdXRoU2VydmljZS***",
"expireAt": 1716378329574
}
请联系聆思首席大模型FAE 覃工(xqqin@listenai.com) 获取产品productid和secretid。
本接口是基于 WebSocket 协议实现数据的传输的。接口提供两个版本:
gateway
ws[s]://api.listenai.com/v1/interaction
dispatch
ws[s]://api.listenai.com/v1/dispatch
请求时headers带上
Authorization: Bearer token
其中token
为步骤一获取的授权结果。
请求参数
参数格式:
param=value1&token=value2
请求参数需要urlencode(Base64 编码)
请求参数说明:
参数 | 类型 | 必须 | 说明 | 示例 |
---|---|---|---|---|
param | string | 是 | Base64 编码后的字符串,详见 param 字段说明 | eyJhdXRoX2lkIjogIkxTMjAyNDAzMjYwMDMifQ== |
token | string | 否 | 大模型设备端接入token,详见 token 字段说明,该字段为字段,其作用等同于headers带入token值,两种方式选其一即可 | eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9*** |
param参数说明:
{
"auth_id": "LS20240326003",
"llm_app": "9ebe7088"
}
参数 | 类型 | 必须 | 说明 | 示例 |
---|---|---|---|---|
auth_id | string | 是 | 用户唯一ID(即步骤一所述deviceId) | 4d0c4412769b |
llm_app | string | 否 | 由客户端指定的大模型应用ID,如“儿童闲聊”应用对应一个ID,"口语私教"应用对应另一个ID | 9ebe7088 |
token参数说明:
token值为步骤一获取的鉴权信息,已经过base64编码,直接带入即可。websocket连接支持token值作为参数信息上传云端,示例:
eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJVQUp3dE9hdXRoU2VydmljZSIsI***
请求示例:
ws[s]://api.listenai.com/v1/interaction?param=xxxx&token=xxx
websocket请求示例代码
aiui_handle_t aiui_conn_init(void)
{
...
char *url = aiui_generate_url();
csk_ws_client_config_t config = {
.host = AIUI_HOST,
.scheme = AIUI_SCHEME,
.path = url,
.optional_headers = extra_headers,
};
//
csk_ws_client_create(&aiui_handle->ws_handle, &config);
csk_free(url);
csk_ws_client_eventcb_config_t cb_config = {
.connected_handler = ws_connected_cb,
.connected_arg = aiui_handle,
.disconnected_handler = ws_disconnected_cb,
.disconnected_arg = aiui_handle,
.message_handler = ws_data_cb,
.message_arg = aiui_handle,
};
// 注册websocket回调
csk_ws_client_config_event_cb(aiui_handle->ws_handle, &cb_config);
// 建立websocket
csk_ws_client_connect(aiui_handle->ws_handle);
k_sem_take(&ws_connected_sem, K_FOREVER);
return aiui_handle;
...
}
详细内容请参考CSK6接入大模型示例代码:
duomotai_ap\components\csk_websocket_client\csk_websocket_client.c
csk_websocket_client.c
csk_websocket_client.h
返回信息
{
"action":"connected",
"cid":"cidf415ba9c@dx8d0d18b0d6dd3eef00",
"code":"0",
"data":"",
"desc":"success"
}
{
"action": "error",
"cid": "3def61d96bdd",
"code": "400",
"data": "",
"desc": "设备在其他地方上线"
}
Websocket连接建立之后,进入实时通信阶段,可以进行会话的创建、数据的上传、结果的接收等。
整体的交互协议分为指令和数据,分别对应TEXT消息和BINARY消息。
连接建立之后,就可以开始创建会话,创建会话是为了在长连接中标识一次会话请求的起点,定义BINARY消息类型(如文本数据和音频数据)、需要的结果类型和各类型的参数。创建会话之后,在会话结束之前,所有请求的数据和指令均属于该会话。
{
"action":"start",
"params":{
"data_type":"audio",
"aue":"raw",
"features":[
"nlu",
"tts"
],
"asr_properties":{
"ent":"home-va"
},
"tts_properties":{
"vcn":"x4_lingxiaoqi_oral"
},
"nlu_properties":{
"sn":"xxxxxxxx"
}
}
}
参数说明
参数 | 类型 | 必须 | 说明 | 示例 |
---|---|---|---|---|
action | string | 是 | 指令类型 | start |
params | object | 是 | 业务参数 | |
params.fullduplex | string | 否 | 是否开启全双工:"1"(开启)、"2"(关闭),默认关闭 | "1" |
params.data_type | string | 是 | 数据类型:audio、text | audio |
params.aue | string | 是 | 数据类型为audio时必填,指定音频格式: raw: pcm数据,格式16khz 16bit LE, 单声道 speex: speex格式 speex-wb: speex-wb格式 ico: ico格式 |
raw |
params.speex_size | int | 否 | 音频数据格式为speex、speex-web时必填 | 70 |
features | array | 否 | 自定义结果类型功能,默认为["nlu"、"tts"]: nlu: 语义理解与回复(aiui或llm) tts: 下发tts音频链接 如不需要使用nlp功能,传空数组 |
["nlu", "tts"] |
params.asr_properties | object | 否 | 设备端指定的asr参数,详见asr_properties参数说明 | |
params.tts_properties | object | 否 | 设备端指定的tts参数,详见tts_properties参数说明 | |
params.nlu_properties | object | 否 | 设备端指定的nlu参数,nlu_properties参数说明 |
asr_properties参数说明
参数 | 类型 | 必须 | 说明 | 示例 |
---|---|---|---|---|
asr_appid | string | 否 | 自定义appid,一般不使用 | |
asr_apikey | string | 否 | 自定义apikey,一般不使用 | |
asr_apisecret | string | 否 | 自定义apisecret,一般不使用 | |
evad | string | 否 | 是否开启LS Gateway提供的vad "1": 开启 "0": 关闭 |
"1" |
vad_eos | int | 否 | vad后端点 | 800 |
ent | string | 否 | 设备端指定的识别引擎 | sms-aiui16k |
pd | string | 否 | ||
svad | int | 否 | 由于云端有3个vad:语义vad、普通引擎vad、聆思vad,如需关闭vad,可将eos拉到4000,或者增加上传"svad"=0 |
tts_properties
参数 | 类型 | 必须 | 说明 | 示例 |
---|---|---|---|---|
tts_appid | string | 否 | 自定义appid,一般不使用 | |
tts_apikey | string | 否 | 自定义apikey,一般不使用 | |
tts_apisecret | string | 否 | 自定义apisecret,一般不使用 | |
vcn | string | 否 | 发音人 | "x4_lingxiaoyue_oral" |
ent | string | 否 | 引擎 | "xtts" |
volume | string | 否 | 音量:1-100 | "50" |
speed | string | 否 | 语速:1-100 | "50" |
pitch | string | 否 | 声调:1-100 | "50" |
nlu_properties
参数 | 类型 | 必须 | 说明 | 示例 |
---|---|---|---|---|
aiui_appid | string | 否 | 自定义appid,一般不使用 | |
aiui_apikey | string | 否 | 自定义apikey,一般不使用 | |
nlp_mode | string | 否 | nlp类型: aiui llm |
"aiui" |
scene | string | 否 | 自定义scene,默认main | "main" |
sn | string | 否 | 设备sn | "xxxxxx" |
lat | string | 否 | 纬度 | |
lng | string | 否 | 经度 | |
auth_id | string | 否 | ||
clean_dialog_history | string | 否 | "user" | |
abilities | object | 否 | 设备支持的能力列表 |
abilities示例
// 设备支持闹钟能力,创建和删除
{
"params":{
"nlu_properties":{
"abilities": [
{
"name": "alarm",
"intents": ["create", "delete"]
},
// ...
]
}
}
}
会话创建帧响应
会话创建成功后,云端会返回以下响应:
{
'action':'started',
'cid': 'ee60b72f0ec7',
'code':'0',
'data':'',
'desc':'success',
'fid': 'f79a8ef4307d',
'sid':'33737e7ce2f74497baa2ee51b344df8a'
}
设备端上传数据有两种方式,文本数据和音频数据。在交互过程中,设备端不断构造 binary message 发送到服务端,内容是音频或文本的二进制数据。一个会话中,二进制文本数据只需要发送一次。
发送数据示例:
python:
CHUNK_SIZE = 512
SAMPLE_RATE = 16000 # 采样率
CHANNELS = 1
stream = audio.open(format=pyaudio.paInt16,
channels=CHANNELS,
rate=SAMPLE_RATE,
input=True,
frames_per_buffer=CHUNK_SIZE)
audio_data = stream.read(CHUNK_SIZE)
ws.send(audio_data, opcode=websocket.ABNF.OPCODE_BINARY)
rtos示例:
enum websocket_opcode {
WEBSOCKET_OPCODE_CONTINUE = 0x00,
WEBSOCKET_OPCODE_DATA_TEXT = 0x01,
WEBSOCKET_OPCODE_DATA_BINARY = 0x02,
WEBSOCKET_OPCODE_CLOSE = 0x08,
WEBSOCKET_OPCODE_PING = 0x09,
WEBSOCKET_OPCODE_PONG = 0x0A,
};
int csk_ws_client_send_bin(csk_ws_client_handle_t ws_handle, uint8_t *data, int data_len)
{
uint32_t free_num = k_msgq_num_free_get(&csk_ws_send_msgq);
if(free_num <= 3){
LOG_WRN("send bin no more free space: %d", free_num);
return -1;
}
uint8_t *send_data = (uint8_t *)csk_malloc(data_len);
if(send_data == NULL){
LOG_ERR("%s %d", __FUNCTION__, __LINE__);
return -ENOMEM;
}
memcpy(send_data, data, data_len);
csk_ws_send_msg_t msg = {
.opcode = WEBSOCKET_OPCODE_DATA_BINARY,
.data = send_data,
.len = data_len,
};
int ret = k_msgq_put(&csk_ws_send_msgq, &msg, K_NO_WAIT);
if(ret < 0){
LOG_WRN("send bin k_msgq_put ret = %d", ret);
csk_free(send_data);
return ret;
}
return 0;
}
客户端可以主动结束音频发送,云端会结束识别并进行对应的处理。指令内容如下:
{
"action": "end"
}
参数说明
参数 | 类型 | 说明 | 必须 |
---|---|---|---|
action | string | 状态标识(类型详见action参数说明) | 是 |
code | string | 云端返回编码 | 是 |
sid | string | 云端对话id(可用于查询交互记录) | 是 |
action参数说明
参数 | 类型 | 说明 | 必须 |
---|---|---|---|
started | string | 会话创建状态标识 | 是 |
result | string | 识别结果返回状态标识 | 是 |
finish | string | 会话结束状态标识 | 是 |
data参数说明
参数 | 类型 | 说明 | 必须 |
---|---|---|---|
sub | string | 详见sub状态结果 | 是 |
is_last | bool | 是否是最后识别结果(详见is_last参数说明) | 是 |
code | string | 云端返回编码 | 是 |
auth_id | string | 是 | |
result_id | int | 是 |
sub参数说明
参数 | 类型 | 说明 | 必须 |
---|---|---|---|
iat | string | 识别结果 | 是 |
vad | string | vad结果(注:vad结果可能会在最终识别结果前下发) | 是 |
tts | string | tts结果 | 是 |
nlp | string | 语义结果 | 是 |
is_last参数说明
参数 | 类型 | 说明 | 必须 |
---|---|---|---|
True | string | 是最后识别结果 | 是 |
False | string | 非最后识别结果 | 是 |
识别结果(流式)
{
'code': '0',
'data': {
'sub': 'iat',
'is_last': True,
'auth_id': 'd9ec6e29f379cf919d9daed0d2459612',
'result_id': 5,
'text': '今天广州的天气怎么样?需要带伞吗??'
},
'action': 'result',
'desc': 'success',
'sid': '5404f85784014c068bdc8a90a66d3d4a',
'fid': '10f3eca6a9c5',
'cid': 'ee60b72f0ec7'
}
vad结果(注:vad结果可能会在最终识别结果前下发,表示静音段超过eos,请设备端停止录音)
{
'code': '0',
'data': {
'sub': 'vad',
'result_id': 0,
'info': 'end'
},
'action': 'result',
'desc': 'success',
'sid': '5404f85784014c068bdc8a90a66d3d4a',
'fid': '10f3eca6a9c5',
'cid': 'ee60b72f0ec7'
}
语义结果
{
"code":"0",
"data":{
"sub":"nlp",
"auth_id":"4d0c4412769b62f0f44134ce9f2db11f",
"result_id":1,
"intent":{
"semantic":[
{
"slots":[
{
"name":"location.city",
"value":"广州市",
"normValue":"广州市"
},
{
"name":"location.cityAddr",
"value":"广州",
"normValue":"广州"
},
{
"name":"location.type",
"value":"LOC_BASIC",
"normValue":"LOC_BASIC"
},
{
"name":"queryType",
"value":"确认"
},
{
"name":"questionWord",
"value":"需要"
},
{
"name":"subfocus",
"value":"伞"
},
{
"name":"datetime",
"value":"今天",
"normValue":"{\"datetime\":\"2023-11-16\",\"suggestDatetime\":\"2023-11-16\"}"
}
],
"intent":"QUERY"
}
],
"shouldEndSession":"true",
"dialog_stat":"DataValid",
"data":{
"result":[
]
},
"save_history":true,
"uuid":"ara3d116b3a@dx000118b0e1c0a10b00",
"version":"171.0",
"sid":"ara3d116b3a@dx000118b0e1c0a10b00",
"rc":0,
"answer":{
"text":"不用带伞,今天广州阴",
"type":"T"
},
"used_state":{
"state_key":"fg::weather::default::default",
"state":"default"
},
"service":"weather",
"state":{
"fg::weather::default::default":{
"state":"default"
}
},
"text":"今天广州的天气怎么样?需要带伞吗?",
"category":"IFLYTEK.weather"
}
},
"action":"result",
"desc":"success",
"sid":"ebd611e1-f4ec-4d3c-be4d-ed8c122ea96d"
}
tts结果
请注意content需要base64解码后方可访问。
{
"code":"0",
"data":{
"sub":"tts",
"is_last":true,
"auth_id":"3fdc7e97c0d351991975662bd425e999",
"result_id":0,
"content":"aHR0cDovL2ludGVncmF0aW9uLXR0cy5pZmx5b3MuY24vbGl2ZS9kNjVkYzUyNDJhNGRhMGY4NWFjZDZhMTY3YmM0OWMyOWNlOTdkNGI1Lm1wMw=="
},
"action":"result",
"desc":"success",
"sid":"9470b468-ab3b-44de-ba95-24ab9dc60770"
}
在会话所有结果均已下发,则会下发一个结束指令消息,表示该会话已经结束。
{
'code': '0',
'data': '',
'action': 'finish',
'desc': 'success',
'sid': '279e517195ab47d496d0fd4925e959d9',
'fid': '874e07bceeb9',
'cid': 'ee60b72f0ec7'
}
创建会话
{
"action": "start",
"params": {
"data_type": "audio",
"aue": "raw",
"features": [
"nlu",
"tts"
],
"asr_properties": {
"ent": "home-va",
"evad":0,
"vad_eos":800,
},
"tts_properties": {
"vcn": "x4_lingxiaoqi_oral"
},
"nlu_properties": {
"sn": auth_info.device_id,
}
}
}
发送音频数据
int aiui_audio_send_start(aiui_handle_t handle)
{
#define ONESHOT_START_FORMAT "{\"action\": \"start\", \"params\": {\"eos\": \"250\", \"ent\": \"sms-haier-test\", \"evad\": \"1\", \"data_type\": \"audio\", \"result_level\": \"plain\", \"clean_dialog_history\": \"user\", \"aue\": \"raw\"}, \"tts_properties\": {\"vcn\": \"x4_lingxiaoqi_oral\"}}"
#define CONTINUE_START_FORMAT "{\"action\":\"start\",\"params\":{\"fullduplex\": \"1\", \"eos\": \"250\", \"ent\":\"sms-haier-test\", \"evad\":\"1\", \"data_type\":\"audio\",\"result_level\":\"plain\",\"clean_dialog_history\":\"user\",\"aue\":\"raw\",\"dwa\":\"\"}}"
LOG_INF("func: %s, line: %d", __FUNCTION__, __LINE__);
int ret = 0;
aiui_inter_handle_t *aiui_handle = handle;
char *start_format = (aiui_get_interactive_mode() == AIUI_INTER_ONESHOT) ?
ONESHOT_START_FORMAT : CONTINUE_START_FORMAT;
ret = csk_ws_client_send_text(aiui_handle->ws_handle, start_format);
if (ret < 0) {
LOG_ERR("%s: %d, ret = %d", __FUNCTION__, __LINE__, ret);
}
return ret;
}
int aiui_audio_send_data(aiui_handle_t handle, const void *audio, int len)
{
aiui_inter_handle_t *aiui_handle = handle;
int ret = csk_ws_client_send_bin(aiui_handle->ws_handle, (void *)audio, len);
if (ret < 0) {
LOG_ERR("%s: %d, ret = %d", __FUNCTION__, __LINE__, ret);
}
return ret;
}
发送end指令
int aiui_audio_send_end(aiui_handle_t handle)
{
aiui_inter_handle_t *aiui_handle = handle;
const char *end_tag = "{\"action\":\"end\"}";
int ret = csk_ws_client_send_text(aiui_handle->ws_handle, (void *)end_tag);
if (ret != 0) {
return ret;
}
LOG_INF("func: %s, line: %d", __FUNCTION__, __LINE__);
return 0;
}
云端响应
音频请求返回过程数据示例.json
创建会话
{
"action": "start",
"params": {
"data_type": "text",
"features": [
"nlu",
"tts"
],
"tts_properties": {
"vcn": "x4_lingxiaoqi_oral"
},
"nlu_properties": {
"sn": auth_info.device_id,
}
}
}
发送文本数据
int csk_ws_client_send_text(csk_ws_client_handle_t ws_handle, char *text_msg)
{
char *send_data = (char *)csk_malloc(strlen(text_msg) + 1);
if(send_data == NULL){
LOG_ERR("%s %d", __FUNCTION__, __LINE__);
return -ENOMEM;
}
strcpy(send_data, text_msg);
csk_ws_send_msg_t msg = {
.opcode = WEBSOCKET_OPCODE_DATA_TEXT,
.data = send_data,
.len = strlen(text_msg),
};
int ret = k_msgq_put(&csk_ws_send_msgq, &msg, K_NO_WAIT);
if(ret < 0){
LOG_WRN("send text k_msgq_put ret = %d", ret);
csk_free(send_data);
return ret;
}
return 0;
}
详细内容请参考CSK6接入大模型示例代码:
duomotai_ap\components\aiui_inter_conn\aiui_conn.c
示例代码获取:
git clone --branch v1.6.0 https://cloud.listenai.com/CSKG962172/duomotai_ap.git
示例应用目录:apps\LLM_control
更详细文档请参考:聆思大模型应用快速体验
示例代码获取:
https://cloud.listenai.com/listenai_xqqin/aitalk.git
python示例用于快速体验聆思大模型交互,开发者可以先体验整个交互流程后,再去实现设备端接入的开发,效率更高,python示例运行详见根目录readme文档说明。