Tutorial: Add utterances to app using Java

In this tutorial, write a program to add an utterance to an intent using the Authoring APIs in Java.

  • Create Visual Studio console project
  • Add method to call LUIS API to add utterance and train app
  • Add JSON file with example utterances for BookFlight intent
  • Run console and see training status for utterances

For more information, see the technical documentation for the add example utterance to intent, train, and training status APIs.

For this article, you need a free LUIS account in order to author your LUIS application.

Prerequisites

  • Latest Oracle Java/JDK.
  • Google's GSON JSON library.
  • Your LUIS authoring key. You can find this key under Account Settings in the LUIS website.
  • Your existing LUIS application ID. The application ID is shown in the application dashboard. The LUIS application with the intents and entities used in the utterances.json file must exist prior to running the code in AddUtterances.java. The code in this article will not create the intents and entities. It will only add the utterances for existing intents and entities.
  • The version ID within the application that receives the utterances. The default ID is "0.1"
  • Create a new text file named AddUtterances.java.

Note

The complete add-utterances.cs file and an example utterances.json file are available from the LUIS-Samples Github repository.

Write the Java code

Add the Java dependencies to the file.

import java.io.*;
import java.net.*;
import java.util.*;
import com.google.gson.*;

Create AddUtterances class

public class AddUtterances {
    // Insert code here
}

This class contains all code snippets that follow.

Add the LUIS constants to the class. Copy the following code and change to your authoring key, application ID, and version ID.

// Enter information about your LUIS application and key below
static final String LUIS_APP_ID      = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
static final String LUIS_APP_VERSION = "0.1";
static final String LUIS_AUTHORING_ID  = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

// Update the host if your LUIS subscription is not in the West US region
static final String LUIS_BASE        = "https://westus.api.cognitive.microsoft.com";

// File names for utterance and result files
static final String UTTERANCE_FILE   = "./utterances.json";
static final String RESULTS_FILE     = "./utterances_results.json";

static final String UTF8 = "UTF-8";

Add the method to call into the LUIS API.

//
// LUIS Client class
// Contains the functionality for adding utterances to a LUIS application
//
static class LuisClient{

    private final String PATH = "/luis/api/v2.0/apps/{app_id}/versions/{app_version}";

    // endpoint method names
    private final String TRAIN    = "/train";
    private final String EXAMPLES = "/examples";
    private final String APP_INFO = "/";

    // HTTP verbs
    private final String GET  = "GET";
    private final String POST = "POST";

    // Null string value for use in resolving method calls
    private final String NO_DATA = null;

    // Member variables
    private final String key;
    private final String host;
    private final String path;

    LuisClient(String host, String app_id, String app_version, String key) throws Exception {
        this.path = PATH.replace("{app_id}", app_id).replace("{app_version}", app_version);
        this.host = host;
        this.key  = key;

        // Test configuration by getting the application info
        this.get(APP_INFO).raiseForStatus();
    }

    private LuisResponse call(String endpoint, String method, byte[] data) throws Exception {

        // initialize HTTP connection
        URL url = new URL(this.host + this.path + endpoint);

        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.setRequestMethod(method);
        conn.setRequestProperty("Ocp-Apim-Subscription-Key", key);

        // handle POST request
        if (method.equals(POST)) {
            if (data == null)
                    data = new byte[]{};    // make zero-length body for POST w/o data
            conn.setDoOutput(true);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("Content-Length", Integer.toString(data.length));
            try (OutputStream ostream = conn.getOutputStream()) {
                ostream.write(data, 0, data.length);
            }
        }

        // Get response from API call.  If response is an HTTP error, the JSON
        // response is on the error string.  Otherwise, it's on the input string.
        InputStream stream;
        try {
            stream = conn.getInputStream();
        } catch (IOException ex) {
            stream = conn.getErrorStream();
        }
        String body = new Scanner(stream, UTF8).useDelimiter("\\A").next();

        return new LuisResponse(body, conn.getResponseCode(), conn.getResponseMessage());

    }

    // Overload of call() with String data paramater
    private LuisResponse call(String endpoint, String method, String data) throws Exception {
        byte[] bytes = null;
        if (data != null)
            bytes = data.getBytes(UTF8);
        return call(endpoint, method, bytes);
    }

    // Overload of call() with InputStream data paramater
    private LuisResponse call(String endpoint, String method, InputStream stream) throws Exception {
        String data = new Scanner(stream, UTF8).useDelimiter("\\A").next();
        return call(endpoint, method, data);
    }

