Tropo: interesting new IVR platform by Voxeo that supports Groovy

2009/03/14

Late last night, I ran across Tropo, a new IVR platform by Voxeo that supports a large variety of modern scripting languages, including my current favorite, Groovy (it also supports JavaScript, Ruby, Python, Jython, and PHP).

They just opened their “early beta” to the public about 10 days ago and have free accounts for developers to try things out.

They also have a github repository with a bunch of sample applications, and the adapter code that they’re using to make their core functionality available to all these different languages.

It’s nice to see an IVR company support all these modern things. VoiceXML has been rotting in a dungeon for the last 5 years and making a programming language out of an XML syntax was wrongheaded to begin with. Bringing languages like Groovy to bear on IVR problems will enable much more robust applications and quickent development.

Tropo’s documentation is a good start, but there are a number of holes in it since it’s so new. Because we have access to all of the yummy Groovy metaprogramming and reflection, we can find out lots of information about the system and it’s functionality for ourselves.

I’m just starting to dig into this, but here’s a quick groovy script that I threw together to spit out a bunch of information. It includes the version of groovy that they’re using (1.6, yay!) and what each of the variables are that are available through the script bindings. For each variable in the binding, I then spit out the variable’s class and all of the methods that are on it that aren’t the boring methods from the Object class:

import org.codehaus.groovy.runtime.InvokerHelper
 
answer()
say("putting bindings into log")
 
log("groovy version = ${InvokerHelper.version}")
log("binding variables: \n" + this.binding.variables.collect {k, v -> "$k = $v"}.join('\n\t'))
 
this.binding.variables.each {k, v ->
	log("(${v.class.name}) $k interesting methods\n = ${interestingMethods(v).join('\n\t')}")
}
 
say("successfully logged information about environment")
hangup()
 
def interestingMethods(clazz) {
	// remove boring Object methods
	return classMethods(clazz) - classMethods(Object.class)
}
 
def classMethods(clazz) {
    return clazz.metaClass.methods.name.sort().unique()
}

After uploading that script to my account (nice WebDAV support BTW!) and calling my new app with my skype phone, I can look at the logs and see this output towards the bottom:

groovy version = 1.6.0
 
