Developing a Windows Live Agent is much like developing any software solution. Your team creates code, you test it, the customer tests it from the user side, you fix bugs and add functionality, and the cycle goes on until you're finished.

Although the new 5.0 SDK will feature full Visual Studio integration (and with that, Team Foundation Server and/or Visual Sourcesafe integration), current version is a bit more oriented to single-user development.

After working for more than a year developing WLAs, we at ilitia have built a small infrastructure (mostly virtualized/using virtual machines). Right now our WLA department works like this diagram:

Each developer works with a local copy of the WLA (using Visual Sourcesafe to integrate changes), having a local testing Passport-enabled account.

We have a "WLA Server" in which we upload (at least once per week) the latest working build, which runs in another account that we give to our customer since the second or third week of development, so they can start testing the agent early and giving feedback (agile development and that stuff ;), just using their Windows Live Messenger clients.

We create mocks of customer Web Services if it is feasible (somethimes they're too complex, or the customer already has a testing WS created) and the Activity Window web pages and host them too, until the Activity is provisioned to Messenger.

We also have a private WLA intranet with our own tutorials, development guidelines and repository for Buddyscript modules, documents, testing account-password pairs and such.

 

One feature we're still missing is automatic unit test battery launching, like in a Continuous Integration server. But we do have unit testing since two months or so. If you want some info about the platform's default testing capabilities you can check this official posts (one and two) of the WLA team's blog.

The system was somewhat lacking advanced features and more testing options, and coming from the NUnit testing world, I decided to make a new unit testing framework from scratch (although I used some of the original framework as basis, it has been heavily modified and improved).

I won't show any code until the next post, but this is an example of a sample passing battery output:

-----------------------
Begin testing: TestUtilitiesAddon -> TestExCompareQueryArray() Test Battery
Test: "TestExCompareQueryArray"
Test: "TestExCompareQueryArrayNotEqual"
Test: "TestExCompareQueryArrayNotEqual"
Test: "TestExCompareQueryArray"
Done testing TestUtilitiesAddon -> TestExCompareQueryArray() Test Battery
Total tests: 4   Passed tests: 4   Failed tests: 0
Test battery passed
-----------------------

(as you might notice, I've used the framework to test it's own testing/assert methods ;)

A sample output of a failed test could be like this one:

-----------------------
Begin testing: TestUtilitiesAddon -> TestExCompareObjects() Test Battery
Test: "TestExCompareObjects"

Error: "not object #2" failed.
Returned: "not object #2"
"not object #2" not an object
-----------------------
Done testing TestUtilitiesAddon -> TestExCompareObjects() Test Battery
Total tests: 1 Passed tests: 0 Failed tests: 1
Test battery failed
-----------------------

 

In the next post I will show the basics of building a testing framework and how to test both variable values and objects.

This is a small quick reminder of how to access CSS style properties (and, as a bonus, how to detect Internet Explorer version from javascript and switch from PNG to GIFs to avoid not having transparency in IE versions less than 7).

Having this image:

<img id="HeaderTable" src="img/logobackground.png" />

We can have in a CSS file something like (dumb here, but just so you can notice how the property is called):

#HeaderTable
{

    backgroundImage= 'url(img/logobackground.png)'
;
}

With this small script, we detect IE version and if less than 7 we change the image to it's GIF equivalent.

<script type="text/javascript" language="javascript"> 
    var ieVer=/*@cc_on function(){ switch(@_jscript_version){ case 1.0:return 3; case 3.0:return 4; case 5.0:return 5; case 5.1:return 5; case 5.5:return 5.5; case 5.6:return 6; case 5.7:return 7; }}()||@*/0
;
    if
(ieVer<=6
)
    {
       
var image
;
        image = $get('HeaderTable');
        if
(image != null
)
            image.style.backgroundImage
='url(img/logobackground.gif)'
;
   
}
</script>

 

Can you guess how to access properties? just with .style.xxxxxx, where xxxxxx is lowercase name with first letter of the second and consecutive words in capital, and no spaces or hyphens.
Another example: font-family equals fontFamily

Simple, but efficient!

 

Note #1:I know this JS will fail under Firefox and similar, but it was used inside a Messenger Activity Window so it will always be Internet Explorer ;)


Note #2: The $get() notation is because of using the fantastic Microsoft AJAX library :)

Looks like people is going mad lately, and if you don't think so, check this three examples and think again ;)

Voxelstein 3D

Our all-time classic FPS, having lots of excellent engine remakes, gets an interesting twist with voxel technology.

Although still an alpha, looks great (that is, if you don't expect Crysis-like graphics) and adds a new element to the game: destructible environments.

 

Alternativa3D

What about having a 3D engine made in flash? A impressive one, if you consider it is just software rendering (no 3D acceleration).


POVRay Short Code Contest #5

If you knew POVRay (a Raytracing renderer which uses an object-based scene definition language), what could you do in a 512 scene definition file?

Well, you can check the results (as Quicktime videos) and download the source files. Really impressive!!

 

One problem we've recently found is that the current version of the SDK has limited support for XML encodings other than UTF8. In fact, the encoding attribute in the XML files seems to be just ignored.

We are reading spanish encoded XMLs (ISO-8859-1), and we had some problems with accents and special characters. As not displaying them is clearly not an option, I digged in inside the SDK libraries until I found one function that solved the problem, StringUTF8ToLatin1().

Let's see how it works with a simple example.

This is a simple XML with spanish characters:

<?xml version="1.0" encoding="ISO-8859-1"?>
<messages>
<message>
<text>TEST ENCODING ñÑáéíóúÁÉÍÓÚ</text>
</message>
</messages>

 

We build the datasource as usual:

datasource TestEncoding() => TEXT, USER
  file
    testencoding.xml
  simple xml
    messages
      message {loop=content}
        text

 

And a sample pattern that loads the xml and displays each <message> element properly:

+ test_spanishencoding
  MESSAGES = ()
  try
    RESULTS <= TestEncoding()
      - StringUTF8ToLatin1(RESULTS.TEXT)
  catch ERROR
    - ERROR

 

Simple, but works perfectly :)

Posted by Kartones | with no comments
Filed under:

I've added a new link at the left to a list of the Firefox addons I've actually got installed. Every one has a small description of what it does (to save you extra clicks ;)

I'll keep it updated, and you can leave comments of what ones you would recommend to me too, I'll really appreciate it (I think is the best way to find cool addons).

Posted by Kartones | with no comments
Filed under:

It's been a while but I'm finally starting to "adapt" to living alone and buying all basic house stuff, so here are the photos from the Windows Live Training held at Zurich last month:

Posted by Kartones | with no comments
Filed under: ,

In my last WLA post I talked about normalizing xmls to be able to handle complex structures. We learned how splitting the fields in multiple xmls we could do simple querys and get the wanted data.

But what about after having this data? How can we cross it to obtain some manageable info?

The solution I use is very simple if you know the existence of the MakeHash() function.

This function accepts one or two parameters and (among other things), if you call it with the same list object in both parameters, it will cycle through all the list elements and remove repeated ones. For example, if we have a list called MYLIST with the values 1,1,2,3, MakeHash(MYLIST,MYLIST) will return a list with the values 1,2,3.

In this example, we have two simple functions that retrieve values using simple querys:

GetIDsFromTable1(1) returns 1,2,3

GetIDsFromTable2(1) returns 1,3,4

We can easily build a function that returns both results without repeating values:

function GetIDsFromTables(REGISTERID)
  RESULTS = ""
  IDS = GetIDsFromTable1(REGISTERID)
    for value ID in IDS
      insert last in RESULTS ID
  IDS = GetIDsFromTable2(REGISTERID)
    for value ID in IDS
      insert last in RESULTS ID
  RESULTS = MakeHash(RESULTS,RESULTS)
  return RESULTS

So, calling GetIDsFromTables(1) will return 1,2,3,4  :)

