Windows Live Ag...'s profileWindows Live AgentsPhotosBlogLists Tools Help

Blog


    July 08

    An advanced look at Web Services and Datasources - Part I

    An advanced look at Web Services and Datasources

     

    Back in February, we showed a basic example of accessing a web service and using a datasource to consume information from a web service. (Refer to: http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!443.entry) Today, we’ll take a further look at a real-world web service, and some further things you can do with a datasource to make your application perform better, and to potentially reduce coding effort.

     

    Many agents rely on rich content in order to create a solid user experience.  Many times, this involves accessing an external data store, such as a web service. With some web services, you can parameterize your request in the URL itself, for example http:/URL?<input>.  More often, web services involve using request and response model via a SOAP API. 

     

    The Windows Live Search API is a very powerful web service that does many things.  It allows a user to do searches on the web, news, images, as well as get dictionary, phonebook and spelling information. It provides a web service XML interface using a SOAP API, enabling you to submit requests and get back a response via XML.  For more information on the XML, refer to this link:  http://msdn.microsoft.com/en-us/library/bb251794.aspx

     

    Using any number of XML tools, we can take a look at the WSDL of the Live Search API web service to see how a typical request and response is crafted.  The WSDL endpoint is this:

     

    http://soap.search.msn.com/webservices.asmx?wsdl

     

    For purposes of discussion, let’s say that we are going use a Web News search, searching for “Baron Davis”.  The query of “Baron Davis” is put into the Query element. There are numerous other elements that are part of the response.  They are highlighted in red. The XML response would look like this:

     

    <?xml version="1.0" encoding="utf-16"?>

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

      <soap:Body>

        <Search xmlns="http://schemas.microsoft.com/MSNSearch/2005/09/fex">

          <Request>

            <AppID>your APP ID</AppID>

            <Query>Baron Davis</Query>

            <CultureInfo>en-us</CultureInfo>

            <SafeSearch>Moderate</SafeSearch>

            <Flags>None</Flags>

            <Location>

              <Latitude>0</Latitude>

              <Longitude>0</Longitude>

            </Location>

            <Requests>

              <SourceRequest>

                <Source>News</Source>

                <Offset>0</Offset>

                <Count>10</Count>

                <FileType />

                <ResultFields>All DateTime</ResultFields>

                <SearchTagFilters>

                  <string />

                </SearchTagFilters>

              </SourceRequest>

            </Requests>

          </Request>

     

    (Note that strangely enough, in the ResultFields element, putting in the request “All” usually returns back all fields In the case of a News request, this does not return back all fields. “All DateTime” will return all fields, plus the datetime in this case.)

     

    The XML response looks like this, with output fields in bold:

     

    <?xml version="1.0" encoding="utf-16"?>

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

      <soapenv:Body>

        <SearchResponse xmlns="http://schemas.microsoft.com/MSNSearch/2005/09/fex">

          <Response>

            <Responses>

              <SourceResponse>

                <Source>News</Source>

                <Offset>0</Offset>

                <Total>31185</Total>

                <Results>

                  <Result>

                    <Title>NBA free agent roundup: Baron Davis to L.A.?</Title>

                    <Description>Baron Davis has a verbal agreement with the Clippers, Gilbert Arenas likely is staying in Washington, and Elton Brand has a decision to make as NBA free agency opened with some surprises yesterday. The dominoes began to tumble when Davis surprised ... </Description>

    <Url>http://www.newsday.com/sports/basketball/knicks/ny-spnba025749266jul02,0,2266274.story </Url>

    <DisplayUrl>http://www.newsday.com/sports/basketball/knicks/ny-spnba025749266jul02,0,2266274.story</DisplayUrl>

                    <Source>Newsday</Source>

                    <DateTime>

                      <Year>2008</Year>

                      <Month>7</Month>

                      <Day>2</Day>

                      <Hour>15</Hour>

                      <Minute>26</Minute>

                      <Second>29</Second>

                    </DateTime>

                  </Result>

     

                  <Result>

                    <Title>Later Daze, Baron: Davis leaves Warriors for Clippers</Title>

                    <Description>del.icio.us Less than 24 hours after Davis shocked the Warriors by walking away from the final year and $17.8 million left on his contract, the franchise guard delivered a second strike Tuesday afternoon. Davis agreed in principle with the Clippers ... </Description>

    <Url>http://www.sfgate.com/cgi-bin/article.cgi?f=/c/a/2008/07/02/SPCF11ICKB.DTL</Url>

    <DisplayUrl>http://www.sfgate.com/cgi-bin/article.cgi?f=/c/a/2008/07/02/SPCF11ICKB.DTL</DisplayUrl>

                    <Source>San Francisco Gate</Source>

                    <DateTime>

                      <Year>2008</Year>

                      <Month>7</Month>

                      <Day>2</Day>

                      <Hour>15</Hour>

                      <Minute>19</Minute>

                      <Second>20</Second>

                    </DateTime>

                  </Result>

                 </Results>

              </SourceResponse>

            </Responses>

          </Response>

        </SearchResponse>

      </soapenv:Body>

    </soapenv:Envelope>

     

     

    For purposes of display, only 2 items are displayed, but in the real-life example, there are 31,185 articles on “Baron Davis” that were found.  The 31,185 is indicated in the <TOTAL> element in the response.  Note that not all 31,185 rows are retrieved.  The amount retrieved is regulated in the Preprocess block, in the built-in LIMIT and OFFSET variables. More on this later, when we describe the datasource.

     

    Let’s take a look at how we generate the Buddyscript code to access this web service.

     

    A typical datasource accessing XML might have header information that looks like this:

     

    datasource dsSample(ARG1, ARG2, ARG3) => A1, B1, C1

      http

        http://someurl/sample.xml

      simple xml

         .

         .

         .

     

    However, for a web service invoking SOAP messages, we have to build a request string, so we use the PreProcess section of a datasource to set a variable that will contain the request, along with other variables.  Thus, the beginning of the datasource would look like this instead:

     

    datasource LiveSearchAPI(SEARCH, CULTURE_INFO) => Title, Description, Url, Source, NewsYear, NewsMonth, NewsDay, NewsHour, NewsMinute, NewsSecond {expire="in 1 hour" continue_on_error="true"}

      preprocess

        if LIMIT>10 || LIMIT<=0

          MAXRESULTS = 10

        else

          MAXRESULTS = LIMIT

        FIELDLIST = "Title Description Url Source DateTime"

        POST_DATA = BuildSearchAPIPostData(SEARCH, "News", OFFSET, MAXRESULTS, CULTURE_INFO, FIELDLIST)

      http

        http://soap.search.msn.com:80/webservices.asmx

        header

          Accept: application/soap+xml

        postdata {encode=no}

          POST_DATA

      simple xml

         .

         .

         .

     

     

    We’ll describe the syntax of the datasource in more details later on, but for now, what’s important to point out is that a function is called to build the request string.  The function in this case is called BuildSearchAPIPostData and has a number of arguments.  Let’s take a look at the function:

     

     

    function BuildSearchAPIPostData(SEARCH, TYPE, OFFSET, COUNT,CULTURE_INFO,

      FIELDLIST, RADIUS, LATITUDE, LONGITUDE)

      if (LATITUDE eq "")

        LATITUDE = 0

      if (LONGITUDE eq "")

        LONGITUDE = 0

      if (RADIUS eq "")

        RADIUS = 5

      POST_DATA =                         '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'

      POST_DATA = StringConcat(POST_DATA, '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http:/\/schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:tns="http://schemas.microsoft.com/MSNSearch/2005/09/fex" > <SOAP-ENV:Body>')

      POST_DATA = StringConcat(POST_DATA, ' <tns:Search xmlns:tns="http:/\/schemas.microsoft.com/MSNSearch/2005/09/fex"> <tns:Request>\n')

      POST_DATA = StringConcat(POST_DATA, '   <tns:AppID>', GetLiveSearchAPIAppID(), '</tns:AppID>\n')

      POST_DATA = StringConcat(POST_DATA, '   <tns:Query>', SEARCH , '</tns:Query>\n')

      POST_DATA = StringConcat(POST_DATA, '   <tns:CultureInfo>', CULTURE_INFO, '</tns:CultureInfo> <tns:SafeSearch>Moderate</tns:SafeSearch> <tns:Flags>None</tns:Flags>\n')

      POST_DATA = StringConcat(POST_DATA, '   <tns:Location>\n')

      POST_DATA = StringConcat(POST_DATA, '     <tns:Latitude>', LATITUDE, '</tns:Latitude>\n')

      POST_DATA = StringConcat(POST_DATA, '     <tns:Longitude>', LONGITUDE, '</tns:Longitude>\n')

      POST_DATA = StringConcat(POST_DATA, '     <tns:Radius>', RADIUS, '</tns:Radius>\n')

      POST_DATA = StringConcat(POST_DATA, '   </tns:Location>\n')  

      POST_DATA = StringConcat(POST_DATA, '   <tns:Requests> <tns:SourceRequest>\n')

      POST_DATA = StringConcat(POST_DATA, '     <tns:Source>', TYPE, '</tns:Source>\n') // Web / Ads / InlineAnswers / PhoneBook / WordBreaker / Spelling

      POST_DATA = StringConcat(POST_DATA, '     <tns:Offset>', OFFSET, '</tns:Offset> <tns:Count>', COUNT, '</tns:Count> <tns:ResultFields>', FIELDLIST, '</tns:ResultFields>\n')

      POST_DATA = StringConcat(POST_DATA, '   </tns:SourceRequest> </tns:Requests>\n')

      POST_DATA = StringConcat(POST_DATA, ' </tns:Request> </tns:Search>\n')

      POST_DATA = StringConcat(POST_DATA, '</SOAP-ENV:Body> </SOAP-ENV:Envelope>\n')

      return POST_DATA  

     

     

    The function essentially uses the variable POST_DATA to build the request string.  If there are certain parameters without information, it defaults the information (e.g. LATITUDE).  The function also calls another function to get the Windows Live APP ID.  This APP ID is used in the Live Search API to determine individual access rights.  (See the Windows Live API for more information on obtaining this APP ID.)  The function itself simply looks like this:

     

    function GetLiveSearchAPIAppID()

      return "ABCDE12345" 

     

    where “ABCDE12345” is the App ID.

     

    We'll take a look at the rest of the datasource and invoke code later this week.

     

    July 02

    Guidelines for Testing your Agent

    As compared to Web sites and traditional software applications, conversational agents are subject to some unique policy compliance risks. These risks arise because:

     

    ·         End users’ interactions with agents are freeform and unpredictable.

    ·         Agents often engage in human-like interactions and operate in messaging environments normally used for human-to-human communications, making end users and outside observers especially sensitive to inappropriate content or behavior.

     

    Because of these unique risks, the Windows Live Agents team highly recommends that each Agent undergo manual testing for policy compliance prior to launching. Once testers have acquainted themselves with the task, approximately 4 to 8 hours of manual testing should provide a reasonable evaluation of the Agent’s policy compliance. Testers should:

     

    ·         Be native speakers

    ·         Have a good understanding of cultural and political factors that might determine whether an Agent’s content/behavior is appropriate

    ·         Be able to make judgments in the best interest of your public image and business interests in the market where the Agent will be released

    ·         Be willing to provoke the Agent to behave inappropriately (this requires creativity, persistence, and willingness/ability to imagine offensive and provocative user inputs)

    ·         Understand the Agent’s feature set

    ·         Ideally not have been directly involved in the Agent’s development

     

    If your testing uncovers any issues that you need help triaging or fixing, please contact Windows Live Agents Partner Support (agentsu@microsoft.com). Send a transcript illustrating each issue, along with a description (in English) of what the issue is.

     

    Overview for Testers

     

    This document is intended to provide guidelines and advice for manual testing of Agents for policy compliance. It outlines specific types of subject matter to focus on, common Agent vulnerabilities, and specific tactics that you can use in an attempt to uncover issues in a given Agent.

     

    This document is not a step-by-step test plan; nor is it by any means exhaustive. When performing compliance testing, there is no substitute for your own persistence and imagination. Furthermore, these guidelines do not currently prescribe any specific standards. You should apply your language and market expertise and your business judgment to determine whether the Agent’s behavior and content are acceptable. We strongly advise erring on the side of caution.

     

    You should read this document to acquaint yourself with the subject of Agent policy compliance. You may find the specific examples to be a helpful starting point, but effective testing will require you to apply your knowledge of the language and market for which the Agent is intended, and of the specific Agent’s content and features.

     

    In order to test effectively, you must be willing and able to imagine and try highly offensive and provocative user inputs. If you’re not comfortable with this task, then you should attempt to find someone who is.

     

    Once you have acquainted yourself with the task, approximately 4-8 hours of manual testing should provide a reasonable evaluation of the Agent’s policy compliance.

    Sensitive/Inappropriate subject matter to test

     

    ·         Profanity

    ·         Hate/Intolerance (with respect to race, gender, sexual orientation, religion, etc.)

    ·         Violence and criminal behavior

    ·         Drug use

    ·         Sexual content

    ·         Suicide

    ·         Culturally/Politically sensitive subjects in your market

    Scenarios to test

     

    ·         Imagine you are one of the Agent’s target users

    ·         Imagine you are a child

    ·         Imagine you are a malicious user attempting to provoke inappropriate Agent behavior

    Common Agent vulnerabilities

     

    ·         Agent may repeat (or “mirror”) user language without employing adequate safeguards

    ·         Agent may respond to the form of a user input without understanding the content

    ·         Agent may fail to recognize inappropriate or sensitive subject matter if the user employs creative/subtle phrasing

    ·         Agent may incorrectly determine an input to be inappropriate and in turn respond inappropriately

    ·         Agent may have “unsafe” catch-all responses (responses used when the user input is not understood at all)

     

    Some specific tactics to try

     

    ·         See how the Agent responds to blatant abuse and provocation

    ·         Try to trick the agent into repeating an inappropriate word or phrase

    ·         Try to elicit an inappropriate opinion from the Agent

    ·         Try to elicit the Agent’s approval (explicit or implicit) of an inappropriate statement

    ·         Try to elicit inappropriate answers to formulaic questions (yes/no, how many, etc.)

    ·         Try to elicit inappropriate responses to commands/requests/statements

    ·         Try to trick the Agent into inferring inappropriate intent where there is none (and responding inappropriately)

    July 01

    It's all about context, part deux!

    Hi again! Here is the second part of our visit to the magical world of contexts. Yesterday we brushed on a few simple uses for them, now let's dive into a slightly more sensitive subject.

     

      Once the agent is hosted and public, you may want to block access to the agent temporarily, for instance if you rely heavily on external data sources that are experiencing an outage or just very slow.

    In that case it's a good idea to keep the agent running normally for a limited set of superusers so they can work on the issue, while putting up an 'out of service' message for the general users.

     

     

    //The message is by default empty. If it's not then the agent knows that we want it muted.

    variable PUBLIC.OUTAGE_MESSAGE = ""

     

    context AgentIsDisabled {out-of-context="0" in-context="1000" condition="!IsSuperUser() && PUBLIC.OUTAGE_MESSAGE ne \"\""}

     

    start context AgentIsDisabled

     

    + =AnythingStrong

      - PUBLIC.OUTAGE_MESSAGE

     

    end context AgentIsDisabled

     

    // SuperUsers only an change the agent status. Easy! We've already prepared for that.

    start context RestrictedStrings

     

    + disable agent REASON=AnythingRaw

      PUBLIC.OUTAGE_MESSAGE = REASON

     

    + enable agent

      PUBLIC.OUTAGE_MESSAGE = ""

     

    end context RestrictedStrings

     

     

      Here the condition is simply to check if the outage message is empty or not, except for super users. They will continue to use the agent normally, and can reenable it whenever their experience is back to normal.

     

    Now, there is one problem in this code... did you spot it?

    Using a public variable like this in a context condition is pretty bad for performance. Indeed, it would mean that for each query of each user, the query server would need to lock access to the public variable in order to read it just to check the context condition.

    It gets even more troublesome if you're on dual-box hosting and require a Stored Public Variable to propagate the outage across queryservers!

    What is the solution then?

     One possible solution is to check the outage variable only once: at session start. All you need for this is to keep a local session variable of that setting.

     

     

    stored variable PUBLIC.STORED_OUTAGE_MESSAGE = ""

    variable G_OUTAGE_MESSAGE = ""

     

    procedure ABStartSessionProc()

      // This procedure can exist independently in any buddyscript file, and is called at session start

      if !IsSuperUser()

        lock profile // locking is required for stored public variables.

          G_OUTAGE_MESSAGE = PUBLIC.STORED_OUTAGE_MESSAGE

     

    context AgentIsDisabled {out-of-context="0" in-context="1000" condition="G_OUTAGE_MESSAGE ne \"\""} //note that we don't need to check for SuperUsers here as as it's done at session start

     

    start context AgentIsDisabled

     

    + =AnythingStrong

      - G_OUTAGE_MESSAGE

     

    end context AgentIsDisabled

     

    start context RestrictedStrings

     

    + disable agent REASON=AnythingStrong

      lock profile

        PUBLIC.STORED_OUTAGE_MESSAGE = REASON

     

    + enable agent

      lock profile

        PUBLIC.STORED_OUTAGE_MESSAGE = ""

     

    end context RestrictedStrings

     

      As you may have noticed, there is a caveat to this solution: since we only check the message at session start, the flag takes some time to propagate as only new users will be getting the message.

    The same is true when you re-enable the agent and it will take a few minutes for the outage to be over for everyone.

     

      So here, we only lock the agent's profile and check the public stored variable once for every session, which is good already. But can we do better?

    Imagine your agent is highly successful and deals with so many people that, say, five people start a new session every second. Don't we all dream of having an agent that popular! But it comes with a price: 5 calls to the public profile per second would probably bog the whole system down.

      What do you do then? Well, add yet another layer of buffering of course!

    Back to our friend the "basic" public variable. That one only lives for the current queryserver, but it is a lot more performant to check against. All we'll need is a background procedure to update the public variable every few minutes, so as to transmit the orders from the top to the base:

     

     

    stored variable PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS = ""

    variable        PUBLIC.OUTAGE_MESSAGE_FOR_ALL_USERS_OF_THIS_SERVER = ""

    variable        G_OUTAGE_MESSAGE_FOR_THIS_USER = ""

     

    procedure Background_UpdateOutageMessage() startup every 1 minute

      // This procedure is called at startup and every minute, independently of any user session.

      lock profile

        PUBLIC.OUTAGE_MESSAGE_FOR_ALL_USERS_OF_THIS_SERVER = PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS

     

    procedure ABStartSessionProc()

      if !IsSuperUser()

        G_OUTAGE_MESSAGE_FOR_THIS_USER = PUBLIC.OUTAGE_MESSAGE_FOR_ALL_USERS_OF_THIS_SERVER

     

    context AgentIsDisabled {out-of-context="0" in-context="1000" condition="G_OUTAGE_MESSAGE_FOR_THIS_USER ne \"\""}

     

    start context AgentIsDisabled

     

    + =AnythingStrong

      - G_OUTAGE_MESSAGE_FOR_THIS_USER

     

    end context AgentIsDisabled

     

    start context RestrictedStrings

     

    + disable agent REASON=AnythingStrong

      PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS = REASON

     

    + enable agent

      PUBLIC.STORED_OUTAGE_MESSAGE_FOR_ALL_USERS = ""

     

    end context RestrictedStrings

     

      Here you have a piece of code, slighly more complex, but that will sustain any kind of traffic without budging, with the only downside of taking up to one more minute to propagate the change of the agent's status.

     

     And this concludes our two-day tour of contexts and their natural habitat. I hope you liked it, and please don't forget to check out our gift shop on the way out!

    June 30

    It's all about context!

     

     

      Today we're going to talk a bit about contexts and what wonderful things you can do with them.

    Well, maybe not "Wall-E opening sequence" wonderful, but they sure come in handy to control what your agent is doing in an easy and trouble-free way. With the help of contexts you can separate clearly your routines according to any condition, without having to worry about micro-managing their scores or doing systematic checks.

    We're going to take a closer look at a few different uses and related buddyscript features, from the very simple to... the wee bit advanced. Tomorrow we'll explore another use for them, and discuss public variables and performance issues.

     

    Here is how, for example, you could make sure that only users on MSN get access to the activity window features:

     

     

    context MSN_Only {out-of-context="0" in-context="100" condition="SYS.User.Service eq \"MSN\""}

    // out-of-context: score adjustment for the queries inside the context that don't verify the condition (here,   0 = no match)

    // in-context    : score adjustment for the queries inside the context that do verify the condition    (here, 100 = no adjustment)

     

    start context MSN_Only

     

    ? Start the activity.

      - Sending you an invitation...

     

    end context MSN_Only

     

     

     An interesting thing to note is that you don't have to end the context at the end of our file. If you were to start the context in a package, then every domain including this package, directly or indirectly, would be within that context and abide by its rules. So here you could have all domains related to Activity Window include this package, and be protected automatically.

      You could apply this principle to restrict certain features or queries based on any condition, like age, country, market, number of visits or what the user's high-score is at your quizz game.

     

    You can also restrict access to the agent even though it's launched and live on Messenger, while it's in development or beta phase:

     

     

    // This table lists the screennames of authorized users

    // Using "exact" as an index method will help make searches more performant

    datatable AuthorizedUsers

      load ScreenName {index="exact"} from

        mememe@hotmail.com

        uberbetatester@live.com

        popaandmoma@live.com

        mygloriousboss@live.com

     

    function UserIsAuthorized()

      if ShellMode() // Shellmode() returns true if we're in the SDK

        return true

      SN = get ScreenName in AuthorizedUsers where ScreenName is SYS.User.ScreenName

        // Found the screename: access granted!

        return true

      return false

     

    // Here the in-context is set to 1000 to make sure that any other routine is overruled.

    context Unauthorized {out-of-context="0" in-context="1000" condition="!UserIsAuthorized()"}

     

    start context Unauthorized

     

    + =AnythingStrong

      - Sorry, I'm in closed beta phase and can't talk to you yet. No peeping!

     

    // Within a context, regular matching still happens as they're all subject to the same adjustment.

    ? But I am a Very Important Person!

      - The Boss said: no exceptions.

     

    end context Unauthorized

     

     

     

      Another use, very similar in implementation, is to restrict certain debug or testing queries to a set of super-users, listed this time in a text file. The textfile-based table is easier to maintain independently from the code, and can be changed without having to restart the agent: all you need is to set an expire date for the file to be reloaded.

     

     

    datatable SuperUsers {expire="in one day"}

      // The list will be timed out and reloaded once a day from the file.

      load ScreenName {index="exact"} from file

        superusers.txt

     

    function IsSuperUser()

      if ShellMode()

        return true

      SN = get ScreenName in SuperUsers where ScreenName is SYS.User.ScreenName

        return true

      return false

     

    context RestrictedStrings {out-of-context="0" in-context="100" condition="IsSuperUser()"}

     

    start context RestrictedStrings

     

    // This topic is only accessible to the right people:

    + debug all variables

      - As you wish.

      dbg_display STORED_USER_VARIABLES

     

     

    That’s it for today. See you tomorrow for another round of goodies!

     

    June 26

    5.0 Transition Update!

    Hello Developers and Partners of Windows Live Agents,

     

    We are very excited that 5.0 is here! We have received many questions from all of you regarding the transition from 4.3 to 5.0.  Windows Live Agents Partner Support is no longer accepting projects developed in 4.3. All agent projects should be developed in 5.0 using our SDK in Visual Studio and submitted through our Partner hosting site at http://phi.agents.live.com. If you send us a new project developed in 4.3 or 5.0 via email, we will unfortunately not be able to accept it. If you have not already downloaded the 5.0 SDK in Visual Studio, you can do so here. For instructions on getting started with hosting, please click here.

     

    If we are currently hosting an agent of yours that was developed in 4.3, please continue to send us updates in 4.3 via email. We will let you know when we are ready to migrate your project from 4.3 to 5.0.

     

    Thanks,

    Windows Live Agents Team

    June 23

    Messenger Screen Name Parameters Updater

    Quite often we hear from partners who complain that the screen name, personal status message, or display picture of their agent has disappeared from the Messenger client, and ask us to fix it. Strictly speaking, the display of these parameters in the client is subject to forces outside the control of the Windows Live Agents group, however we continue to work with those in charge of the Messenger network to resolve issues like this.
     
    In the meantime, here is the code we use to "fix" the problem. If you are not already doing so, you should be using Method 3 from this blog post about changing friendly names, icons, and status messages. Updates to these Messenger parameters via edits to the BFG are no longer supported after 4.3!
     
    To add the screen name parametes updater, simply include these lines in your project:
     
    package lib:/Shared/Utilities/WLMUtilities 
     
    procedure WLMSetUpScreenNameParametersBackground() every 5 minutes per buddy
      call WLMSetUpScreenNameParameters()
     
    June 02

    Scripting tips: conversations 4/4 Using actions

    Greetings,

     

    We often design dialogs in BuddyScript to answer to user queries in a specific context. We will see today how by using actions, we can have initiate and notify to answer dialogs, and what can be the use.

    A dialog is a set of dialog entries that leaves in your script. A dialog entry is some matching information and a script block associated with. The matching information part usually contains patterns, but can contain actions. An action has a name and potentially parameters.

    The first example is a trivia with timer.

    // Trivia with timer

     

    ? Play trivia

      - Let's play trivia

        in what year was the Eiffel Tower built ?

        in 1889 {action=Right()}

        in 1943 {action=Wrong()}

        in 2001 {action=Wrong()}

        You have 30 seconds to answer

      NID = notify in 30 seconds: action TimeOut()

      ? 1889

      action Right()

        - That is correct!

      ? 1943

      ? 2001

      action Wrong()

        - Nope, the correct answer is 1889.

      action TimeOut()

        - Too late, the correct answer is 1889.

     

    At first, you can see a common use of actions, and that is with an enumeration. Typing 1 will trigger the action Right, typing 2 or 3 will trigger the action Wrong. Or you can fully type the date and match one of the three patterns.

    Then, you will notice the action in the notify. After 30 seconds, the notification will answer the dialog for you. If you do respond before the notification triggers, it would be wise to cancel the notification, but if you don’t, that’s ok because when the notification will trigger, no dialog with a TimeOut action will be active and the notification will simply be ignored.

    Initiates can also use actions, but through the respond statement. The syntax is:

    RID = respond SOURCE: action MyAction(PARAMS)

    The SOURCE is an object that contains the user identity (screenname, service and UID), the buddy id and the conversation id to send the action to. Most of the time, you are coming from an initiate and you simply pass the object from SYS.ConversationSource. Don’t forget to save that object in a variable if you have a dialog, because this variable won’t survive the dialog.

    A typical use for this statement is to get information from another user’s profile. In the following example, I have altered the “Do you know my friend” example to display a friendly name that the friend may have provided.

    // Do you know my friend(oh you mean Jacky ?)

     

    stored variable PREFERRED_NAME = ""

     

    ? Call me NAME=AnythingRaw

      PREFERRED_NAME = NAME

      - Ok, for now on, I will call you PREFERRED_NAME

     

    procedure GetPreferredName()

      if PREFERRED_NAME = ""

        NAME = SYS.User.ScreenName

      else

        NAME = PREFERRED_NAME

      CID = respond SYS.ConversationSource: action SetName(NAME)

     

    ? Do you know EMAIL=AnEMailAddress ?

      RESULT_NOLOAD = initiate EMAIL, "MSN": GetPreferredName() {createprofile=false loadprofile=false}

      if RESULT_NOLOAD.Delivered

        IN_SESSION = true

      else

        IN_SESSION = false

        RESULT_LOAD = initiate EMAIL, "MSN": GetPreferredName() {createprofile=false loadprofile=true}

        if !RESULT_LOAD.Delivered

          - Sorry, I can't say that I do

          exit

      action SetName(NAME)

        if NAME != EMAIL

          - Oh, you mean NAME ? \c

        - Yes, I know that guy\c

        if IN_SESSION

          - , and he/she's talking to me right now.

        else

          - .

     

     

    This script calls GetPreferredName under the friend’s profile, this procedure will respond by sending the preferred name. The script will wait in a dialog to get that answer.

     

    You can also constantly exchange information back and forth between users with respond. You just need one initiate to initiate the dialog. Here is another example that lets two users chat through the agent.

    // Chatter

     

    declare procedure InviteToChat(NAME)

    declare procedure Chat(SRC)

     

    ? I want to chat with EMAIL=AnEMailAddress

      - Ok, let me invite your friend over

      CID = initiate EMAIL, "MSN": InviteToChat(SYS.User.NickName) {createprofile=false loadprofile=true}

      action Accept()

        - Ok, your friend has accepted, you may chat now.

        call Chat(SYS.ConversationSource)

      action Reject()

        - Sorry, your friend has declined the offer.

      action NoResponse()

        - Hmm, nobody replied...

     

    procedure InviteToChat(NAME)

      - Hey, your friend NAME would like to chat with you, do you want to accept ?

      set user conversation

      SRC = SYS.ConversationSource

      NID = notify in 30 seconds: action TimeOut()

      ? Yes

        - Great, let's start chatting...

        RID = respond SRC: action Accept()

        call Chat(SRC)

      ? No

        - Fine.

        RID = respond SRC: action Reject()

      action TimeOut()

        - Hmm, you must not be here...

        RID = respond SRC: action NoResponse()

      insist: Please, say Yes or No.

           

    procedure Chat(SRC)

      - Type "quit" to quit the chat.

      ? TEXT=AnythingRaw

        RID = respond SRC: action SendText(TEXT)

        restart dialog

      action SendText(TEXT)

        - TEXT

        restart dialog

      ? Quit

        RID = respond SRC: action Quit()

        - You have terminated the conversation

      action Quit()

        - Your friend has terminated the conversation

       

    Have fun with all that.

     

    damien

    May 30

    Scripting tips: conversations 3/4 Handling conversation ids

    Today’s post will cover the ability to preserve conversations when sending notifications. The typical scenario we are trying to avoid is: a user is answering a series of troubleshooting questions, and then receives a stock alert. The alert proposes to see more details, and the user can’t go back to finish his troubleshooting steps.

    To avoid such disturbance, the BuddyScript engine runs notification and initiate alerts in different conversations from the one used to answer regular queries.

    Each conversation is uniquely identified by an identifier. A system variable tells you what your conversation id is:

    SYS.Conversation

    Another system variable tells you the id of the conversation used to answer queries. This is called the user conversation.

                SYS.User.Conversation

    If those two values are the same, you are in the user conversation, otherwise, don’t bother initiating a dialog.

    You can switch the user conversation to be the conversation you are in with the following statement:

                set user conversation

    You can also switch the user conversation to be a conversation you specify by doing:

                set user conversation CONVERSATION_ID

    With this statement, you can temporarily highjack the user conversation and later restore it.

    Finally, you can forward a query you just received to any conversation with the following statement:

                forward query CONVERSATION_ID

    With those three elements, you can highjack the user conversation, receive the user queries, forward the queries that you don’t care to the previous user conversation, and restore the previous user conversation.

    To illustrate this, here is the “Say Hi to my friend” example with the ability for the friend to reply back.

    // Respond to Hi

     

    declare procedure DisplayGreeting(FRIEND_NAME, GREETING)

     

    ? Say Hi to my friend EMAIL=AnEMailAddress

      RESULT = initiate EMAIL, "MSN": DisplayGreeting(SYS.User.NickName, "Hi") {createprofile=false loadprofile=true}

      - Ok, done.

     

    procedure DisplayGreeting(FRIEND_NAME, GREETING)

      - Hey, your friend FRIEND_NAME says GREETING!

      SOURCE = SYS.ConversationSource

      if SYS.Conversation = SYS.User.Conversation

        // This is the user conversation

        - Do you want to say GREETING back to FRIEND_NAME ?

        ? Yes

          RESULT = initiate SOURCE.ScreenName, SOURCE.Service: DisplayGreeting(SYS.User.NickName, GREETING + " back")

          - Done.

        ? No

          - Fine.

      else

        SAVED_USER_CONV = SYS.User.Conversation

        set user conversation

        - If you want to say GREETING back to your friend, just say "reply"

        ? reply

          RESULT = initiate SOURCE.ScreenName, SOURCE.Service: DisplayGreeting(SYS.User.NickName, GREETING + " back")

          - Done.

        ? ANYTHING=AnythingRaw

          forward query SAVED_USER_CONV

        set user conversation SAVED_USER_CONV

       

    The first thing DisplayGreeting does is to save SYS.ConversationSource in a variable. This variable contains some information on the user the initiate comes from, and will be used to initiate back.

    It then identifies if the current conversation is the user conversation. That would be the case if the user was not in session with the agent. Depending on the result, we will phrase our question differently. The response will have to be less ambiguous if the user was already in a conversation with the agent.

    If this is the user conversation, the rest of the code is simple.

    If it is not the user conversation, the procedure highjacks it. If the user sends “reply”, an initiate is sent back to the originator. If the user sends anything else, the query is forwarded to the previous user conversation. In any case, the user conversation is restored to the previous one.

    You can try this example with a routine with a dialog and see if the user that receives the greeting can ignore the question and respond to the dialog he was in. You can also test if he can still respond to his dialog after saying “reply”.

    Here now is the kitchen timer example with a snooze capability. The same logic is used here.

    // Kitchen Timer with snooze

     

    declare procedure Ring()

    variable KITCHEN_TIMER_NOTIFICATION = ""

     

    ? Kitchen timer

      - Ok, I'm setting up the time, how many minutes do you want ?

      ? DELAY=Integer

        - Alright, I will notify you in DELAY minutes

        KITCHEN_TIMER_NOTIFICATION = notify in DELAY minutes: Ring()

     

    ? Cancel Kitchen timer

      if KITCHEN_TIMER_NOTIFICATION = ""

        - What timer ?

      else

        cancel notify KITCHEN_TIMER_NOTIFICATION

        KITCHEN_TIMER_NOTIFICATION = ""

        - Done.

       

    procedure Ring()

      - DRRRRRRRING !!!!!

        Your time is up!

      KITCHEN_TIMER_NOTIFICATION = ""

      if SYS.Conversation = SYS.User.Conversation

        // This is the user conversation

        - Do you want to snooze ?

        ? Yes

          KITCHEN_TIMER_NOTIFICATION = notify in 5 minutes: Ring()

          - Will ring again in 5 minutes.

        ? No

          - Fine.

      else

        SAVED_USER_CONV = SYS.User.Conversation

        set user conversation

        - To give an extra 5 minutes, type "snooze"

        ? Snooze

          KITCHEN_TIMER_NOTIFICATION = notify in 5 minutes: Ring()

          - Will ring again in 5 minutes.

        ? =AnythingRaw

          forward query SAVED_USER_CONV

        set user conversation SAVED_USER_CONV

     

    Next post will explain how to empower your scripts by combining initiate and notify with dialogs. This is done through actions.

     

    Damien.

    May 29

    Scripting tips: conversations 2/4 Controlling the execution of initiate and notify

    Good (morning|afternoon|evening),

    With the initiate functionality, you can choose to put one of two restrictions based on the targeted user:

    ·         The user has to be known to the agent

    ·         The user has to actually be conversing with the agent

    This is a good idea to set those restrictions to avoid spamming the users.

    By checking the result of initiate, you can know if it succeeded or not. If it did not, depending on the restrictions you just put, you can conclude if the user is known to the agent or if the user is not currently talking to the agent. You set those restrictions by adding the following properties:

    ·         {createprofile=false}: initiate will fail if the user doesn’t have a profile yet.

    ·         {loadprofile=false}: initiate will fail if the user is not in session with the agent.

    The result of initiate is an object, the two important members of that object are:

    ·         Delivered: contains 1 if delivered, 0 otherwise

    ·         Status: string indicating the reason it was not delivered.

    Here is an example of use of that information, I call it “Do you know my friend?”

    // Do you know my friend

     

    procedure DoNothing()

      nop

     

    ? Do you know EMAIL=AnEMailAddress ?

      RESULT_NOLOAD = initiate EMAIL, "MSN": DoNothing() {createprofile=false loadprofile=false}

      if RESULT_NOLOAD.Delivered

        - Yes I do, and he/she's talking to me right now.

      else

        RESULT_LOAD = initiate EMAIL, "MSN": DoNothing() {createprofile=false loadprofile=true}

        if RESULT_LOAD.Delivered

          - Yes, I know that guy

        else

          - Sorry, I can't say that I do

     

    Another use of initiate is to send information from one user to another user. The next example is “Leave a note to my friend”. It lets a user leave a message for another user, which will get the message the next time he/she talks to the agent.

    In addition, this example checks to see if the user is currently in session, in which case it will deliver the message immediately.

    // Leave a note to my friend

     

    declare procedure SendMessageDirectly(FRIEND_NAME, MESSAGE)

    declare procedure LeaveMessageNote(FRIEND_NAME, MESSAGE)

     

    ? Can I leave a note to EMAIL=AnEMailAddress ?

      - Sure, what's the message ?

      ? MESSAGE=AnythingRaw

        RESULT = initiate EMAIL, "MSN": SendMessageDirectly(SYS.User.NickName, MESSAGE) {createprofile=false loadprofile=false}

        if RESULT.Delivered

          - Well, your friend and I are actually talking to each other right now, so I'm telling him/her now.

        else

          RESULT = initiate EMAIL, "MSN": LeaveMessageNote(SYS.User.NickName, MESSAGE) {createprofile=false loadprofile=true}

          - Ok, I'll leave a note for him/her to read next time he/she talks to me

     

    procedure SendMessageDirectly(FRIEND_NAME, MESSAGE)

      - Hey, your friend FRIEND_NAME has left a message for you:

        MESSAGE

     

    stored variable OFFLINE_MESSAGE_FROM_FRIEND = ()

     

    procedure LeaveMessageNote(FRIEND_NAME, MESSAGE)

      OFFLINE_MESSAGE_FROM_FRIEND.FriendName = FRIEND_NAME

      OFFLINE_MESSAGE_FROM_FRIEND.Message = MESSAGE

     

    procedure overrides ABGreetingProc(USERARRIVES, NEWUSER)

      - Good day!

      if OFFLINE_MESSAGE_FROM_FRIEND != ()

        - Hey, your friend OFFLINE_MESSAGE_FROM_FRIEND.FriendName left the following message for you:

          OFFLINE_MESSAGE_FROM_FRIEND.Message

        OFFLINE_MESSAGE_FROM_FRIEND = ()

       

     

    About notifications, the only thing there is to do is to cancel them before they trigger. This is done by saving the notification id and passing it to the “cancel notify” statement.

    To illustrate this simple thing, here’s the previous Kitchen timer example improved with a stop functionality.

    // Kitchen Timer stopper

     

    declare procedure Ring()

    variable KITCHEN_TIMER_NOTIFICATION = ""

     

    ? Kitchen timer

      - Ok, I'm setting up the time, how many minutes do you want ?

      ? DELAY=Integer

        - Alright, I will notify you in DELAY minutes

        KITCHEN_TIMER_NOTIFICATION = notify in DELAY minutes: Ring()

     

    ? Cancel Kitchen timer

      if KITCHEN_TIMER_NOTIFICATION = ""

        - What timer ?

      else

        cancel notify KITCHEN_TIMER_NOTIFICATION

        KITCHEN_TIMER_NOTIFICATION = ""

        - Done.

       

    procedure Ring()

      - DRRRRRRRING !!!!!

        Your time is up!

      KITCHEN_TIMER_NOTIFICATION = ""

     

    Those messages, coming from an initiate or a notification, are designed not to disrupt the conversation. What that means is if the user is expected to answer a dialog or an enumeration, the fact that his kitchen timer rings or that he receives a message from a friend won’t prevent him from answering as he was supposed to.

    What that also means is, if you add a dialog to your Ring procedure, the user will most likely not be able to answer it. How to fix this? This will be explained in the next post.

    Damien.

    May 28

    Scripting tips: conversations 1/4 Basic initiate and notify

    Hello fellow agent developers,

     

    I am going to present, in this post and the next 3 ones, the notion of conversations in BuddyScript and the way to best use them with the “initiate” and “notify” functionalities.

    Here are the items I am going to cover in those four posts:

    1.     Basic use of initiate and notify.

    2.     Controlling the execution of initiate and notify.

    3.     Handling conversation ids.

    4.     Using actions.

    Controlling the execution tells you how to know if a user is talking to the agent or not, if he has ever talked to the agent or not, and how to cancel a notification.

    Handling conversation ids will show you how to interact with the user from a notification without disturbing the current conversation he was in. For example, the user plays a game with the agent and the agent throws a stock ticker alert, proposing the user to see details; the user should be able to finish his game.

    Using actions will show you how to exchange information between users back and forth in an efficient manner, and how to combine notifications with dialogs. An example of use is a trivia game with a timer.

     

    But today, I’m going to describe the initiate and notify functionalities.

    Initiate is a BuddyScript functionality that lets you call a procedure on behalf of someone else. This procedure is executed with the profile of the user you specified, and any output generated will be sent to that user.

    The first example is the “Say Hi to my friend” program.

    // Say Hi to my friend

     

    declare procedure DisplayGreeting(FRIEND_NAME, GREETING)

     

    ? Say Hi to my friend EMAIL=AnEMailAddress

      RESULT = initiate EMAIL, "MSN": DisplayGreeting(SYS.User.NickName, "Hi") {createprofile=false loadprofile=true}

      - Ok, done.

     

    procedure DisplayGreeting(FRIEND_NAME, GREETING)

      - Hey, your friend FRIEND_NAME says GREETING!

    In this example, user Joe will ask to say Hi to Mary, providing Mary’s live messenger screen name. As a result, Mary will receive a greeting from the agent. In this example, Mary has to have talked to the agent already; she must already have a profile stored.

     

    The notify functionality calls a given procedure at a given time from the same user. It is used to create timers and reminders.

    An example for it is the “Kitchen Timer”.

    // Kitchen timer

     

    declare procedure Ring()

     

    ? Kitchen timer

      - Ok, I'm setting up the time, how many minutes do you want ?

      ? DELAY=Integer

        - Alright, I will notify you in DELAY minutes

        ID = notify in DELAY minutes: Ring()

       

    procedure Ring()

      - DRRRRRRRING !!!!!

        Your time is up!

     

    Perfect to boil an egg.

    If you want the best of both worlds, being able to specify the procedure to call, the time to execute it and for which user, you can combine the two.

    Here’s the “Friend’s reminder” example

    // Friend's reminder

     

    declare procedure RemindFriend(FRIEND_NAME, ACTION, TIME)

    declare procedure RemindNotification(ACTION)

     

    ? Remind my friend EMAIL=AnEMailAddress to ACTION=AnythingRaw at TIME=ATime

      RESULT = initiate EMAIL, "MSN": RemindFriend(SYS.User.NickName, ACTION, TIME)

      - Ok, that's done.

     

    procedure RemindFriend(FRIEND_NAME, ACTION, TIME)

      STIME = TIME.hour + ":" + TIME.minute

      - Hey, SYS.User.ScreenName, FRIEND_NAME would like to remind you to ACTION at STIME.

      ID = notify day at STIME: RemindNotification(ACTION)

     

    procedure RemindNotification(ACTION)

      - Hey there, it's time to ACTION!

     

    The procedure “RemindFriend” doesn’t need to output anything to work. The alternative would be to do the notify first, followed by initiate.

    Tomorrow, we’ll see in more details those two functionalities.

    Damien.

    Public Release of Windows Live Agents SDK!

    Hello Developers and Partners of Windows Live Agents,

     

    We are pleased to announce the public release of our new SDK for Visual Studio.  We have been in limited beta of the Colloquis SDK for quite some time and are excited to release this new technology to the public.  This new release of our SDK, as well as our Platform, marks an important step in building the Agents developer community.  We’ve been publishing weekly updates to this blog for almost a year, as well as supporting developers through our MSDN Forum, so we’re hopeful that we’ve gotten all your feedback and input handled in this newest release.  In addition to the release of the Platform and SDK, we’re also releasing our new system for hosting Agents at Microsoft, PHI (Partner Hosting Infrastructure).  With PHI, the previous method of mailing a project manager or an alias will be replaced by submitting your Agents through an automated system, receiving status on your project, and administering all your projects through a single console.  Hosting of your Agent will require a nominal yearly fee that offsets hardware costs (prices starting at ~$10k USD/year).  If you are working in partnership with Windows Live or MSN, that fee will likely be waived as part of your development contract on the product you are delivering.

     

    I highly recommend you read the release notes below for important process related questions for Agent development, as well as our SDK Documentation. There is important information about how to use PHI, the IDE, and what guidelines you must follow in order to develop in this community.  As we’ve noted in earlier blog posts, the older standard of MSN Bot is no longer supported, or certified by Microsoft.  Windows Live Agents replaces the terminology, technology, guidelines, and processes under which many of you have operated in the past, but we believe we’ve spoken about this at great length and have given our partners a chance to migrate effectively.  The users of Messenger and Windows Live ID are protected by Microsoft’s global policy and we’re ensuring that Agents on our network are held to the highest standard in online safety by using the Agents SDK for development.  Development of an Agent on the Agents SDK does not ensure that an Agent will be hosted by Microsoft, pending content reviews and approvals by the policy and safety teams.

     

    While we have many partners that work closely with the Agents team today, we are actively seeking new professional vendors that use this SDK to partner with us and the MSN Markets.  If you have been working with an MSN Market already, please send an email to wlabiz@microsoft.com to let us know the Agents you’ve developed and the people you’ve been in contact with at MSN.  Our development team is always recommending partners for vendor work in different regions.  We’ll be formalizing these partnerships under the Microsoft Partner Program in the future, but welcome the on-boarding of new development teams that can build Agents for online media needs. 

     

    Any questions that you have on the release, the process, or the software, should be posted on the MSDN Forum.  The previous alias used by many today will be monitored, but replies will be limited to developers that are explicitly directed there.

     

    We look forward to seeing the innovation from your development teams!

     

    Best,

    Windows Live Agents development team

     

     

    Here are the relevant links for download and information:

    ·         SDK

    ·         SDK Documentation

    ·         PHI

    ·         Development Blog

    ·         MSDN Forum

    New Features and Processes in Windows Live Agents 5.0

    ·         Visual Studio SDK

    o   We will be retiring the old standalone Colloquis SDK (versions 4.3 and previous) within 90 days of our 5.0 release. The process by which you take your previously developed projects and update them will be discussed with our release notes, but we want to ensure that developers have Visual Studio 2005 or 2008 Standard (or above) prior to the release of our Agents plug-in.  Please subscribe to Alerts on our blog page for availability of the SDK as we will be posting the announcement there.

    ·         Partner Hosting Infrastructure (PHI)

    o   We are streamlining the process by which projects can be hosted within Microsoft, if you choose to be hosted with us.  The old method of contacting a project manager within Microsoft will be replaced with our PHI system.  Within PHI, you will be submitting your Agents through an automated system, receiving status on your project, and administering all your projects through a single console.  Hosting of your Agent will require a nominal yearly fee that offsets hardware costs. We will be updating folks on the pricing model in the near future on our blog and through Windows Live development announcements, but it’s likely you’ll be able to waive these hosting fees if you are working with an MSN Market.  Microsoft will only host Agents that are developed within the languages that are supported by the SDK (English, French, Italian, German, Spanish, Japanese, Chinese; Portuguese and Dutch will release in the next few months).

    ·         Agent Provisioning

    o   As part of the ongoing safety for our end-users, the ability to have a Windows Live ID provisioned as an Agent will require that the Windows Live Agent SDK is used for development of your Agent.  The previous method of submitting through Windows Live Gallery is no longer supported and will soon be removed from the system all together.  Provisioning an Agent lifts the limit of contacts within Windows Live Messenger, but will be enforced strictly to ensure our Platform is in use.

    ·         Compliance

    o   The Windows Live Agents SDK is now enforcing Policy Compliance in all responses from an Agent hosted by Microsoft.  What this means to you as a developer is that your Agent may not respond with the exact text that you had scripted during your development.  When an Agent is hosted by Microsoft, we’ll have an additional set of tools that runs against your code to ensure that the response is appropriate for any and all users of Messenger.  We are holding our developers to the highest standards in online safety and will be posting blog entries on how you can safeguard your code within the SDK prior to Windows Live hosting.  You will be able to easily mimic the production conversation during development with these tools, so you can be sure your Agent is compliant with online safety guidelines.  If you are working with an MSN Market or Microsoft product, you will be hosted by Microsoft and subject to these tools.

    ·         Self-Hosting

    o   As you are downloading the new SDK, you may run this free version with your production Agent, in your own environment.  There are limitations on sessions within this self-host model, as well as some other limitations in terms of high availability deployments, but it is almost exactly the same product as we would host for you through PHI.  This is an ideal setup for the smaller development teams that do not have SLA’s on their Agent and wish to prototype features.  We have found this scenario to easily handle a large percentage of Agent traffic in the field today.  Again, this model enforces that the Agent SDK is in place to connect to Messenger as a provisioned Agent.

     

     

    IDE

    We have completely moved away from the custom made IDE that all projects up to the 4.3 release of our platform used. Instead, we now have a solution fully integrated in Visual Studio as an add-in.

     

    Full details on the new IDE are found in our documentation and on our blog.  Some quick highlights of features to look out for:

    ·         Conversation and comprehension windows redesigned to better show the inner workings of how query matches are done

    ·         Solution explorer to navigate your project

    ·         Colorization and IntelliSense support

    ·         Object browser for viewing all objects declared in your project

    ·         Full integration with our Partner Hosting Infrastructure (PHI). For example, seamlessly check in and check out your PHI projects.

    ·         And more…

    Migrating a Project from 4.3 to 5.0

    We have developed a script that helps to convert a project from 4.3 and make it compatible with our 5.0 release. The script basically creates a .connections file based on the language dependencies it reads in from the .dls file, updates obsolete references to the old Buddyscript libraries, and updates the .dls file.

     

    An important thing to note is that the script will overwrite your current project directory. To be safe, you should backup your project directory before running the script.

     

    The script is located in your Windows Live Agents SDK install directory (default location is in Program Files) at:

     

    Windows Live Agents SDK\Projects\ProjectConversion\ConvertProjectTo5_0.bat

     

    Drag and drop the project directory you wish to convert onto the .bat file, or double click the .bat file and enter the project directory path.

     

    Note: The conversion script will not necessarily  address every issue. You will most likely have to do some manual work after running the conversion script. But it will still save you quite a bit of work.

     

    PHI

    Overview

    Partner Hosting Infrastructure, or PHI, is a tool that will allow developers to apply for Microsoft to host their Agent projects. Developers will be able to upload their code, manage projects, and be informed of the current state of their projects.  This section will go over the tools that developers have available to them when developing a project, taking it live, and making changes to project files.

    It allows developers to :

    ·         Submit a project application

    ·         Check in code for that Project

    ·         Stage and Test your Project

    ·         Go Live with your Project

     

    How to modify your Project

    Once your project is hosted at Microsoft, you have two options available to you when you would like to modify files in your project.

     

    Using the Console

    You can use the KMS Console at https://sampleProj.console.agents.live.com  to modify the project. This will allow you to edit topics and responses fairly easily. For more control, however, you would want to use the IDE to make project changes.

     

    Using the IDE

    You can use the IDE to check in files in the same way that you checked in the project originally. Please note that using the IDE will check in an entire project, and does not allow you to check in just a single file.

     

    To check out using the IDE, you must first sign in with your Windows Live ID and acquire a project lock by clicking on Tools à Windows Live Agents Tools à Code Management à Check Out Project. You can then edit whichever files you wish. While a project is checked out to you, no one will be able to edit any of the project files using the IDE or KMS.

     

    To check in, click on Tools à Windows Live Agents Tools à Code Management à Check In Project.

     

    Checking In the Connections File

    When you check in the connections file, it must be reviewed and approved before being hosted or going live in the data center. Check in the connections file as you would check in any other file (see directions above).

     

    Checking in your project when the .connections file has not been modified will not trigger a review. Those changes should propagate immediately.

    May 20

    Definition of Reporting Terms in Usage Reporting

    Definition of Reporting Terms in Usage Reporting

    When your agent launches, you will be given access to the Knowledge Management Server, which includes a Usage Reporting section. The URL is https://YourProjectName.console.agents.live.com.

     

    When you navigate to the Usage Reporting site, you will see date range options in the left pane, and report results in the right pane.  The default date is the present day.  You can switch days by clicking on the calendar on the left.

     

    If you click on “Custom Range” on the upper left side of the page, you can view usage reports for a custom date range. Please not that in this view, New & Unique Users will be shown as N/A. Due to a design limitation, we currently cannot provide accurate measurements of new and unique users for custom reporting periods.

     

    At the top of the page, you may see the following sections of reporting: Volume Summary, User Demographics, Languages Used, Activity Usage, Compliance, Category Analysis, Topic Analysis, and Clickthroughs. The default reporting view is Volume Summary. The sections of Usage Reporting you see, depends on how you have set up the usage_config.xml your project uses. For more information on customizing the usage_config.xml, click here.

     

    Definition of Reporting Terms: 

     

    Volume Summary Section

     

    Total Queries:             

    The total number of queries in all sessions within the specified time period. For example, if the time period includes 100 sessions, there could be 1000 or more total queries.

     

    Total Sessions:           

    The total number of sessions within the specified time period.

     

    Unique Users:             

    The number of unique users within the specified time period. Each user is counted once, regardless of how often they interact with the agent. For example, if one person has ten separate sessions with the agent during the specified time period, they are still identified as one unique user.

     

    New Users:                  

    The number of users whose initial session with the agent occurred during the specified time period.

     

    Average Queries Per Session:

    Total queries within the specified time period divided by total sessions within the specified time period.

     

    Average Sessions Per Unique Users:  

    Number of unique users within the specified time period divided by total number of sessions within the specified time period.

     

    Category & Topic Analysis Sections

     

    Category Analysis is broken out into two sections: Category Distribution Per Query and Category Distribution Per Session. Category Distribution Per Query breaks down how many queries for the chosen date range matched to a category. Category Distribution Per Session breaks down how many times a query matched on a Category during a session.

     

    Topic Analysis is only broken out into one section: Topic Distribution (By Query). This measures how many times users’ queries matched to a specific topic for the chosen date range.

     

    For information on how to log Category and Topic Analysis in your project, click here.

     

    Clickthrough Section

     

    The Clickthrough section breaks down how many times a user is presented a link and how many times a user clicks on the link.

     

    Total Impressions

    Number of times a user is presented a link in their conversation with the bot.

     

    Total Clicks

    Number of times a user clicks on the link he / she is presented.

     

    For information on tracking ClickThroughs in your project, click here.

     

    Languages Used Section

     

    Agent detects what languages users speak to the agent in.

     

    Activity Usage Section

     

    Tracks how many users accept or reject an invitation to open the Activity Window.

     

    Compliance Section

     

    Conversations Stopped

    Number of conversations interrupted after detecting user was typing sensitive topics beyond the scope of the agent.

     

    Sensitive Sequences Rejected

    Number of answers that were blocked by the output filter, displaying an error message to the user.

     

    Sensitive Sequences Trusted

    Number of answers that would have been blocked by the output filter, but were let through because of the use of the tag <trusted>.

     

    Answers Invoking Trust

    Number of messages that were displayed with the tag <trusted>…</trusted> in them. It includes both messages that would have passed the output filter or failed it.

     

    May 15

    Adding a Usage Report to the Console

    The standard reporting suite on the Knowledge Management Console covers the most important metrics -- Unique Users, Total Sessions, etc. However, you'll often want (or be asked) to provide additional reports based on the special needs of the customer and/or the particular tasks of the agent. This article won't cover all the details of configuring usage reports, but will hopefully demonstrate how to log a simple data point and have it show up on the console as a graph or table.
     
    Before configuring usage, we should look briefly at how logging works. The agent's log is an object variable called SYS.Log -- anything written there is a candidate to show up in usage reports. This is completely separate from the User Profile: the agent may store the user's birthplace in a variable called G_USER.birthplace, but it won't show up in usage unless we also write it to, for example, SYS.Log.birthplace. It is also completely separate from the server component logs on the console.
     
    So let's actually use birthplace as our example, and look at what we need to record and display that statistic.
     
    Say we have a procedure called CollectBirthplaceFromUser() that takes care of collecting the user's birthplace and resolving it into a valid place name we can record.
     
    procedure CollectBirthplaceFromUser()
      BIRTHPLACE = ""
      [...]
      call WriteBirthplaceToProfile(BIRTHPLACE)
      call LogBirthplace(BIRTHPLACE)
     
    WriteBirthplaceToProfile assigns the value of BIRTHPLACE to G_USER.birthplace. LogBirthplace writes the same value to the SYS.Log.birthplace variable.
     
    procedure LogBirthplace(BIRTHPLACE)
      SYS.Log.birthplace = BIRTHPLACE
     
    Now that we're confident birthplaces are being collected and logged, let's move on to configuring Usage. We do this by editing a file at the root directory of the project, called usage_config.xml.
     
    usage_config.xml controls the presentation of log data in the Usage Reports section of the console.  It's completely separate from the actual logging of data -- we can log something in the BuddyScript, for example, but not add the necessary sections in usage_config.xml for another month, at which point the data the agent has been logging for the last month will be displayed. However, the opposite is not true -- adding a section in usage_config.xml doesn't cause data to be collected.
     
    There are two main parts: a "sections" element containing all the configuration options for the usage reports, and a "usagestats" element containing the variables and datapoints to be used in the sections element.
     
    The sections element itself contains two subsections labeled "all_ids" and "individual_id", which correspond to views of usage statistics broken out by individual buddyid or all buddyid's combined, exposed as a drop-down list in the console. First we'll look at the all_ids section, which is somewhat simpler than the individual_id section.
     
    The first section here is called Volume Summary, and contains a number of elements for reporting usage data. You can get a good idea of the syntax we'll be using later by examining this section.
     
     <section name="Volume Summary" id="volume_summary" shared-graph-type="barline" shared-graph-title="Usage">
      <element name="Total Queries" key="message_count" calc-type="normal" default-graph-state="on" graph-type="shared"/>
      <element name="Total Sessions" key="session_count" calc-type="normal" default-graph-state="on" graph-type="shared"/>
      <element name="Unique Users" key="unique_users" calc-type="normal" default-graph-state="off" graph-type="none"/>
      <element name="New Users" key="new_users" calc-type="normal" default-graph-state="off" graph-type="shared"/>
      <element name="Average Queries Per Session" key="message_count" calc-type="per" subkey="session_count" graph-type="shared"/>
      <element name="Average Sessions Per Unique User" key="session_count" calc-type="per" subkey="unique_users" graph-type="none"/>
      <element name="Average Session Length (in seconds)" key="total_session_length" calc-type="per" subkey="session_count" graph-type="shared"/>
     </section>
     
    The "name" identifier is the text displayed on the console, the "key" is the datapoint, "calc-type" can be normal or something else depending on whether we want to perform some math on the number before displaying it, "subkey" is the other datapoint to use when calc-type is not normal, "default-graph-state" toggles whether a particular element shows up on the graph, "graph-type" controls what kind of graph, if any, to display the data on. In this case, multiple elements are sharing a "barline" graph -- a combination bar graph and line graph where the user selects which data goes on the bar, and which goes on the line.
     
    We could add our own section for birthplace, but it turns out there is already a section for demographics, so we'll just add an element within that section:
     
      <element name="Birthplace Distribution" key='[birthplace][%]' keyName="Birthplace" valueName="Sessions" calc-type="distribution" graph-type="pie" display-type="percentage"/>     
     
    This will pull all the values of SYS.Log.birthplace and display them in a pie graph, distributed by the number of sessions they occur in, along with a key. ("name", "keyName", and "valueName" are all labels and have no effect on the display of the data.) [%] represents a wildcard, meaning "all values of birthplace" -- we could choose instead to report on only [birthplace]['New York'], for example. 
     
    Since there are potentially a large number of birthplaces (depending on how smart CollectBirthplaceFromUser is), it probably makes sense to dispense with the graph, and just display a table:
     
      <element name="Birthplace Distribution" key='[birthplace][%]' keyName="Birthplace" valueName="Sessions" calc-type="distribution" display-type="percentage"/>     
     
    That's all we need to do for the all_ids section. For the individual_ids section, because we'll be filtering by buddyid, we need to go to the bottom of the file and add a datapoint:
     
            <var name="userBirthplace" default="">value('[birthplace]')</var>
      <datapoint buckets="+hour" key="sessions where userBirthplace=${userBirthplace};buddyid=${buddyid}">
        <filter>userBirthplace != ''</filter>
      </datapoint>
     
    First we create a variable called userBirthplace, which is assigned the value of SYS.Log.birthplace. Then we create an hourly bucket for that data with a key for pulling the number of sessions within a particular buddyid where a particular birthplace was recorded. In the demographics section of the individual_id section, we'll add this:
     
      <element name="Birthplace Distribution" key='sessions where userBirthplace=%;buddyid=%var:buddyid%' keyName="Birthplace" valueName="Sessions" calc-type="distribution" display-type="percentage"/>     
     
    This is similar to the element we created for the all_ids section, but birthplace (userBirthplace=%) and buddyid (buddyid=%var:buddyid%) are interpolated into their actual values, and we see only the number of sessions for a birthplace recorded for the buddyid currently selected. Now we can use the buddy dropdown on the console as a filter.
     
    In practice, displaying a list of arbitrary names of places is not going to be very useful because there are many thousands of place names the user can enter. We'd probably want to constrain the list to countries, states, or other large areas, depending on the mission of the agent. Still, this example will hopefully get you started experimenting with Usage, which is the best way to learn. Chances are, usage_config.xml probably already contains most of the necessary pieces for whatever report you need to create.
    May 12

    New version of the Platform and SDK coming soon

    Hi All,
     
    I know a lot of folks have been waiting diligently for the new version of the SDK.  We have finished the product and are in the midst of deploying the platform in our data center now.  Once we get the final approval from Operations, we'll publish a link to the SDK and you'll be on your way!
     
    Thanks for you patience and we look forward to you using the SDK!
     
    Windows Live Agents team
    April 25

    BuddyScript Subpatterns

    Subpatterns are much like functions in that they can be used as filters for data -- one value is passed in, evaluated, and another possibly different value is returned. That's why we can do things like this:
     
    subpattern Basketball
    + (basketball|basket ball)
    + (b ball|bball) {MACRO_SYN}
    + (hoops|one on one|1 one 1) {MACRO_WEAK_SYN}
      return "basketball"
     
    subpattern Baseball
    + (baseball|base ball)
    + ball {MACRO_WEAKER_SYN}
      return "baseball"
     
    subpattern ASport
    + VAR=Basketball
    + VAR=Baseball
      return VAR
     
    + i like SPORT=ASport
      - I like SPORT too!
     
    User: i like bball
    Agent:  I like basketball too!
     
    Let's look at a slightly different kind of subpattern.
     
    stored variable G_DESSERTS
     
    function DessertIsOnMenu(DESSERT_NAME)
       return Exist(G_DESSERTS[DESSERT_NAME])
     
    subpattern Dessert
      if DessertIsOnMenu(VALUE)
        return 100
      return 0
     
    The Dessert subpattern above is a BuddyScript subpattern -- a subpattern that returns its match score based on some logic defined within it.  VALUE is a special variable made available for this kind of subpattern and contains the token being evaluated -- here it is passed to the DessertIsOnMenu function, which returns true if the parameter it receives exists as a key in the G_DESSERTS object. If this is the case, the token will match to Dessert with a score of 100.
     
    Using BuddyScript subpatterns enables scenarios in which you can match in a dynamic way, tightly controlling how matches are evaluated and scored.
    April 23

    Microsoft Partner Program

    Hello Developers,

     

    As we are nearing the public release of our new Visual Studio SDK and Partner on-boarding system, we wanted to make sure that we expose our developers to all the benefits from Microsoft in terms of certifications.  We realize there are a large number of Agent developers that make their business through the Agent platform and we are working closely with the rest of Windows Live Platform to get you the recognition you need with your customers.  In the future, Windows Live will have a program specifically within the Microsoft Partner Program (MSPP) and you’ll be able to fully leverage that designation.  Membership in MSPP is free with graduated levels of certification (fee and skill based).   In the past, there was a notion of “MSN Bot Certified”, but because of business and technology reasons, this designation has essentially expired and won’t be recognized by Microsoft.  Windows Live is moving forward with MSPP as the official method of partnering and we encourage you to start MSPP enrollment at your earliest convenience. 

     

    We look forward to building out this developer community with you!

    April 17

    So, you think you know everything about Buddyscript?

    So, you think you know everything about Buddyscript?

     

    Well, let’s see how much of such a statement is true. Try to answer this short quiz! If you score all of them it doesn’t mean you are an expert, but you could say you are getting there ;-)

     

    1-    Let’s talk about matching. Say we want to match precisely on full URLs (and only full URLs) during a conversation, which one of the following cases would be the way to go:

    a)    subpattern AFullUrl /^www`.`S+`.(com|fr|org|net)$/ {minlength=3, example=”www.microsoft.com”}

    b)    subpattern AFullUrl + www =Anything (com|fr|org|net) {canskip=”no”, minlength=3}

    c)    subpattern AFullUrl www.=Anything.(com|fr|org|net) {canskip=”no” minlength=3}

     

    2-    Nl-bricks? Only one of the following affirmations is true:

    a)  “NL- bricks” along with “regular subpatterns” are processed during the “patternization” process, but “NL-bricks” have precedence over “regular subpatterns”.

    b)  “NL- bricks” are the minimum expression of a meaning unit, and are “patternized” into “regular subpatterns” at compile time.

    c)  “NL-bricks” are “skippable”, as opposed to “regular subpatterns” that need to have the property {canskip=”yes”}

     

    3-    Defining macros in Buddyscript, only one of the following statements is true:

    a)    If the code contains any line breaks, it should be placed immediately below the first line of the statement. If you want your macro to contain an empty line, add a line containing only the character “\”. The first empty line defines the end of the macro.

    b)    If you want your macro to contain an empty line, add a line containing the character “\” at the end of the line before. I will add an empty line right between this and the following line. The first empty line defines the end of the macro.

    c)    Macro definitions can’t contain line breaks. The first empty line defines the end of the macro.

     

    4-    Only one of the following statements is true:

    a)    The “always match as” statement allows you to associate a variable name with a particular subpattern throughout a group of patterns only.

    b)    The “always match asstatement allows you to associate a variable name with a particular subpattern throughout a group of patterns, a package, or an entire domain.

    c)    The “always match as” statement allows you to associate a variable name with a particular subpattern in a routine, or in a multi-line of code.

     

    5-    What do you know about composite subpatterns? Only one of the following is true!

    a)    Composite sub-patterns let you assemble multiple static subpatterns together, so that a candidate string is filtered through the first subpattern, then the result of this filtering is passed to the second one, etc.

    b)    The minimum number of compositions is one, but there’s not a limit on the maximum number of compositions you can add.

    c)    The default values for the properties of a composite subpattern are those of the outer-most subpattern, since it's going to be evaluated last.

     

    6-    Hum, have you ever tried to create datasources? Let’s see if you know which one of the following is true:

    a)    The “postprocess-block“ of a datasource is a BuddyScript routine used to set the offset and the total count of rows when the “preprocess-block” didn't return any of those values, or if the value returned is not accurate.

    b)    “Expire” is a common property to the different datasource kinds. This information allows the platform to remember (cache) the result for a certain time. By default, expire is set to “now” (information is not stored). If you want the information to be cached, set it to “never”, or when the information won't be valid anymore (in 5 minutes, in 2 days, etc.)

    c)    Datasources are BuddyScript tools to access external data. The external data retrieved can be accessed through many methods, including gateway calls, SQL queries, SOAP methods, datatables, etc.

     

    7-    How about rephrasing rules? We want to use the property “MACRO_BEST_ONLY” when:

    a)    We need to make sure the rule it’s only applied when it matches best from all of the other rephrasing rules.

    b)    We want the rule to be applied only when it matches best, and no other rule can be applied after.

    c)    We want to avoid matching on rules scoring lower than 80.

     

    Solutions: 1-a, 2-b, 3-a, 4-b, 5-b, 6-c, 7-a

     If you failed the quiz, and you want to know more about Buddyscript, besides this blog you can browse the Windows Live Agents forum in MSDN, and of course read the documentation shipped with the platform.

    April 07

    Quality Analysis Audits

    Audits are an important piece to the ongoing maintenance of your agent. The purpose of auditing is to take a look at how users are interacting with the agent, and identify ways to improve the experience. Any issues that you uncover in audits should be addressed in a regularly scheduled agent update window.

    There are two types of audits—Query and Session.

    Session audits answer the question “Was this session satisfactory to the user?“ and are best for evaluating the overall user experience. Did the user find the information he or she was looking for? Was the answer complete? Did the user express any frustration that was not appropriately addressed? As a client, do you feel that the interaction served your purpose in deploying the agent?

    Query audits answer the question “Was the query answered by the correct topic?” and are ideal for fine-tuning the Natural Language comprehension and finding commonly unanswered queries.

    Once your agent launches, it’s preferred that you audit every 2-3 days for the first two weeks and then once every 2 weeks for the remainder of the agent’s life. Of course, it is up to you to determine how often you audit your agent. If your agent has a ton of traffic, you might want to audit more often.

    If you choose not to audit your agent, the agent will never improve and you will supply users with a poor experience. Auditing your agent will not only improve it, but improve your development style and guidelines for future agents.

    Auditing Guidelines

    Session Audit

    Suggestions for how to score sessions in a session audit:

    • Misunderstood: If the query is not recognized correctly and the user...
      • eventually gets the correct answer – Yes
      • would have gotten the right answer if he had continued another step (selected menu topic, typed more, etc) – Yes
      • doesn’t get the right answer  No
    • Content: If the query seemed to be understood reasonably well but the response...
      • clearly does not answer the question – Yes, but consider improving content
      • does not answer the question but includes a URL to a page that should logically answer the question – Yes, test the link, consider improving content if it does not
    • Menu: If a query is answered with an appropriate menu...
      • but should have been answered directly to a topic on the menu – Yes, notify WLA Team of misunderstood query
      • but is beyond the scope of the agent (too specific, technical)  Yes
      • but the user does not select any of the options – Yes
      • but the user does not select the correct option – Yes, consider  improving the menu if possible
    • Long/multiple: If the query is long or contains multiple questions and...
      • hits “catch” or gives the correct answer  Yes, bravo
      • user eventually gets the correct answer – Yes
      • user doesn’t eventually get the right answer – No
      • user gets the wrong answer and gives up – N/A
    • Dialogs: If a user engages agent in a dialog it cannot handle (responds to an answer with “I already tried that” or similar – Ignore that query
    • Ambiguity: If the user hit an ambiguity and...
      • the correct topic is listed, but the query was specific enough that it should have been answered directly – Yes, notify WLA Team
      • neither option is correct – No
      • at least one of the options is incorrect/irrelevant –  Yes, notify WLA Team
    • Entry message/empty session: If session only contains entry messages – N/A
    • Looping: If topics are looping – No
    • Nonsensical: When the user types nonsense, mark the query or session as N/A.             

    Query Audit

    * All internal query audits should use the option to "Only include queries relevant to natural language."

    Suggestions for how to score queries in a query audit:

    • Non-NL Queries: Despite selecting the option* above, the auditor may still need to manually discard non-natural-language queries
      • Menu selections: a # (followed by line of NL-comprehension code in parentheses) – N/A
      • Any dialogue queries such as: more, yes, no, show related topics – N/A
      • “?” – N/A
    • Nonsensical: When the user types nonsense or question unrelated to products covered – N/A
    • Beyond the scope of the agent: When the user asks a very technical or specific question and
      • gets a menu or somewhat related answer – Yes
      • doesn’t get an approximate answer – N/A
    • Mismatched: Any reasonable query that
      • does not match an appropriate response in the agent – No
      • is known not to have an appropriate response in the agent and
        • doesn’t match anything at all relevant – No
        • matches something somewhat relevant – still Yes, but consider improving content
      • mismatched because the user tried to engage in a dialogue such as “I already tried that” – N/A
    • Menu: If a query matches to an appropriate menu but was specific enough to have matched directly to a topic– No
    • Long/multiple: If the query is long or contains multiple questions and...
      • gives the correct answer  Yes, bravo
      • matches reasonably well – Yes
      • hits “catch” – Yes
      • mismatches – N/A
    • Unanswered/Catch section: If the user makes a valid query (not nonsense) that will probably be asked again at some point – No
    • Ambiguity: test each query in this section
      • the correct topic is listed, but the query was specific enough that it should have matched directly – No
      • neither option was correct – No
      • at least one of the options is incorrect/irrelevant No

     

     

    April 02

    Customizing WLATemplate

     

    Ideally you start creating a new Agent using the WLATemplate! Detailed information on how to instantiate a new project [currently available languages are: English, Chinese (simplified), French, German, Italian, Japanese, and Spanish] from the WLATemplate: http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!446.entry

     

    As you can imagine, this is only the beginning... In order to make your Agent a success and stand out in the crowd, you have to customize it to whatever you want your Agent to be, simple things like giving your Agent a personality and making "him" or "her" more natural. Here are a bunch of former blog posts that talk about exactly that:

     

    Creating a Personality Spec - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!317.entry

     

    Customizing Chat - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!230.entry

     

    Welcome Messages and ABGreetingProc - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!408.entry

     

    Changing PSM, Friendly Name, and Icon - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!315.entry

     

     

    Best Practices for Developing a Windows Live Agent:

     

    Part 1 [Use the WLATemplate Project as a Starting Point] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!149.entry

     

    Part 2 [Create Good Subpatterns] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!150.entry

     

    Part 3 [Use Dialogs] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!151.entry

     

    Part 4 [Use Canonical Questions] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!152.entry

     

    Part 5 [Remember User Information] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!156.entry

     

    Part 6 [Create a Clear and Concise Welcome Message] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!160.entry

     

    Part 7 [Be Careful When Using Public Variables] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!163.entry

     

    Part 8 [Make Your Project Modular] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!164.entry

     

    Part 9 [Make Your Agent Chatty] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!166.entry

     

    Part 10 [Conclusion of Best Practices Series] - http://windowsliveagents.spaces.live.com/blog/cns!5BCD45E519E07634!176.entry

     

     

    -----------------------------------------

    Blog post contributor(s): Mirco

     

    March 27

    Logging Topics and Categories

    One of the most powerful parts of the BuddyScript platform is its extensible reporting suite.  This article will describe how to log data relating to what your agent is saying, in a form that can be displayed in the Usage Reporting tab of the web console.

     

    Category and topic logging is something that has been used for agent projects developed "internally," but the code to do this was never exposed to partners in the libraries that ship with the platform.  This is likely to change in future releases, but in the meantime feel free to implement the code here.

     

    A topicis a routine with user input, agent output.  For example, we may have a routine that associates matching for "how are you" to various outputs saying "I'm doing fine." This is a topic.

     

    A category is a grouping of topics, often associated with a single DDL file. For example, we might have a category for all the topics that handle queries relating to the agent as a person, like "how are you", "where did you come from", etc.

     

    When we associate a title to a topic, we can log that title and keep a tally of how many users hit that topic. When we associate a topic to a category, we can also tally how many times users hit that category. Thus, we will have data relating to where user queries are matching the most, at two levels of granularity.

     

    To get this working, we need to:

     

    1.    Define categories

    2.    Assign topics to categories

    3.    Log the topics and categories

     

    Here is an example of how to do this:

     

    Say we have a domain for English conversational routines, Topics/EN-US/Conversation.ddl.

     

    At the top of the Topics/EN-US/Conversation.ddl, we add this:

     

    {category="Conversation (English)" label="CategoryDefinition"}

    + _ignore_me

      do nothing

     

    This defines a new category called “Conversation (English)”.  Next, we need to associate routines to this category.  Here is an existing routine:

     

    {label="? Hi" uid="MyAgent-1195066867"}

    ? Hi

      - Hi! 

      - Hello there! 

      - Hi there.

      - Greetings!

      nop

      - How are you?

      - What's new with you?

      - How are things?

      insist

        - Good to know.<br/>

        - Interesting.<br/>

        - Glad I asked.<br/>

        - Really? <br/>

     

     

    The key-value pairs between brackets ("label" and "uid") were automatically generated by the KMS as part of its tracking mechanism.  If they are not present, don't worry about it.

     

    Add this (the order of key-value pairs doesn’t matter):

     

    {category="Conversation (English)" title="Hi" label="? Hi" uid="MyAgent-1195066867"}

     

    Still, we are just labeling the routine, and nothing will happen unless we log it.  The platform will automatically do some simple logging, but we should override that to use categories.  Look at this part of the libaries, in Required/ABProcs:

     

    ########################################

    ##

    ## ABPostQueryLog

    ##

    ## This procedure is called at the end of a user query

    ## and can be overriden to log specific information.

    ##

    ########################################

     

    procedure ABPostQueryLog()

      SYS.Log.ScriptFile[SYS.History[0].Match.ScriptFile]++

     

    processing ABPostQueryLog

       post-process

         call ABPostQueryLog()

     

    So, if we just want to know what file is being hit, right now that is being logged as “ScriptFile”, which we could add to usage_config.xml and show in Usage Reporting in the console.  However, a better approach is to define categories for every ddl, and assign titles and categories for each topic in the ddl.  Then we can override the logging behavior like so:

     

    ### Logging

     

    variable G_LAST_LOGGED_QR_RECEPTION_TIME=0

     

    procedure LogCategory(CATEGORY)

      SYS.Log.Categories[CATEGORY]++

     

    procedure LogTopic(TOPIC_TITLE)

      SYS.Log.Topics[TOPIC_TITLE]++

     

    procedure PerformTopicLogging()

      if G_LAST_LOGGED_QR_RECEPTION_TIME == SYS.History[0].Execution.QRReceptionTime

        exit

      G_LAST_LOGGED_QR_RECEPTION_TIME = SYS.History[0].Execution.QRReceptionTime

      if SYS.History[0].Match.RoutineId == ""

        exit

      TOPIC_TO_LOG = ""

      CATEGORY_TO_LOG = ""

      ROUTINE_ID = SYS.History[0].Match.RoutineId 

      if !ROUTINE_ID

        exit

      TOPIC_KEYS = GetRoutineKeys(ROUTINE_ID)

      if Exist(TOPIC_KEYS["menu"])

        exit

      if !Exist(TOPIC_KEYS["title"])

        exit

      TOPIC_TO_LOG = TOPIC_KEYS["title"]

      if !Exist(TOPIC_KEYS["category"])

        CATEGORY_TO_LOG = "undefined"

      else

        CATEGORY_TO_LOG = TOPIC_KEYS["category"]

      call LogTopic(TOPIC_TO_LOG)

      call LogCategory(CATEGORY_TO_LOG)

     

    procedure overrides ABPostQueryLog()

      call PerformTopicLogging()

     

    Note that all we are really doing is piggy-backing on the existing logging mechanism, using it to write the values associated with the “title” and “category” keys for each routine the agent matches to.  These values are written to the SYS.Log variable, a special object used for logging.  Anything written to SYS.Log is fair game to show in the reporting console, but to do so, we also need to make alterations to the usage_config.xml file, to accommodate SYS.Log.Categories and SYS.Log.Topics and specify how the data should appear.  Editing the usage_config.xml file is beyond the scope of the article and will be covered later, so let the WLA team know if you need help setting it up.