    // Shortcut for GET requests
    private LuisResponse get(String endpoint) throws Exception {
        return call(endpoint, GET, NO_DATA);
    }

    // Shortcut for POST requests -- byte[] data
    private LuisResponse post(String endpoint, byte[] data) throws Exception {
        return call(endpoint, POST, data);
    }

    // Shortcut for POST requests -- String data
    private LuisResponse post(String endpoint, String data) throws Exception {
        return call(endpoint, POST, data);
    }

    // Shortcut for POST requests -- InputStream data
    private LuisResponse post(String endpoint, InputStream data) throws Exception {
        return call(endpoint, POST, data);
    }

    // Shortcut for POST requests -- no data
    private LuisResponse post(String endpoint) throws Exception {
        return call(endpoint, POST, NO_DATA);
    }

    // Call to add utterances
    public LuisResponse addUtterances(String filename) throws Exception {
        try (FileInputStream stream = new FileInputStream(filename)) {
            return post(EXAMPLES, stream);
        }
    }

    public LuisResponse train() throws Exception {
        return post(TRAIN);
    }

    public LuisResponse status() throws Exception {
        return get(TRAIN);
    }

}

Add the method for the HTTP response from the LUIS API.

//
// LUIS Response class
// Represents a response from the LUIS client.  All methods return
// the instance so method calls can be chained.
//
static class LuisResponse {

    private final String    body;
    private final int       status;
    private final String    reason;
    private JsonElement     data;

    LuisResponse(String body, int status, String reason) {
        JsonParser parser = new JsonParser();
        try {
            this.data = parser.parse(body);
        }
        catch (JsonSyntaxException ex) {
            this.data = parser.parse("{ \"message\": \"Invalid JSON response\" }");
        }
        this.body   = new GsonBuilder().setPrettyPrinting().create().toJson(data);
        this.status = status;
        this.reason = reason;
    }

    LuisResponse write(String filename) throws Exception {
        File file = new File(filename);
        if (!file.exists()) file.createNewFile();
        try (FileOutputStream stream = new FileOutputStream(file)) {
            stream.write(this.body.getBytes(UTF8));
            stream.flush();
        }
        return this;
    }

    LuisResponse print() {
        System.out.println(this.body);
        return this;
    }

    LuisResponse raiseForStatus() throws StatusException {
        if (this.status < 200 || this.status > 299) {
            throw new StatusException(this);
        }
        return this;
    }
}

Add exception handling.

//
// LUIS Status Exception class
// Represents an exception raised by the LUIS client for HTTP status errors
// Includes details extracted from the JSON response and the HTTP status
//
static class StatusException extends Exception {

    private String details = "";
    private final int status;

    StatusException(LuisResponse response) {
        super(String.format("%d %s", response.status, response.reason));
        JsonObject jsonInfo = (JsonObject)response.data;
        if (jsonInfo.has("error"))
            jsonInfo = (JsonObject)jsonInfo.get("error");
        if (jsonInfo.has("message"))
            this.details = jsonInfo.get("message").getAsString();
        this.status = response.status;
    }

    String getDetails() {
        return this.details;
    }

    int getStatus() {
        return this.status;
    }

}

Add output/print handling.

static void printExceptionMsg(Exception ex) {
    System.out.println(String.format("%s: %s",
            ex.getClass().getSimpleName(), ex.getMessage()));

    StackTraceElement caller = ex.getStackTrace()[1];
    System.out.println(String.format("    in %s (line %d?)",
            caller.getFileName(), caller.getLineNumber()));
    if (ex instanceof StatusException)
       System.out.println(((StatusException)ex).getDetails());
}

