呼入需在FSGUI的路由配置中,将某个路由指向ai use mrcp类型,则会直接进入到对话流程中,其余部分,呼入呼出在接口中无区别.3.4,3.5,3.6三个service为通用service
有关智能外呼的接口如下:
1 注册获取token接口login
` curl -d ‘{“username”: “admin”, “password”: “admin”}’ -H “Content-Type:application/json” http://192.168.0.200:8085/api/login
返回值
正常:
{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjIwNzM2ODksImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTUyMjA3MDA4OX0.x2KXWs5KNdSu2t3teGBfZMVs5oUp2zPPlgWpnfyp1pk"
}
即意味着返回了一个带token的json串
非正常返回:
{
"Error": "Not Authorized"
}
2 发起呼叫的接口
2.1 不带最长呼叫时间,默认20分钟
curl -H "Content-Type: application/json" -d '{"Job_uuid":"feew32223dd3e32re32e32","Callernum":"18621575908", "Calleenum":"999888","A_gatewayname":"nway1","A_effective_caller_id_num":"021168686868", "A_external":"true","Cdr_url":"http://127.0.0.1:8085/fs/cdr","Event_url":"http://127.0.0.1:8085/fs/callstate","Record_file":"mytest.wav"}' -H "Authorization:Nway TOKEN" http://127.0.0.1:8085/api/originate_i
用前边的获取到的token来替代TOEKN这几个字符,Authorization:Nway是必须要有的。
Job_uuid: 每通呼叫都不一样的一个uuid值,最长64位
Callernum:要呼叫的号码
Calleenum:在此项目中必须为999888
A_gatewayname:如果号码需要出局,则指定这个gateway为出局的落地网关
A_effective_caller_id_num:外显号码
A_external:是否呼外线,如果为false,则只呼内线,true呼外线
Cdr_url:由系统回推话单的url
Event_url:由系统回推事件的url
Record_file:录音文件名,建议采用Job_uuid+”_”+Callernum,不需要带.wav
返回值
正常:
{
"code": "0",
"msg": "successed",
"sessionid": "feew32223dd3e32re32e92"
}
非正常:
{
"Error": "Not Authorized"
}
2.2 带最长呼叫时间长度的呼叫
curl-H"Content-Type:application/json"-d'{"Job_uuid":"1234","Callernum":"68018621575908", "Calleenum":"999888","A_gatewayname":"yn1","A_effective_caller_id_num":"057156215110","A_external":"true","Cdr_url":"http://182.254.137.78:8090/ningweifs/cdr","Event_url":"http://182.254.137.78:8090/ningweifs/callstate","Record_file":"kdksltest.wav","Max_call_time":"0" ,"Call_timeout":"30"}' -H"Authorization:Nway" http://127.0.0.1:8085/api/originate_i_ext
Max_call_time为最长通话时长,秒数
Call_timeout为呼叫时超时长度,秒数,比如呼30秒不接听就不呼
用前边的获取到的token来替代TOEKN这几个字符,Authorization:Nway是必须要有的。
返回值
正常:
{
"code": "0",
"msg": "successed",
"sessionid": "feew32223dd3e32re32e92"
}
非正常:
{
"Error": "Not Authorized"
}
3 需要实现的三个接口
cdr接口,event接口,人机处理接口
cdr接口用于接收通话清单
event接口用于推送通话过程中产生的各类事件
人机处理接口用于进行ASR、TTS、通信、放音等等
3.1 notify定义
enter : 表明有电话刚进入,则我们需要进行开场白了,呼入的为:您好,有什么能帮您? 呼出的为:您好,我是xx公司,做xx的!
asr_result:是由nway_power通过采集数据后送给识别引擎后识别结果送过来了
vad_short_sentence_file:在mode为2时,进行放音时采集,用于更精准的打断功能
bridge_result:转接回应
playback_result:放音回应结果
getdtmf_result:获取按键的回应结果
3.2 action定义
asr:用于播放tts合成语音或放a.wav等预录制的语音
hangup:用于放音后挂机
bridge:转到某个座席,可为内线也可为外线
vad_stop:用于在播放一个长语音时,由于识别引擎识别到人工部分说语音而中止当前的流程(内置于nway_power,不需要调用方直接调用,当有打断需求时,直接调用asr/bridge等action)
skip: 用于在nway_power告知业务系统有vad_short_sentence_file或asr_result时,但是业务不需要打断; 或因为打断,而nway_power回了一个带 use_recognize=false的asr_result ,那么我们不需要让系统再重新去执行一个事件,使用skip
3.3 response params定义
prompt:用于将交互时的要合成的tts或预录的语音文件传给系统,用.wav文件直接则播放和sounddir为主路径下的该预录制文件,如果是不带.wav则先tts合成再播放,如果是file_string:则顺序播放以”,”分隔的多个.wav录音文件
cause:用于标明挂机原因
usermsg:用户定义参数
number:用于标明要呼转的号码
callerid:用于标明呼出的号码,即外显号码,如果是呼入,则是呼入号码
calleeid:用于标明被叫号码,如果是呼入,则是呼入的落地号码
gateway:用于标明是通过哪个网关呼出,如果为空,则是呼转给内线号码
max_waiting_ms:最长等待时长
retry:尝试次数
mode:0为只要有响动就打断,1为不打断一直到放音结束,2为做实时采集用户人工语音,并识别后送给业务层处理,按用户命令来走一下步
profile: 指定使用的mrcp的profile
grammar: 指定使用的mrcp服务的grammar
3.4 request params定义
message: 识别后的文字内容
errorcode:识别后的错误码等
record: 录音文件存放路径
callid:呼叫的唯一标识符
notify:通知的消息,看3.1
flowdata: 随路数据,暂时不需考虑
use_recognize:是否要识别处理,用于打断后,此句做了识别,但还是不需要回应让处理,检测到此为false,直接回应skip
file_exist: exist/lose 采集的文件存在与否,如果是送的vad_short_sentence_file,那么无此项
3.5 golang业务处理示例
这里的示例是由nway_speech来请求httpserver,即可以用java、php、c#、golang等开发一个http service,由语音系统部分来请求。
/*
名称:智能语音自动处理和应答
开发者:李浩
所有权:上海宁卫信息技术有限公司
时间:2017-10-21
*/
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
/*
if (prompt.find(".wav,") 第二个接口增加就是这里,如果有a.wav,b.wav,c.wav就会自动的认为是多条语音播放
*/
func AsrServer(w http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
return
}
}()
con, _ := ioutil.ReadAll(req.Body)
if req != nil {
defer req.Body.Close()
}
//fmt.Println(string(con))
var dat map[string]interface{}
fmt.Println("-------------------------------------------------------------------------------------------")
fmt.Println(string(con))
err := json.Unmarshal(con, &dat)
if err == nil {
if dat["notify"] != nil && dat["calleeid"] != nil && dat["callerid"] != nil && dat["callid"] != nil {
notify := dat["notify"].(string)
//calleeid := dat["calleeid"].(string)
//callerid := dat["callerid"].(string)
//callid := dat["callid"].(string)
errorcode := 0
if dat["errorcode"] != nil {
errorcode = int(dat["errorcode"].(float64))
}
message := ""
if dat["message"] != nil {
message = dat["message"].(string)
}
use_recognize := "true"
if dat["use_recognize"] != nil {
use_recognize = dat["use_recognize"].(string) //如果是打断模式打断触发中断当前通话,则这里会为fasle
}
file_exist := "exist"
if dat["file_exist"] != nil {
file_exist = dat["file_exist"].(string) //当messsage为空时,可以通过这个参数来确认是因为没产生文件还是没识别内容
}
prompt := "您好!在吗?"
if notify == "enter" {
jsonStr := `{"action":"asr","flowdata":"","params":{"prompt":"/opt/nway/welcome.wav","max_waiting_ms":7000,"retry":0,"mode":2}}`
//w.WriteHeader()
w.Write([]byte(jsonStr))
} else if notify == "asr_result" && use_recognize == "false" {
jsonStr := `{"action":"skip","flowdata":"","params":{"cause":0,"usermsg":"caller response"}}`
w.Write([]byte(jsonStr))
} else if notify == "asr_result" {
if errorcode == 0 {
if strings.Contains(message, "智能") {
prompt = "/opt/nway/ai.wav"
jsonStr := `{"action":"hangup","flowdata":"","params":{"prompt":"` + prompt + `","cause":0,"usermsg":"caller request"}}`
w.Write([]byte(jsonStr))
} else if strings.Contains(message, "呼叫") {
prompt = "/opt/nway/cc.wav"
jsonStr := `{"action":"hangup","flowdata":"","params":{"prompt":"` + prompt + `","cause":0,"usermsg":"caller request"}}`
w.Write([]byte(jsonStr))
} else if strings.Contains(message, "再见") || strings.Contains(message, "好的") {
prompt = "/opt/nway/goodbye.wav"
jsonStr := `{"action":"hangup","flowdata":"","params":{"prompt":"` + prompt + `","cause":0,"usermsg":"caller request"}}`
w.Write([]byte(jsonStr))
} else {
fmt.Println(message)
}
} else if errorcode == -1 {
//prompt = "您在吗?"
} else if errorcode == -2 || file_exist == "lose" {
//是不是磁盘满了
prompt = "/opt/nway/unknown.wav"
}
if prompt != "" {
jsonStr := `{"action":"asr","params":{"prompt":"` + prompt + `","max_waiting_ms":10000,"retry":0,"mode":2}}`
fmt.Println(jsonStr)
w.Write([]byte(jsonStr))
}
}
} else if dat["notify"] != nil && dat["callid"] != nil {
notify := dat["notify"].(string)
message := ""
if dat["message"] != nil {
message = dat["message"].(string)
}
prompt := "您好!在吗?"
if notify == "vad_short_sentence_file" {
fmt.Println(notify)
if strings.Contains(message, "再见") || strings.Contains(message, "拜拜") {
prompt = "/opt/nway/goodbye.wav"
jsonStr := `{"action":"hangup","flowdata":"","params":{"prompt":"` + prompt + `","cause":0,"usermsg":"caller request"}}`
w.Write([]byte(jsonStr))
} else if strings.Contains(message, "不用") || strings.Contains(message, "好的") {
jsonStr := `{"action":"skip","flowdata":"","params":{"cause":0,"usermsg":"caller response"}}`
w.Write([]byte(jsonStr))
} else {
prompt = "/opt/nway/unknown.wav"
jsonStr := `{"action":"asr","params":{"prompt":"` + prompt + `","max_waiting_ms":10000,"retry":0,"mode":2}}`
fmt.Println(jsonStr)
w.Write([]byte(jsonStr))
}
}
}
} else {
fmt.Println("unmarshal event message error:", err, " &&& message:", string(con))
}
}
func Start() {
http.HandleFunc("/asr", AsrServer)
err := http.ListenAndServe(":10086", nil) // http.ListenAndServeTLS(ipport, nway_path.GetCurrentDirectory()+crtfile, nway_path.GetCurrentDirectory()+keyfile, nil)
if err != nil {
fmt.Println("ListenAndServe: ", err)
}
}
func main() {
Start()
}
3.6 CDR Service
func cdr(w http.ResponseWriter, req *http.Request) {
con, _ := ioutil.ReadAll(req.Body)
if req != nil {
defer req.Body.Close()
}
var dat map[string]interface{}
err := json.Unmarshal(con, &dat)
if err != nil {
logs.Error(err)
return
}
var dataCd map[string]interface{}
data, err := json.Marshal(dat["callRecordCdr"])
if err != nil {
logs.Error(err)
return
}
json.Unmarshal(data, &dataCd)
var dataCdr map[string]interface{}
datas, err := json.Marshal(dataCd["callerCdr"])
if err != nil {
logs.Error(err)
return
}
json.Unmarshal(datas, &dataCdr)
var cdr = ai_db.DBCdr{}
var cdrModel = ai_db.AiCdr{}
var runTime = ai_db.DBRunTime{}
var rtModel = ai_db.RunTimeModel{}
user_data := dataCdr["user_data"].(string)
rtModel, err = runTime.RunTimeListByCallId(dataCdr["sessionid"].(string))
admin, err := cdr.QueryUserInfoByOwenr(rtModel.Tpl_id)
cdrModel.Account_id = admin.Id
cdrModel.Callee = dataCdr["dst"].(string)
cdrModel.Caller = dataCdr["calleridnum"].(string)
if cdrModel.Caller == "0000000000" {
cdrModel.Caller = dataCdr["dst"].(string)
}
cdrModel.Start_time = dataCdr["calldate"].(string)
cdrModel.End_time = dataCdr["endtime"].(string)
cdrModel.Route_id = rtModel.Gateway_id
cdrModel.Fee_rate = admin.Org_fee_rate
cdrModel.Hangup_dispostion = dataCdr["hangup_dispostion"].(string)
a, err := strconv.Atoi(dataCdr["billsec"].(string))
cdrModel.Duration = a
var duration = a / 60
if a%60 > 0 {
duration += 1
}
cdrModel.Bill_balance = cdrModel.Fee_rate * float32(duration)
cdrModel.Record_base = Record_base
cdrModel.Record_path = dataCd["recordurl"].(string)
cdrModel.Task_id = rtModel.Taskid
cdrModel.Intention = rtModel.Intention
cdrModel.Call_id = dataCdr["sessionid"].(string)
cdrModel.Term_cause = dataCd["term_cause"].(string)
cdrModel.Term_status = dataCd["term_status"].(string)
cdrModel.Talk_crycle = strconv.Itoa(rtModel.Talk_crycle)
currentTime:=time.Now()
err = cdr.InsertCdr(cdrModel, user_data)
if err != nil {
logs.Error(err)
}
intdata,err:= runTime.SelctRunTimeByCallToInterval(rtModel.Taskid)
time.Sleep(time.Duration(intdata) * time.Millisecond)
err = runTime.DeleteRunTimeByCall(dataCdr["sessionid"].(string))
if err != nil {
logs.Error(err)
return
}
}
3.7 EVENT Service
func event(w http.ResponseWriter, req *http.Request) {
con, _ := ioutil.ReadAll(req.Body)
if req != nil {
defer req.Body.Close()
}
var data map[string]interface{}
err := json.Unmarshal(con, &data)
if err != nil {
fmt.Println(err)
}
var runtime = ai_db.DBRunTime{}
model, err := runtime.RunTimeListByCallId(data["sessionid"].(string))
if err != nil {
logs.Error(err)
return
}
if data["status"] == "caller-start" {
model.Call_state = 1
} else if data["status"] == "caller-ringing" {
model.Call_state = 1
err = runtime.UpdateRunTimeByCall2(model)
if err != nil {
logs.Error(err)
}
} else if data["status"] == "caller-answered" {
model.Call_state = 2
err = runtime.UpdateRunTimeByCall2(model)
if err != nil {
logs.Error(err)
}
} else if data["status"] == "caller-hangup" {
model.Call_state = 3
err = runtime.UpdateRunTimeByCall2(model)
if err != nil {
logs.Error(err)
}
}
var dbnumber = ai_db.DBNumber{}
err = dbnumber.UpdateNumberByGroupId(model.Call_state, model.Number_map_id)
if err != nil {
logs.Error(err)
return
}
}