How to query into LUIS programmatically - c#

By default this is how can we send text to LUIS for processing and returns intents.
[Serializable]
public class LuisDialogController : LuisDialog<FAQConversation>
{
private readonly BuildFormDelegate<FAQConversation> _newConversation;
public LuisDialogController(BuildFormDelegate<FAQConversation> newConversation) : base(new LuisService(new LuisModelAttribute(
ConfigurationManager.AppSettings["LuisAppId"],
ConfigurationManager.AppSettings["LuisAPIKey"],
domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
{
this._newConversation = newConversation;
}
[LuisIntent("None")]
public async Task NoneIntent(IDialogContext context, LuisResult result)
{
await this.ShowLuisResult(context, result);
}
}
I am wondering how can I send text to LUIS programmatically.
//pseudocode
var foo = new Luis();
var luisIntent = foo.processLanguage("How are you?");
switch(luisIntent)
{
case LuisIntent.Inquiry:
{
//do something; break;
}
default:
{
//do something else; break;
}
}
I've been looking in this solution, however he did not answer by giving a regex.
Would the idea be possible?

In publish section of your LUIS model you have "Resources and Keys" subsection
Below "Endpoint" column you have url(s) that may be used to retrieve data from LUIS by http GET:
https://*.api.cognitive.microsoft.com/luis/v2.0/apps/
*?subscription-key=*&verbose=true&timezoneOffset=0&q=this%20is%20test%20sentence
It will provide you JSON result with structure similar to this:
{
"query": "this is test sentence",
"topScoringIntent": {
"intent": "None",
"score": 0.522913933
},
"intents": [
...
],
"entities": []
}
See more detail and sample C# code here.
Alternatively you may use:
using Microsoft.Bot.Builder.Luis;
...
var model = new LuisModel() {};
var luisService = new LuisService(model);
var result = await luisService.QueryAsync(textToAnalyze, CancellationToken.None);

Related

HTTP client method null exception

I have an API project and I need to develop a web project using the API I wrote some code but not able to find the exception and problem and not getting data from the link.
Here is my Service Code:
public async Task<IEnumerable<AgentReadDto>> GetAgent()
{
IEnumerable<AgentReadDto> agents = new List<AgentReadDto>();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://localhost:44331/api/");
var response = client.GetAsync("Agent/GetAllAgent");
response.Wait();
var result = response.Result;
if (result.IsSuccessStatusCode)
{
var readTask =JsonConvert.DeserializeObject<IList<AgentReadDto>>(await result.Content.ReadAsStringAsync());
agents = readTask;
}
}
return agents;
}
And my controller code is look like this:
public IActionResult AgentLists()
{
var agentsList = _agentRespositoryWeb.GetAgent();
if (agentsList != null )
{
ViewBag.Message = "There was a problem retrieving agent from the database or no agents exists";
}
ViewBag.SuccessMessage = TempData["SuccessMessage"];
return View(agentsList);
}
My api return the value following:
{
"agentDetail": [
{
"usersId": 85,
"firstName": "Amit",
"lastName": "One",
"gender": "Male",
"informationTips": [
{
"video": "https://www.w3schools.com/html/movie.mp4"
},
{
"video": "https://www.w3schools.com/html/movie.mp4"
},
]
},
{
"usersId": 86,
"firstName": "Amit",
"lastName": "Two",
"gender": "Male",
"informationTips": [
{
"video": "https://www.w3schools.com/html/movie.mp4"
}
]
}
]
}
For exception I added image there is three image that take screen on the different steps:
Your model is set to IEnumerable<AgentReadDto>, but you've forgotten to await the call to GetAgent inside of the AgentLists action. This means there's a mismatch between what the view expects (IEnumerable<AgentReadDto>) and what it receives (Task<IEnumerable<AgentReadDto>>).
To fix this, convert AgentLists to an async method and then await the call to GetAgent. Here's a fixed version of the AgentLists action:
public async Task<IActionResult> AgentLists()
{
var agentsList = await _agentRespositoryWeb.GetAgent();
if (agentsList != null)
{
ViewBag.Message =
"There was a problem retrieving agent from the database or no agents exists";
}
ViewBag.SuccessMessage = TempData["SuccessMessage"];
return View(agentsList);
}
It looks like you also have a mismatch between the type you expect to be returned and the JSON actually being returned. The JSON represents an object with a list inside of it, but you're attempting to parse it as a simple list. To fix that, create a wrapper class that matches the structure of the response. For example, create the following class:
public class ApiResponse
{
public IEnumerable<AgentReadDto> AgentDetail { get; set; }
}
Update the deserialization logic to use this new type:
var apiResponse = JsonConvert.DeserializeObject<ApiResponse>(...);
var agentsLit = apiResponse.AgentDetail;

How to autoformat API response in .NET core 5 Web API project?

I Have an API with formatted response look's like this following JSON:
{
"statusCode": 200,
"totalRecord": 2,
"message": "Succesfully get merchants",
"data": [
{
"id": 1,
"name": "Leo Shop",
"address": "Flower Street 9A",
"isComplete": false,
"createdAt": "2021-05-30T14:16:27.654233",
"updatedAt": "2021-05-30T14:16:28.515476"
},
{
"id": 3,
"name": "Test Shop",
"address": "Playing Street 12A",
"isComplete": false,
"createdAt": "2021-05-30T14:16:27.654233",
"updatedAt": "2021-05-30T14:16:28.515476"
}
]
}
And code behind those response look's like this following code :
[HttpGet]
public async Task<ActionResult<IEnumerable<Merchant>>> GetMerchants()
{
var data = await _context.Merchants.ToListAsync();
ApiResponse res = new ApiResponse{StatusCode = 200, Message = "Succesfully get merchants", TotalRecord = data.Count, Data = data}; // Focus on this line
return Ok(res);
}
My question, how to automatically convert default response As ApiResponse model without repeating to write new ApiResponse() model on every action return inside every controller?
Hopefully anyone can help me.. Thanks in advance guys
You can use Result Filter in MVC Filter pipeline
MVC Filter pipeline
So change your controller looks like this
[HttpGet]
public Task<List<Merchant>> GetMerchants()
{
return _context.Merchants.ToListAsync();
}
Add new class, called ResultManipulator.cs
public class ResultManipulator : IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context)
{
// do nothing
}
public void OnResultExecuting(ResultExecutingContext context)
{
//run code immediately before and after the execution of action results. They run only when the action method has executed successfully. They are useful for logic that must surround view or formatter execution.
var result = context.Result as ObjectResult;
var resultObj = result.Value;
//change this ResultApi with your ApiResponse class
var resp = new ResultApi
{
Path = context.HttpContext.Request.Path.HasValue ? context.HttpContext.Request.Path.Value : "",
Method = context.HttpContext.Request.Method
};
if (resultObj is not null && resultObj is not Unit)
resp.Payload = resultObj;
//you can also change this from System.Text.Json to newtonsoft if you use newtonsoft
context.Result = new JsonResult(resp, new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
});
}
}
And dont forget to add ResultManipulator class in Startup
services.AddControllers(options =>
{
options.Filters.Add(new ResultManipulator());
})
I hope it solve your problem
Try this as a middleware solution. It will transform the response.
public class YourResponseMiddleware
{
private readonly RequestDelegate _next;
public YourResponseMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var existingBody = context.Response.Body;
using (var newBody = new MemoryStream())
{
context.Response.Body = newBody;
await _next(context);
var newResponse = await FormatResponse(context.Response);
context.Response.Body = new MemoryStream();
newBody.Seek(0, SeekOrigin.Begin);
context.Response.Body = existingBody;
var newContent = new StreamReader(newBody).ReadToEnd();
// Send modified content to the response body.
//
await context.Response.WriteAsync(newResponse);
}
}
private async Task<string> FormatResponse(HttpResponse response)
{
//We need to read the response stream from the beginning...and copy it into a string...I'D LIKE TO SEE A BETTER WAY TO DO THIS
//
response.Body.Seek(0, SeekOrigin.Begin);
var content= await new StreamReader(response.Body).ReadToEndAsync();
var yourResponse = new YourResponse (); // CREATE THIS CLASS
yourResponse.StatusCode = response.StatusCode;
if(!IsResponseValid(response))
{
yourResponse.ErrorMessage = content;
}
else
{
yourResponse.Content = content;
}
yourResponse.Size = response.ToString().Length;
var json = JsonConvert.SerializeObject(yourResponse );
//We need to reset the reader for the response so that the client an read it
response.Body.Seek(0, SeekOrigin.Begin);
return $"{json}";
}
private bool IsResponseValid(HttpResponse response)
{
if ((response != null)
&& (response.StatusCode == 200
|| response.StatusCode == 201
|| response.StatusCode == 202))
{
return true;
}
return false;
}
}
public static class ResponseMiddleware
{
public static void UseYourResponseMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware<YourResponseMiddleware>();
}
}
In Startup.cs (Configure())
app.UseYourResponseMiddleware();