Posted by Kartones | with no comments
Filed under:

As Pedro has just posted, we've finally launched a new section for the portal, Demos.Kartones.Net (available too from the home), which contains and will contain all web mashups and demos that we make (now being able to use .NET FW 3.5 even cooler ones!).

It is mostly the work of Pedro and has been done with Silverlight 1.0 and JSON. It already contains some of our past demos.

If any of our community bloggers wants to have their demo published just contact me and I'll upload to the subsite ;)

Posted by Kartones | with no comments
Filed under:

Buddyscript is capable of managing xmls with ease, and accessing external databases for example with a web service.

But sometimes we may need to have non-trivial xmls locally, and those xmls may have complex structures not so easy to handle.

 

This is the graphical representation of a sample xml that may not be easy to read:

This is a possible xml content:

<?xml version="1.0" encoding="utf-8"?>
<fields>
 
<field>
   
<field1>1</field1>
   
<field2>
     
<value>121</value>
     
<value>122</value>
     
<value>123</value>
   
</field2>
   
<field3>13</field3>
 
</field>
 
<field>
   
<field1>2</field1>
   
<field2>
     
<value>221</value>
     
<value>222</value>
     
<value>223</value>
   
</field2>
   
<field3>23</field3>
 
</field>
</fields>

 

The buddyscript code to read this xml you could incorrectly think that it might be something like this:

