65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 VoiceXML 开发交互式电话调查应用程序

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (6投票s)

2006年7月18日

CPOL

7分钟阅读

viewsIcon

62032

downloadIcon

1074

VoiceXML 示例。

引言

VoiceXML 是一种用于创建语音用户界面的标记语言。它使用语音识别和/或按键(DTMF 键盘)作为输入,并使用预录音频和文本转语音合成(TTS)作为输出。它基于万维网联盟的可扩展标记语言(XML),并利用网络范式进行应用程序开发和部署。借助 VoiceXML,通过使用熟悉的网络基础设施,包括工具和 Web 服务器,语音识别应用程序的开发得到了极大的简化。

通过拥有共同的语言,应用程序开发人员、平台供应商和工具提供商都可以从代码的可移植性和重用中受益。对于此示例,我们将使用 Voicent Gateway 作为我们的 VoiceXML 服务器。可以从 此处 下载免费版网关。您应该能够将此示例移植到其他 VoiceXML 网关服务器,只需进行很少的更改。

在本文中,我们将为一家汽车服务店开发一个自动客户满意度调查应用程序。此示例应用程序将执行以下操作:

  1. 从带来汽车进行服务的客户列表中读取信息。

    我们将随机选择客户姓名、电话号码、服务日期和汽车制造商。如果您使用数据库作为客户列表,其原理也应该相同。

  2. 自动呼叫这些客户并收集他们对所提供服务的评分(1-5)。

    调查消息:“您好,这里是 ACME 汽车服务中心,呼叫 [客户姓名]。我们已于 [服务日期] 为您的 [汽车制造商] 汽车提供了维护服务。请您为我们的服务评分,1 到 5 分,5 分为最佳。感谢您的时间。”

    由于无法从答录机中收集任何反馈,因此答录机消息是:“您好,这里是 ACME 汽车服务中心,呼叫 [客户姓名]。我们已于 [服务日期] 为您的 [汽车制造商] 汽车提供了维护服务,我们感谢您的惠顾,如果您需要进一步的帮助,请联系我们。”

  3. 生成一份调查报告。

    我们想知道拨打了多少电话,有多少电话被答录机接听,线路是否忙,以及有多少客户对每个评分等级的服务进行了评分。

本文涵盖的主题包括:

安排外呼

要进行外呼,必须向 Voicent Gateway 发送呼叫请求。网关教程已涵盖呼叫请求处理程序的基本功能。要进行呼叫,只需向呼叫调度程序发送 HTTP POST 请求。

一旦呼叫请求与呼叫调度程序安排好,它将根据其呼叫时间被放入呼叫队列。在指定的呼叫时间,网关将进行外呼。

外呼控制流程

当系统进行外呼时,控制流程会经历以下步骤:

  1. 根据呼叫请求中指定的 starturl 获取起始 VXML 文件。
  2. 如果线路有拨号音,则拨打电话号码。
  3. 检测线路状态,例如无应答、线路忙、答录机、真人接听。
  4. 如果呼叫被答录机接听,则获取答录机的起始 VXML 文件。最初获取的起始 VXML 被丢弃。
  5. 在网关检测到真人接听(有人说“你好”)或检测到答录机提示音后执行起始 VXML 文件。
  6. 网关根据 VXML 应用程序与被呼叫者进行交互。
  7. 网关断开呼叫。
  8. 网关保存呼叫状态。

对真人接听和答录机播放不同的消息

如上一节所述,网关知道两个起始 VXML 文件,一个用于真人接听,另一个用于答录机。真人接听的起始 VXML 文件在 starturl 中定义。网关首先获取没有参数的 VXML 文件,如果呼叫被答录机接听,网关会丢弃之前获取的 VXML 文件,然后获取带有“ans=t”的 VXML 文件。

例如,如果您的 starturl 定义为 http://mydomain/myapp/start.jsp。网关将通过向定义的 URL 发送 HTTP 请求来获取起始 VXML 文件,即 http://mydomain/myapp/start.jsp。如果呼叫被答录机接听,网关将向同一 URL 发送另一个带有参数 ans=t 的 HTTP 请求,即 http://mydomain/myapp/start.jsp?ans=t

以下示例 JSP 文件将为真人接听播放 live.wav,为答录机播放 answering.wav。WAV 文件必须位于 audio 目录下。暂时,在这两个 WAV 文件中录制任何消息。我们将随着本教程的开发添加更多功能。

<?xml version="1.0"?>
<vxml version="1.0">

<%  
    String ans = request.getParameter("ans");
    boolean isAnsweringMachine = ("t".equals(ans));
%>
<form id="td">
  <block>

<% if (isAnsweringMachine) { %>
    <audio src="audio/answering.wav"/>
<% } else { %>
    <audio src="audio/live.wav"/>
<% } %>

  </block>
</form>
</vxml>

除了简单的应用程序,大多数语音应用程序都需要动态生成的 VXML 文件。当您的应用程序需要与网站或数据库集成时,尤其如此。

知道按下了哪个键

通过动态生成的 VXML 文件,您几乎可以随心所欲地处理您的应用程序。在此示例中,我们将从客户那里收集按键 1-5。以下是更新后的 start.jsp 文件:

<?xml version="1.0"?>
<vxml version="1.0">

<%  
    String ans = request.getParameter("ans");
    boolean isAnsweringMachine = ("t".equals(ans));
%>
<form id="td">

<% if (isAnsweringMachine) { %>

  <block>
    <audio src="audio/answering.wav"/>
  </block>

<% } else { %>
  <field name="rating">
    <prompt timeout="10s">
      <block>
        <audio src="audio/live.wav"/>
      </block>
    </prompt>
    <dtmf>
      1 | 2 | 3 | 4 | 5
    </dtmf>
    <filled>
      <submit next="recordrating.jsp" namelist="rating"/>
    </filled>
  </field>

<% } %>
</form>
</vxml>