Model state error filter

On my DTO objects I have several attributes to check it's validity
And I catch such a body response when validation is failed
{
"TransactionId": [
"Max length is 20"
],
"AdditionalInfo": [
"Additional Info has to be no longer than 30 chars"
]
}
But I need to unify all the errors to be with "Error" key.
Something like that
{
"Error": [
"Max length is 20",
"Additional Info has to be no longer than 30 chars"
]
}
I wrote special filter and registered it in Startup.cs
public class ModelStateErrorHandlingFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
context.ModelState.SetModelValue("Errors", new ValueProviderResult(new StringValues(context.ModelState.ToString())));
context.Result = new BadRequestObjectResult(context.ModelState);
}
else
{
await next().ConfigureAwait(false);
}
}
}
But nothing changes. I also have tried to change the key, but it has privat setter
You would need to provide you own custom IActionResult or build the desired object model and pass it to an appropriate ObjectResult.
public class ModelStateErrorHandlingFilter : IAsyncActionFilter {
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
if (!context.ModelState.IsValid) {
var model = new {
Error = context.ModelState
.SelectMany(keyValuePair => keyValuePair.Value.Errors)
.Select(modelError => modelError.ErrorMessage)
.ToArray()
};
context.Result = new BadRequestObjectResult (model);
} else {
await next().ConfigureAwait(false);
}
}
}
setting context.Result will short-circuit the request and pass it your custom response with desired content.