datasource Table1XMLFileLoader() => Field1, Field2, Field3
  file
    table1.xml
  simple xml
      fields
        field {loop=content}
          field1
          field2
            value {loop=content}
          field3

The problem is that if you execute this code, the agent can incorrectly loop the values, because it doesn't supports multiple anidated loops. This gets worse with three or more "looping fields". The XML should only have one looping tag/element.

 

So, how to fix this limitation? Well, I do it applying what I call xml normalization: Like with a DB normalization, re-estructure your xml in multiple files, splitted so that each sub-table now only has as simple 1-1 relations (but they can be repeated as many times as wanted, that'sd the trick ;)

This is the graphical representation of the normalized xmls:

This are our normalized xmls:

Table1_1.xml

<?xml version="1.0" encoding="utf-8"?>
<fields>
 
<field>
   
<field1>1</field1>
   
<field2>121</field2>
 
</field>
 
<field>
   
<field1>1</field1>
   
<field2>122</field2>
 
</field>
 
<field>
   
<field1>1</field1>
   
<field2>123</field2>
 
</field>
 
<field>
   
<field1>2</field1>
   
<field2>221</field2>
 
</field>
 
<field>
   
<field1>2</field1>
   
<field2>222</field2>
 
</field>
 
<field>
   
<field1>2</field1>
   
<field2>223</field2>
 
</field>
</fields>

Table2_2.xml

<?xml version="1.0" encoding="utf-8"?>
<fields>
 
<field>
   
<field1>1</field1>
   
<field3>13</field3>
 
</field>
 
<field>
   
<field1>2</field1>
   
<field3>23</field3>
 
</field>
</fields>

 

And finally the buddyscript code to read them, this time correctly:

datasource Table1_1XMLFileLoader() => Field1, Field2
  file
    table1.xml
  simple xml
      fields
        field {loop=content}
          field1
          field2

datasource Table1_2XMLFileLoader() => Field1, Field3
  file
    table1.xml
  simple xml
      fields
        field {loop=content}
          field1
          field3

 

We can now do simple querys against each of the sub-tables and cross the results (with <field1>) to obtain one or more registers (original Table1 <field>) with multiple subfields (origintal Table1 <field2>).

Posted by Kartones | 1 comment(s)
Filed under:

When we use XMLs, they come with UTF-8 encoding, so there is no problem in storing into them international characters, like for example the spanish word "España".

We may have for example an XML with a list of countries written in spanish, and why not with first capital letter (for writing directly a country from the xml to the conversation window without uppercasing first letter ;).

We create a subpattern and load it with that xml data:

subpattern CountriesSubPattern get Name, Name in CountriesTable {score=MACRO_STRONG_SCORE}

 

Testing our Agent we will detect that he doesn't understands "España" as a valid country.

The problem is that loading XMLs we might have incorrectly setup the index property of the DataTable's parameter, or how the subpattern needs to receive the user input.

datatable CountriesTable {expire="in 1 day"}
  load Name {index=case-insensitive} from datasource
    CountriesXMLFileLoader()

subpattern CountriesSubPattern get Name, Name in CountriesTable {style=raw}

For international characters, like spanish ones, we need "style=raw", because otherwise the Agent will use the thawed version (in the 4.3 SDK, "España" transforms into "espan a").

 

Note: This was a problem we recently had with the 4.3 version of the SDK, and just using XML files.
Using the Beta 5.0 SDK with a non-xml subpattern like the following the matching is perfect without adding properties:

subpattern CountriesSubPattern
+ españa
+ inglaterra

+ COUNTRY=CountriesSubPattern {score=MACRO_STRONG_SCORE}
- COUNTRY is a country

Posted by Kartones | with no comments
Filed under: ,

I've only been working for near 6 years, but I've been in 4 different companies and lots of customers, so I've "tasted" very heterogeneous "work ecosystems".

I love my work, and I enjoy a lot developing, so whenever I can, I try to add a humour component to my daily work in order to make it even more enjoyable.

This is specially useful when you have tedious tasks, or a tight schedule, or you enter in a crunch-time phase. I've worked with teams in a "hostile environment" and the results were clearly worse than in happy, nice ones.

 

Now that at ilitia we've got a "Windows Live Team" (small one but I hope to have it increased soon), I try to keep it "in good shape" by making all sort of jokes and encouraging my pals to do the same.

A good thing is that working developing Windows Live Agents is fun "by itself": When your agent doesn't works is not like getting an exception in .NET and your application crashing. Your agent may "be shy" (not respond to something expected), may be "drunk" or "lost" (answering wrong things) or it may even "come alive" (when does something really unexpected :)

Also, we sometimes like to play painting images and creating fake logos to have some fun. Here there are a few ones:

enjutoilitia
Living the Enjuto Mojamuto way of life!

logobrownteam 
Our "Windows Live Team" unofficial signature logo :)

