Dmitry
Dmitry

Reputation: 792

Dialogflow V2 (beta1) SDK for Obj-C

I would like to use dialogflow service in the app written using obj-c. Have been using api.ai library for a while but could not seem to find a library for obj-c for dialogflow v2(beta1) apis. My agent is upgraded to v2 already, but the api.ai internally is using /v1/ endpoints and I need to use v2beta1 specific features like access to knowledge bases. (https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1#queryparameters - knowledge_base_names). The dialogflow api is a standard REST API, so all I need to have is OAuth2.0 & REST client, but coding this sounds like re-inventing the wheel.

Please advice. Thank you

Upvotes: 1

Views: 326

Answers (2)

Dmitry
Dmitry

Reputation: 792

Following @Tlaquetzal example, I ended up doing something like below

In pod file

pod 'GoogleAPIClientForREST'
pod 'JWT'

Using ServiceGenerator and Discovery Document as mentioned above, generated set of DialogFlow v2beta1 classes. Command line

./ServiceGenerator --outputDir . --verbose --gtlrFrameworkName GTLR --addServiceNameDir yes --guessFormattedNames https://dialogflow.googleapis.com/\$discovery/rest?version=v2beta1

And added them to the project.

#import "DialogflowV2Beta1/GTLRDialogflow.h"

Next step is to generate JWT Token. I have used this library JSON Web Token implementation in Objective-C. I want to connect using a service account.

NSInteger unixtime = [[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] integerValue];
    NSInteger expires = unixtime + 3600;    //expire in one hour
    NSString *iat = [NSString stringWithFormat:@"%ld", unixtime];
    NSString *exp = [NSString stringWithFormat:@"%ld", expires];

    NSDictionary *payload = @{
        @"iss": @"<YOUR-SERVICE-ACCOUNT-EMAIL>",
        @"sub": @"<YOUR-SERVICE-ACCOUNT-EMAIL>",
        @"aud": @"https://dialogflow.googleapis.com/",
        @"iat": iat,
        @"exp": exp
    };

    NSDictionary *headers = @{
        @"alg" : @"RS256",
        @"typ" : @"JWT",
        @"kid" : @"<KID FROM YOUR SERVICE ACCOUNT FILE>"
    };

    NSString *algorithmName = @"RS256";
    NSData *privateKeySecretData = [[[NSDataAsset alloc] initWithName:@"<IOS-ASSET-NAME-JSON-SERVICE-ACCOUNT-FILE>"] data];
    NSString *passphraseForPrivateKey = @"<PASSWORD-FOR-PRIVATE-KEY-IN-CERT-JSON>";

    JWTBuilder *builder = [JWTBuilder encodePayload:payload].headers(headers).secretData(privateKeySecretData).privateKeyCertificatePassphrase(passphraseForPrivateKey).algorithmName(algorithmName);
NSString *token = builder.encode;

// check error
if (builder.jwtError == nil) {
    JwtToken *jwtToken = [[JwtToken alloc] initWithToken:token expires:expires];
    success(jwtToken);
}
else {
    // error occurred.
    MSLog(@"ERROR. jwtError = %@", builder.jwtError);

    failure(builder.jwtError);
}

When token is generated, it can be used for an hour (or time you specify above).

To make a call to dialogflow you need to define your project path. To create a project path for the call, append to the code below your unique session identifier. Session is like a conversation for dialogflow, so different users should use different session ids

#define PROJECTPATH @"projects/<YOUR-PROJECT-NAME>/agent/sessions/"

Making dialogflow call

    // Create the service
    GTLRDialogflowService *service = [[GTLRDialogflowService alloc] init];

    //authorise with token
    service.additionalHTTPHeaders = @{
        @"Authorization" : [NSString stringWithFormat:@"Bearer %@", self.getToken.token]
    };

    // Create the request object (The JSON payload)
    GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentRequest *request = [GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentRequest object];

    //create query
    GTLRDialogflow_GoogleCloudDialogflowV2beta1QueryInput *queryInput = [GTLRDialogflow_GoogleCloudDialogflowV2beta1QueryInput object];

    //text query
    GTLRDialogflow_GoogleCloudDialogflowV2beta1TextInput *userText = [GTLRDialogflow_GoogleCloudDialogflowV2beta1TextInput object];


    userText.text = question;
    userText.languageCode = LANGUAGE;
    queryInput.text = @"YOUR QUESTION TO dialogflow agent"; //userText;

    // Set the information in the request object
    //request.inputAudio = myInputAudio;
    //request.outputAudioConfig = myOutputAudioConfig;
    request.queryInput = queryInput;
    //request.queryParams = myQueryParams;

    //Create API project path with session
    NSString *pathAndSession = [NSString stringWithFormat:@"%@%@", PROJECTPATH, [self getSession]];

    // Create a query with session (Path parameter) and the request object
    GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent *query = [GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent queryWithObject:request session:pathAndSession];


    // Create a ticket with a callback to fetch the result
//    GTLRServiceTicket *ticket =
    [service executeQuery:query
        completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentResponse *detectIntentResponse, NSError *callbackError) {

        // This callback block is run when the fetch completes.
        if (callbackError != nil) {
            NSLog(@"error");
            NSLog(@"Fetch failed: %@", callbackError);

            //TODO: Register failure with analytics

            failure( callbackError );
        }
        else {

//            NSLog(@"Success");
          // The response from the agent
//          NSLog(@"%@", detectIntentResponse.queryResult.fulfillmentText);
            NSString *response = detectIntentResponse.queryResult.fulfillmentText;
            success( response );
        }

    }];

