|
In this monthly column, an industry expert will answer
common questions about VoiceXML and related technologies.
Readers are encouraged to submit questions about VoiceXML,
including development, voice-user interface design,
and speech technology in general, or how VoiceXML is
being used commercially in the marketplace. If you have
a question about VoiceXML, e-mail it to speak.and.listen@voicexmlreview.org
and be sure to read future issues of VoiceXML Review
for the answer.
Q. How do I make my voice application bullet-proof,
providing my callers with application-specific prompts
even in the event of an unexpected failure?
A.
To make your application bullet-proof you need to understand
how it can fail. These can be summed up as follows:
+ A network or Web server error.
+ A syntax or run-time error in your voice application code.
A
first step, and by no means a trivial one, is to make
sure your network is reliable. VoiceXML interpreters
communicate with document servers via HTTP, and regardless
of whether the interpreter running your voice application
is located across the country or across a room, hardware
can fail and connections can be severed. You'll need
to check your purse strings to determine if your voice
application is mission critical enough to mitigate these
risks by hosting your voice application on multiple
document servers in geographically redundant locations.
These same requirements hold true for the VoiceXML interpreters
and their associated telephony, recognition, and audio
resources used to process your application.
Given
network reliability, let's assume that the interpreter
is able to fetch your voice application's start page,
application root document, and any resources (e.g. scripts
libraries, grammars)
referenced by these documents. You need to ensure that
your VoiceXML code properly handles all runtime errors.
Errors of this sort are typically HTTP or semantic errors,
and the VoiceXML interpreter maps these errors to events
which it throws to your application, for example, "error.badfetch.http.404"
or "error.semantic". If you neglect to handle
these events, the interpreter typically handles them
in a generic fashion, sometimes by playing Text-To-Speech
(TTS), or, worse yet, by hanging up on the caller. In
a truly professional application, both are to be avoided,
and it's easy to do so in your application root document.
The
following application root document implements a number
of event handlers. The first two are nomatch and noinput
handlers. You typically handle these events local to
the input item (e.g. field) where they are thrown so
that you can provide context-sensitive prompts to aid
the user.
If, however, one of your VoiceXML developers neglects
to handle these events, it would be a shame to hang
up on the caller, so you include some generic ones in
the application root.
The
next handler, connection.disconnect.*, handles situations
where the session is terminated, either because the
user hung up, was disconnected via the <disconnect/>
element, or was blind transferred. The final handler
is a "catch-all", that is, a handler that
catches any event that is not specifically handled elsewhere
in your application. This handler should always appear
last in your application root for reasons described
in 5.2.4 of the
VoiceXML 2.0 specification, "Catch Element Selection".
<!--
application root document -->
<vxml version="2.0">
<nomatch>
I'm sorry. I didn't get that.
<reprompt/>
</nomatch>
<noinput>
I'm sorry. I didn't hear you.
<reprompt/>
</noinput>
<catch
event="connection.disconnect.*">
<log>caller hung up or was disconnected</log>
</catch>
<catch>
<log>app catch-all caught <value
expr="_event"/></log>
I'm sorry. An unexpected error occurred.
Please try again later.
<disconnect/>
</catch>
</vxml>
|
Let's
revisit network reliability. Specifically, what happens
if the VoiceXML interpreter, upon answering a call,
is unable to fetch the start page of your voice application.
This can happen if the document servers hosting your
voice application are unavailable or you have a coding
error in the start page, the application root, or another
resource referenced by these documents. In that case,
it's up to the interpreter to provide default handling.
Some interpreters may allow you to customize that default
handling. Consult your VoiceXML platform documentation,
or contact your provider to find out.
Q. Playing a custom prompt in the event of a
failure is great, but hanging up on the caller
seems less than customer-focused. In the event of application
failure, how do I properly transfer my user to a live
agent?
A.
As you know, the transfer element is a form item, so
you'll need to navigate from the catch handler to a
form where the transfer can be executed. Easy enough:
|
<!-- application root document -->
<vxml version="2.0">
<!-- other catches omitted -->
<catch>
<log>app catch-all caught <value expr="_event"/></log>
<goto next="transfer.vxml"/>
</catch>
</vxml>
<!-- transfer.vxml -->
<vxml version="2.0">
<form id="xfer">
<transfer dest="+18005558355" bridge="false"/>
</form>
</vxml>
|
What if, in spite of all your attempts at network reliability,
the link to your document servers are down?
You won't be able to fetch another document containing
the transfer form. You'll need to include that form
in a document that's currently loaded. During execution
of a voice application, two documents are typically
loaded at any given time: the current leaf document
and the application root document. Alas, you won't know
ahead of time which of the leaf documents in your application
will be loaded when the failure occurs, so you have
a couple of options:
- Include a transfer form with a consistent name in
every single leaf document.
- Include a single transfer form in your application
root document.
The former seems like an onerous option to maintain,
unless you're using a template engine to generate your
pages, either on the server at run-time or at build-time
before publishing your application to your document
servers. The latter, on the other hand, seems straightforward
and far easier to maintain. Before jumping to that conclusion,
however, you need to keep the following in mind from
5.2 of the VoiceXML 2.0 specification:
- Event handlers are executed "as if by copy".
- "Relative URI references in a catch element are
resolved against the active document and not relative
to the document in which they were declared."
Although the catch-all handler appears in your application
root document, it will be executed as if it were copied
into the document where the unexpected event occurred.
For example, the following is invalid:
|
<!-- application root document -->
<vxml version="2.0">
<!-- other catches omitted -->
<catch>
<log>app catch-all caught <value expr="_event"/></log>
<goto next="#xfer"/>
</catch>
<form id="xfer">
<transfer dest="..."/>
</form>
</vxml>
|
So, if the documents that comprise your voice application
are organized in a complex directory hierarchy, you
need to either use an absolute URL which has its own
associated maintenance issues, or use relative paths
and make sure you get the path to the application root
right regardless of the leaf document that's loaded
when the error occurs. If you keep all the VoiceXML
document in your application located in the same directory
as the application root for example, navigation is simple:
|
<catch>
<log>app catch-all caught <value expr="_event"/></log>
<goto next="application_root.vxml#xfer"/>
</catch>
|
If your developers insist on breaking up the modules
of your application into separate directories, another
strategy is to require them to include a consistently
named variable at document scope in each leaf document
of the application. The value of the variable must be
set to the depth of the document from top-level directory
of the application. Given the location of the application
root document from the application's top-level directory,
you can easily compose the path from any leaf document
to the root.
|
<!-- some leaf document -->
<vxml version="2.0" application="../../application_root.vxml">
<var name="depth" expr="'../../'"/>
</vxml>
<!-- application root document -->
<vxml version="2.0">
<!-- other catches omitted -->
<catch>
<log>app catch-all caught <value expr="_event"/></log>
<goto expr="('string' == typeof depth ? depth
: '') + 'application_root.vxml#xfer'"/>
</catch>
</vxml>
|
Q.
I've implemented a subdialog to obtain a credit card
number, and I want to use it in multiple applications.
Each application has its own error handling which includes
transferring to a distinct agent. In addition, if the
user hangs up in the middle of credit card collection,
each application performs its own hangup post-processing.
What's the most efficient way to handle unexpected errors
and hangups from within the subdialog that is consistent
with the handling implemented within the calling application?
A.
You're probably aware that when a VoiceXML interpreter
executes a subdialog, it creates a new context isolated from
the application that called it. This means that, typically,
you don't reference the application root document of your
application from within the subdialog implementation. This
maximizes reusability of the subdialog within multiple applications.
It also allows your subdialog to execute more efficiently
since it doesn't need to reload and reinitialize the application
root document. By the same token, it also means that your
subdialog doesn't inherit all the robust error handling you
undoubtedly included in your application root or in the calling
document. To answer your question, the most efficient way
to handle unexpected errors in your subdialog is to return
them and to let the caller deal with them. The following code
illustrates:
<vxml
version="2.0">
<catch>
<return eventexpr="_event"/>
</catch>
<form id="get_credit_card">
<!-- subdialog implementation
-->
</form>
</vxml>
|
Regardless
of the event thrown by the interpreter, the catch-all handler
returns it to the calling subdialog. You can, of course, handle
the events your subdialog is prepared to handle by overriding
the catch-all. In the following example, the subdialog explicitly
handles nomatch and noinput events.
<vxml
version="2.0">
<catch>
<return eventexpr="_event"/>
</catch>
<form id="get_credit_card">
<field name="cnum">
<prompt>Say
your card number</prompt>
<nomatch>
I'm
sorry I didn't get that.
Please
speak or touch tone your credit card number.
</nomatch>
<noinput>
I'm
sorry I didn't hear you.
Please
speak or touch tone your credit card number.
</noinput>
</field>
<!--
more code to get expiration date -->
</form>
</vxml>
|
See
the catch element selection algorithm described in section
5.2.4 of the VoiceXML 2.0 specification.

back
to the top

Copyright
© 2001-2004 VoiceXML Forum. All rights reserved.
The VoiceXML Forum is a program of the
IEEE
Industry Standards and Technology Organization (IEEE-ISTO).
|