Ground-Up with the Bot Framework

It seems I can’t write about code these days without a warmup rant. So feel free to jump directly to the next section if you like. But where’s the fun in that?

My mixed (ok negative) feelings about “quickstarts” go back all the way to the invention of “Wizards” at Microsoft in the early 1990s. They serve a worthy goal, guiding users through a complex process to deliver value quickly. But even in those earliest days, it was clear that the reality was little more than a cheap dopamine hit, mostly good for demos and maybe helping show what’s possible. The problem comes down to two (IMNSHO) fatal flaws:

First, quickstarts abandon users deep in the jungle with a great SUV but no map or driver’s license. Their whole reason to exist is to avoid annoying details and optionality, but that means that the user has no understanding of the context in which the solution was created. How do you change it? What dependencies does it require? How does it fit into your environment? Does it log somewhere? Is it secured appropriately for production? How much will it cost to run? The end result is that people constantly put hacked-up versions of “Hello World” into production and pay for it later when they have no idea what is really going on.

Second, they make developers even lazier than they naturally are anyways. Rather than start with the basics, quickstarts skip most of the hard stuff and lock in decisions that any serious user will have to make for themselves. If this was the start of the documentation, that’d be fine — but it’s usually the end. Instead of more context, the user just gets dropped unceremoniously into auto-generated references that don’t provide any useful narrative. Even worse, existence of the quickstart becomes an excuse for a sloppy underlying interface design (whether that’s an API or menus and dialogs) — e.g., why worry about the steering wheel if people take the test-drive using autopilot?

Anyways, this is really just a long-winded way to say that the Bot Framework quickstart is pretty useless, especially if you’re using Java. Let’s do better, shall we?

What is the Bot Framework?

There are a bunch of SDKs and builders out there for creating chatbots. The Microsoft Bot Framework has been around for a while (launched out of Microsoft Research in 2016) and seems to have pretty significant mindshare. Actually the real momentum really seems to be with no-code or low-code options, which makes sense given how many bots are shallow marketing plays — but I’m jumping right into the SDK here because that’s way more fun, and it’s my blog.

The framework is basically a big normalizer. Your bot presents a standardized HTTPS interface, using the Bot Framework SDK to help manage the various structures and state. The Azure Bot Service acts as a hub, translating messages in and out of various channels (Teams, Slack, SMS, etc.) and presenting them to your interface. Honestly, that’s basically the whole thing. There are additional services to support language understanding and speech-to-text and stuff like that, but it’s all additive to the basic framework.

WumpusBot and RadioBot

I introduced WumpusBot in my last post … basically a chatbot that lets you play a version the classic 1970s game Hunt the Wumpus. The game logic is adapted from a simplified version online and lives in Wumpus.java, but I won’t spend much time on that. I’ve hooked WumpusBot up to Twillio SMS, so you can give it a try by texting “play” to 706-943-3865.

The project also contains RadioBot, a second chatbot that knows how to interact with the Shutdown Radio service I’ve talked about before. This one is hooked up to Microsoft Teams and includes some slightly fancier interactions — I’ll talk about that after we get a handle on the basics.

Build Housekeeping

All this is hosted in an Azure Function App — so let’s start there. The code is on github. You’ll need git, mvn and a JDK. Build like this:

git clone https://github.com/seanno/shutdownhook.git
cd shutdownhook/toolbox
mvn clean package install
cd ../radio/azure
mvn clean package

To run you’ll need two Cosmos Containers (details in Shutdown Radio on Azure, pay attention to the Managed Identity stuff) and a local.settings.json file with the keys COSMOS_ENDPOINT, COSMOS_ DATABASE, COSMOS_CONTAINER and COSMOS_CONTAINER_WUMPUS. You should then be able to run locally using “mvn azure-functions:run.”

Getting a little ahead of myself, but to deploy to Azure you’ll need to update the “functionAppName” setting in pom.xml; “mvn azure-functions:deploy” should work from there assuming you’re logged into the Azure CLI.

The Endpoint