Error using QnAMaker sample with feedback

I have been trying to use Microsoft Cognitive and AI toolkit with QnAMaker API, in order to create a simplistic chat bot.
While my normal qnaMakerAi chat bot works fine, there is an issue while I was trying to enhance it's feature and include the bot feedback within the response.
I have been following the exact code sample as is referred here.
The issue I'm having is:
Exception: Object reference not set to an instance of an object.
[File of type 'text/plain'].
The debugger is giving error in the code section - (in the file WebApiConfig.cs)
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
I have also raised a detailed description of the issue in - https://github.com/Microsoft/BotBuilder/issues/4267.
Please check and suggest.
Based on the user comments, here is the code for MessagesController -
using System;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.Dialogs;
using System.Web.Http.Description;
using System.Net.Http;
using QnABot.Dialogs;
namespace Microsoft.Bot.Sample.QnABot
{
[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// receive a message from a user and send replies
/// </summary>
/// <param name="activity"></param>
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity.GetActivityType() == ActivityTypes.Message)
{
//await Conversation.SendAsync(activity, () => new RootDialog());
await Conversation.SendAsync(activity, () => new QnaDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
}
For QnADialog -
using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.CognitiveServices.QnAMaker;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace QnABot.Dialogs
{
[Serializable]
public class QnaDialog : QnAMakerDialog
{
public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))
{
}
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// answer is a string
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
string[] qnaAnswerData = answer.Split(';');
int dataSize = qnaAnswerData.Length;
string title = qnaAnswerData[0];
string description = qnaAnswerData[1];
string url = qnaAnswerData[2];
string imageURL = qnaAnswerData[3];
HeroCard card = new HeroCard
{
Title = title,
Subtitle = description,
};
card.Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
};
card.Images = new List<CardImage>
{
new CardImage( url = imageURL)
};
reply.Attachments.Add(card.ToAttachment());
await context.PostAsync(reply);
}
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// get the URL
var answer = result.Answers.First().Answer;
string[] qnaAnswerData = answer.Split(';');
string qnaURL = qnaAnswerData[2];
// pass user's question
var userQuestion = (context.Activity as Activity).Text;
context.Call(new FeedbackDialog(qnaURL, userQuestion), ResumeAfterFeedback);
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (await result != null)
{
await MessageReceivedAsync(context, result);
}
else
{
context.Done<IMessageActivity>(null);
}
}
}
}
For FeedBackDialog -
using Microsoft.ApplicationInsights;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace QnABot.Dialogs
{
[Serializable]
public class FeedbackDialog : IDialog<IMessageActivity>
{
private string qnaURL;
private string userQuestion;
public FeedbackDialog(string url, string question)
{
// keep track of data associated with feedback
qnaURL = url;
userQuestion = question;
}
public async Task StartAsync(IDialogContext context)
{
var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");
feedback.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "👍", Type=ActionTypes.PostBack, Value=$"yes-positive-feedback" },
new CardAction(){ Title = "👎", Type=ActionTypes.PostBack, Value=$"no-negative-feedback" }
}
};
await context.PostAsync(feedback);
context.Wait(this.MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var userFeedback = await result;
if (userFeedback.Text.Contains("yes-positive-feedback") || userFeedback.Text.Contains("no-negative-feedback"))
{
// create telemetry client to post to Application Insights
TelemetryClient telemetry = new TelemetryClient();
if (userFeedback.Text.Contains("yes-positive-feedback"))
{
// post feedback to App Insights
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"URL", qnaURL },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("Yes-Vote", properties);
}
else if (userFeedback.Text.Contains("no-negative-feedback"))
{
// post feedback to App Insights
}
await context.PostAsync("Thanks for your feedback!");
context.Done<IMessageActivity>(null);
}
else
{
// no feedback, return to QnA dialog
context.Done<IMessageActivity>(userFeedback);
}
}
}
}
1st, bad config
Ok, the 1st problem is the fact that you inverted 2 parameters in your QnaDialog declaration:
public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))
The syntax is: Qn
public QnAMakerAttribute(string subscriptionKey, string knowledgebaseId, ...
Here you inverted your Key and your knowledgebaseId. The Guid should be in 2nd position, not 1st.
Note that I modified your subscription key in the question and reply, you should note share them like that.
Code improvement
The sample that you used seems to be not valid:
in the case there is no match
when your answer is not made of a string with ; separator (like when you type "hi", the reply is "hello"
I added some security to avoid errors in those cases:
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// answer is a string
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
var qnaAnswerData = answer.Split(';');
var dataSize = qnaAnswerData.Length;
if (dataSize == 3)
{
var title = qnaAnswerData[0];
var description = qnaAnswerData[1];
var url = qnaAnswerData[2];
var imageUrl = qnaAnswerData[3];
var card = new HeroCard
{
Title = title,
Subtitle = description,
Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
},
Images = new List<CardImage>
{
new CardImage(url = imageUrl)
},
};
reply.Attachments.Add(card.ToAttachment());
}
else
{
reply.Text = answer;
}
await context.PostAsync(reply);
}
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
if (result.Answers.Count > 0)
{
// get the URL
var answer = result.Answers.First().Answer;
var qnaAnswerData = answer.Split(';');
var dataSize = qnaAnswerData.Length;
if (dataSize == 3)
{
var qnaUrl = qnaAnswerData[2];
// pass user's question
var userQuestion = (context.Activity as Activity).Text;
context.Call(new FeedbackDialog(qnaUrl, userQuestion), ResumeAfterFeedback);
}
else
{
await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
}
}
else
{
await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
}
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (await result != null)
{
await MessageReceivedAsync(context, result);
}
else
{
context.Done<IMessageActivity>(null);
}
}