This is a basic implementation, but works and good for demo. Good luck

Upvotes: 0

Tlaquetzal
Tlaquetzal

Reputation: 2850

I don't think there's a library written specifically for Dialogflow v2; however, the library google-api-objectivec-client-for-rest is a generic library provided by Google, that simplifies the code to consume their Rest APIs.

This library is updated to be used with Dialogflow V2. In order to use it, you'll need to match the Rest API, with the "Queries" (API methods) and "Objects" (API types) in the library, which is not that difficult because the names are basically the same.

For example, the detectIntent method full name is:

projects.agent.sessions.detectIntent

In the library, it is the equivalent to the Query:

GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent

Here's an example of a detectIntent request:

// Create the service
GTLRDialogflowService *service = [[GTLRDialogflowService alloc] init];

// Create the request object (The JSON payload)
GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentRequest *request =
                     [GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentRequest object];

// Set the information in the request object
request.inputAudio = myInputAudio;
request.outputAudioConfig = myOutputAudioConfig;
request.queryInput = myQueryInput;
request.queryParams = myQueryParams;

// Create a query with session (Path parameter) and the request object
GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent *query =
    [GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent queryWithObject:request
                                                            session:@"session"];

// Create a ticket with a callback to fetch the result
GTLRServiceTicket *ticket =
    [service executeQuery:query
        completionHandler:^(GTLRServiceTicket *callbackTicket,
                            GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentResponse *detectIntentResponse,
                            NSError *callbackError) {
    // This callback block is run when the fetch completes.
    if (callbackError != nil) {
      NSLog(@"Fetch failed: %@", callbackError);
    } else {
      // The response from the agent
      NSLog(@"%@", detectIntentResponse.queryResult.fulfillmentText);
    }
}];

You can find more information and samples, in the library wiki. Finally, the library also has a sample code using Google Cloud Storage which ilustrates its use with GCP services.

I think that without a specific library for Dialogflow V2, this might be the next thing to try before implementing it from scratch.

EDIT

Oops, I was missing the fact that the generated service for Dialogflow does not contain v2beta1.

In this case, it is needed an additional first step, which is to use the Dialogflow v2beta1 DiscoveryDocument and the ServiceGenerator, to create the service interface for v2beta1. Then you can continue working the same as I mentioned before.

Upvotes: 1

Related Questions