Add the main function.

    // ------------------------------------------------------------------------
    //
    // Command-line entry point
    //
    public static void main(String[] args) {

        // uncomment a line below to simulate command line options
        // if (args.length == 0) args = new String[]{"-train"};
        // if (args.length == 0) args = new String[]{"-status"};

        LuisClient luis = null;

        try {
            luis = new LuisClient(LUIS_BASE, LUIS_APP_ID,
                    LUIS_APP_VERSION,LUIS_AUTHORING_ID);
        } catch (StatusException ex) {
            int status = ex.getStatus();
            switch (status) {
                case 401:
                    System.out.println("Invalid access key. Set the variable LUIS_AUTHORING_ID to a valid LUIS access key");
                    System.out.println("in the Java source file " + ex.getStackTrace()[0].getFileName());
                    break;
                case 400:
                    System.out.println("Invalid app ID or version. Set the variable LUIS_APP_ID to a valid LUIS app ID");
                    System.out.println("and the variable LUIS_APP_VERSION to a valid version of that application");
                    System.out.println("in the Java source file " + ex.getStackTrace()[0].getFileName());
                    break;
                default:
                    printExceptionMsg(ex);
                    break;
            }
            System.exit(0);
        } catch (Exception ex) {
            printExceptionMsg(ex);
            System.exit(0);
        }

        try {

            if (args.length > 0) {  // handle command line flags
                String option = args[0].toLowerCase();
                if (option.startsWith("-"))     // strip leading hyphens
                    option = option.substring(option.lastIndexOf('-') + 1);
                if (option.equals("train")) {
                    System.out.println("Adding utterance(s).");
                    luis.addUtterances(UTTERANCE_FILE)
                            .write(RESULTS_FILE)
                            .raiseForStatus();
                    System.out.println("Added utterance(s). Requesting training.");
                    luis.train()
                            .write(RESULTS_FILE)
                            .raiseForStatus();
                    System.out.println("Requested training. Requesting training status.");
                    luis.status()
                            .write(RESULTS_FILE)
                            .raiseForStatus();
                } else if (option.equals("status")) {
                    System.out.println("Requesting training status.");
                    luis.status()
                            .write(RESULTS_FILE)
                            .raiseForStatus();
                }
            } else {
                System.out.println("Adding utterance(s).");
                luis.addUtterances(UTTERANCE_FILE)
                        .write(RESULTS_FILE)
                        .raiseForStatus();
            }

            System.out.println("Success! Results in " + RESULTS_FILE);

        } catch (Exception ex) {
            printExceptionMsg(ex);
        }
    }

}

Specify utterances to add

Create and edit the file utterances.json to specify the array of utterances you want to add to the LUIS app. The intent and entities must already be in the LUIS app.

Note

The LUIS application with the intents and entities used in the utterances.json file must exist prior to running the code in AddUtterances.java. The code in this article will not create the intents and entities. It will only add the utterances for existing intents and entities.

The text field contains the text of the utterance. The intentName field must correspond to the name of an intent in the LUIS app. The entityLabels field is required. If you don't want to label any entities, provide an empty list as shown in the following example.

If the entityLabels list is not empty, the startCharIndex and endCharIndex need to mark the entity referred to in the entityName field. Both indexes are zero-based counts meaning 6 in the top example refers to the "S" of Seattle and not the space before the capital S.

[
    {
        "text": "go to Seattle",
        "intentName": "BookFlight",
        "entityLabels": [
            {
                "entityName": "Location::LocationTo",
                "startCharIndex": 6,
                "endCharIndex": 12
            }
        ]
    },
    {
        "text": "book a flight",
        "intentName": "BookFlight",
        "entityLabels": []
    }
]

Add an utterance from the command-line

Compile AddUtterance with the dependencies

> javac -classpath gson-2.8.2.jar AddUtterances.java

Calling AddUtterance with no arguments adds the LUIS utterances to the app, without training it.

> java -classpath .;gson-2.8.2.jar AddUtterances

This sample creates a file with the results.json that contains the results from calling the add utterances API. The response field is in this format for utterances that was added. The hasError is false, indicating the utterance was added.

    "response": [
        {
            "value": {
                "UtteranceText": "go to seattle",
                "ExampleId": -5123383
            },
            "hasError": false
        },
        {
            "value": {
                "UtteranceText": "book a flight",
                "ExampleId": -169157
            },
            "hasError": false
        }
    ]

Add an utterance and train from the command-line

Call add-utterance with the -train argument to send a request to train.

> java -classpath .;gson-2.8.2.jar AddUtterances -train

Note

Duplicate utterances aren't added again, but don't cause an error. The response contains the ID of the original utterance.

When you call the app with the -train argument, it creates a training-results.json file indicating the request to train the LUIS app was successfully queued.

The following shows the result of a successful request to train:

{
    "request": null,
    "response": {
        "statusId": 9,
        "status": "Queued"
    }
}

After the request to train is queued, it can take a moment to complete training.

Get training status from the command line

Call the app with the -status argument to check the training status and write status details to a file.

> java -classpath .;gson-2.8.2.jar AddUtterances -status

Clean up resources

When you are done with the tutorial, remove Visual Studio and the console application if you don't need them anymore.

Next steps