I am new to Google APIs. I want to know how to call Google Dialogflow API in C# to get intent form the input text. But I can't find any example to call Dialogflow using C#.
Please provide some example to call Dialogflow from C#.
If I understand your question correctly you want to call the DialogFlow API from within a C# application (rather than writing fulfillment endpoint(s) that are called from DialogFlow. If that's the case here's a sample for making that call:
using Google.Cloud.Dialogflow.V2;
...
...
var query = new QueryInput
{
Text = new TextInput
{
Text = "Something you want to ask a DF agent",
LanguageCode = "en-us"
}
};
var sessionId = "SomeUniqueId";
var agent = "MyAgentName";
var creds = GoogleCredential.FromJson("{ json google credentials file)");
var channel = new Grpc.Core.Channel(SessionsClient.DefaultEndpoint.Host,
creds.ToChannelCredentials());
var client = SessionsClient.Create(channel);
var dialogFlow = client.DetectIntent(
new SessionName(agent, sessionId),
query
);
channel.ShutdownAsync();
In an earlier version of the DialogFlowAPI I was running into file locking issues when trying to re-deploy a web api project which the channel.ShutDownAsync() seemed to solve. I think this has been fixed in a recent release.
This is the simplest version of a DF request I've used. There is a more complicated version that passes in an input context in this post:
Making DialogFlow v2 DetectIntent Calls w/ C# (including input context)
(Nitpicking: I assume you know DialogFlow will call your code as specified/registered in the action at DialogFlow? So your code can only respond to DialogFlow, and not call it.)
Short answer/redirect:
Don't use Google.Apis.Dialogflow.v2 (with GoogleCloudDialogflowV2WebhookRequest and GoogleCloudDialogflowV2WebhookResponse) but use Google.Cloud.Dialogflow.v2 (with WebhookRequest and WebhookResponse) - see this eTag-error. I will also mention some other alternatives underneath.
Google.Cloud.Dialogflow.v2
Using Google.Cloud.Dialogflow.v2 NuGet (Edit: FWIW: this code was written for the beta-preview):
[HttpPost]
public dynamic PostWithCloudResponse([FromBody] WebhookRequest dialogflowRequest)
{
var intentName = dialogflowRequest.QueryResult.Intent.DisplayName;
var actualQuestion = dialogflowRequest.QueryResult.QueryText;
var testAnswer = $"Dialogflow Request for intent '{intentName}' and question '{actualQuestion}'";
var dialogflowResponse = new WebhookResponse
{
FulfillmentText = testAnswer,
FulfillmentMessages =
{ new Intent.Types.Message
{ SimpleResponses = new Intent.Types.Message.Types.SimpleResponses
{ SimpleResponses_ =
{ new Intent.Types.Message.Types.SimpleResponse
{
DisplayText = testAnswer,
TextToSpeech = testAnswer,
//Ssml = $"<speak>{testAnswer}</speak>"
}
}
}
}
}
};
var jsonResponse = dialogflowResponse.ToString();
return new ContentResult { Content = jsonResponse, ContentType = "application/json" }; ;
}
Edit: It turns out that the model binding may not bind all properties from the 'ProtoBuf-json' correctly (e.g. WebhookRequest.outputContexts[N].parameters),
so one should probably use the Google.Protobuf.JsonParser (e.g. see this documentation).
This parser may trip over unknown fields, so one probably also wants to ignore that. So now I use this code (I may one day make the generic method more generic and thus useful, by making HttpContext.Request.InputStream a parameter):
public ActionResult PostWithCloudResponse()
{
var dialogflowRequest = ParseProtobufRequest<WebhookRequest>();
...
var jsonResponse = dialogflowResponse.ToString();
return new ContentResult { Content = jsonResponse, ContentType = "application/json" }; ;
}
private T ParseProtobufRequest<T>() where T : Google.Protobuf.IMessage, new()
{
// parse ProtoBuf (not 'normal' json) with unknown fields, else it may not bind ProtoBuf correctly
// https://github.com/googleapis/google-cloud-dotnet/issues/2425 "ask the Protobuf code to parse the result"
string requestBody;
using (var reader = new StreamReader(HttpContext.Request.InputStream))
{
requestBody = reader.ReadToEnd();
}
var parser = new Google.Protobuf.JsonParser(JsonParser.Settings.Default.WithIgnoreUnknownFields(true));
var typedRequest = parser.Parse<T>(requestBody);
return typedRequest;
}
BTW: This 'ProtoBuf-json' is also the reason to use WebhookResponse.ToString() which in turn uses Google.Protobuf.JsonFormatter.ToDiagnosticString.
Microsoft's BotBuilder
Microsoft's BotBuilder packages and Visual Studio template.
I havent't used it yet, but expect approximately the same code?
Hand written proprietary code
A simple example of incoming request code (called an NLU-Response by Google) is provided by Madoka Chiyoda (Chomado) at Github. The incoming call is simply parsed to her DialogFlowResponseModel:
public static async Task<HttpResponseMessage> Run([...]HttpRequestMessage req, [...]CloudBlockBlob mp3Out, TraceWriter log)
...
var data = await req.Content.ReadAsAsync<Models.DialogFlowResponseModel>();
Gactions
If you plan to work without DialogFlow later on, please note that the interface for Gactions differs significantly from the interface with DialogFlow.
The json-parameters and return-values have some overlap, but nothing gaining you any programming time (probably loosing some time by starting 'over').
However, starting with DialogFlow may gain you some quick dialog-experience (e.g. question & answer design/prototyping).
And the DialogFlow-API does have a NuGet package, where the Gactions-interface does not have a NuGet-package just yet.
Related
I have been using restsharp to make this simple request to a webservice:
string sParmDb = Newtonsoft.Json.JsonConvert.SerializeObject(parmsDb);// parmsDb is some object too large to be passed as a query parameter
sParmDb = System.Web.HttpUtility.UrlEncode(sParmDb);
RestSharp.RestRequest;
RestSharp.RestClient cl = new RestSharp.RestClient();
RestSharp.RestResponse rsp;
string sQueryStr = "http://myWebService";
restCall = new RestSharp.RestRequest(sQueryStr, RestSharp.Method.POST);
restCall.AddJsonBody(sParmDb);
rsp = (RestSharp.RestResponse)cl.Execute(restCall);
this works fine on NuGet version 106.9.0 but when I updated the package to 108.0.1 it doesn't compile.
RestSharp.Method doesn't contain POST, though it does have a Post, so perhaps that's a simple change.
The main problem is that RestRequest no longer contains AddJsonBody.
What would be the simplest, quickest code change that will fix this?
Thanks for your help.
UPDATE 26/7/2022
----------------
I realized that the reason that restCall.AddJsonBody was not available was that I needed to include the RestSharp.Extensions as a using directive.
Now this code compiles under version 108, the current version:
RestSharp.RestClient cl = new RestSharp.RestClient();
RestSharp.RestRequest restCall = new RestSharp.RestRequest("http://localhost:60484/api/db", RestSharp.Method.POST);
restCall.AddJsonBody("abcdefg");
RestSharp.RestResponse rsp = (RestSharp.RestResponse)cl.Execute(restCall);
This reaches the web service method, which I am running in debug mode on localhost, but the string value parameter is null.
Do I need another line(s) of code in the calling client to make it work as it does under version 106?
The web service looks like this:
public string Post([FromBody] string value)
{
//do something with value and return some string
}
string sQueryStr = "http://myWebService";
RestSharp.RestClient cl = new RestSharp.RestClient(sQueryStr);
var restCall = new RestRequest() { Method= Method.Post};
...
restCall.AddStringBody(sParmDb, RestSharp.ContentType.Json);
article https://restsharp.dev/usage.html#request-body
As part of ML automation process I want to dynamically create new AutoML model. I'm using C# (.net framework) and Google.Cloud.AutoML.V1.
After trying to run CreateDataSet code:
var autoMlClient = AutoMlClient.Create();
var parent = LocationName.FromProjectLocation(_projectId, _locationId);
var dataset = new Google.Cloud.AutoML.V1.Dataset();
dataset.DisplayName = "NewDataSet";
var response = autoMlClient.CreateDataset(parent, dataset);
I get the following error:
Field: dataset.dataset_metadata; Message: Required field not set
According to this user manual I should set Dataset Metadata Type, but the list contains only specific types of classifications (Translation/ImageClassifications etc.), I can't find a simple classification type.
How do I create a simple classification data set with the API ? in the AutoML UI its just with a simple button click ("NEW DATASET") - and have to provide only name & region - no classification type.
I also tried to set:
dataset.TextClassificationDatasetMetadata =
new TextClassificationDatasetMetadata() { ClassificationType = ClassificationType.Multiclass };
But I was unable to import data to it (got too many errors of invalid inputs from the input CSV file), I guess its related to the reason that the input format is not suitable for Text Classification.
UPDATE
I've just notice that the Nuget works with AutoML v1 but v1 beta does contains TablesDatasetMetadata Dataset Metadata Type for normal classifications. I'm speechless.
I also experienced this scenario today while creating a dataset using the NodeJS client. Since the Google AutoML table service is in the beta level you need to use the beta version of the AutoML client. In the Google cloud documentation they have used the beta client to create a dataset.
In NodeJS importing the beta version require('#google-cloud/automl').v1beta1.AutoMlClient instead of importing the normal version (v1) require('#google-cloud/automl').v1 worked for me to successfully execute the create dataset functionality.
In C# you can achieve the same through a POST request. Hope this helps :)
After #RajithaWarusavitarana comment, and my last question update , below is the code that did the trick. The token is being generated by GoogleClientAPI nuget and AutoML is handled by REST.
string GcpGlobalEndPointUrl = "https://automl.googleapis.com";
string GcpGlobalLocation = "us-central1"; // api "parent" parameter
public string GetToken(string jsonFilePath)
{
var serviceAccountCredentialFileContents = System.IO.File.ReadAllText(jsonFilePath);
var credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(serviceAccountCredentialFileContents);
var initializer = new ServiceAccountCredential.Initializer(credentialParameters.ClientEmail)
{
Scopes = new List<string> { "https://www.googleapis.com/auth/cloud-platform" }
};
var cred = new ServiceAccountCredential(initializer.FromPrivateKey(credentialParameters.PrivateKey));
string accessToken = cred.GetAccessTokenForRequestAsync("https://oauth2.googleapis.com/token").Result;
return accessToken;
}
public void GetDataSetList(string projectId, string token)
{
var restClient = new RestClient(GcpGlobalEndPointUrl);
var createDataSetReqUrl = $"v1beta1/projects/{projectId}/locations/{GcpGlobalLocation}/datasets";
var createDataSetReq = new RestRequest(createDataSetReqUrl, Method.GET);
createDataSetReq.AddHeader("Authorization", $"Bearer {token}");
var createDatasetResponse = restClient.Execute(createDataSetReq);
createDatasetResponse.Dump();
}
I took the token generation code from google-api-dotnet-client Test File
Due to the lack of examples in C#, I can't get reflection in gRPC\Protobuf working. One application would be supplying a version of all interfaces and messages.
syntax = "proto3";
import "google/protobuf/descriptor.proto";
option csharp_namespace = "Addressbook.Services";
extend google.protobuf.FileOptions {
string version = 50000;
}
option (version) = "1.2.3.0";
service AddressBookService {
...
Is it possible that a client parses the connected server with reflection? Is the version supplied to all interfaces and messages? How to I do that in C#?
Thanks for any help...
If I understand the question correctly, you want to see if client and server are in sync with all .proto files.
Here's a solution that will have to be tweaked but otherwise will work.
This only works if server has reflection enabled - maybe not an option everywhere.
In ServiceName.Descriptor.File ServiceName refers to class generated by protoc.
using var channel = _channelFactory.GetChannel();
var client = new ServerReflection.ServerReflectionClient(channel);
using var call = client.ServerReflectionInfo();
// To get all service names on the server, you can use this:
// await call.RequestStream.WriteAsync(new ServerReflectionRequest{ ListServices = ""});
// await call.ResponseStream.MoveNext(CancellationToken.None);
// foreach (var serviceResponse in call.ResponseStream.Current.ListServicesResponse.Service)
// {
// _logger.LogInformation("Service name on server: {ServiceName}", serviceResponse.Name);
// }
await call.RequestStream.WriteAsync(new ServerReflectionRequest{FileContainingSymbol = "name.space.service_name"});
await call.ResponseStream.MoveNext(CancellationToken.None);
var descriptorResponse = call.ResponseStream.Current;
// This list has to be reversed. Check `.BuildFromByteStrings` docstring for more info.
var fileDescriptors = FileDescriptor.BuildFromByteStrings(descriptorResponse.FileDescriptorResponse.FileDescriptorProto.Reverse());
var localDescriptor = ServiceName.Descriptor.File;
var relevantFileDescriptor = fileDescriptors.Single(descriptor => descriptor.Name == localDescriptor.Name);
if (relevantFileDescriptor.SerializedData != localDescriptor.SerializedData)
{
_logger.LogWarning("Remote .proto differs from local copy. Please update.");
}
else
{
_logger.LogInformation(".proto definition matches between client and server.");
}
await call.RequestStream.CompleteAsync();
Using Visual Studio, and AWS .NET V 3.0.
I'm trying to perform a real-time Predict operation, and to verify the basic setup works, I first perform a GetMLModel() which works and returns the endpoint (Somewhere in the documentation is was mentioned to use that result as the service endpoint, but it's the same that is listed in the console). Is has status "READY", so far so good.
The exception occurs below on the line below "Prediction P = RTP.Predict(Data)". Data contains a Dictionary with all the prediction values.
Error: Error making request with Error Code UnknownOperationException and Http Status Code BadRequest. No further error information was returned by the service.
public static APIResult GetRealTimePrediction(Dictionary<string, string> Data, string PayloadJSON = null) {
AmazonMachineLearningConfig MLConfig = new AmazonMachineLearningConfig();
MLConfig.RegionEndpoint = Amazon.RegionEndpoint.USEast1;
MLConfig.Validate();
AmazonMachineLearningClient MLClient = new AmazonMachineLearningClient("xxx", "xxx", MLConfig);
GetMLModelResponse MLMOdelResp = MLClient.GetMLModel("xxx"); // <-- WORKS
MLConfig.ServiceURL = MLMOdelResp.EndpointInfo.EndpointUrl;
Console.WriteLine(MLConfig.ServiceURL);
MLConfig.Validate();
Amazon.MachineLearning.Util.RealtimePredictor RTP = new Amazon.MachineLearning.Util.RealtimePredictor(MLClient, "xxx");
Prediction P = RTP.Predict(Data); // <----------------EXCEPTION HERE
}
(Obviously replace xxx with relevant values) :)
It turns out that this line:
MLConfig.ServiceURL = MLMOdelResp.EndpointInfo.EndpointUrl;
cases the MLConfig.RegionEndpoint to be reset. Even though the documentation indicates the RegionEndpoint can be determined from the ServiceURL (I'm pretty sure I read that), the RegionEndpoint needs to be set again before the RTP.Predict(Data) call.
Once I figured that out, I was able to reduce the code to just this, in case anyone else needs help. I guess adding too much information to the Configuration is NOT a good thing, as the AWS. NET library seems to figure all this out on its own.
public static APIResult GetRealTimePrediction(Dictionary<string, string> Data, string PayloadJSON = null) {
AmazonMachineLearningConfig MLConfig = new AmazonMachineLearningConfig();
MLConfig.RegionEndpoint = Amazon.RegionEndpoint.USEast1;
MLConfig.Validate(); // Just in case, not really needed
AmazonMachineLearningClient MLClient = new AmazonMachineLearningClient("xxx", "xxx", MLConfig);
Amazon.MachineLearning.Util.RealtimePredictor RTP = new Amazon.MachineLearning.Util.RealtimePredictor(MLClient, "xxx");
Prediction P = RTP.Predict(Data);
}
I am currently working on an MVC site that will hopefully consume an old .asmx Web Service. The documentation for the Web Service provides the following example:
// Construct a request object
TrimRequest request = new TrimRequest();
// Construct a RecordStringSearchClause, with type
// TitleWord, and argument "reef"
RecordStringSearchClause clause = new RecordStringSearchClause();
clause.Type = RecordStringSearchClauseType.TitleWord;
clause.Arg = "reef";
// Construct a record search, and put our search clause in it
WorkerPortalTest.TRIMWS.RecordSearch search = new WorkerPortalTest.TRIMWS.RecordSearch();
search.Items = new RecordClause[] { clause };
// If we had more than one clause, it would look like:
// search.Items = new RecordClause[] { clause1, clause2, clause3 }
// Put our search operation into our TrimRequest
request.Items = new Operation[] { search };
// Send it off. Whatever comes back will be in response
Engine engine = new Engine();
engine.Credentials = newSystem.Net.NetworkCredential(username, password);
TrimResponse response = engine.Execute(request);
As a fairly new C# programmer I understand all of it apart from the last three lines. I have never seen or used the Engine object and Visual Studio does not know of it either.. I looked through MSDN and found this page but it said it was deprecated.
I am just looking for some pointers in the right direction to call the Web Service and receive back the desired result.
Thanks.
Engine is an example of the webservice class. It calls a method in Engine class known as "Execute"..