如您所见,按键由 VXML 文件的“rating”字段捕获。此值随后提交给名为 recordrating.jsp 的下一个 JSP 文件。

<?xml version="1.0"?>
<vxml version="1.0">

<%  
    String key = request.getParameter("rating");
    int ratetotal = 1;
    Integer RateTotal = (Integer) application.getAttribute("rate" + key);
    if (RateTotal != null)
        ratetotal = RateTotal.intValue() + 1;
    application.setAttribute("rate" + key, new Integer(ratetotal));
%>

<form id="td">
  <block>
    <audio src="audio/thankyou.wav"/>
  </block>
</form>
</vxml>

获取线路状态:线路忙、无应答等.

VoiceXML 中的线路状态由 VXML 异常处理。以下是异常值:

"telephone.noanswer"
"telephone.noline"
"telephone.linebusy"
"telephone.linedrop"

您当然可以在自己的 VXML 代码中捕获这些异常并相应地处理这些异常。

<form id="td">
...

  <catch event="telephone.noline">
    <submit next="..." namelist="..."/>
  </catch>

  <catch event="telephone.linebusy">
    <submit next="..." namelist="..."/>
  </catch>

...
</form>

知道网关正在请求哪个自定义记录

引言部分描述的调查消息:

“您好,这里是 ACME 汽车服务中心,呼叫 [客户姓名]。我们已于 [服务日期] 为您的 [汽车制造商] 汽车提供了维护服务。请您为我们的服务评分,1 到 5 分,5 分为最佳。感谢您的时间。”

当网关呼叫一位客户时,它将通过 starturl 参数中指定的 URL 获取动态生成的 VXML 文件。当我们向网关发送呼叫请求时,我们实际上不知道网关何时会回调以获取 VXML 文件。如果我们使用的是多线系统,将存在对 starturl 的并发访问。

为了解决这个问题,例如,您可以在 starturl 字符串中嵌入客户 ID。因此,当网关回拨时,它会将客户 ID 提交回 starturl。更新后的 start.jsp 文件如下所示:

<?xml version="1.0"?>
<vxml version="1.0">

<%  
    String ans = request.getParameter("ans");
    boolean isAnsweringMachine = ("t".equals(ans));
    String customer_name = request.getParameter("customer_name");
    String car_maker = request.getParameter("car_maker");
    String service_date = request.getParameter("service_date");
%>
<form id="td">

<% if (isAnsweringMachine) { 

    int anstotal = 1;
    Integer AnsTotal = (Integer) application.getAttribute("anstotal");
    if (AnsTotal != null)
        anstotal = AnsTotal.intValue() + 1;
    application.setAttribute("anstotal", new Integer(anstotal));

%>

<block>
  <audio src="audio/acme.wav"/>
    <%=customer_name%>
    <audio src="audio/we_provide.wav"/>
    <%=car_maker%>
    <audio src="audio/maintenance.wav"/>
    <%=service_date%>
    <audio src="audio/thanks.wav"/>
</block>

<% } else { %>
  <field name="rating">
    <prompt timeout="10s">
      <block>
        <audio src="audio/acme.wav"/>
          <%=customer_name%>
          <audio src="audio/we_provide.wav"/>
          <%=car_maker%>
          <audio src="audio/maintenance.wav"/>
          <%=service_date%>
          <audio src="audio/please_press_15.wav"/>
      </block>
    </prompt>
    <dtmf>
      1 | 2 | 3 | 4 | 5
    </dtmf>
    <filled>
      <submit next="recordrating.jsp" namelist="rating"/>
    </filled>
  </field>

<% } %>
</form>
</vxml>

调查起始页

现在我们已经开发了应用程序 VXML 文件,我们可以开始实现调查控件和报告功能。调查起始页如下所示:

Sample screenshot

为了简化示例,起始页只接受一个逗号分隔的电话号码列表。在服务器端,应用程序将随机分配其他必要的值,例如汽车制造商和服务日期。在真实的调查应用程序中,起始页可能会执行一些数据库查询并相应地执行操作。但与 Voicent Gateway 相关的关键功能应该完全相同。

调查报告页

点击“开始调查”按钮后,surveyHandler.jsp 会将所有呼叫发送到网关,然后返回一个调查报告页面。

Sample result

当您点击“显示当前调查报告”按钮时,浏览器会向同一 surveyHandler.jsp 页面发送 POST 请求,并设置 action=report。处理程序将执行以下操作:

// check call status from the list
int callsToBeMade = 0;
int callsFailed = 0;
for (int i = 0; i < m_callRecords.size(); i++) {
    CallRecord rec = (CallRecord) m_callRecords.get(i);
    if (rec.m_callStatus == null) {
        getCallStatus(rec);
        if (rec.m_callStatus == null) {
            callsToBeMade++;
            continue;
        } 
    }
    if (rec.m_callStatus.equals("Call Failed"))
        callsFailed++;
}

// get the rest from application vars
int answeringTotal = getRateTotal(application, "anstotal");
int rate1 = getRateTotal(application, "rate1");
int rate2 = getRateTotal(application, "rate2");
int rate3 = getRateTotal(application, "rate3");
int rate4 = getRateTotal(application, "rate4");
int rate5 = getRateTotal(application, "rate5");

有关详细信息,请查看示例的源代码。音频文件也包含在示例中。它们是使用 Voicent 自然文本转语音引擎自动生成的。

历史

  • 2006 年 7 月 18 日:首次发布
© . All rights reserved.