AdvertisingTheBrownTeam
The A-Team "mashup" (in Spanish)

 

So, if you don't already do it, try to add humour to your work and you will notice the advantages ;)

Work happy, work better!

Yesterday an update for iZ, our first WLA was uploaded and applied.

The agent now writes in red color! :D

Now, seriously, we've made some improvements. One good thing about internet is that you can get early feedback from the users. We received bad feedback from one of the mayor national newspapers, in part due to the previous spanish WLA, Robin, who was a complete failure and a bad start.
The negative feedback argumented that the agent could not find proposals, and as we've noticed, having a so large initial help/salutation most users don't read it and don't know the don't start searching proposals.

So, what I've done is modify the "catch-all" conversation pattern (called if the agent doesn't have an answer), and make a "silent call" to the proposal search system. If what the user has typed is recognized as a valid keyword and there are proposals related, user is sent to search mode automatically and the proposals start to show.

Now searching is quite easier, because you can start a conversation and type "vivienda" (housing), and the agent will find more than 35 related proposals as if you first had typed "buscar" (search) or some similar pattern.

 

It is great to be able to improve you agent capabilities so easily, in part due to a correct programming (the whole search system is very extensible and works in multiple steps, so can be adapted to different situations) but in part too because this is like a web portal (which you can update anytime) and not like a desktop application (in which a change means new update or redistributable, which not all users will apply).

Posted by Kartones | with no comments
Filed under:

Today it's been just launched our first public WLA! Although really is the second one the first is in it's final touches with the customer, but we expect to have it ready quite soon too.