.Net Filter For Wrapping JsonResult Actions Response

I've built a Web API application and found an issue (which currently treated badly in my code), the issue summarized in wrapping all Json objects which returned from All API actions with custom nodes(roots).
i.e: I have this json (array) response:
[
{
"Category": "Pages",
"Users": [
{
"ID": "1",
"Fname": "Foo",
"Lname": "Bar"
}
]
}
]
And Need this response:
{
"Object": {
"Body": [
{
"Category": "Pages",
"Users": [
{
"ID": "1",
"Fname": "Foo",
"Lname": "Bar"
}
]
}
]
}
}
So here I just wrapped the response inside {"Object":{"Body": <Response Here>}}
And this I need it to be applied on all API Json responses of type Array.
And for simple Json object response, I need it just to be wrapped like {"Object": <Response Here>}
I wrapped the Json response currently in each controller action by this code:
public JsonResult Categories()
{
return Json(new { Object= new { Body= GetCategoriesList() } }, JsonRequestBehavior.AllowGet);
}
Sure this achievement is so bad because I have to repeat this wrapping in each action.
My Question Is:
How to create ActionFilterAttribute to be called after each action execution to wrap the response as per the above Json sample?
i.e. for creating the filter:
public class JsonWrapper: System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
i.e. for calling the filter:
[JsonWrapper]
public class APIController : Controller
And also to set the response content type in the same filter "application/json"
If suppose here if what you looking for:
public class JsonWrapperAttribute : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuted(ActionExecutedContext context)
{
//Check it's JsonResult that we're dealing with
JsonResult jsonRes = context.Result as JsonResult;
if (jsonRes == null)
return;
jsonRes.Data = new { Object = new { Body = jsonRes.Data } }
}
}
Here is how you can use it:
[JsonWrapper]
public JsonResult Index()
{
var data = new
{
a = 1,
b = 2
};
return Json(data, JsonRequestBehavior.AllowGet);
}
Result will be:
{"Object":{"Body":{"a":1,"b":2}}}
To prevent yourself having to repeat wrapping in each action you could either write an extension method which would do the wrapping for you
public static class ControllerExtensions
{
public static JsonResult WrappedJson(this Controller controller, object data, JsonRequestBehavior behavior)
{
return new JsonResult
{
Data = new { Object = new { Body = data } },
JsonRequestBehavior = behavior
};
}
}
or create a new ActionResult class (and add extension methods to return that)
public class WrappedJsonResult : JsonResult
{
public new object Data
{
get
{
if (base.Data == null)
{
return null;
}
return (object) ((dynamic) base.Data).Object.Body;
}
set { base.Data = new {Object = new {Body = value}}; }
}
}

Categories