Your bot needs to expose an HTTPS endpoint that receives JSON messages via POST. The Java SDK would really like you to use Spring Boot for this, but it 100% isn’t required. I’ve used a standard Azure Function for mine; that code lives in Functions.java. It really is this simple:

  1. Deserialize the JSON in the request body into an Activity object (line 68).
  2. Pull out the “authorization” header (careful of case-sensitivity) sent by the Bot Framework (line 71).
  3. Get an instance of your “bot” (line 52). This is the message handler and derives from ActivityHandler in WumpusBot.java.
  4. Get an instance of your “adapter.” This is basically the framework engine; we inherit ours from BotFrameworkHttpAdapter in Adapter.java.
  5. Pass all the stuff from steps 1, 2 and 3 to the processIncomingActivity method of your Adapter (line 74).
  6. Use the returned InvokeResponse object to send an HTTPS status and JSON body back down the wire.

All of which is to say, “receive some JSON, do a thing, send back some JSON.” Wrapped up in a million annoying Futures.

The Adapter

The BotAdapter acts as ringmaster for the “do a thing” part of the request, providing helpers and context for your Bot implementation.

BotFrameworkHttpAdapter is almost sufficient to use as-is; the only reason I needed to extend it was to provide a custom Configuration object. By default, the object looks for configuration information in a properties file. This isn’t a bad assumption for Java apps, but in Azure Functions it’s way easier to keep configuration in the environment (via local.settings.json during development and the “Configuration” blade in the portal for production). EnvConfiguration in Adapter.java handles this, and then is wired up to our Adapter at line 34.