binding variables: 
	callFactory = com.voxeo.fluffer.core.SimpleCallFactory@f4502f
	currentApp = TropoApp@1578f73
	appInstance = Application[36255:groovy:http://hosting.tropo.com/36255/www/helloWorld.groovy](sas_2-8-smonh87dfluffer)
	currentCall = TropoCall@18acac9, context = javax.script.SimpleScriptContext@10754f6
	incomingCall = SimpleCall [("null")sip:redacted]
	out = java.io.PrintWriter@5b112a
 
(com.voxeo.fluffer.core.SimpleCallFactory) callFactory interesting methods = 
	call
 
(TropoApp) currentApp interesting methods = 
	getBaseDir 
	getMetaClass 
	getProperty 
	get_app 
	invokeMethod 
	setBaseDir 
	setMetaClass 
	setProperty 
	set_app
 
(com.voxeo.fluffer.app.SimpleInstance) appInstance interesting methods = 
        block 
	getApp 
	getApplicationSession 
	getCurrentApplicationInstance 
	log 
	run 
	terminate
 
(TropoCall) currentCall interesting methods = 
	answer 
	ask 
	await 
	getCalledID 
	getCalledName 
	getCallerID 
	getCallerName 
	getMetaClass 
	getProperty 
	get_call 
	hangup 
	invokeMethod 
	isActive 
	log 
	prompt 
	record 
	redirect 
	reject 
	say 
	setCalledID
	setCalledName
	setCallerID 
	setCallerName 
	setMetaClass 
	setProperty 
	set_call 
	state 
	transfer
 
(javax.script.SimpleScriptContext) context interesting methods = 
	getAttribute 
	getAttributesScope 
	getBindings 
	getErrorWriter 
	getReader 
	getScopes
	getWriter 
	removeAttribute 
	setAttribute 
	setBindings 
	setErrorWriter 
	setReader 
	setWriter
 
(com.voxeo.fluffer.core.SimpleIncomingCall) incomingCall interesting methods = 
	answer 
	await 
	block 
	getASR 
	getCalledId 
	getCalledName 
	getCallerId 
	getCallerName 
	getState 
	getTTS 
	hangup 
	isActive 
	lock 
	log 
	prompt 
	promptWithRecord 
	redirect 
	reject 
	setState 
	signal 
	transfer 
	unlock 
	updateEndpoint
 
(java.io.PrintWriter) out interesting methods = 
	append 
	checkError 
	close 
	flush 
	format 
	print 
	printf 
	println 
	write

Looks like most of the coolest stuff is on the currentCall and incomingCall objects.

From what I’ve seen, I think there are a couple of holes in what Tropo currently supports for it to be a replacement for people using current VoiceXML technology in enterprise environments (stuff like backup TTS for missing .wav files), but I’ve found that their support people are very responsive (even at 1AM on a Friday night :). I believe that they have plans to fill in many of these gaps as the product matures and moves out of beta.

There’s plenty here for people to start experimenting with things and creating some sample applications.

I hope Voxeo continues to develop this platform as it has a lot of potential. I could see a fun grails plugin in the future to easily add IVR capabilities to an application.

There are 3 comments in this article:

  1. 2009/03/15Jonathan Taylor say:

    Ted, thanks for looking at some of the insides of Tropo. A few comments:

    >Looks like most of the coolest stuff is on the currentCall and incomingCall objects.

    incomingCall is the Java object that implements the Tropo “raw” API. We have what we call a shim script in each programming language Tropo supports that uses the raw API to implement a per-language core API. The core API is what you see in currentCall. In the output above currentCall is the Groovy implementation of the core API.

    The current Tropo documentation is all about the core API, but we’re exposing the raw API too to encourage development of other frameworks. In fact, we already have an enhanced framework that was built by a Ruby-guru (I’ll let him announce it) that we plan to bring to Groovy as well.

    > [missing things] like backup TTS for missing .wav files

    Yep, this is an item we discussed – but didn’t settle on an answer before the launch.

    Currently you play an audio file by simply including a URL in any prompt string you send to the Tropo prompt/say/ask commands, like this:

    “Hello, this is a bell: http://tropo.com/bell.wav Did you like it?”

    We are looking at either:

    1. Adding a Wiki-like syntax, like this:

    {{url|alternate-text}}

    Example:

    “Hello, this is a bell {{http://tropo.com/bell.wav|bell}}. Did you like it?

    2. removing the simple inline-URL syntax and only having the Wiki syntax above. One reason is to make it easy to read back a URL as a string in text-to-speech. Currently you have to escape or format a URL for Tropo to read it instead of play it.

    I’d love to hear any thoughts or alternatives anyone has related to the above options.

    > I believe that they have plans to fill in many of these gaps as the product matures and moves out of beta

    Absolutely. Over the next few months the Tropo API will get support for nearly all of the features found in our Prophecy CallXML, CCXML, and VoiceXML browsers. Tropo re-uses most of our Prophecy technology, including the SIPmethod Java JSR-289 SIP servlet engine and our Java JSR-309 Media Control API talking to our own MRCP-based media server… so its easy to get things going quickly.

    We’re also adding support for a few more programming languages. :)

    -Jonathan Taylor
    -CEO (Coding Executive Officer) Tropo / Voxeo

  2. 2009/03/15tednaleid say:

    Thanks for the detailed reply Jonathan. I think a wiki style syntax could work for backup TTS for a wav file. Of the 2 options that you present, I’d probably prefer the 2nd one for consistency (always requiring the wiki like syntax). It’d be easy enough to create a helper function in the shims (or in the end user’s code) to allow the easy playing/escaping of a wav file if needed:

    def sayWav(String wavUrl, List backupTts) {
        def URL_REGEXP = /http(?:s?):\/\/\S+/
        def index = 0
        def sayMe = wavUrl.replaceAll(URL_REGEXP) { match ->
            return "[ $match | ${backupTts[index++]} ]"
        }
        say(sayMe)
    }
     
    sayWav("I'm sorry http://example.com/dave.wav I'm afraid I can't do http://example.com/that.wav.",
         ["dave", "that"])
    // equivalent to:
    // say("I'm sorry [ http://example.com/dave.wav | dave ] I'm afraid I can't do [ http://example.com/that.wav. | that ]")

    Rather than double curly brackets, I personally prefer single square brackets (as media-wiki and some other wikis use), many text editors have nicer support for single character brackets and there’s not much reason for those types of characters in normal TTS dialog.

    Another alternative that I was thinking of would be similar to the wiki syntax, but might read nicer. It’s use sort of a prepared statement syntax where you’d have placeholder TTS that would be replaced by the values in an array of wav files in the 2nd parameter (if they can be found):

        say("I'm sorry [Dave] I'm afraid I can't do [that].", 
            ["http://example.com/dave.wav", "http://example.com/that.wav"])

    When I’m doing VXML apps, I’m normally coding the TTS version first as the audio hasn’t been recorded yet, so I think this style would fit my workflow better. That might not be true for everyone.

    The great thing about you guys opening up the platform to using languages like groovy is that even if I don’t like the particular solution that you come up with, as long as you provide the basic building blocks, I can write code to adapt things to my liking :).

  3. 2009/03/30Tropo: Interesting online IVR platform and a nice intro by Ted Naleid | devRealm.org say:

    [...] online IVR platform and a nice intro by Ted Naleid. Here is the original link. Share and [...]

Write a comment: