Skip to content
This repository has been archived by the owner on May 6, 2022. It is now read-only.

Commit

Permalink
Redesign the NLU API
Browse files Browse the repository at this point in the history
These changes allow NLU classification requests
to follow either a synchronous or asynchronous
pattern.

They also introduce a context object for NLU
operations so that trace events can be
surfaced to interested parties. Doing this
involved an abstraction of `SpeechContext`'s
tracing capabilities to make them resuable
across components.
  • Loading branch information
space-pope committed Feb 17, 2020
1 parent 716d57b commit b93f227
Show file tree
Hide file tree
Showing 15 changed files with 411 additions and 104 deletions.
60 changes: 20 additions & 40 deletions src/main/java/io/spokestack/spokestack/SpeechContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.content.Context;
import androidx.annotation.Nullable;
import io.spokestack.spokestack.util.EventTracer;

import java.util.Deque;
import java.util.List;
Expand Down Expand Up @@ -46,31 +47,8 @@ public String toString() {
}
}

/** trace levels, for simple filtering. */
public enum TraceLevel {
/** all the traces. */
DEBUG(10),
/** performance traces. */
PERF(20),
/** informational traces. */
INFO(30),
/** no traces. */
NONE(100);

private final int level;

TraceLevel(int l) {
this.level = l;
}

/** @return the trace level value */
public int value() {
return this.level;
}
}