The adapter uses its configuration object to fetch the information used in service-to-service authentication. When we register our Bot with the Bot Service, we get an application id and secret. The incoming authentication header (#2 above) is compared to the “MicrosoftAppId” and “MicrosoftAppSecret” values in the configuration to ensure the connection is legitimate.

Actually, EnvConfiguration is more complicated than would normally be required, because I wanted to host two distinct bots within the same Function App (WumpusBot and RadioBot). This requires a way to keep multiple AppId and AppSecret values around, but we only have one System.env() to work with. The “configSuffix” noise in my class takes care of that segmentation.

There are a few other “providers” you can attach to your adapter if needed. The most common of these is the “AuthenticationProvider” that helps manage user-level OAuth, for example if you want your bot to access a user’s personal calendar or send email on their behalf. I didn’t have any need for this, so left the defaults alone.

Once you get all this wired up, you can pretty much ignore it.

The Bot

Here’s where the fun stuff starts. The Adapter sets up a TurnContext object and passes it to the onTurn method of your Bot implementation. The default onTurn handler is really just a big switch on the ActivityType (MESSAGE, TYPING, CONVERSATION_UPDATE, etc.) that farms out calls to type-specific handlers. Your bot can override any of these to receive notifications on various events.

The onMessageActivity method is called whenever your bot receives a (duh) message. For simple text interactions, simply call turnContext.getActivity().getText() to read the incoming text, and turnContext.sendActivity(MessageFactory.text(responseString)) to send back a response.

The Bot Framework has tried to standardize on markdown formatting for text messages, but support is spotty. For example Teams and WebChat work well, but Skype and SMS just display messages as raw text. Get used to running into this a lot — normalization across channels is pretty hit or miss, so for anything complex you can expect to be writing channel-specific code. This goes for conversation semantics as well. For example from my experience so far, the onMembersAdded activity:

  • Is called in Teams right away when the bot enters a channel or a new member joins;
  • Is called in WebChat only after the bot receives an initial message from the user; and
  • Is never called for Twilio SMS conversations at all.

Managing State

Quirks aside, for a stateless bot, that’s really about all there is to it. But not all bots are stateless — some of the most useful functionality emerges from a conversation that develops over time (even ELIZA needed a little bit of memory!) To accomplish that you’ll use the significantly over-engineered “BotState” mechanism you see in use at WumpusBot.java line 57. There are three types of state:

All of these are the same except for the implementation of getStorageKey, which grovels around in the turnContext to construct an appropriate key to identify the desired scope.

The state object delegates actual storage to an implementation of a CRUD interface. The framework implements two versions, one in-memory and one using Cosmos DB. The memory one is another example of why quickstarts are awful — it’s easy, but is basically never appropriate for the real world. It’s just a shortcut to make the framework look simpler than it really is.

The Cosmos DB implementation is fine except that it authenticates using a key. I wanted to use the same Managed Identity I used elsewhere in this app already, so I implemented my own in Storage.java. I cheated a little by ignoring “ETag” support to manage versioning conflicts, but I just couldn’t make myself believe that this was going to be a problem. (Fun fact: Cosmos lets you create items with illegal id values, but then you can’t ever read or delete them without some serious hackage. That’s why safeKey exists.)

Last and very important if you’re implementing your own Storage — notice the call to enableDefaultTyping on the Jackson ObjectMapper. Without this setting, the ObjectMapper serializes to JSON without type information. This is often OK because you’re either providing the type directly or the OM can infer reasonably. But the framework’s state map is polymorphic (it holds Objects), so these mechanisms can’t do the job. Default typing stores type info in the JSON so you get back what you started with.

Once you have picked your scope and set up Storage, you can relatively easily fetch and store state objects (in my situation a WumpusState) with this pattern:

  1. Allocate a BotState object in your Bot singleton (line 39).
  2. Call getProperty in your activity handler to set up a named property (line 57).  
  3. Fetch the state using the returned StatePropertyAccessor and (ugh) wait on the Future (lines 58-60). Notice the constructor here which is used to initialize the object on first access.  
  4. Use the object normally.
  5. Push changes back to storage before exiting your handler (line 68). Change tracking is implicit, so be sure to update state in the specific object instance you got in step #3. This is why Wumpus.newGame() never reallocates a WumpusState once it’s attached.

Testing your Bot Locally

Once you have your Function App running and responding to incoming messages, you can test it out locally using the Bot Framework Emulator. The Emulator is a GUI that can run under Windows, Mac or Linux (in X). You provide your bot’s endpoint URL (e.g., http://localhost:7071/wumpus for the WumpusBot running locally with mvn azure-functions:run) and the app establishes a conversation that includes a bunch of nifty debugging information.

Connecting to the Bot Service

The emulator is nice because you can manage things completely locally. Testing with the real Bot Service gets a little more complicated, because it needs to access an Internet-accessible endpoint.

All of the docs and tutorials have you do this by running yet another random tool. ngrok is admittedly kind of cute — it basically just forwards a port from your local machine to a random url like https://92832de0.ngrok.io. The fact that it can serve up HTTPS is a nice bonus. So if you’re down for that, by all means go for it. But I was able to do most of my testing with the emulator, so by the time I wanted to see it live, I really just wanted to see it live. Deploying the function to Azure is easy and relatively quick, so I just did that and ended up with my real bot URL: https://shutdownradio.azurewebsites.net/wumpus.

The first step is to create the Bot in Azure. Search the portal for “Azure Bot” (it shows up in the Marketplace section). Give your bot a unique handle (I used “wumpus”) and pick your desired subscription and resource group (fair warning — most of all this can be covered under your free subscription plan, but you might want to poke around to be sure you know what you’re getting into). Java bots can only be “Multi Tenant” so choose that option and let the system create a new App ID.

Once creation is complete, paste your bot URL into the “Messaging Endpoint” box. Next copy  down the “Microsoft App Id” value and click “Manage” and then “Certificates & secrets.” Allocate a new client secret since you can’t see the value of the one they created for you (doh). Back in the “Configuration” section of your Function app, add these values (remember my comment about “configSuffix” at the beginning of all this):

  • MicrosoftAppId_wumpus (your app id)
  • MicrosoftAppSecret_wumpus (your app secret)
  • MicrosoftAppType_wumpus (“MultiTenant” with no space)

If you want to run RadioBot as well, repeat all of this for a new bot using the endpoint /bot and without the “_wumpus” suffixes in the configuration values.

Congratulations, you now have a bot! In the Azure portal, you can choose “Test in Web Chat” to give it a spin. It’s pretty easy to embed this chat experience into your web site as well (instructions here).

You can use the “Channels” tab to wire up your bot to additional services. I hooked Wumpus up to Twilio SMS using the instructions here. In brief:

  • Sign up for Twilio and get an SMS number.
  • Create a “TwiML” application on their portal and link it to the Bot Framework using the endpoint https://sms.botframework.com/api/sms.
  • Choose the Twilio channel in the Azure portal and paste in your TwiML application credentials.

That’s it! Just text “play” to 706-943-3865 and you’re off to the races.

Bots in Microsoft Teams

Connecting to Teams is conceptually similar to SMS, just a lot more fiddly.

First, enable the Microsoft Teams channel in your Bot Service configuration. This is pretty much just a checkbox and confirmation that this is a Commercial, not Government, bot.

Next, bop over to the Teams admin site at https://admin.teams.microsoft.com/ (if you’re not an admin you may need a hand here). Under “Teams Apps” / “Setup Policies” / “Global”, make sure that the “Upload custom apps” slider is enabled. Note if you want to be more surgical about this, you can instead add a new policy with this option just for developers and assign it to them under “Manage Users.”

Finally, head over to https://dev.teams.microsoft.com/apps and create a new custom app. There are a lot of options here, but only a few are required:

  • Under “Basic Information”, add values for the website, privacy policy and terms of use. Any URL is fine for now, but they can’t be empty, or you’ll get mysterious errors later.
  • Under “App Features”, add a “Bot.” Paste your bot’s “Microsoft App Id” (the same one you used during the function app configuration) into the “Enter a Bot ID” box. Also check whichever of the “scope” checkboxes are interesting to you (I just checked them all).

Save all this and you’re ready to give it a try. If you want a super-quick dopamine hit, just click the “Preview in Teams” button. If you want to be more official about it, choose “Publish” / “Publish to org” and then ask your Teams Admin to approve the application for use. If you’re feeling really brave, you can go all-in and publish your bot to the Teams Store for anyone to use, but that’s beyond my pay grade here. Whichever way you choose to publish, once the app is in place you can start a new chat with your bot by name, or add them to a channel by typing @ and selecting “Get Bots” in the resulting popup. Pretty cool!

A caveat about using bots in channels: your bot will only receive messages in which they are @mentioned, which can be slightly annoying but net net probably makes sense. Unfortunately though, it is probably going to mess up your message parsing, because the mention is included in the message text (e.g., “<at>botname</at> real message.”). I’ve coded RadioBot to handle this by stripping out anything between “at” markers at line 454. Just another way in which you really do need to know what channel you’re dealing with.

Teams in particular has a whole bunch of other capabilities and restrictions beyond what you’ll find in the vanilla Bot Framework. It’s worth reading through their documentation and in particular being aware of the Teams-specific stuff you’ll find in TeamsChannelData.

We made it!

Well that was a lot; kind of an anti-quickstart. But if you’ve gotten this far, you have a solid understanding of how the Bot Framework works and how the pieces fit together, start to finish. There is a bunch more we could dig into (for instance check out the Adaptive Card interfaces in RadioBot here and here) — but we don’t need to, because you’ll be able to figure it out for yourself. Teach a person to fish or whatever, I guess.

Anyhoo, if you do anything cool with this stuff, I’d sure love to hear about it, and happy to answer questions if you get stuck as well. Beyond that, I hope you’ll enjoy some good conversations with our future robot overlords, and I’ll look forward to checking in with another post soon!

You are in a maze of twisty little languages, all alike.

It seems like everywhere I go these days I’m talking to a bot. Now don’t get me wrong, I’m all for technology that keeps me from having to interact with actual humans. And truth be told, they’re getting pretty good — talking to Alexa has just become something I do without thinking about it. But it super-annoys me when I visit some random website and their chatbot pops up in my face pretending to be a real person (I’m looking at you, oreilly.com).

I think it’s good for us to know when we’re talking to a computer and when we’re not. And that’s not only some squishy ethical thing — it just works better. I have different expectations talking to a bot than I do to a human, and I’m more than happy to adjust my speaking pattern to increase the chances of success. For example, I know that “shuffle” and “album” are Alexa keywords for music, so I ask her to “shuffle album Cake and Pie” (which works) rather than “please play Cake and Pie in random order” (sad Alexa noise).

And you know what? This is fine! Speech recognition aside (amazing stuff there), we use specialized and restricted dialects for specialized purposes all the time, even between humans. Curlers yell “clean” or “hurry” and the sweepers immediately know what they mean. I tell the guy at the lumber yard that I put “16 feet of 2×12 treated” into my car and he knows what to charge me. This kind of jargon removes ambiguity from communication, and that’s a big plus when you’re trying to get something done together.

So what’s my point? There’s an interesting dichotomy here, because the hype around chatbots is all about artificial intelligence, but the reality is that it’s much more about the creation of purpose-built “little languages.” Those are way more interesting to me, so that’s what I’m going to dig into today.

Little Languages

Jon Bently wrote an incredible pair of books in Programming Pearls and More Programming Pearls. Both are essential reading for anyone who cares about software, even though some (not all!) of the specific technology is showing its age. They’re entertaining too, thanks to the way he weaves together anecdotes and concrete code. What I’m saying here is, buy the books.

Anyways, I first encountered “little languages” in More Programming Pearls, but you can read the original article about them online here. Bentley was part of the UNIX crowd at Bell Labs and loved (as all good programmers do) the idea of pipelines — how programs can work together to do increasingly complex things (really just top-down-design in different clothes, but since pretty much all problems converge back to TDD that’s cool by me). In the article, he demonstrates the concept using picture generators that used to be (maybe still are?) commonly used for technical papers. For example, the chem language allows folks to concisely describe and depict chemical structures. Here’s LSD:

.cstart
B:  benzene pointing right
F:  flatring5 pointing left put N at 5 double 3,4 with .V1 at B.V2
    H below F.N
R:  ring pointing right with .V4 at B.V6
    front bond right from R.V6 ; H
R:  ring pointing right with .V2 at R.V6 put N at 1 double 3,4
    bond right from R.N ; CH3
    back bond -60 from R.V5 ; H
    bond up from R.V5 ; CO
    bond right ; N(C2H5)2
.cend

You can run this yourself on most Linux systems; if it’s not there already, use your package manager to install groff (groff is the GNU version of the typesetting app troff). Save the code above as lsd.chem and use the command:

cat lsd.chem | chem | pic | groff -Tpdf > lsd.pdf

This has always stuck with me because it’s such a beautiful specialized-to-generic pipeline:

  • chem lets you easily specify chemical structures, generating input for
  • pic, which creates any kind of picture, generating input for
  • groff, which formats any kind of document for presentation.

Adventure

Bentley’s little languages are primarily static, used as input to an interpreter. But the concept applies equally well to conversations, and we’ve been having conversations with computers for a long time. Exhibit A is Colossal Cave Adventure, the granddaddy of “interactive fiction.” If you had access to a computer in the seventies or eighties there’s a 100% chance you played it or one of its descendants like Zork or the early Roberta Williams titles. Interactive fiction today generally uses point-and-click, but you can very much still feel the connection to their early, text-based ancestors.

In Adventure, the computer acts as a dungeon master, describing your location and acting on your instructions (“go north”, “take lamp”, “fight dragon,” and so on). Your goal is to explore a network of underground caves (based on the real Mammoth Cave), accumulating gold and treasure along the way. You can give it a try yourself in the browser — I recommend keeping track of where you are by building up a map on paper along the way.

There are a million versions of the game. The one I first played was written by Don Woods as a modification of the original by Will Crowther. The FORTRAN code for the original Crowther version is on github (of course it is). The “little language” implemented there is shockingly expressive given its tiny vocabulary of 192 words.

  • In the GETIN subroutine, an input line is broken into one or two words: a VERB and an optional OBJECT. Each is truncated to five characters for processing, but extra characters are retained for display purposes.
  • Starting at label 2020, the words are matched to entries in the 192-word vocabulary table which are implicitly associated with classes (motion/action/special for verbs, normal/treasure for objects) based on their assigned number.
  • The verb is then dispatched to the correct handler. Most action verbs are handled using special-case logic, but motion verbs run through a state machine defined in the motion table. If you think about the cave as a graph, each row in the motion table is an edge that describes the verbs and game state required to move the player from the location in the first column to the location in the second.

Of course there’s a lot more to it than that. If you want to really dig into the code, there is a well-commented copy of the Woods version at the Interactive Fiction Archive, downloadable as a zipped tarball. You’ll still have to brush up on your FORTRAN to really get it, but who doesn’t love a good DO/CONTINUE loop packed with GOTO statements?

If you’ve played the game, it’s impossible not to be impressed with how immersive it is. With nothing more than VERB/OBJECT pairs, you can explore a world, solve puzzles, and even kill a dragon (with what? your bare hands?). I hope you get so sucked into the cave that you don’t come back to this post for a month.

Late breaking news: turns out this post is pretty timely, because Ken and Roberta Williams just announced that they are rebooting Colossal Cave for a new generation of folks … WOOT!

ELIZA the DOCTOR

Rogerian psychology is serious stuff. In (very) short, the goal is help patients understand themselves through a self-driven, internal dialogue. The therapist is there to provide safety and empathy, and to reflect back the observations and themes they are hearing. It has helped millions of people. But it’s also the easy butt of jokes, because on the surface it seems that the therapist isn’t really doing anything:

  • How many Rogerian therapists does it take to change a lightbulb?
  • I hear you wanting to know how many Rogerian therapists it takes to change a lightbulb.

Way back in 1965, Joseph Weizenbaum created ELIZA and DOCTOR, an engine and script respectively that do a remarkably good job of emulating a conversation with that satirized therapist. If you’ve never heard of ELIZA, you definitely should click on over to this site and say hello to her; she’s actually pretty impressive, especially when you consider that there is absolutely zero contextual “understanding” under the covers.

ELIZA’s little language is quite different from Adventure’s. Her goal is to transform input into responses that will seem relevant and appropriate to a human observer, especially one primed to expect a Rogerian conversation. The original source is available, but it’s written in MAD-SLIP and that one is even too arcane for me. Instead I’ll refer to Weizenbaum’s original paper in the ACM which is pretty great and totally worth reading.

The language is primarily defined by rules for recognizing input patterns and transforming them into responses. For example, suppose we receive the input “why do you hate me” and want to respond with “Why do you think I hate you.”

  1. The decomposition rule (0 you 0 me) is matched against the input. It’s basically a proto-regex. The number 0 means “match any 0 or more words,” while the alphabetic strings must match exactly. If the pattern matches, each section of the rule is “assigned” text from the input. In this case it would match as ((why do) (you) (hate) (me)).
  2. An associated recomposition rule (why do you think I 3 you) is used as a template to generate the response. Numbers are replaced with the Nth input grouping generated during decomposition (3 = “hate” in this example) to create the desired response: why do you think I hate you?

Note that even this simple pair of rules provides significant flexibility in our ability to respond to arbitrary input:

  • Why do you hate me / Why do you think I hate you
  • It seems that you no longer care about me / Why do you think I no longer care about you
  • You love me / Why do you think I love you
  • Apparently you are bananas for me / Why do you think I are bananas for you

Whoops! That last one uncovered a flaw in our engine — the second person singular “are” should have been transformed into the first person singular “am.” ELIZA uses additional rules called transforms to fix this up. There are also a bunch of other interesting details that make her respond in a mostly-believable way; a few examples:

  • Decomposition rules can have multiple possible recomposition rules; ELIZA selects from the list randomly but with minimal repetition.
  • Decomposition rules are associated with keywords that have a ranked order. In this way, more specific or interesting responses can be preferred over simpler ones. (This keyword-to-rule association was created primarily as a performance benefit to account for the limited processing power of the day, but the ranking is a nice side benefit.)
  • Fallback rules keep the conversation moving when no decomposition rules match successfully.
  • A “memory” feature keeps a short stack of important phrases used in the conversation that can be inserted to enhance a sense of continuity.

The actual syntax used to express the language is pretty hairy — basically a nest of parenthesized lists, just as you’d expect from a LISP variant. Here’s a short snip from DOCTOR that I’ve indented to be a tiny bit more readable; the full script is included at the end of the paper:

(CANT = CAN'T)
(WONT = WON'T)
(REMEMBER 5 
	((0 YOU REMEMBER 0) 
		(DO YOU OFTEN THINK OF 4)
		(DOES THINKING OF 4 BRING ANYTHING ELSE TO MIND)
		(WHAT ELSE OO YOU REMEMBER)
		(WHY DO YOU REMEMBER 4 JUST NOW)
		(WHAT IN THE PRESENT SITUATION REMINDS YOU OF 4)
		(WHAT IS THE CONNECTION BETWEEN ME AND 4))

It turns out that Charles Hayden reimplemented Eliza in Java and dramatically improved the little language. But aesthetics aside, just like Adventure, the ELIZA script language packs a great deal of smarts using a quite restricted syntax. Truth be told, I’d definitely choose to talk to her than to most of the marketing bots that get up in my face on the web every day.

Today’s Conversation Models

Modern little languages certainly look fancier than these early examples. If you’ve been reading this blog for awhile, you may recall my experience writing an Alexa “skill” to manage stuff in my house. I won’t repeat all the details here, but in short an Alexa “Interaction Model” include the following elements:

  • Intents: things that the user wants to do (e.g. turn on a particular configuration of lights).
  • Utterances: one or more template phrases that capture an intent (e.g., “turn family room lights on”).
  • Slots: placeholders in an utterance that capture meaningful parameters in the user’s request (e.g., “turn ROOM_SLOT ACTION_SLOT”).

Azure provides basically the same functionality in its Conversational Language Understanding suite (this is a new version of what used to be LUIS; it’s hard to keep up).

Feeling a little Deja Vu? Intents are basically ELIZA keywords. Utterances are decomposition rules. Slots are the numeric placeholders used in recomposition. It’s actually kind of startling just how similar they are. Of course there’s a ton of advanced processing now that notably improves the matching process — it would be wrong to minimize the advances there. But let’s give the old guys some credit, hey?

Hunt the Wumpus (by Text!)

When I started writing this article, the plan was to dig go pretty deep into the details of implementing a “bot” using the Microsoft Bot Framework. But as I look up from the keyboard, I’m two thousand words in already and haven’t even started that … so maybe better to save it for next time. But I’d hate to leave you without at least a little something concrete, so let’s at least introduce the WumpusBot and give it a spin.

Hunt the Wumpus is actually the very first computer game I ever played, over a printing teletype connected from my elementary school to a central PDP-11 somewhere. The goal is to explore a series of connected rooms, trying to shoot the “Wumpus” before he eats you or you fall into a bottomless pit. Along the way, bats may pick you up and move you randomly to another room. In the original game, you had magic “crooked arrows” that could travel between many rooms, but I chose to implement the simpler version described here where you can just shoot into any adjacent room.

Anyways, take out your trusty smartphone and text “play” to 706-943-3865 to give it a try for real. WumpusBot will reply with some instructions to get you started — its language is little indeed, pretty much just “move” and “shoot.”

The game logic is in Wumpus.java, and the bot innards are all in this directory. The cool thing about the Bot Framework is that it can multiplex your logic across a ton of “channels” — web chat, SMS, Teams, Slack, Facebook, and a bunch more. WumpusBot consists of:

Anyways, for now just enjoy the game — we’ll get into the details and the usual Microsoft devex bashing next time. Pro tip: whenever you feel a draft or smell something rotten, just backtrack and approach from a different room … unless a bat does you wrong, you’ll get him every time.