We are developing a library bot using Microsoft bot framework.
We have
Intent : BookSearch
Entiry : BookName, BookAuthor
When I search "I need a java book", it understands that 'java' is an entity, and provides the java book with other details.
My question is, Once I received the java book details, I want to issue the book, so again, when we ask "Issue me book Java", here it conflicts and will show java book details again, like if its going in loop.
Please help us in this scenario, how can we determine different context in our question?
Code :
[LuisIntent("BookSearch")]
public async Task BookSearch(IDialogContext context, LuisResult result)
{
if (result.Entities.Any())
{
DBConnect dbConnect = new DBConnect();
string combindedString = string.Empty;
string mysqlQuery = string.Empty;
foreach (var item in result.Entities)
{
switch (item.Type.ToString())
{
case "BookAuthor":
break;
case "BookName":
break;
case "BookIssue":
break;
default:
break;
}
}
}
else
{
await context.PostAsync("Which book are you searching.");
context.Wait(MessageReceived);
}
}
Here BookName and BookIssue are the entities,
So when we ask "I need java book" => It should go into the BookName entity to provide book details.
When we ask "Issue me java book" => It should go into the BookIssue entity to process issuing formality.
Thanks in advance
BookIssue should be its own intent & method. In your code you appear to be treating BookIssue as an entity.
Do what you did for BookSearch but with the utterances and logic you want to have in your BookIssue method.
Right now you're "stuck" in your BookSearch intent because you have not defined another one and LUIS is matching those utterances to search.
By the way, for this application you might want to consider using FormFlow.
Related
In the screenshot below, I have got an utterance conflict, which is obvious because I am using similar patterns of samples in both the utterances.
My question is, the skill I am developing requires similar kind of patterns in multiple utterances and I cannot force users to say something like “Yes I want to continue”, or “I want to store…”, something like this.
In such a scenario what is the best practice to avoid utterance conflicts and that too having the multiple similar patterns?
I can use a single utterance and based on what a user says, I can decide what to do.
Here is an example of what I have in my mind:
User says something against {note}
In the skill I check this:
if(this$inputs.note.value === "no") {
// auto route to stop intent
} else if(this$inputs.note.value === "yes") {
// stays inside the same intent
} else {
// does the database stuff and saves the value.
// then asks the user whether he wants to continue
}
The above loop continues until the user says “no”.
But is this the right way to do it? If not, what is the best practice?
Please suggest.
The issue is really that for those two intents you have slots with no context around them. I'm also assuming you're using these slots as catch-all slots meaning you want to capture everything the person says.
From experience: this is very difficult/annoying to implement and will not result in a good user experience.
For the HaveMoreNotesIntent what you want to do is have a separate YesIntent and NoIntent and then route the user to the correct function/intent based on the intent history (aka context). You'll have to just enable this in your config file.
YesIntent() {
console.log(this.$user.$context.prev[0].request.intent);
// Check if last intent was either of the following
if (
['TutorialState.TutorialStartIntent', 'TutorialLearnIntent'].includes(
this.$user.$context.prev[0].request.intent
)
) {
return this.toStateIntent('TutorialState', 'TutorialTrainIntent');
} else {
return this.toStateIntent('TutorialState', 'TutorialLearnIntent');
}
}
OR if you are inside a state you can have yes and no intents inside that state that will only work in that state.
ISPBuyState: {
async _buySpecificPack() {
console.log('_buySpecificPack');
this.$speech.addText(
'Right now I have a "sports expansion pack". Would you like to hear more about it?'
);
return this.ask(this.$speech);
},
async YesIntent() {
console.log('ISPBuyState.YesIntent');
this.$session.$data.productReferenceName = 'sports';
return this.toStatelessIntent('buy_intent');
},
async NoIntent() {
console.log('ISPBuyState.NoIntent');
return this.toStatelessIntent('LAUNCH');
},
async CancelIntent() {
console.log('ISPBuyState.CancelIntent()');
return this.toStatelessIntent('LAUNCH');
}
}
I hope this helps!
if (!meeting.location) {
builder.Prompts.text(session, 'where is the location');
} else {
next();
}
This is a part of my node.js code where my bot tries to identify the location in the first message , if he doesn't find it he takes as location any value the user gives, might be as random as "83748yhgsdh".
My question is how can i allow my system to check the user input at each step and only take reasonable ones.
You can probably manually call the LUIS recognizer
e.g:
var recognizer = new builder.LuisRecognizer(model);
recognizer.recognize(
"hello",
model,
function (err, intents, entities) {
console.log(intents);
}
)
How can a search context for website like stack overflow be modelled following Domain driven design?
Let's say in my domain, I have three type of entities as Questions, Answers, Tags of Questions.
I have to model a search context in a way that it accepts a search string and return matching questions, answers and tags.
I want to understand that, In search context, is search going to be just a ddd-service which will perform search or can there be some entities, aggregates etc.
It can be assumed that matching algorithm is simple sql like query.
You need to implement the search in a Query Service.
CQRS fits really good with DDD because when you want to update the model you use commands, such as in your case:
AnswerToQuestionCommand, PostNewQuestionCommand, etc.
These commands are sent to your application service, which updates the entity, which in turn send a DomainEvent, which is intercepted in the hexagonal architecture and updates the search index. You can do it in the hexagonal architecture: by calling a service dedicated to maintain the index in the same transaction. You consider an entity persisted if the index has also been updated for instance. I would go this way.
Here is a sample with Spring #TransactionalEventListener
#Component
public class ProjectRenamedListener {
#Value("${spring.jpa.properties.hibernate.search.default.indexBase:target}")
private String indexBase;
private final Logger logger = LoggerFactory.getLogger(ProjectRenamedListener.class);
#TransactionalEventListener
public void projectRenamed(final ProjectRenamed event) {
try (final StandardAnalyzer analyzer = new StandardAnalyzer();
final Directory directory = NIOFSDirectory.open(Paths.get(indexBase, ProjectData.class.getName()));
final IndexWriter writer = new IndexWriter(directory, new IndexWriterConfig(analyzer))){
final Document document = new Document();
document.add(new StringField("project_id", event.projectId().identity(), Field.Store.YES));
document.add(new StringField("tenant_id", event.tenant().identity(), Field.Store.YES));
document.add(new TextField("name", event.name(), Field.Store.YES));
writer.updateDocument(new Term("project_id", event.projectId().identity()), document);
} catch (IOException e) {
logger.warn("Unable to update index for project name.", e.getMessage());
}
}
}
When you need to query your Questions, Answers, etc. you go through a query service. You ask the index, and load the entities that you want to return to your client.
The beauty of this is that it does not have to be the same object when you modify it and query it. It sounds a bit wierd when you start because of the code duplication but on the long run its clearly superior solution as you have no coupling between reading / query operation which occurs a lot and should be fast, and writing operation which should not occur a lot and does ont have to be specially super fast.
I would suggest the approach of Vaughn Vernon on query services (https://github.com/VaughnVernon/IDDD_Samples), I used lucene on my part for a query service, here is some code:
#PreAuthorize("hasAuthority('Administrator')")
public Page<ProjectData> searchProjectsData(
final String tenantId,
final String queryText,
final Pageable pageable) {
if (!StringUtils.isEmpty(tenantId)) {
return this.searchProjectsDataOfTenant(tenantId, queryText, pageable);
}
final StandardAnalyzer analyzer = new StandardAnalyzer();
final QueryBuilder builder = new QueryBuilder(analyzer);
final Query query = builder.createPhraseQuery("name", queryText);
try {
final IndexReader reader = DirectoryReader.openIfChanged(this.directoryReader);
final IndexSearcher searcher = new IndexSearcher(reader);
final TopDocs documents = searcher.search(query, pageable.getPageSize());
return this.fetchProjectsData(searcher, documents, pageable);
} catch (IOException e) {
throw new RuntimeException(String.format("Unable to search project for query: %s", queryText), e);
}
}
You probably don't need the whole array of DDD tactical patterns for a Search bounded context, no. Except if you're planning on storing search preferences or creating search templates, maybe - even then, it seems very CRUDish.
Designing how the search context indexes or accesses the data from other BC's can be a more complex matter, though.
Regarding the Microsoft Bot Framework, we all know the samples given by Microsoft. Those samples, however, normally have "one single purpose", that is, the Pizzabot is only for ordering Pizzas, and so on.
Thing is, I was hoping on creating a more complex Bot that actually answers a series of things. For this I am creating a "lobby" dialog where all the messages go, using this MessageController:
return await Conversation.SendAsync(message, () => new LobbyDialog());
On that "Lobby" dialog I have a series of LUIS intents for different things, and since it picks the Task based on the intent, it works nicely.
However, for more complex operations, I was hoping on using the FormFlow mechanism so I can have forms like in the PizzaBot sample. The problem is that all of the "form bots" that are sampled always use this message controller type:
return Chain.From(() => new PizzaOrderDialog(BuildForm)
And in the same MessagesController stablishes the builder flow, like this:
var builder = new FormBuilder<PizzaOrder>();
ActiveDelegate<PizzaOrder> isBYO = (pizza) => pizza.Kind == PizzaOptions.BYOPizza;
ActiveDelegate<PizzaOrder> isSignature = (pizza) => pizza.Kind == PizzaOptions.SignaturePizza;
ActiveDelegate<PizzaOrder> isGourmet = (pizza) => pizza.Kind == PizzaOptions.GourmetDelitePizza;
ActiveDelegate<PizzaOrder> isStuffed = (pizza) => pizza.Kind == PizzaOptions.StuffedPizza;
return builder
// .Field(nameof(PizzaOrder.Choice))
.Field(nameof(PizzaOrder.Size))
.Field(nameof(PizzaOrder.Kind))
.Field("BYO.Crust", isBYO)
.Field("BYO.Sauce", isBYO)
.Field("BYO.Toppings", isBYO)
.Field(nameof(PizzaOrder.GourmetDelite), isGourmet)
.Field(nameof(PizzaOrder.Signature), isSignature)
.Field(nameof(PizzaOrder.Stuffed), isStuffed)
.AddRemainingFields()
.Confirm("Would you like a {Size}, {BYO.Crust} crust, {BYO.Sauce}, {BYO.Toppings} pizza?", isBYO)
.Confirm("Would you like a {Size}, {&Signature} {Signature} pizza?", isSignature, dependencies: new string[] { "Size", "Kind", "Signature" })
.Confirm("Would you like a {Size}, {&GourmetDelite} {GourmetDelite} pizza?", isGourmet)
.Confirm("Would you like a {Size}, {&Stuffed} {Stuffed} pizza?", isStuffed)
.Build()
;
My big question here is, is it possible to start the conversation with the MessagesController that I used and then in the LobbyDialog, use an Intent that fires a Form and returns it? That is, start a flow from a dialog? Or is better to use DialogChains for that?
Because, from what I tried, it appears that I can ONLY do forms if they are called from teh MessagesController class with the methods I described, that is, how Microsoft sampled it in the Pizzabot.
I appreciate any help or input on the matter. Thanks for your time.
Sure you can! Instantiating a form from a dialog is a pretty common scenario. To accomplish that you can do the following inside the LUIS intent method:
var form = new FormDialog<YourFormModel>(
<ExistingModel>,
<TheMethodThatBuildTheForm>,
FormOptions.PromptInStart,
result.Entities);
context.Call(form, <ResumeAfterCallback>);
using the PizzaBot sample, it should looks like:
var form = new FormDialog<PizzaOrder>(
null,
BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call(form, <ResumeAfterCallback>);
In the ResumeAfterCallback you will usually get the result of the form, catch exceptions and perform a context.Wait so the dialog can keep receiving messages. Below a quick example:
private async Task ResumeAfterCallback(IDialogContext context,
IAwaitable<PizzaOrder> result)
{
try
{
var pizzaOrder = await result;
// do something with the pizzaOrder
context.Wait(this.MessageReceived);
}
catch (FormCanceledException<PizzaOrder> e)
{
string reply;
if (e.InnerException == null)
{
reply = "You have canceled the operation. What would you like to do next?";
}
else
{
reply = $"Oops! Something went wrong :(. Technical Details: {e.InnerException.Message}";
}
await context.PostAsync(reply);
context.Wait(this.MessageReceived);
}
}
How can I have 2 conversations going concurrently? I'm currently using TextBot and LuisDialog to build a bot. I start off by having a conversation with the user to obtain data. Then while doing some processing in a different method, I discover that I need additional information from the user. How can I create a new conversation with the user just to get that additional information? I have some code below that attempts to show what I want to do. Thanks for your suggestions.
File 1: foo.js
var dialog = new builder.LuisDialog(model);
var sonnyBot = new builder.TextBot();
sonnyBot.add('/', dialog);
dialog.on('intent_1', [
function(session, args, next) {
name = builder.Prompts.text(session,"What is your name?");
},
function(session, result) {
session.dialogData.name= results.response;
getFamilyTree(session.dialogData.name);
}
]);
File 2: getFamilyTree.js
function getFamilyTree(name) {
find family tree for name
if (need place of birth) {
begin new dialog
prompt user place of birth
get place of birth from user
end dialog
}
finish getting the family tree
}
i guess you could pass session object and then use that object to start a new dialog .
Edit 1
can't you use some thing like
session.beginDialog('/getFamilyTree',{name:result.response});
and then you can access name like
args.name
inside 'getFamilyTree' dialog
I posted the same question on GitHub and received the answer from Steven Ickman, who is involved in the development of the node.js SDK. The link to the answer is https://github.com/Microsoft/BotBuilder/issues/394#issuecomment-223127365