package.path = "/usr/local/share/lua/5.2/?.lua"
package.cpath = "/usr/local/lib/lua/5.2/?.so;"
local socket = require "socket"
local http = require "socket.http"
http.TIMEOUT = 10
local ltn12 = require("ltn12")
--智能客服机器人的hashcode
local robothashcode = ""
--能力平台appkey
local appKey = "ac5d5453"
--机器人问答渠道id
local channelId = "1";
local receiverId = 8185
--broker问答地址
local brokerAddress = "http://10.248.17.3:8080/CSRBroker"
--主叫号码
local call_num_link = session:getVariable("caller_id_number")
--被叫号码
local talkerId = session:getVariable("destination_number")
local uuid = argv[1]
math.randomseed(os.time())
--通话的唯一标识
local callUserId = math.random() * 100000000000000
--TTS名称
local ttsName = "ZhuRuiv9"
--用户信息接口
local getCustomerInfo = "http://10.248.17.3:8083/api/customer/basic/getCustomerInfo"
--结果回传接口
local updateUserInfoUrl = "http://10.248.17.3:8083/api/customer/basic/updateResult"
--呼叫开始时间
local startTime = ""
local endTime = ""
--local record_file = session:getVariable("record_file")
--录音文件保存地址
local subdir = os.date("%Y-%m-%d", os.time())
local datestr = session:getVariable("datestr")
local record_file = "/home/data/recordings/archive/renrendai/" .. subdir .. "/" .. call_num_link .. "_" .. datestr .. ".wav"
function printLog(text)
if (text) then
freeswitch.consoleLog("warning", text .. "\n")
else
freeswitch.consoleLog("warning", "object is nil")
end
end
--字符串分隔方法
function string_split(str, split_char)
local sub_str_tab = {};
while (true) do
local pos = string.find(str, split_char);
if (not pos) then
sub_str_tab[#sub_str_tab + 1] = str;
break;
end
local sub_str = string.sub(str, 1, pos - 1);
sub_str_tab[#sub_str_tab + 1] = sub_str;
str = string.sub(str, pos + 1, #str);
end
return sub_str_tab;
end
--分隔主叫号码传递参数
local str_Arr = string_split(call_num_link, "*")
--主叫号码
--local call_num = "15034032390"
local call_num = str_Arr[1]
--业务类型
--local transparam = "7"
local transparam = str_Arr[2]
--local userId = str_Arr[3]
local userId = "20"
--[[
--主叫号码
local call_num = "15034032390"
--业务类型
local transparam = 7
local userId = "20"
--分隔主叫号码传递参数
local str_Arr = string_split(call_num_link, "*")
if(#str_Arr == 3) then
call_num = str_Arr[1]
transparam = str_Arr[2]
userId = str_Arr[3]
end
--]]
freeswitch.consoleLog("warning", "*******************LOG*****************" .. uuid)
freeswitch.consoleLog("warning", "*****************call_num_link:*****************[" .. call_num_link .. "]")
freeswitch.consoleLog("warning", "*****************talkerId:*****************[" .. talkerId .. "]")
callUserId = "renrendai_" .. call_num .. "_" .. math.floor(callUserId)
function parseargs_xml(s)
local arg = {}
string.gsub(s, "(%w+)=([\"'])(.-)%2", function(w, _, a)
arg[w] = a
end)
return arg
end
function parse_xml(s)
local stack = {};
local top = {};
table.insert(stack, top);
local ni, c, label, xarg, empty;
local i, j = 1, 1;
while true do
ni, j, c, label, xarg, empty = string.find(s, "<(%/?)(%w+)(.-)(%/?)>", i);
if not ni then
break
end
local text = string.sub(s, i, ni - 1);
if not string.find(text, "^%s*$") then
table.insert(top, text);
end
if empty == "/" then
table.insert(top, { label = label, xarg = parseargs_xml(xarg), empty = 1 });
elseif c == "" then
top = { label = label, xarg = parseargs_xml(xarg) };
table.insert(stack, top);
else
local toclose = table.remove(stack);
top = stack[#stack];
if #stack < 1 then
error("nothing to close with " .. label);
end
if toclose.label ~= label then
error("trying to close " .. toclose.label .. " with " .. label);
end
table.insert(top, toclose);
end
i = j + 1;
end
local text = string.sub(s, i);
if not string.find(text, "^%s*$") then
table.insert(stack[stack.n], text);
end
if #stack > 1 then
error("unclosed " .. stack[stack.n].label);
end
return stack[1];
end
-- Used to parse the XML results.
function getResults(s)
local xml = parse_xml(s);
local stack = {}
local top = {}
table.insert(stack, top)
top = { grammar = xml[1].xarg.grammar, score = xml[1].xarg.score, text = xml[1][1][1] }
table.insert(stack, top)
return top;
end
local function json2true(str, from, to)
return true, from + 3
end
local function json2false(str, from, to)
return false, from + 4
end
local function json2null(str, from, to)
return nil, from + 3
end
local function json2nan(str, from, to)
return nul, from + 2
end
local numberchars = {
['-'] = true,
['+'] = true,
['.'] = true,
['0'] = true,
['1'] = true,
['2'] = true,
['3'] = true,
['4'] = true,
['5'] = true,
['6'] = true,
['7'] = true,
['8'] = true,
['9'] = true,
}
local function json2number(str, from, to)
local i = from + 1
while (i <= to) do
local char = string.sub(str, i, i)
if not numberchars[char] then
break
end
i = i + 1
end
local num = tonumber(string.sub(str, from, i - 1))
if not num then
--error(_format('json格式错误,不正确的数字, 错误位置:{from}', from))
end
return num, i - 1
end
local function json2string(str, from, to)
local ignor = false
for i = from + 1, to do
local char = string.sub(str, i, i)
if not ignor then
if char == '\"' then
return string.sub(str, from + 1, i - 1), i
elseif char == '\\' then
ignor = true
end
else
ignor = false
end
end
--error(_format('json格式错误,字符串没有找到结尾, 错误位置:{from}', from))
end
local function json2array(str, from, to)
local result = {}
from = from or 1
local pos = from + 1
local to = to or string.len(str)
while (pos <= to) do
local char = string.sub(str, pos, pos)
if char == '\"' then
result[#result + 1], pos = json2string(str, pos, to)
--[[ elseif char == ' ' then
elseif char == ':' then
elseif char == ',' then]]
elseif char == '[' then
result[#result + 1], pos = json2array(str, pos, to)
elseif char == '{' then
result[#result + 1], pos = json2table(str, pos, to)
elseif char == ']' then
return result, pos
elseif (char == 'f' or char == 'F') then
result[#result + 1], pos = json2false(str, pos, to)
elseif (char == 't' or char == 'T') then
result[#result + 1], pos = json2true(str, pos, to)
elseif (char == 'n') then
result[#result + 1], pos = json2null(str, pos, to)
elseif (char == 'N') then
result[#result + 1], pos = json2nan(str, pos, to)
elseif numberchars[char] then
result[#result + 1], pos = json2number(str, pos, to)
end
pos = pos + 1
end
--error(_format('json格式错误,表没有找到结尾, 错误位置:{from}', from))
end
function _G.json2table(str, from, to)
local result = {}
from = from or 1
local pos = from + 1
local to = to or string.len(str)
local key
while (pos <= to) do
local char = string.sub(str, pos, pos)
if char == '\"' then
if not key then
key, pos = json2string(str, pos, to)
else
result[key], pos = json2string(str, pos, to)
key = nil
end
--[[ elseif char == ' ' then
elseif char == ':' then
elseif char == ',' then]]
elseif char == '[' then
if not key then
key, pos = json2array(str, pos, to)
else
result[key], pos = json2array(str, pos, to)
key = nil
end
elseif char == '{' then
if not key then
key, pos = json2table(str, pos, to)
else
result[key], pos = json2table(str, pos, to)
key = nil
end
elseif char == '}' then
return result, pos
elseif (char == 'f' or char == 'F') then
result[key], pos = json2false(str, pos, to)
key = nil
elseif (char == 't' or char == 'T') then
result[key], pos = json2true(str, pos, to)
key = nil
elseif (char == 'n') then
result[key], pos = json2null(str, pos, to)
key = nil
elseif (char == 'N') then
result[key], pos = json2nan(str, pos, to)
key = nil
elseif numberchars[char] then
if not key then
key, pos = json2number(str, pos, to)
else
result[key], pos = json2number(str, pos, to)
key = nil
end
end
pos = pos + 1
end
--error(_format('json格式错误,表没有找到结尾, 错误位置:{from}', from))
end
--json格式中表示字符串不能使用单引号
local jsonfuncs = {
['\"'] = json2string,
['['] = json2array,
['{'] = json2table,
['f'] = json2false,
['F'] = json2false,
['t'] = json2true,
['T'] = json2true,
}
function _G.json2lua(str)
local char = string.sub(str, 1, 1)
local func = jsonfuncs[char]
if func then
return func(str, 1, string.len(str))
end
if numberchars[char] then
return json2number(str, 1, string.len(str))
end
end
--打印table的函数
function debug.dump(obj)
local getIndent, quoteStr, wrapKey, wrapVal, isArray, dumpObj
getIndent = function(level)
return string.rep("\t", level)
end
quoteStr = function(str)
str = string.gsub(str, "[%c\\\"]", {
["\t"] = "\\t",
["\r"] = "\\r",
["\n"] = "\\n",
["\""] = "\\\"",
["\\"] = "\\\\",
})
return '"' .. str .. '"'
end
wrapKey = function(val)
if type(val) == "number" then
return "[" .. val .. "]"
elseif type(val) == "string" then
return "[" .. quoteStr(val) .. "]"
else
return "[" .. tostring(val) .. "]"
end
end
wrapVal = function(val, level)
if type(val) == "table" then
return dumpObj(val, level)
elseif type(val) == "number" then
return val
elseif type(val) == "string" then
return quoteStr(val)
else
return tostring(val)
end
end
local isArray = function(arr)
local count = 0
for k, v in pairs(arr) do
count = count + 1
end
for i = 1, count do
if arr[i] == nil then
return false
end
end
return true, count
end
dumpObj = function(obj, level)
if type(obj) ~= "table" then
return wrapVal(obj)
end
level = level + 1
local tokens = {}
tokens[#tokens + 1] = "{"
local ret, count = isArray(obj)
if ret then
for i = 1, count do
tokens[#tokens + 1] = getIndent(level) .. wrapVal(obj[i], level) .. ","
end
else
for k, v in pairs(obj) do
tokens[#tokens + 1] = getIndent(level) .. wrapKey(k) .. " = " .. wrapVal(v, level) .. ","
end
end
tokens[#tokens + 1] = getIndent(level - 1) .. "}"
return table.concat(tokens, "\n")
end
return dumpObj(obj, 0)
end
--将mrcp返回的结果转化成lua可用的jsonlua play_and_detect_speech
function makeXml2Json(str)
str = string.sub(str, 21, #str - 1)
resultJson = json2lua(str)
return resultJson
end
function TTSXML(message)
grammar = ""
grammar = grammar .. ""
grammar = grammar .. message
grammar = grammar .. ""
return grammar
end
function initGrammarXML()
times = os.time()
grammar = ""
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. " "
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. " "
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. "- {\"regular\":{\"qa\":{\"address\":\"" .. brokerAddress .. "/queryAction\",\"robot\":\"" .. robothashcode .. "\",\"channel\":\"" .. channelId .. "\",\"appKey\":\"" .. appKey .. "\", \"protocolId\": 5, \"talkerId\": \"" .. talkerId .. "\",\"receiverId\": \"" .. receiverId .. "\",\"type\": \"voice\",\"isNeedClearHistory\": 0, \"isQuestionQuery\": 0, \"userId\":\"" .. callUserId .. "\",\"time\":" .. times .. ",\"msgId\":\"0\"},\"result\":\"singleNode.answerMsg,singleNode.cmd,answerTypeId\"}}
"
grammar = grammar .. ""
grammar = grammar .. ""
grammar = grammar .. "\r\n\r\n"
return grammar
end
--向机器人发送文字
function sendMessage2NLU(message)
printLog("-----renrendai---sendMessage2NLU#message-----" .. call_num .. "--" .. message)
local response_body = {}
times = os.time()
local reqbody = 'query=' .. message .. '&protocolId=5&userId=' .. callUserId .. '&receiverId=' .. receiverId .. '&talkerId=' .. talkerId .. '&platformConnType=' .. channelId .. '&appKey=' .. appKey .. '&robotHashCode=' .. robothashcode;
res, code = http.request {
url = "http://10.248.17.3:8080/CSRBroker/queryAction",
method = "POST",
headers =
{
["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8",
["Content-Length"] = string.len(reqbody)
},
source = ltn12.source.string(reqbody),
sink = ltn12.sink.table(response_body)
}
return table.concat(response_body);
end
function replaceChar(word)
word = word.gsub(word, """, "\"");
word = word.gsub(word, "
", "\r\n");
return word
end
--taskId
local taskId = "";
--姓名
local name = "";
--记录是否使用TTS播报
local useTTS = true;
--记录交互次数 初始值为2表示包含进入场景时的两次文本信息交互
local interCount = 0;
--行动码
local actionCode = ""
--逾期天数
local overdueDay = ""
--识别错误次数
local errorNum = 0
--记录没有说话的次数
local noSpeakNum = 0;
--播报语
local prompt = ""
--返回结果的次数,规定值返回一次
local updateTime = 0
--开始时间
local createdTime = "";
local defaultCode = "ERROR"
local isOver = false
--出错时无法理解的情况
local errReqPrompt = "喂,你好,这里是人贷借款,您在我司平台还有尚未处理的账单,请尽快还款,如果您已经还款了,请忽略本次提醒,祝您生活愉快,再见。"
--异常处理话术
local notFoundMsg = "请您再说一遍,我没明白"
--查询场景
function getStrategyMessage()
printLog("-----renrendai---getStrategyMessage()----phone:" .. call_num .. "------transparam:" .. transparam)
response_body = {}
times = os.time()
local reqbody = '{\"userId\":'..userId..',\"phone\":'..call_num..',\"transparam\":'..transparam..'}';
--local req_url = '..getUserInfoUrl..'\"?phone=\"' ..call_num.. '\"&transparam=\"' ..transparam.. ''
printLog("----0926----getStrategyMessage()###reqbody:" .. reqbody)
printLog("----0926----getStrategyMessage()###getCustomerInfo:" .. getCustomerInfo)
local res, code = http.request {
url = getCustomerInfo, --客户信息接口
method = "POST",
headers =
{
["Content-Type"] = "application/json;charset=utf-8",
["Content-Length"] = string.len(reqbody)
},
source = ltn12.source.string(reqbody),
sink = ltn12.sink.table(response_body)
}
printLog("---0926---resbody: " .. table.concat(response_body))
getInfoResult = json2table(response_body[1])
if (getInfoResult.rtnCode == "0000") then
strategyResult = getInfoResult.bean
strategy = strategyResult.strategy
callobject = strategyResult.callobject
uname = strategyResult.uname
product = strategyResult.product
company = strategyResult.company
transparam = strategyResult.transparam
gender = strategyResult.gender
overdueday = strategyResult.overdueday
repayable = strategyResult.repayable
csphone = strategyResult.csphone
createdTime = strategyResult.createdtime
robothashcode = strategyResult.rebotkey
taskId = strategyResult.taskId
remark = strategyResult.remark
extend1 = strategyResult.extend1
extend2 = strategyResult.extend2
extend3 = strategyResult.extend3
extend4 = strategyResult.extend4
printLog("---0926---" .. uname .. "---" .. call_num .. "---robothashcode--" .. robothashcode)
sendMessage2NLU(strategy)
--sendMessage2NLU("产品类型人贷拨打对象本人欠款人姓名朱瑞欠款人性别女欠款金额1000逾期天数1客服热线4008006800")
sendMessage2NLU("产品类型" .. product .. "拨打对象" .. callobject .. "欠款人姓名" .. uname .. "欠款人性别" .. gender .. "欠款金额" .. repayable .. "逾期天数" .. overdueday .. "客服热线" .. csphone)
else
printLog("------ 获取策略异常!------")
session:speak(errReqPrompt)
--获取信息失败直接更新,并挂断电话
forceEndHandle()
end
end
--更新通话结果
function updateResultInfo(actionCode)
printLog("-----renrendai---updateResultInfo#actioninfo()-----taskId:" .. taskId .. "--phone:" .. call_num .. "--transparam:" .. transparam .. "---callResult:" .. actionCode)
response_body = {}
times = os.time()
endTime = os.date("%Y-%m-%d %H:%M:%S", os.time())
printLog("----0926----updateResultInfo()###endTime:" .. endTime)
local reqbody = '{\"userId\":'..userId..',\"taskId\":\"' .. taskId .. '\",\"phone\":\"' .. call_num .. '\",\"transparam\":\"' .. transparam .. '\",\"callResult\":\"' .. actionCode .. '\",\"outCalls\":\"' .. interCount .. '\",\"startTime\":\"' .. startTime .. '\",\"endTime\":\"' .. endTime .. '\",\"outFile\":\"' .. record_file .. '\"}';
printLog("----0926----updateResultInfo()###reqbody:" .. reqbody)
res, code = http.request {
url = updateUserInfoUrl,
method = "POST",
headers =
{
["Content-Type"] = "application/json;charset=utf-8",
["Content-Length"] = string.len(reqbody)
},
source = ltn12.source.string(reqbody),
sink = ltn12.sink.table(response_body)
}
printLog("---0926---updateResultInfo ##resbody: " .. table.concat(response_body))
getUpdateResult = json2table(response_body[1])
if (getUpdateResult.rtnCode == "0000") then
printLog("---0926---" .. getUpdateResult.rtnMsg .. "---更新成功!---")
elseif (getUpdateResult.rtnCode == "8002") then
printLog("---0926---" .. getUpdateResult.rtnMsg .. "---更新异常!---")
else
printLog("---0926---更新失败!---")
end
end
function toSpeak()
matchWav()
interCount = interCount + 1
if useTTS == true then
--session:playAndGetDigits(1,1,1,1,'',"say:unimrcp:"..ttsName..":"..prompt,"",'[0123456789*#]')
session:speak(prompt)
else
--session:playAndGetDigits(1,1,1,1,'',prompt,"",'[0123456789*#]')
session:streamFile(prompt)
end
printLog("-----renrendai---toSpeak#prompt-----" .. call_num .. "--")
end
--强制挂断流程 比如用户已经挂电话
function forceEndHandle()
printLog("-----renrendai0608---forceEndHandle###call_num-----" .. call_num .. "--updateTime--" .. updateTime .. "---actionCode---" .. actionCode)
if (endTime == "") then
endTime = os.date("%Y-%m-%d %H:%M:%S", os.time())
end
if (updateTime == 0) then
if (string.len(actionCode) == 0) then
updateResultInfo(defaultCode)
updateTime = updateTime + 1
else
updateResultInfo(actionCode)
updateTime = updateTime + 1
end
end
session:hangup()
end
function textToNluHandle(msg)
resultBack = sendMessage2NLU(msg)
luaResult = json2table(resultBack)
answerTypeId = luaResult.answerTypeId
prompt = luaResult.singleNode.answerMsg
--处理不是场景,也即无法理解的情况
if (answerTypeId ~= 4) then
isOver = true
end
if (answerTypeId == 4) then
if (luaResult.singleNode.cmd ~= nil) then
if (string.len(luaResult.singleNode.cmd) > 0) then
dealActionCode(luaResult.singleNode.cmd)
end
end
end
end
--处理行动码 其他时候不返回
--0+行动码(优先级最高,不挂断)
--1+行动码(优先级最高,挂断)
function dealActionCode(cmdCode)
printLog("-----renrendai---textToNluHandle#cmd-----" .. call_num .. "--" .. cmdCode)
endCode = string.sub(cmdCode, 1, 1)
if (endCode == "0") then
isOver = false
if (string.len(cmdCode) > 1) then
actionCode = string.sub(cmdCode, 2, -1)
end
elseif (endCode == "1") then
isOver = true
if (string.len(cmdCode) > 1) then
actionCode = string.sub(cmdCode, 2, -1)
end
else
isOver = false
end
end
--mrpc识别用户语音流
function mrcpInputHandle()
--session:execute("detect_speech", "stop")
local grammar = initGrammarXML()
session:execute("play_and_detect_speech", "say:unimrcp:" .. ttsName .. ":,,,, detect:unimrcp {start-input-timers=true,no-input-timeout=5000,recognition-timeout=10000,Sensitivity-Level=0.13,speech-complete-timeout=250} inline:" .. grammar)
xml = session:getVariable('detect_speech_result')
if (xml ~= nil) then
xml = replaceChar(xml)
end
if (xml ~= nil and string.len(xml) > 50) then
noSpeakNum = 0
nluResult = parse_xml(xml)[2][1][1][1][1]
speechResult = parse_xml(xml)[2][1][2][1]
jsonResult = json2table(nluResult)
results = jsonResult.results
--6,8代表有结果
if (results == nil) then
prompt = notFoundMsg
end
printLog("-----renrendai---mrcpInputHandle#results[1].answerTypeId-----" .. call_num .. "--" .. results[1].answerTypeId)
if (results[1].answerTypeId == 4) then
if (results[1].answerMsg ~= nil) then --airesult中answer不为空时
errorNum = 0
prompt = results[1].answerMsg
if (results[1].cmd ~= nil) then
dealActionCode(results[1].cmd)
end
else
errorNum = errorNum + 1
printLog("-----renrendai---mrcpInputHandle#没有结果-----" .. call_num .. "--" .. errorNum)
prompt = notFoundMsg
if (errorNum == 2) then
isOver = true
else
isOver = false
end
end
else
errorNum = errorNum + 1
printLog("-----renrendai---mrcpInputHandle#无法理解-----" .. call_num .. "--" .. errorNum)
prompt = notFoundMsg
if (errorNum == 2) then
isOver = true
else
isOver = false
end
end
else
printLog("-----renrendai---mrcpInputHandle#静音-----" .. call_num .. "--" .. noSpeakNum)
noSpeakNum = noSpeakNum + 1
--记录静音的总次数
--silenceCount = silenceCount + 1
if (noSpeakNum >= 1) then
textToNluHandle("silent")
end
end
end
--主方法
function welcomeBegin()
--session:speak("你好,如果听到这句话,表示TTS声音正常!")
--获取场景入口
getStrategyMessage()
--sendMessage2NLU("欠款人姓名王晶")
--sendMessage2NLU("策略1")
--sendMessage2NLU("产品类型人贷拨打对象本人欠款人姓名朱瑞欠款人性别女欠款金额1000逾期天数1客服热线4008006800")
--开始场景对话
textToNluHandle("开始")
if (session:ready() ~= true) then
printLog("-----renrendai---mrcpInputHandle#未循环挂断-----" .. call_num)
forceEndHandle()
end
while (session:ready() == true) do
--转到lua脚本的时候,用户实际已经接通了,但由于外呼系统可能在用户尚未接通的时候,就已经把通话转过来了
if (startTime == "") then
startTime = os.date("%Y-%m-%d %H:%M:%S", os.time())
end
--播报
toSpeak()
--判断是否挂断 如果是则挂断
if isOver == true then
forceEndHandle()
printLog("-----renrendai---mrcpInputHandle#场景需要挂断-----" .. call_num)
break
end
--判断session是否存在
if (session:ready() ~= true) then
forceEndHandle()
printLog("-----renrendai---mrcpInputHandle#播报后挂断-----" .. call_num)
break;
end
--监听用户说话
mrcpInputHandle()
--判断session是否存在
if (session:ready() ~= true) then
forceEndHandle()
printLog("-----renrendai---mrcpInputHandle#循环末挂断-----" .. call_num)
break;
end
end
if (session:ready() ~= true) then
forceEndHandle()
printLog("-----renrendai---mrcpInputHandle#循环外挂断-----" .. call_num)
end
end
--录音与TTS播报方法
function matchWav()
useTTS = true
end
session:set_tts_params("unimrcp:jtts", ttsName)
welcomeBegin()