AIAPI开发文档
AIAPI 接口
AIAPI 是一套类似于 HTTAPI(mod_httapi
)的接口,在简单接口基础上增强了交互能力。
mod_ai
是实现为 XSwitch 里面的一个模块, 跟类似于mod_httapi
, 让应用服务器用 HTTP 协议跟 XSwitch 进行交互, 以便控制呼叫和媒体。不同的是, mod_httapi
基于 XML 格式, 而mod_ai
则基于 JSON 格式。此外, mod_ai
还提供外呼功能。
呼入配置
对于呼入电话,在 XSwitch 中做如下配置。添加一条路由,参数如下:
- 被叫字冠:如
8888
- 最大号长:如
4
- 目的地类型:系统
- 内容:
ai {url=http://192.168.2.100/ivr}
然后呼叫8888
即可在 HTTP 服务器端收到请求。
如果没有图形界面,在 Dialplan 中使用如下配置:
<extension name="ai"> <condition field="destination_number" expression="^8888$"> <action application="answer"/> <action application="ai" data="{url=http://192.168.2.100/ivr}"/> </condition> </extension>
如上所示, 用户呼叫8888
, XSwitch 在路由呼叫时向指定的应用服务器(WebServer)发 HTTP 请求, 然后根据返回内容(Body)做相应的动作, 诸如语音播放/TTS/语音识别/录音/转移呼叫/挂机/会议等。
HTTP 协议细节方面, 是这样的:
mod_ai
发 HTTP POST 到应用服务器,Content-Type 是application/json
。- 应用服务器回应 200 OK,Content-Type 也必须是
application/json
。
响应 Body 的组成一般是action
+ next
+ variables
+ privateData
。编码必须是 UTF8。
这里有一个例子:
mod_ai
向应用服务器发 HTTP POST 请求:
POST /ivr_entry HTTP/1.1 Host: 192.168.2.100 Content-Type: application/json { "uuid": "eb7d45e0-4eb5-11e8-99df-53f214a93242", "url": "http://192.168.2.117:4567/ivr_entry", "hostname": "debian", "cid_number": "1018", "dest_number": "8888", "call_status": "ringing", "direction": "inbound", "channel_data": { "Caller-Direction": "inbound", "Caller-Logical-Direction": "inbound", "Caller-Username": "1018", "Caller-Dialplan": "XML", "Caller-Caller-ID-Name": "1018", "Caller-Caller-ID-Number": "1018", . . . } }
这里的uuid
指的是 XSwitch 内部通道的uuid
。url
来自 Dialplan 配置。channel_data
包含的内容是 XSwitch 通道变量。
应用服务器回应 200 OK:
HTTP/1.1 200 OK Content-Type: application/json { "action": "play", "next": "http://192.168.2.100/play_ack", "file": "say:您好!请说出您要办理的业务", "name": "dialed_digits", "input_timeout": "5000", "asr_engine": "iflyrest", "asr_grammar": "{accent=mandarin, barge-in=true, speech-timeout=30000}default", "variables": { "tts_engine": "iflyrest", "tts_voice": "xiaoyan" }, "privateData": { "myseq": "1" } }
其中:
asr_grammar
中补充参数如下:
speech-timeout
为通话时长(包括说话时间和一句话结束后的等待时间,单位毫秒)。no-input-timeout
是没有任何语音输入时等待时长,单位毫秒。silence-ms
是一句话结束后等待多少时间,单位毫秒。silence-ms
是一句话结束后等待多少时间,单位毫秒。threshold
声音阈值,越低越灵敏。
上述应用中参数解释:
action
是通知mod_ai
要做的动作, 比如play
(语音播音),execute
(执行 App),hangup
(挂机)等等。next
指定下次请求的 URL,mod_ai
做完指定动作之后向next
指定的 URL 发送 HTTP POST 请求。variables
是 XSwitch 通道变量, 比如做 TTS 时候指定 TTS 引擎(tts_engine
)、播放多个语音文件时指定文件分隔符(playback_delimiter
)等。上面的例子就是指定了 TTS 引擎。 通道变量可参考这里, https://freeswitch.org/confluence/display/XSwitch/Channel+VariablesprivateData
是自定义数据,mod_ai
一般不做任何解释, 下次 HTTP POST 时自动回传。
很显然,mod_ai
就是在 FreeWITCH 和应用服务器之间提供了一层 HTTP 接口, 让应用服务器控制呼叫和媒体。
支持的
play
作用: 播放语音文件(或者 tts), 启动 asr(可选) 属性:
- file: 要播放的语音文件, 可以是本地文件, 也可以是 HTTP 路径, 比如
http://www.xxx.com/abc.wav
- error_file: 如果输入错误(与正则表达式不匹配), 则播放此文件
- name: 参数名称, 用于存储收到的 DTMF 字符串,
mod_ai
下次发 HTTP 请求时自动带回来 - digit_timeout: 等待 DTMF 的超时时间
- input_timeout: 多个 DTMF 输入的最大间隔时间
- loops: 输入错误(与正则表达式不匹配)情况下的最大重试次数
- asr_engine: ASR 引擎
- asr_grammar: ASR 语法, 如果要启动语音识别, 那么需要同时指定 asr_engine 和 asr_grammar)
- bind: 定义类似 bind_digit_action App 一样绑定一个正则表达式, 如果输入与正则表达式不匹配, 就播放 error_file(例如 invalid.wav)
bind
支持的属性:
- strip: 在 DTMF 串中去掉相关字符, 一般是#, 我们经常用作结束符但实际并不需要它。
- input: 定义输入的正则表达式
常用的组合,播放放获取 DTMF。例子:
{ "action": "play", "next": "http://192.168.2.100/play_ack", "file": "welcome.wav", "error_file": "error.wav", "loops": "3", "name": "dialed_digits", "input_timeout": "5000", "digit_timeout": "3000", "bind": { "strip": "#", "input": "~\\d+" } }
目的是播放语音 welcome.wav, 输入任意数字键
- tts_and_asr
例子:
{ "action": "play", "next": "http://192.168.2.100/play_ack", "file": "say:您好!请说出您要办理的业务", "name": "dialed_digits", "input_timeout": "5000", "digit_timeout": "3000", "asr_engine": "iflyrest", "asr_grammar": "{accent=mandarin, barge-in=true, speech-timeout=30000}default", "variables": { "tts_engine": "iflyrest", "tts_voice": "xiaoyan" } }
- file_and_asr
例子:
{ "action": "play", "next": "http://192.168.2.100/play_ack", "file": "welcome.wav!silence_stream://1000!greet.wav", "name": "dialed_digits", "input_timeout": "5000", "asr_engine": "iflyrest", "asr_grammar": "{accent=mandarin, barge-in=true, speech-timeout=30000}default", "variables": { "playback_delimiter": "!" } }
这里要播放多个文件, 所以要指定文件名的分隔符。
- 只播放文件, 不启动语音识别, 不等待 DTMF
例子:
{ "action": "play", "next": "http://192.168.2.100/play_ack", "file": "welcome.wav", "name": "playonly" }
record
作用: 录音, 一般做留言用, 呼叫结束前将录音文件 HTTP 上传回应用服务器。 属性:
- file: 文件名称, 可以是本地文件或 HTTP 路径
- next: 指定一个新的 URL(后续请求使用)
- name: 参数名称, mod_ai 下次发 HTTP 请求时自动带回来。默认是 attached_file
- error_file: 如果输入错误(与正则表达式不匹配), 则播放此文件
- digit_timeout: 等待 DTMF 的超时时间, 单位是毫秒
- input_timeout: 多个 DTMF 输入的最大间隔时间, 单位是毫秒
- limit: 最大时长,单位是秒, 0 是不限制
- beep_file: 录音开始之前播放 beep 提示
- silence_hits: 静音时间,单位是秒。默认是 2 秒,这意味着如果检查 2 秒都是静音, 那么录音就自动结束
- bind: 定义类似
bind_digit_action
App 一样绑定一个正则表达式, 如果输入与正则表达式不匹配, 就播放 error_file(例如 invalid.wav)
bind 支持的属性:
- strip: 在 DTMF 串中去掉相关字符, 一般是#, 我们经常用作结束符但实际并不需要它。
- input: 定义输入的正则表达式
例子:
{ "action": "record", "next": "http://192.168.2.100/record_ack", "file": "010203.wav", "name": "recordName", "limit": "20", "silence_hits": "5", "bind": { "strip": "#", "input": "~\\d|*#" } }
最大时长 20 秒, 任意 DTMF 结束录音
record_call
作用: 录音, 但跟之前的 record 不同, record 一般用作留言, 只录进来的声音。record_call 是录 session, 一般采用双声道录制, 分别保存进来和出去两个方向的声音。 属性:
- limit: 最大时长, 0 是不限制
- file: 录音文件名称
- name: 参数名称, mod_ai 下次发 HTTP 请求时自动带回来
- next: 指定一个新的 URL(后续请求使用)
说明: mod_ai 收到 record_call 之后, 马上给 next URL 发一次 HTTP POST 在 reporting 阶段(呼叫结束前)再给 next URL 发一次 HTTP POST 后面的这次用的是 form fileupload 方式上传录音文件(详情可参考测试案例)
例子:
{ "action": "record_call", "next": "http://192.168.2.100/record_call_ack", "file": "20180516/010203.wav", #建议使用相对路径 "name": "record_callName" }
或者
{ "action": "record_call", "next": "http://192.168.2.100/record_call_ack", "file": "20180516/010203.wav", "name": "record_callName", "variables": { "RECORD_STEREO": "false", "RECORD_READ_ONLY": "true" } }
这样只录进来的语音。
execute
作用: 执行 XSwitch Application
属性:
- next: 指定一个新的 URL(后续请求使用)
- application: XSwitch Application
- data: Application 参数
默认情况下只允许执行 answer, sleep, hangup, info。如果要执行别的 Application, 则需要修改配置文件
例子:
{ "action": "execute", "next": "http://192.168.2.100/execute_ack", "application": "hangup" }
这是另外一个例子
{ "action": "execute", "next": "http://192.168.2.100/sleep", "application": "sleep", "data": "1000" }
transfer
作用: 根据 callee-id-numbe 的不同或者执行外呼(bridge), 或者执行转移呼叫(transfer 到 dialplan 重新路由)
属性:
- next: 指定一个新的 URL(后续请求使用)
- context: Dialplan context, 默认是 default
- dialplan: Dialplan dialplan, 默认是 XML
- caller-id-name: 主叫名称
- caller-id-number: 主叫号码
- callee-id-number: 指定被叫号码(不包含"/")或者呼叫字符串(包含"/")
说明:
- 如果 callee-id-number 是"1001"这种形式的号码(也就是不包含"/"), mod_ai 会 transfer 到 dialplan 里面路由
- 如果是这种形式"user/1001"或者"sofia/gateway/gw1/137xxx"(包含了"/"), 那么其实就是执行 bridge
一般的建议是, 呼叫外线可直接指定网关名称, 比如"sofia/gateway/gw1/137xxx"(走 gw1)。 呼叫内线则在内线号码前加上字冠, 然后 transfer 到 dialplan 里面路由, 比如"a1001"。这样更灵活一些。(在 dialplan 里面去掉字冠, 呼叫内线。如果呼叫 1001 失败还可以继续呼叫 1002, 等等)
例子:
{ "action": "transfer", "next": "http://192.168.2.117:4567/transfer_ack", "context": "default", "dialplan": "XML", "caller_id_number": "1002", "callee_id_number": "a1001" "variables": { "ringback": "${cn-ring}", "transfer_ringback": "local_stream://moh", "instant_ringback": "true" } }
例子中部分参数说明:
- ringback: 回铃音(即,通话后用户听到的等待音)。
- transfer_ringback: 转接音(如果将通话转接,则,转接过程中听到的音乐),也可填入自定义的语音文件(填入语音文件所在绝对路径即可)。
- instant_ringback:是配合 ringback 使用,目的在于 bridge 后立即回回铃音(可用也可不用)。
conference
作用: 会议
属性:
- profile: 指定 Conference profile, 默认值是 default
- next: 指定一个新的 URL(后续请求使用)
- pin: 指定会议密码
- name: 会议的名称, 这个参数不能缺少, 否则就做挂机处理,会议名称为英文、下划线和数字,不能有空格和特殊字符。
例子:
{ "action": "conference", "next": "http://192.168.2.117:4567/conference_ack", "pin": "8888", "name": "cnftest" }
answer
作用: 应答
属性:
- next: 指定一个新的 URL(后续请求使用)
例子:
{ "action": "answer", "next": "http://192.168.2.117:4567/answered" }
hangup
作用: 挂机
属性:
- next: 指定一个新的 URL(后续请求使用)
- cause: 挂机原因
例子:
{ "action": "hangup", "next": "http://192.168.2.117:4567/hangup_ack" }
uuid_kill
作用:对某一路通话进行挂机
获取到想要挂机的通话的唯一uuid
,执行如下指令,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"uuid_kill", "params": "toggle 0898bb2f-dc57-4b05-af5e-d0bdf774ca4f", //需要挂机的通话的`uuid` "url":"http://localhost:4567/asr", "private_data": { "aaa": "1", "bbb": "2" } } }
log
作用: 写日志
属性:
- next: 指定一个新的 URL(后续请求使用)
- level: 日志级别 CRIT ERR WARNING NOTICE INFO DEBUG
- clean: 如果为 true, 那么不添加时间
- data: 日志内容
例子:
{ "action": "log", "next": "http://192.168.2.117:4567/log_ack", "level": "DEBUG", "data": "you are welcome!" }
break
作用: 退出 ai 模块, 执行 dialplan 里面的后续动作
属性:
- next: 指定一个新的 URL(后续请求使用)
例子:
{ "action": "break", "next": "http://192.168.2.117:4567/break_ack" }
dialplan 如果是这样的话:
<extension name="ai"> <condition field="destination_number" expression="^8888$"> <action application="answer" /> <action application="ai" data="{url=http://192.168.2.100/ivr_entry}" /> <action application="playback" data="bye.wav" /> </condition> </extension>
退出 ai 模块之后, 自动播放 bye.wav
continue
作用: 继续, 什么也不做, 一般用来过度一下。
属性:
- next: 指定一个新的 URL(后续请求使用)
例子:
{ "action": "continue", "next": "http://192.168.2.117:4567/continue_ack" }
getVar
作用: 获取通道变量
属性:
- next: 指定一个新的 URL(后续请求使用)
- permanet: 永久参数或者仅仅一次
例子:
{ "action": "getVar", "next": "http://192.168.2.117:4567/getVar_ack" }
voicemail
作用: 检查或执行语音邮箱
属性:
- check: 如果为 true 则检查邮箱中的语音邮箱
- authOnly: 仅鉴权
- profile: 指定 Profile, 默认为 default
- domain: 域, 默认为全局变量 domain
- id: ID, 如果为空则会询问 ID
例子:
{ "action": "voicemail", "next": "http://192.168.2.117:4567/voicemail_ack", "check": "true", "authOnly": "1", "profile": "default", "domain": "192.168.1.101", "id": "1010" }
speak
作用: 用 TTS 播放语音
属性:
- next: 指定一个新的 URL(后续请求使用)
- name: 参数名称, mod_ai 下次发 HTTP 请求时自动带回来
- tts_engine: TTS 引擎
- tts_voice: TTS 嗓音
- text: 要播放的文本
- digit_timeout: 等待 DTMF 的超时时间
- input_timeout: 多个 DTMF 输入的最大间隔时间
- loops: 输入错误(与正则表达式不匹配)情况下的最大重试次数
- bind: 定义类似 bind_digit_action App 一样绑定一个正则表达式, 如果输入与正则表达式不匹配, 就播放 error_file(例如 invalid.wav)
bind 支持的属性:
- strip: 在 DTMF 串中去掉相关字符, 一般是#, 我们经常用作结束符但实际并不需要它。
- input: 定义输入的正则表达式
例子:
{ "action": "speak", "next": "http://192.168.2.117:4567/speak_ack", "name": "name-tts", "tts_engine": "baidu", "tts_voice": "0", "text": "您好!欢迎来到xxx" }
呼出
上面列举的全部是呼入后的处理。如果要让mod_ai
呼出, 那么应用服务器给 XSwitch 发 JSON-RPC 请求即可。
有关 JSON-RPC 的资料见附件。
呼出分ai.dial
和ai.dial2
两种,其中前者适用一路呼叫,或呼叫后再转接的场景,后者可同时发起两路呼叫并桥接。
ai.dial
适用单路外呼的场景。
JSON-RPC 的内容如下:
{ "jsonrpc": "2.0", "method": "ai.dial", "id": 1, //id可以自己指定 "params": { "application": "ai", "dial_string":"user/1006", //可选 "outbound_gateway": "可选, 网关名称, 平台指定, 默认为default", "user_gateway": "可选, 网关sip注册到XSwitch", "from": "主叫号码", "to": "被叫号码", "url": "呼叫成功后要访问的url", "external_tracking_id":"1234556778", //应用侧ID, 任意字符串, 在应用侧应该唯一 "apps":[ // 可选 { "app":"需要调用的app名称", "data":"app对应的参数" }, ... ] "private_data": { 随路数据(私有数据, 目前只支持string类型) . . . } } }
- id: id 由应用服务器自己指定。
- application: 呼叫成功后自动执行的 application, 目前支持 ai 和 httapi, 建议使用 ai。
- from: 主叫号码。
- to: 被叫号码。
- dial_string: XSwitch 呼叫字符串, 优先级最高, 可选。
- user_gateway: 可选, 网关 sip 注册到 XSwitch。用这种方式处理被叫难以传递的问题
- outbound_gateway: 可选, 外呼网关, 平台指定, 默认是 default。
- url: 呼叫成功后要访问的 URL。
- apps: 指定呼叫前需要执行的 app 或者需要设置的参数({"app":"set","data":"需要设置的参数"}), 可选
- private_data: 随路数据, 应用服务器自己指定。需要注意的是, 目前仅支持 string 类型。
特别注意:dial_string
、user_gateway
与outbound_gateway
为三选一,根据实际呼叫类型不同,选择不同呼叫方式,比如,呼叫内部分机可使用dial_string
,此时无须再增加outbound_gateway
的使用。
下面是一个呼叫内线(user)的实际例子,使用dial_string
,没有使用outbound_gateway
,如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "jsonrpc": "2.0", "method": "ai.dial", "id": 1, "params": { "application": "ai", "dial_string": "{leg_timeout=30,execute_on_answer='sched_hangup +10'}user/1007", "from": "8888", "url": "http://192.168.2.117:4567/resp", "external_tracking_id":"123453", "private_data": { "param1": "1", "seq": "2" } } }
下面是一个通过用户网关呼叫的实际例子,使用user_gateway
,如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "jsonrpc": "2.0", "method": "ai.dial", "params": { "from": "gaofei", //主叫名称/号码 "user_gateway": "1007", //用户网关 "originate_timeout":"10", //呼叫超时时长设置,比如10秒后如果未接听自动挂断 "max_seconds":"30", //通话最大时长,比如30秒后通话自动挂断 "to":"1009", //被叫号码 "application": "ai", //固定模块调用值 "url":"http://192.168.3.100:4567/gaofei", //回调地址 "external_tracking_id":"12333" //应用侧ID, 任意字符串, 在应用侧应该唯一 }, "id": 7888 }
下面是一个透过 XSwitch gateway 呼叫外线的实际例子,也使用dial_string
,如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 Content-Type: application/json [HTTP request 1/1] { "jsonrpc": "2.0", "method": "ai.dial", "id": 7888, "params": { "application": "ai", "dial_string": "sofia/gateway/vos/10000", #XSwitch事先配置好vos网关 "from": "8888", "url": "http://192.168.2.117:4567/resp", "external_tracking_id":"123330999", //应用侧ID, 任意字符串, 在应用侧应该唯一 "private_data": { "param1": "1", "seq": "2" } } }
或者使用outbound_gateway
呼叫外线,效果和上面例子相同,如下所示:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 Content-Type: application/json [HTTP request 1/1] { "jsonrpc": "2.0", "method": "ai.dial", "id": 1, "params": { "application": "ai", "to": "10000" "outbound_gateway": "vos", #XSwitch事先配置好vos网关 "from": "8888", "url": "http://192.168.2.117:4567/resp", "external_tracking_id":"1777333", "private_data": { "param1": "1", "seq": "2" } } }
ai.dial2
呼叫双方,并桥接通话。
JSON-RPC 的内容如下:
{ "jsonrpc": "2.0", "method": "ai.dial2", "id": 1, //id可以自己指定 "params": { "external_tracking_id": "应用侧ID, 任意字符串, 在应用侧应该唯一", "from": "主叫, 在回呼中为第一路的被叫", "to": "被叫, 在回呼中为第一路的主叫", "cid_name": "主叫名称, 可选", "cid_number": "主叫号码, 可选, 如有则覆盖to", "auto_answer": bool, 可选, 默认为true, 需要话机支持, "dial_string": "可选, XSwitch呼叫字符串, 优先级最高, 可选", "user_gateway": "可选, 注册上来的网关名称, 优先级次之", "outbound_gateway": "可选, 网关, 优先级低", "dial_flag":"fifo", "可选, 通话标志, 可根据这个设置,定义每路通话是ai还是fifo", "record_session": "可选, 是否录音,如果是,true", "early_media": 可选, 默认为false, "callback_url": "回调地址", "callback_method": "回调方法, 默认为POST", "b": { // 第二路参数, 全部可选 "auto_routing": "可选, 自动路由, 如果为true 就自动进行路由, 自动路由无法跟踪第二路状态", "cid_name": "可选, 主叫号码, 默认为from", "cid_number": "可选, 被叫号码, 默认为to", "auto_answer": "bool, 可选, 默认为false", "dial_string": "可选, XSwitch 呼叫字符串", "user_gateway": "可选, 注册上来的网关名称", "outbound_gateway": "可选, 外呼网关", "early_media": true, "可选, 默认为true" }, "private_data": { "随路数据, 会在回调中带回", 仅接收字符串键值对, 如: "a": "aaaa", "b": "bbbb" } } }
下面是一个通过gateway
呼叫外线再桥接到内线(user)的实际例子如下:
Hypertext Transfer Protocol POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "jsonrpc": "2.0", "method": "ai.dial2", "id": 1, "params": { "to":"1001", "from": "15666092344", "outbound_gateway":"vos1", "url": "http://192.168.2.117:4567/resp", "private_data": { "param1": "1", "seq": "2" } } }
结果为:通过网关呼叫15666092344
,手机接通后,呼叫账号 1001,1001 接通后,双方正常通话。
先通过gateway
呼叫外线再桥接到内线(user)情况下,如果你想修改坐席看到的号码显示(有些时候,不想让坐席人员看到客户信息),请参考如下使用:
Hypertext Transfer Protocol POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id": 15, "jsonrpc": "2.0", "method": "ai.dial2", "params": { "callback_url": "http://192.168.3.1:8000/seat/dial/callback", "from": "1566669999", "originate_params":"effective_caller_id_name=156xxxxx999,effective_caller_id_number=156xxxxx999", "outbound_gateway":"vos1", "private_data": { "callType": "2", "connectSort": 1, "conversationId": "123060001", "taskId": "12306" }, "record_session": true, "to": "819" } }
下面是先呼通坐席,再通过gateway
外呼,且启动录音,然后桥接的例子。具体指令如下:
{ "jsonrpc":"2.0", "method":"ai.dial2", "params": { "early_media":true, "dial_flag":"fifo", //坐席话单标志,通过话单可查询是否为人工坐席话单 "record_session":true, //坐席是否录音,true为是,false为否 "from":"1006", "to":"1008", "dial_string": "{RECORD_BRIDGE_REQ=true}user/1001", //其中,RECORD_BRIDGE_REQ=true,为当前通道桥接成功后才启动录音。 "b":{ "outbound_gateway":"vos1" }, "url":"http://192.168.3.45:4567/record", "external_tracking_id":"fdfdgegegfgvcxcvs" } }
同理,如果在先呼通坐席再呼叫手机的情况下,想修改坐席看到的号码显示(有些时候,不想让坐席人员看到客户信息),请参考如下:
{ "id": 16, "jsonrpc": "2.0", "method": "ai.dial2", "params": { "callback_url": "http://192.168.3.1:888/seat/dial/callback", "from": "1566669999", "outbound_gateway":"vos1", "b":{ "cid_name":"15xxxx9999", "cid_number":"156xxxx9999", "dial_string":"user/888" }, "record_session": true, "to":"15xxxx9999" } }
或者
{ "id": 16, "jsonrpc": "2.0", "method": "ai.dial2", "params": { "callback_url": "http://192.168.3.1:888/seat/dial/callback", "from": "1130", "b":{ "outbound_gateway":"vos1" }, "dial_string":"{RECORD_BRIDGE_REQ=true,origination_caller_id_name=156xxxx9999,origination_caller_id_number=xxx}user/1130 "record_session": true, "to":"15666669999", "external_tracking_id":"123060001" } }
监听
下面是先拨通用户账号再监听指定uuid
通话的实际例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "jsonrpc":"2.0", "method":"ai.dial", "id":1, "params": { "application":"eavesdrop", "app_data":"2f80ff31-aac3-4b28-9c8c-b8c7ce20c578", // 填入需要监听的uuid "from":"1008", "dial_string":"user/1008", //监听者的用户号码 "url":"http://192.168.3.45:4567/record", "external_tracking_id":"fdfdgegegfgvcxcvs" } }
三方通话
将指定uuid
进行三方通话,下面例子是针对呼叫内部分机进行三方通话,具体如下:
Hypertext Transfer Protocol POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "jsonrpc":"2.0", "method":"ai.dial", "id":1, "params": { "application":"three_way", "app_data":"2f80ff31-aac3-4b28-9c8c-b8c7ce20c578", // 填入uuid "from":"1008", "dial_string":"user/1008", //1008为注册到本机的内部分机 "url":"http://192.168.3.45:4567/record", "external_tracking_id":"fdfdgegegfgvcxcvs" } }
下面例子是针对呼叫外部服务器分机或手机等进行三方通话,dial_string
以及outbound_gateway
使用方法参考外呼,具体如下
Hypertext Transfer Protocol POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "jsonrpc":"2.0", "method":"ai.dial", "id":1, "params": { "application":"three_way", "app_data":"2f80ff31-aac3-4b28-9c8c-b8c7ce20c578", // 填入uuid "from":"1008", "outbound_gateway":"2999", // 2999为用户网关 "url":"http://192.168.3.45:4567/record", "external_tracking_id":"fdfdgegegfgvcxcvs" } }
url
中收到的回调消息如下:
{ "cause"=>"SUCCESS", "uuid"=>"eb4db8c2-91da-4cac-863c-4143ede38c51", "ai.dial"=>"success", "external_tracking_id"=>"fdfdgegegfgvcxcvs", "app_data"=>"b973f8a4-3ae2-4194-b5d9-e76cd48f3c3e" }
转接
通过api
接口将指定uuid
转接到指定路由。例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"uuid_transfer", "params": "0898bb2f-dc57-4b05-af5e-d0bdf774ca4f 1008", //将通话转接到1008上 "url":"http://localhost:4567/asr" } }
呼叫保持/解除保持
通过api
接口将指定uuid
处在保持状态。例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"uuid_hold", "params": "toggle 0898bb2f-dc57-4b05-af5e-d0bdf774ca4f", //需要保持的账号uuid "url":"http://localhost:4567/asr" } }
再次执行上一步指令即可实现解除呼叫保持:具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"uuid_hold", "params": "toggle 0898bb2f-dc57-4b05-af5e-d0bdf774ca4f", //相同账号的uuid "url":"http://localhost:4567/asr" } }
通过api
接口获取指定账号,比如 1006 的通道状态,例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"show", "params": "channels 1006", //查询1006账号的uuid "url":"http://localhost:4567/asr" //此url为接收查询信息的url,即,查询信息送往此url } }
分机注册状态查询
通过api
接口获取分机注册信息,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"sofia", "params": "status profile internal reg", //其中internal为用户注册源,默认为internal,也可根据实际情况自行命名。 "external_tracking_id":"" "url":"http://localhost:4567/asr" //此url为接收查询信息的url,即,查询信息送往此url } }
其中回调消息如下:
{ "result": "\nRegistrations:\n=================================================================================================\nCall-ID: \t60d36a4572774328b38bc960d8f0cf86\nUser: \t1001@192.168.3.59\nContact: \t\"\" <sip:1001@192.168.3.27:55460;ob>\nAgent: \tMicroSIP/3.19.7\nStatus: \tRegistered(UDP)(unknown) EXP(2019-07-19 10:18:23) EXPSECS(110)\nPing-Status:\tReachable\nPing-Time:\t0.00\nHost: \tdebian\nIP: \t192.168.3.27\nPort: \t55460\nAuth-User: \t1001\nAuth-Realm: \t192.168.3.59\nMWI-Account:\t1001@192.168.3.59\n\nTotal items returned: 1\n=================================================================================================\n" }
通道状态查询
通过api
接口获取通话通道状态例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"show", "params": "channels", "url":"http://localhost:4567/asr" //此url为接收查询信息的url,即,查询信息送往此url } }
通过api
接口获取指定账号,比如 1006 的通道状态,例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"show", "params": "channels 1006", //查询1006账号的uuid "url":"http://localhost:4567/asr" //此url为接收查询信息的url,即,查询信息送往此url } }
挂机接口
{ "id": "1", "jsonrpc": "2.0", "method": "ai.api", "params": { "command": "uuid_kill", "params": "af937190-26ba-4a8a-bed7-1e0417948dbf", //需要挂机的uuid "url": "http://localhost:4567/kill" } }
通话查询
通过api
接口获取当前通话数及通话状态例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params":{ "command":"show", "params": "calls", //所有通话状态信息 "url":"http://localhost:4567/asr" //此url为接收查询信息的url,即,查询信息送往此url } }
坐席队列信息查询
通过api
接口获取系统队列信息,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json [HTTP request 1/1] { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"fifo", "params": "list", //所有通话状态信息 "url":"http://localhost:4567/asr" //此url为接收查询信息的url,即,查询信息送往此url } }
坐席队列状态查询
通过api
接口获取系统队列状态信息,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json { "id":"1", "jsonrpc":"2.0", "method":"ai.api", "params": { "command":"fifo", "params": "status", //所有坐席状态信息 "url":"http://localhost:4567/asr" //此url为接收查询信息的url,即,查询信息送往此url } }
fsapi 调用
所有上述调用 ai.api 的查询接口均可直接使用 fsapi 调用获取。fsapi 和 ai.api 区别在于,调用 fsapi 后可以直接获取信息,无需将获取到的信息传给指定的 url,如下面所示
通道状态查询
通过api
接口获取通话通道状态例子,具体如下:
POST /v1 HTTP/1.1 //url是/v1 Host: 192.168.2.129:8081 //一般是8081端口 Content-Type: application/json { "id":"1", "jsonrpc":"2.0", "method":"fsapi", "params": { "cmd": "show", "arg": "channels as json" } }
补充说明
关于呼入的应答处理
<extension name="ai"> <condition field="destination_number" expression="^8888$"> <action application="answer"/> <action application="ai" data="{url=http://192.168.2.100/ivr_entry}"/> </condition> </extension>
在上面的例子中, 可以在路由里面执行完 answer 之后再执行 ai, 发 HTTP 请求到应用服务器。 也可以在路由里面不执行 answer, 直接执行 ai 这个 application。 dialplan 这么写:
<extension name="ai"> <condition field="destination_number" expression="^8888$"> <action application="ai" data="{url=http://192.168.2.100/ivr_entry}"/> </condition> </extension>
应用服务器回一个这样的 200 OK:
HTTP/1.1 200 OK Content-Type: application/json { "action":"execute", "application", "answer", "next":"http://xxx" }
让应用服务器控制应答, 好处是可以先播放完彩铃再应答, 更加灵活。 当然, 如果不想应答直接拒绝呼叫, 也没问题。
关于 session 结束
应用服务器应在每次收到 mod_ai 的 HTTP POST 消息的时候检查是否有 exiting 字段, 如果有, 那么就说明这个 session 已经结束。
关于 bind
<bind strip="#">~\\d+</bind>
输入任意一个数字, 正确。<bind strip="#">~\\d{6}</bind>
输入 6 个数字, 正确, 不需要输入#<bind strip="#">~\\d{6}#</bind>
输入 6 个数字, 再加上#, 正确。
ai.api
这里举个例子:
{ "jsonrpc": "2.0", "method": "ai.api", "id": 100, "params": { "command": "sofia_contact", "params": "1001", "url": "http://192.168.2.140:4567/xxx", "private_data": { "aaa": "111", "bbb": "2222" } } }
mod_ai
回复:
{ . . . "result": "sofia/default/sip:1002@192.168.101.58:5060;fs_nat=yes;fs_path=sip%3A1002%40180.173.70.168%3A1921" . . . }
关于录音
mod_ai
有 2 个action
: record
和record_call
, 前者一般称作为留言,是阻塞的。后者可同时录通道发送和接收的语音,是非阻塞的,可用在一对一通话的场景。
record
record 把录音文件保存在/tmp
目录下, 等到留言结束时(到达了 limit 或者收到 DTMF)发送 http fileupload 请求,把录音文件发送出去。 目前本地保留了录音文件的副本,在以后的版本里面可能会删除副本。
record_call
record_call 把录音文件保存在$$recordings_dir
目录, 一般是/usr/local/freeswitch/recordings
。
http 方面,record_call 是 2 次提交。一次是 record_call 执行成功后马上发送 http 请求,还有一次是在呼叫结束 reporting 阶段, record_call 发送 http fileupload 请求, 跟 record 一样,目前本地保留了录音文件的副本。
分段录音
分段录音是否开启通过slice-record
决定,当asr
中设置slice-record=true
则开启分段录音,录音存储到原录音文件下的以uuid
创建的文件夹里。
回调信息中通过record_file
查询分段录音地址。
例子:
{ "action": "play", "next": "http://192.168.2.100/play_ack", "file": "say:您好!请说出您要办理的业务", "name": "dialed_digits", "input_timeout": "5000", "digit_timeout": "3000", "asr_engine": "aliyun", "asr_grammar": "{accent=mandarin,threshold=800,silence-ms=500,no-input-timeout=3000,speech-timeout=30000,slice-record=true}default", "variables": { "tts_engine": "aliyun", "tts_voice": "ting ting" } }
fifo.api
fifo.api 主要实现增加和删除坐席。
JSON-RPC 的内容如下:
{ "jsonrpc": "2.0", "method": "fifo.api", "id": 100, //id可以自己指定 "params": { "action": "add", "fifo_name": "fifo队列名称", "user": "需要添加的成员", "simo": "设置并发数", "timeout": "设置超时数", "lag": "设置延迟数", "url": "http://localhost:4567/xxx", } }
其中 action 为 add 或者 del,若为 del 时,simo,timeout,lag 等值不需要设置,若为 add 时,这些值可以自己设置,也可以不设置采取默认值。
添加或删除后可以去 freeswitch 后台使用 fifo list 命令查看效果。
配置文件
配置文件是 ai.conf.xml, 一般情况下不需要修改。
http 服务例子
Ruby 语言示例:
require 'sinatra' require 'json' BASEURL="http://192.168.3.87:4567" before do if (request.content_type && request.content_type.include?("application/json") && (request.content_length.to_i > 1)) request.body.rewind @params = JSON.parse(request.body.read.to_s) end end get '/' do 'Hello AI' end post '/httapi' do # httapi test content_type 'text/xml' xml = <<EOF <document type="xml/freeswitch-httapi"> <params> <someparam>someval</someparam> </params> <variables> <somevar>someval</somevar> </variables> <work> <playback name="test" file="/wav/vacation.wav" xerror-file="/tmp/invalid.wav" digit-timeout="3000" input-timeout="5000"> <bind strip="#">~[0-9*#]{4}|#</bind> </playback> </work> </document> EOF puts xml xml end post '/httasr' do # httapi test content_type 'text/xml' xml = <<EOF <document type="xml/freeswitch-httapi"> <params> <someparam>someval</someparam> </params> <variables> <somevar>someval</somevar> </variables> <work> <playback name="test" file="#{BASEURL}/welcome.wav" xerror-file="/tmp/invalid.wav" asr-engine="baidu" asr-grammar="default"> <bind strip="#">~[0-9*#]{4}|#</bind> </playback> </work> </document> EOF end post '/json' do # httapi test content_type 'application/json' # puts request.inspect puts "---------- request -------------" puts params.to_s obj = {:action => "record_call", :next => "#{BASEURL}/asr", :file => "#{BASEURL}/vacation.wav", :name => "record_callName" } puts "========== response =============" puts obj.to_json puts obj.to_json end post '/asr' do # httapi test content_type 'application/json' puts "---------- request -------------" puts params.to_s obj = { :action => "play", :file => "say:尊敬的客户,您好,请说一句话", :loops => 1, :breakable => true, :"asr_engine" => "baidu", :"asr_grammar" => "{accent=mandarin, barge_in=true,threshold=1000,silence_ms=3000, no_input_timeout=5000,speech_timeout=100000}default", :voice=> "0", :asr => true, :vad_mode => 0, :next => "#{BASEURL}/asr_result", "variables": { "tts_engine": "baidu", "tts_voice": "baidu" }, :private_data => { :data1 => "a", :data2 => 2 } } puts "========== response =============" puts obj.to_json puts obj.to_json end post '/asr_result' do # httapi test content_type 'application/json' text = nil puts "---------- request -------------" puts params.to_s if params["asr_result"] && params["asr_result"]["text"] text = params["asr_result"]["text"]; end if text == nil || text == "" text = "对不起,我没听清您说什么" else text = "您说的是:" + text end obj = { :action => "play", :file => "say:" + text, :loops => 1, :vadMode => -1, :next => "#{BASEURL}/callback", "variables": { "tts_engine": "baidu", "tts_voice": "baidu" }, :private_data => { :data1 => "a", :data2 => 2 } } #obj = {:action => "hangup"} puts "========== response =============" puts obj.to_json obj.to_json end post '/play' do # 播放指定语音文件及次数 content_type 'application/json' puts "---------- request -------------" puts params.to_s obj = { :action => "speak", :file => "http://xswitch.cn/download/sounds/huawei.wav", //语音文件来源地址 :play_loops => "5", //播放次数 :next => "#{BASEURL}/asr_result" } puts "========== response =============" puts obj.to_json puts obj.to_json end post '/tts' do # 使用tts播放文字 content_type 'application/json' puts "---------- request -------------" puts params.to_s obj = { :action => "speak", :text => "烟台小樱桃致力于建设新的基于云计算的下一代互联网通信系统,帮助大型企业构建复杂的内外部音、视频通信系统,帮助中小企业使用最新的通信技术并向新的IP通信技术转型等", "tts_engine": "baidu", "tts_voice": "baidu" } puts "========== response =============" puts obj.to_json puts obj.to_json end post '/hangup' do # 挂机指令 content_type 'application/json' puts "---------- request -------------" puts params.to_s obj = {:action => "hangup"} puts "========== response =============" puts obj.to_json obj.to_json end post '/xswitch/event' do # 接收通话事件 puts params.to_s "" end