About

The SL Developer’s Corner is my place in the virtual world “Second Life”, where i experiment and tinker with the features of this platform. I try to publish my experiences and often also include the source code of the scripts on this site. Other posting related to Second life can be also found on Happy Coding 2nd-life section.

Friday, 13 July 2007

A.L.I.C.E. Chatbot Connection in Second Life using Ruby on Rails

Like i told you some month ago, i have quick-hacked a Chatbot for Second Life, which connects to an A.L.I.C.E.-Bot at www.pandorabots.com. In this posting i want to show the LindenScript-Code and the server-side code i've used. On the server-side its Ruby on Rails, as usual. ;)

The Ruby on Rails serverside script:

I have built an "bot_controller.rb" file which looks like this:


require 'net/http'
require 'uri'
require 'cgi'

class BotController < ApplicationController

def ask_question
question = params[:q]
custid = params[:cid]

res = Net::HTTP.post_form(URI.parse('http://www.pandorabots.com/pandora/talk-xml'),
{'botid'=>'###YOUR BOT ID###', 'custid' => custid, 'input'=>question})
case res
when Net::HTTPSuccess, Net::HTTPRedirection

xmlDoc = REXML::Document.new(res.body)

botCustid = xmlDoc.elements["result"].attributes["custid"]
statusCode = xmlDoc.elements["result"].attributes["status"]


case statusCode
when "0"
render :text => xmlDoc.elements["result/that"].get_text.value+"||"+botCustid
else
render :text => 'Help, this error occurred: ' + xmlDoc.elements["result/message"].get_text.value+"||"+botCustid
end

else
render :text => 'error'
end

end

end
The controller just takes a given conversation-id (custId) and the question from the person in 2nd-Life. Like you'll see later, the custId is just for possible future improvements and has now no special use. :)
The other parts in the script are like in the Twitter/Jaiku scripts which i did. They retrieve the XML-document and get the right parts using XPath-expressions. And after that the script builds a nice "||"-separated result-string for the Second Life world.
You also need your own bot-id from the pandorabots-site. Create an account there and you get your own.

The LindenScript:

key requestId;
string custid;

default
{
state_entry()
{
llListen( 0, "", NULL_KEY, "" );
custid = "";
}

touch_start(integer total_number)
{
llWhisper(0, "Talk to me on Chat-Channel 0.");
}

listen(integer channel, string name, key id, string message)
{
requestId = llHTTPRequest("###YOUR URL###/bot/ask_question",[HTTP_METHOD,"POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"],"q="+message+"&cid="+custid);
}

http_response(key request_id, integer status, list metadata, string body) {

if (request_id == requestId) {

if(body == "erro") {
llWhisper(0, "An error occurred. Please try again later");
} else {

list parts = llParseString2List(body,["||"],[]);

string that = llList2String(parts, 0);
custid = llList2String(parts, 1);
llWhisper(0, that);
}

} else {
llWhisper(0, "An error occurred. Please try again later");
}
}


}

The script is really easy. It just listens on Channel 0, gets the message from the user and calls the Ruby on Rails script. After that it gets the result, splits the string using the "||" separator and then prints the answer for the question.

Possible future extensions:

There are a lot of possible extensions for this script. The most important one i the problem with the conversation-id.
It would be nice, if the Chatbot in Second Life could remember the different users. For example on the server-side we could create a database which manages every person which has talked to the chatbot. We store the SecondLife-username and the appropriate conversation-id in a database-structure. Everytime the user comes back, we get the conversation-id from the database and use this one. So the A.L.I.C.E.-Bot can optimize its questions for every user.

You can also add some error-handling. So that the user gets nice messages if something goes wrong.

So, hope you like it. :)

16 comments:

Anonymous said...

thanks

Anonymous said...

Great posting really got me thinking,


I do some working with SL and mobile.

If you'd like some free space on a private Island for development and social purposes look me up in SL sometime.

Alexander Regent

Anonymous said...

great integration.

Anonymous said...

Awesome publish! Thanks to your example, i have rewritten it to a version that does not require a server in between to query and receive http responses.

Phr0zen Katsu

daniel said...

@Phr0zen Katsu:

thats of course a much smoother integration into SL. cool, that you did it.

mbulu

osgoh said...

Hi Phr0zen Katsu,

Your idea are brilliant, may I know you do it?

osgoh said...

I had developed my own bot using Perl and PHP, can I integrated with SL?

Andrew said...

I have looked at your development and find it amazing. My question is how does the LSL script know to post to my url where the ruby on rails script is?

daniel said...

@ andrew

It knows it, because you start a listen process in your linden script (using llListen and by implementing the listen() function).
So every time a second life avatar stands before your chatbot and talks using the given chat-channel (here: 0), the listen() will be called.

Hope, that helps.

Eric said...

Re Phr0zen Katsu's rewrite with no server: it's nice to share :)

Same thing happens on other forums, someone posts "I fixed it" but doesn't tell how. :(

Eric said...

Example code that doesn't use Ruby (not that it wasn't worthwhile exploring that whole solution!)

key requestId;
string custid;

string botId="***YOUR BOT ID***";
string botURL="http://www.pandorabots.com/pandora/talk-xml";

default
{
state_entry()
{
llListen( 0, "", NULL_KEY, "" );
custid = "";
}

touch_start(integer total_number)
{
llWhisper(0, "Talk to me on Chat-Channel 0.");
}

listen(integer channel, string name, key id, string message)
{
requestId = llHTTPRequest(botURL,[HTTP_METHOD,"POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"],"input="+message+"&botid="+botId);

}

http_response(key request_id, integer status, list metadata, string body) {

if (request_id == requestId) {

if(body == "error") {
llWhisper(0, "An error occurred. Please try again later");
} else {

integer start = llSubStringIndex(body, "<that>");
integer end = llSubStringIndex(body, "</that>");

if (start != -1 && end != -1)
llSay(0, llGetSubString(body, start+6, end-1));

}

} else {
llWhisper(0, "An error occurred. Please try again later");
}
}


}

daniel said...

@Eric: Thanks for your contribution. I like the idea of a none-server approach. But the server also offers nice possibilities like additional processing and you can handle the XML in a nicer way. Or is there a XML API in the meanwhile in Linden Script? That would be awesome.

Daniel

Henning M. said...

Eric, with that code, is it possible to somehow sent a custid code along with the chatter?
Right now, everyone's called '.'

Eric said...

Hm maybe that's older code... I started making the custid out of the avatar first+last
name to get unique connections to the Chatbot. If there's a local variable 'custid'
that gets set in the listen() method like this:

listen(integer channel, string name, key id, string msg)
{
custid=name;
llListenRemove(listening);
llSetTimerEvent(5.0);
message = msg;
}

Then when sending the request to the chatbot server, it gets baked into the URL at
the end like this:

requestId = llHTTPRequest(botURL,[HTTP_METHOD,"POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"],"input="+message+"&botid="+botId+"&custid="+custid);

fasihuddin said...

Hey dan i wonder if u could help me m trying to build chat bot and connect it to sl so could tell me the pocedure for doin that

Ron said...

I could not get Eric's script (including is updated code snippet)sample to work. Can someone provide me with the exact code? It is much appreciated!