The agent's name is iZ@psoe.es and it has been made near one month and a half for the current spanish government by PedroA and me, targeted to the general elections that are taking place right now and will finish the 9th of march.


It has lots of functionalities related to the elections, like consulting the census, near 1300 government proposals, where do you have to vote, multimedia videos and MP3, a pack of custom emoticons, customized funny responses...


The Activity window is used a lot, for example to play the videos and flash animations:



It has a Windows Live Maps mashup to show for example where do you have to vote after asking you some basic address parameters:

 
We've developed a big search engine to filter, query and ask for political proposals, being able to show key ones, refine your searches with more keywords, show related videos for some proposals... along with a tag cloud that contains the general keywords (there are 156 general keywords and hundreds of synonims to ease the searchs).


The agent has lists of political parties, candidate lists, related blogs, websites, information, volunteer pages...

We didn't wanted to force the user into searching by default, so the agent shows some information in a menu when saluted, and lets the user do something, or just chit-chat with the agent for a while (after some chatting it will remind the user it's main function, speaking about the political proposals).

Once you start either using the search engine or the other options, you are "driven" through the steps until either you cancel or finish. In the search, you start entering keywords and synonims to refine your search "query"; if key proposals are found, they are automatically loaded in the Activity Window while you keep searching; When you've finally found just one proposal, or you want to see your current search results, it is loaded too at the AW.

Initial feedback from some users says that some people do not read the help and they think they can search from the very beginning (it is triggered with patterns like "search" or "proposals"), so maybe we should have either forced an initial search, or made a smaller initial help command so that more users noticed how the search is activated.

 

It's been a quite intensive development because we had a great release date deadline limitation, and we had to deal with challenges like XML file cachings and quite complex data transformations and manipulations (we used SQL Server 2005 DTS and two custom .NET Applications to import data, transform and manipulate it and export it to our desired destinations).


Official info about the agent can be found here (in spanish).

 

It is the first WORKING Agent in Spain. Another one was made before, but was removed due to both technical and content problems.

It is a titanic work for two people and while it might not be perfect, we're really proud of the amount of work done in so few time... We've had also a lot of fun during the development, and customer feedback and agent testing has been very useful to improve the recognized patterns.

Enjoy it and if you want, leave a comment telling what you think of the agent :)

Update: Added more info explaining how the search works and how it is triggered.

In a previous post we talked about basic use of the Anything pattern.

One "poltergeist" issue we may face is having an Anything pattern not catching long, multiple word user inputs. In our specific case it was only taking up to 4 word street names.

Everything seems to be working perfectly, but "mysteriously" 5+ word street names are not taken, what is the problem or how do we solve it?

Well, it took me a few to understand why wasn't working, and I found the answer looking at the BasicMatching.pkg library (where the Anything patterns are stored):

subpattern Anything {minlength="1" maxlength="100" score="MACRO_WEAK_SCORE" style="raw"}

subpattern AnythingWeaker {minlength="1" maxlength="100" score="MACRO_WEAKER_SCORE" style="raw"}

[...]

As I've marked in bold in the code fragment, the default Anything gets matched with something with a weak score (45 "points"), but there is a AnythingWeaker version which can handle even worse scorings (35 "points").

This is logic, because long user inputs will probably not only be unmatched, but get lower scores than short ones (the different between existing patterns and failed one gets bigger as the input increases length).

I've been able to capture 12 word user inputs using AnythingWeaker, so it should be enough... but for a real "catch-all", there is an unused score macro called FAILED_SCORE which captures inputs with 0 score :O

Posted by Kartones | with no comments
Filed under:

In few hours I'll be going to the <MIND camp /> event organized by the people of the Madrid dotNETClubs (one of the two events I talked about last month).

The agenda for both events has been finished:

Notice that the DNCMadDay event has been posponed one week, to the 14th of march (PI day! Thanks for the tip Blanca ;)

 

So, expect a review of the event the next week!

Posted by Kartones | with no comments
Filed under:
More Posts Next page »