private final List<OnSpeechEventListener> listeners = new ArrayList<>();
private final int traceLevel;
private final EventTracer tracer;
private Context appContext;
private Deque<ByteBuffer> buffer;
private boolean speech;
Expand All @@ -85,9 +63,11 @@ public int value() {
* @param config speech configuration
*/
public SpeechContext(SpeechConfig config) {
this.traceLevel = config.getInteger(
int traceLevel = config.getInteger(
"trace-level",
TraceLevel.NONE.value());
EventTracer.Level.NONE.value());

this.tracer = new EventTracer(traceLevel);
}

/**
Expand Down Expand Up @@ -239,7 +219,7 @@ public SpeechContext reset() {
* @return this
*/
public SpeechContext traceDebug(String format, Object... params) {
return trace(TraceLevel.DEBUG, format, params);
return trace(EventTracer.Level.DEBUG, format, params);
}

/**
Expand All @@ -249,7 +229,7 @@ public SpeechContext traceDebug(String format, Object... params) {
* @return this
*/
public SpeechContext tracePerf(String format, Object... params) {
return trace(TraceLevel.PERF, format, params);
return trace(EventTracer.Level.PERF, format, params);
}

/**
Expand All @@ -259,7 +239,16 @@ public SpeechContext tracePerf(String format, Object... params) {
* @return this
*/
public SpeechContext traceInfo(String format, Object... params) {
return trace(TraceLevel.INFO, format, params);
return trace(EventTracer.Level.INFO, format, params);
}

/**
* indicates whether a message will be traced at a level.
* @param level tracing level
* @return true if tracing will occur, false otherwise
*/
public boolean canTrace(EventTracer.Level level) {
return this.tracer.canTrace(level);
}

/**
Expand All @@ -270,25 +259,16 @@ public SpeechContext traceInfo(String format, Object... params) {
* @return this
*/
public SpeechContext trace(
TraceLevel level,
EventTracer.Level level,
String format,
Object... params) {
if (canTrace(level)) {
if (this.tracer.canTrace(level)) {
this.message = String.format(format, params);
dispatch(Event.TRACE);
}
return this;
}

/**
* indicates whether a message will be traced at a level.
* @param level tracing level
* @return true if tracing will occur, false otherwise
*/
public boolean canTrace(TraceLevel level) {
return level.value() >= this.traceLevel;
}

/**
* dispatches a speech event.
* @param event the event to publish
Expand Down
119 changes: 119 additions & 0 deletions src/main/java/io/spokestack/spokestack/nlu/NLUContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.spokestack.spokestack.nlu;

import io.spokestack.spokestack.SpeechConfig;
import io.spokestack.spokestack.util.EventTracer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Context for NLU operations, including request metadata and a facility for
* dispatch of trace events.
*/
final class NLUContext {

private final EventTracer tracer;
private final List<TraceListener> listeners;
private Map<String, Object> requestMetadata;

/**
* Create a new dispatcher.
*
* @param config the dispatcher's configuration
*/
NLUContext(SpeechConfig config) {
int traceLevel = config.getInteger(
"trace-level",
EventTracer.Level.NONE.value());

this.tracer = new EventTracer(traceLevel);
this.listeners = new ArrayList<>();
}

/**
* Add a listener interested in receiving NLU trace events.
*
* @param listener the listener to add
*/
public void addTraceListener(TraceListener listener) {
this.listeners.add(listener);
}

/**
* @return the metadata for the current request.
*/
public Map<String, Object> getRequestMetadata() {
return requestMetadata;
}

/**
* Set the metadata for the current request.
* @param metadata the metadata for the current request.
*/
public void setRequestMetadata(Map<String, Object> metadata) {
this.requestMetadata = metadata;
}

/**
* Traces a debug level message.
*
* @param format trace message format string
* @param params trace message format parameters
*/
public void traceDebug(String format, Object... params) {
trace(EventTracer.Level.DEBUG, format, params);
}

/**
* Traces a performance level message.
*
* @param format trace message format string
* @param params trace message format parameters
*/
public void tracePerf(String format, Object... params) {
trace(EventTracer.Level.PERF, format, params);
}

/**
* Traces an informational level message.
*
* @param format trace message format string
* @param params trace message format parameters
*/
public void traceInfo(String format, Object... params) {
trace(EventTracer.Level.INFO, format, params);
}

/**
* Raises a trace event.
*
* @param level tracing level
* @param format trace message format string
* @param params trace message format parameters
*/
public void trace(
EventTracer.Level level,
String format,
Object... params) {
if (this.tracer.canTrace(level)) {
String message = String.format(format, params);
dispatchTrace(message);
}
}

/**
* Dispatches an NLU trace message.
*
* @param message the trace message to publish
*/
public void dispatchTrace(String message) {
for (TraceListener listener : this.listeners) {
try {
listener.onTrace(message);
} catch (Exception e) {
// failed traces fail in silence
}
}
}
}
25 changes: 0 additions & 25 deletions src/main/java/io/spokestack/spokestack/nlu/NLURequest.java

This file was deleted.

18 changes: 11 additions & 7 deletions src/main/java/io/spokestack/spokestack/nlu/NLUService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.spokestack.spokestack.nlu;

import java.util.Map;
import java.util.concurrent.Future;
import io.spokestack.spokestack.util.AsyncResult;

/**
* A simple interface for components that provide intent classification and slot
Expand All @@ -10,12 +9,17 @@
public interface NLUService {

/**
* Classifies a user utterance into an intent asynchronously.
* Classifies a user utterance. Classification should be performed on a
* background thread, but the use of an {@link AsyncResult} allows the
* caller to either block while waiting for a result or register a callback
* to be executed when the result is available.
*
* @param utterance The user utterance to be classified.
* @param context Any contextual information that should be sent along
* with the utterance to assist classification.
* @return A {@code Future} representing the completed classification task.
* @param context The current NLU context, containing request metadata and
* the ability to fire trace events.
* @return An {@link AsyncResult} representing the result of the
* classification task.
* @see AsyncResult#registerCallback(io.spokestack.spokestack.util.Callback)
*/
Future<NLUResult> classify(String utterance, Map<String, Object> context);
AsyncResult<NLUResult> classify(String utterance, NLUContext context);
}
14 changes: 14 additions & 0 deletions src/main/java/io/spokestack/spokestack/nlu/TraceListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.spokestack.spokestack.nlu;

/**
* A simple interface implemented by classes interested in receiving trace
* events.
*/
public interface TraceListener {

/**
* A notification that a trace event has occurred.
* @param message the trace event's message.
*/
void onTrace(String message);
}
61 changes: 61 additions & 0 deletions src/main/java/io/spokestack/spokestack/util/AsyncResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.spokestack.spokestack.util;

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
* A subclass of {@link FutureTask} that allows the user to register a callback
* to be executed when the task's result is available.
*
* @param <T> The type of result produced by the task and expected by the
* optional callback.
*/
public class AsyncResult<T> extends FutureTask<T> {

private Callback<T> completionCallback;

/**
* Create a new task.
*
* @param callable The {@code Callable} representing the task's work.
*/
public AsyncResult(@NotNull Callable<T> callable) {
super(callable);
}

/**
* Register a callback to be executed when the task's result is available.
* If the task has already been completed, the callback will be called
* immediately.
*
* @param callback The function to be called with the task's result.
*/
public void registerCallback(Callback<T> callback) {
this.completionCallback = callback;
if (isDone()) {
try {
callback.call(get());
} catch (CancellationException | InterruptedException
| ExecutionException e) {
callback.onError(e);
}
}
}

@Override
protected void done() {
if (this.completionCallback != null) {
try {
completionCallback.call(get());
} catch (CancellationException | InterruptedException
| ExecutionException e) {
completionCallback.onError(e);
}
}
super.done();
}
}
24 changes: 24 additions & 0 deletions src/main/java/io/spokestack/spokestack/util/Callback.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.spokestack.spokestack.util;

/**
* A simple interface representing a callback function called with a single
* argument. Since callbacks are often used to propagate results of
* asynchronous operations, the callback may also receive an error generated
* during the task meant to produce its result.
*
* @param <T> The type of result with which the callback should be executed.
*/
public interface Callback<T> {

/**
* Call the callback with the specified argument.
* @param arg The callback's argument.
*/
void call(T arg);

/**
* Call the callback with an error generated during an asynchronous task.
* @param err An error generated instead of the callback's intended result.
*/
void onError(Throwable err);
}
Loading

0 comments on commit b93f227

Please sign in to comment.