| Windows Live Ag... 的个人资料Windows Live Agents照片日志列表 | 帮助 |
|
10月24日 Customizing ChatThe Language Libraries make developing an agent easy—just add a screen name and you’re done! Not quite. To ensure all of our agents don’t sound exactly the same, we must customize the chat. Chat routines are called in the chat .ddls, included in the Chat Directory of each project you instantiate using the WLATemplate. The chat routine procedures are defined in the BuddyScriptLib. You can find them here: lib:/LANGUAGE/Extensions/Chat (Replace “LANGUAGE” with the appropriate language of your agent.) When you customize chat, keep your agent’s purpose in mind. If the agent’s purpose is to market teen deodorant, your agent should sound like a teenage girl. If the agent provides game scores and statistics for the NFL, the agent should make NFL references. For Example: Britt says (5:34 PM): you are funny Gridiron Guru says (5:34 PM): Thanks. Have you read the book The Old Man and the Sea? It's about Brett Favre.
Speaking of which, is Vinnie Testaverde playing this year? When you are customizing an answer for a chat routine, you should try to come up with as many alternate responses as possible. You should aim for 3-4, but the more the better! For Example: This example comes from an agent we developed for the NFL, so the answers are funny and centered around football. procedure overrides YouSmellLikeAnswer(WHAT_YOU_SMELL_LIKE, CATCH) - Uh, whatever. I can't help it I sweat when I play football. - You'd smell too if you practiced your football moves all day. - No one works this hard on the field without sweating (and smelling). - That's pure pigskin you smell. Create a Chat Customization Schedule Customizing chat is very similar to creative writing—it takes creativity and flair and cannot always be completed in one sitting. You can make customizing chat easier and less of a burden by creating a schedule for yourself. It could look something like this:
Week 1 Customize the following: § “What can you do?” answer § “What is your goal?” answer § “What questions can I ask you?” § Vulgarity.ddl answers are customized
Week 2 Customize the following:
Organize Your Chat Overrides To keep all of your chat overrides organized and in one place, you should create a ChatOverrides.ddl in your project. When you override a chat output procedure, you will need to include the appropriate package in your ChatOverrides.ddl. For Example: package lib:/English/Extensions/Chat/Vulgarity.pkg procedure overrides ProfanityNoInsultSevereAnswer() - Wow. That is HARSH. - Wow. That's something you might hear on the field, but not in polite company. - You swear like an offensive lineman. - Who are you, Bill Parcells? Audit Your Agent The best way to find out which chat routines you should override and customize is to create and complete audits. Auditing your agent gives you the best insight into what users are really talking to your agent about. You may have customized chat routines you assumed would be popular, only to find that users are chatting with your agent about other topics. 10月15日 Writing Tips With DisplayReminderToGetOutOfChat()While we want users to chat with our agents about everything and anything, we want to make them aware of the agent’s main purpose. Adding tips to the procedure DisplayReminderToGetOutOfChat(), which is defined in ChatParams.pkg, is a good way to bring users back to the agent’s purpose.
The procedure is called through the post-process ReminderToGetOutOfChat. The post-process is BuddyScript that is called after the end of each routine that is within the scope of the processing zone. Any .ddl that includes ChatParams.pkg will have the processing zone “started” thanks to that last line “start processing ReminderToGetOutOfChat”. The processing is active for every routine in the .ddl until you “end” it using the line “end processing ReminderToGetOutOfChat”.
You should write 2-3 tips for each piece of the agent’s functionality. You will not override the procedure DisplayReminderToGetOutOfChat(), but simply add to it.
Example: When a user is chatting too much, the agent will pull him/her back into the conversation. For example:
College Football Guru says: Yeah.
Wanna see where your favorite college football team stands? Type "Boston College standings."
Writing Dialog Tips The best use of the procedure DisplayReminderToGetOutOfChat() is to create tips with dialogs. Dialogs keep users engaged and users love to be asked questions.
Example: procedure DisplayReminderToGetOutOfChat() RAND=Rand(14) if RAND=0 - Why don't you ask me something? - Why don't you tell me something new? - Don't you want to ask me something? else if RAND=1 && !SAW_TAROT SAW_TAROT = 1 - Do you want to see what your Tarot Cards say today? ? No - If you ever change your mind, just type StyleTypeThis("tarot card reading.") ? Yes rematch tarot card reading ? Already did. ? I saw it already. ? Did it. ? Did that. - Cool! I hope it was helpful!
Example in the Agent: Astrology Bot says (4:16 PM): Yes.
So anyway... Do you want to see what your Tarot Cards say today? Britt says (4:16 PM): no Astrology Bot says (4:16 PM): If you ever change your mind, just type "tarot card reading."
Creating Tip Variables You should use variables so that users do not see the same tip in a session. The user may see the tip in his/her next session with the agent. Define your variables in ChatParams.pkg:
variable SAW_ROSTER = 0
When you define the variable, you will set it 0. When the user sees the tip, you will set the variable to 1 within the procedure.
else if RAND==3 && !SAW_ROSTER SAW_ROSTER = 1 - I have every college football team roster. Wanna know whose playing on your favorite team? Simply type StyleTypeThisPeriod("UAlbany team roster")
End the Procedure with General Tips If a user has seen every tip in DisplayReminderToGetOutOfChat(), you will want to have your ending “else” include general tips that users can see multiple times in the same session. You should direct the user to type “home” or “?” in order to see all of the agent’s functionalities.
Example: else - Can we talk football now? Type StyleTypeThis("?") and I'll give you examples of what types of questions you can ask. - OK, OK, enough of that. Let's talk football! Ask me how your favorite football team is doing this season. - Yada yada... Can we talk about football?! Type StyleTypeThis("?") and I'll give you examples of what types of questions you can ask. 10月11日 Matching on Datasources and DatatablesMany of the most compelling agents use datasources and datatables to extend their functionality beyond static, written output. For example, if a user asks about the weather, the agent's response can be drawn from a datasource that passes parameters to a web service and retrieves the current weather conditions. Another use of datasources and datatables lies on the input side -- we want to recognize that the user is asking about something contained in this dynamic or preloaded pool of data the agent uses for output. There are a few ways of doing this. Datatables
A simple datatable is just that -- a table with rows and columns, sometimes in a tab-delimited text file, preloaded into memory when the agent compiles. We can use datatables to map one value to others, or simply as a list, and then manipulate these values to display a dynamic output. We can also use the table as the contents of a subpattern that users match on, using a datatable subpattern.
Say we have a list of actors in a text file, with their names and IDs. The ID we use elsewhere, but we can use the names to see if the user is asking about an actor, and do something special if they are. The list looks like this:
485957 Jim Calabrese 485958 Susan Leber 485959 Michael Yanko
Next we need to define the datatable:
datatable ActorIDTable load Id {index=exact}, Name {index=thawed} from file ActorID.txt
Now we can create a subpattern to match on a column in the datatable.
subpattern AllActors get Name, Name in ActorIDTable {maxlength=3}
"get Name, Name in ActorIDTable" means that the subpattern will look at the ActorIDTable datatable and match on the values in the Name column (which are thawed), and return the values in the Name column. It's the equivalent of this:
subpattern AllActors {spellcorrect=”no”} + jim calabrese return "Jim Calabrese" + susan leber return "Susan Leber" + michael yanko return "Michael Yanko"
We can make the agent's actor-recognition more robust by matching on words that look like names, and rolling it all into an actor subpattern.
subpattern AnActor + VAR=AllActors + VAR=APossibleName {score=40} return VAR
The last step is to create topics:
? I'm a fan of ACTOR=AnActor. - Yes, ACTOR deserves an Oscar.
? I'm a fan of VAR=Anything. - You like VAR? I'll keep that in mind.
User: I am a big fan of michael yanko. Agent: Yes, Michael Yanko deserves an Oscar. User: I'm also a fan of ice cream. Agent: You like ice cream? I'll keep that in mind.
Datasources
A datasource is a tool to access external data, like a web service or database. While there is not a "datasource subpattern" in the BuddyScript platform, it is still possible to match on the dynamic data accessed by a datasource, using a variable subpattern.
A variable subpattern matches on the keys of an object, so if we use our datasource to fill an object, we can use that object’s keys in our matching. The return value of the subpattern is the matched key.
For example, say we have a function that calls a datasource that returns a list of TV channels a user has in her area, called ChannelLineup(). It returns an list of objects, each with the channel number and call letters for one channel. We can manipulate that list to populate an object variable, G_USER_CHANNELS, at some point in the user session before the variable subpattern is to be used.
stored variable G_USER_CHANNELS
procedure PopulateUserChannels() CHANNELS = () CHANNELS = ChannelLineup() G_USER_CHANNELS = () for IDX in CHANNELS NUMBER = CHANNELS[IDX].ChannelNumber LETTERS = StringClean(CHANNELS[IDX].CallLetters) G_USER_CHANNELS[NUMBER] = LETTERS G_USER_CHANNELS[LETTERS] = NUMBER
Note that we must use StringClean to make the call letters lowercase -- otherwise the subpattern will contain illegal characters.
Next we create the subpattern:
subpattern AUserTVChannel variable G_USER_CHANNELS {style=raw score=MACRO_STRONG_SCORE}
And topics.
? Do I have CHANNEL=AUserTVChannel? - You do have CHANNEL.
? Do I have VAR=Anything? - I'm not sure if you have VAR.
User: Do I have FOX? Agent: You do have fox. User: Do I have a car? Agent: I'm not sure if you have a car.
However, using a variable subpattern in this case is actually unnecessary. A better solution is to create a datatable subpattern with all possible call letters and channel numbers, and then procedurally figure out whether the user has the channel they’ve matched on.
Consider another example. We have a function calling a datasource that returns a list of products currently available, along with price and description. Every week new products are added and old ones removed, so we never have quite the same list, or know what new products may be added.
The agent must recognize when users are asking how much a product is, even if it is out of stock. What we can do is maintain a list of all products ever sold, and add to the list when we find a new product.
subpattern APossibleProduct variable G_ALL_PRODUCTS {style=raw score=MACRO_STRONG_SCORE}
procedure DisplayProductDetails(NAME) PRODUCTS |= () PRODUCTS = GetProductDetails(NAME) if !PRODUCTS || PRODUCTS == () - Sorry, NAME is no longer available. else for value PRODUCT in PRODUCTS * Here are the products available: - PRODUCT.name, PRODUCT.price PRODUCT.detail
procedure AddProductsToPossibleProducts(NAME) PRODUCTS <= GetProductDetails(NAME) for value PRODUCT in PRODUCTS G_ALL_PRODUCTS[PRODUCT.name] = PRODUCT.price
? Do you have any PRODUCT=APossibleProduct? ? Do you have any VAR=Anything? if PRODUCT call DisplayProductDetails(PRODUCT) else if IsAStockedProduct(VAR) call AddProductsToPossibleProducts(VAR) call DisplayProductDetails(VAR) else - Sorry, I don't recognize VAR as a valid product.
One warning: depending on the size of the object variable, variable subpatterns are potentially big resource hogs, and can harm your agent's response time. So use with care. 10月3日 Fuzzing in BuddyScript: How to use brute force to test your Agent.In his last post, Mirco explained how to add unit tests to your Agent: it’s an absolutely critical investment to add unit tests covering as much of the functionality of your Agent as possible, to make sure it performs the way you want throughout development and beyond. In this post I’ll describe a complementary testing approach (you’ll still need to write unit tests!), known as fuzzing.
Fuzzing??? Fuzzing an Agent works like this: for each handle in your project the platform builds a random question that should match on it. It then asks all these questions to your Agent as a user would. Finally, it reports what questions caused execution errors, and gives you a sorted list of the slowest ones.
For instance, assuming you have the following routine in your code (not that you’d write something like that, of course):
? I was born in YEAR=Anything. AGE = GetCurrentYear() - YEAR - OK, so that makes you about AGE years old, right?
The tool would take your handle “? I was born in YEAR=Anything.”, try to randomly generate a question that would match it, and ask that question. In this case, the tool might come up with something random like “i am born in Blah blah blah” (through stemming, synonyms, etc). Of course trying to execute that question generates an error, which the tool would detect.
Step-by-step That’s for the theory. Concretely to take advantage of this test, you simply need to launch your project in the IDE (via the query window) and type the following as your questions: - !d 0 => this turns off debug info display which speeds up the test substantially - !testmode => enters the Test mode, which offers a variety of options to tweak how the test is run (leaving the defaults is ok) - go => launches the test Depending on the size of your project, the test might take a long time to finish, since it will ask at least one question for each handle of your project.
Important: if you have debug code somewhere in your code, it will be executed unless you “protect” it!
// MACRO_PROTECTED_MATCHING prevents this handle from being “fuzzed” during the test // You wouldn’t want a random amount transferred to your test account… + _for_debug_transfer NUMBER=ANumber dollars to test account {MACRO_PROTECTED_MATCHING} call BankingDataSourceTransfer(NUMBER, G_MY_ACCOUNT, G_TEST_ACCOUNT)
Sample output Below is a sample output of this coverage test, edited for brevity:
joeuser: !d 0 null joeuser: !testmode Automated Agent: ** Entering Test Mode ** ****************************
Type: "help" or just "h" to see a list of available commands.
testmode: go At this point you have to wait for the test to complete, which takes between a few seconds and a few hours depending on your agent and on your machine. Automated Agent: ======================================= Error Report: ************************************************************* That’s a test executed for our sample routine described above Problem encountered testing pattern 325: I was born in YEAR=Anything. (in ? I was born in YEAR=Anything.) Sequence: i am born in Blah blah blah
Problem: Exception: Exception while processing "i am born in Blah blah blah": Error executing script associated to query "i am born in Blah blah blah" Error executing script labeled "? I was born in YEAR=Anything." at line 106 in file "domains:/TestProject/English/UserInfo/UserName.ddl": Error executing command "AGE = GetCurrentYear() - YEAR" in "domains:/TestProject/English/UserInfo/UserName.ddl", line "107" Can't convert expression "Blah blah blah" to a float
======================================= 30 slowest queries: This section tells you which questions were the slowest ones to answer. If you access slow web services that you then cache in memory you might want to run the test twice to get more accurate results. Many factors may slow down a query execution rate. To “debug” slow queries, you need to check how it was analyzed by the NL engine (did it trigger “runaway rephrase rules” or involve a subpattern that happens to be slow?), if the associated script can execute quickly, etc. 588ms: Query(ies) asked: "which gr8 dose common factor of 3 & 3" Pattern represented="What is the Greatest Common Factor of X=AnInteger and Y=AnInteger? (in answers WhatIsTheGreatestCommonFactorOf<X, Y>)". 320ms: Query(ies) asked: "divide the sinus of ( 12.34 ) percent with sq rt ( a million a hundred ) cubed is what in its simplest form" Pattern represented="EXPRESSION=AComplexExpressionNoPlug =EqualsWhat [FORMAT=AResultFormatType] (in +- Calculate EXPRESSION.description)". 271ms: Query(ies) asked: "which being the plural of white dwarf" Pattern represented="What is the plural of NOUN=ANoun? (in answers WhatIsThePluralOfNoun<NOUN>)". 254ms: Query(ies) asked: "we would prefer those you people call us which" Pattern represented="I would prefer that you call me that. (in answers CallMeThat)". 238ms: Query(ies) asked: "sum body calls me which" Pattern represented="People call me that. (in answers CallMeThat)". … edited for brevity… 107ms: Query(ies) asked: "which percents of 3 is pi" Pattern represented="(what|which) =nsPercent of WHOLE=AnAtomicExpression is PART=AnAtomicExpression (in answers WhatPercentOf<WHOLE>Is<PART>)". 107ms: Query(ies) asked: "be u top of the line at math" Pattern represented="Are you good at math? (in answers CanYouHelpMeWithMath)". 105ms: Query(ies) asked: "my names is it" => "call us mrs godowns" => "pass" => "aye" => "nah thank yo u sorry" => "if u insist" => "that s wrong" => "alright" => "nada" => "thats wrong" => "we might prefer which you calls us sir plasmapheresis" Pattern represented="my =nName is it (in answers CallMeThat)". ======================================= Total of 2115 questions asked in 24050 milliseconds. Breakdown of execution time: Rephrasing: 8863 milliseconds 36.852390852391% of processing time Matching: 6329 milliseconds 26.316008316008% of processing time Preprocessing: 1 milliseconds 0.0041580041580042% of processing time ScriptExecution: 6154 milliseconds 25.588357588358% of processing time Postprocessing: 2248 milliseconds 9.3471933471933% of processing time Presentation layer: 100 milliseconds 0.41580041580042% of processing time Rest: 355 milliseconds 1.4760914760915% of processing time and 2114 answers received (99.95%) Average speed for queries answered: 11.381921438713 milliseconds (that's 87.858627858628 QPS) 1 error(s) during the test. Complete test took 13885 milliseconds to process. =======================================
Conclusion Having a thorough test strategy is critical to the quality of your Agent. You want to find bugs before your users do! Nothing replaces good unit tests (see Mirco’s post), but fuzzing is a good way to detect cases you didn’t think about, or to identify slow queries. It’s a good idea to run a fuzzing test on your Agent when you reach critical milestones (before you make a beta available, before you launch, before releasing a major update, etc).
|
|
|