Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion lib/prediction/localparserclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ import {
PredictionCandidate,
PredictionResult,
GenerationResult,
ExactMatcher
ExactMatcher,
GenerationOptions
} from './types';

const SEMANTIC_PARSING_TASK = 'almond';
const NLU_TASK = 'almond_dialogue_nlu';
const NLG_TASK = 'almond_dialogue_nlg';
const TRANSLATION_TASK = 'almond_translate';
const NLG_QUESTION = 'what should the agent say ?';

export interface LocalParserOptions {
Expand Down Expand Up @@ -262,4 +264,15 @@ export default class LocalParserClient {
};
});
}

async translateUtterance(input : string[], entities : string[]|undefined, generationOptions : GenerationOptions) : Promise<GenerationResult[]> {
input = Utils.qpisEntities(input, entities);
const candidates = await this._predictor.predict('', input.join(' '), undefined, TRANSLATION_TASK, undefined, generationOptions);
return candidates.map((cand) => {
return {
answer: cand.answer,
score: cand.score.confidence ?? 1
};
});
}
}
33 changes: 18 additions & 15 deletions lib/prediction/predictor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as child_process from 'child_process';
import * as Tp from 'thingpedia';

import JsonDatagramSocket from '../utils/json_datagram_socket';
import {GenerationOptions} from "./types";

const DEFAULT_QUESTION = 'translate from english to thingtalk';

Expand Down Expand Up @@ -151,14 +152,14 @@ class LocalWorker extends events.EventEmitter {
this._requests.clear();
}

request(task : string, minibatch : Example[]) : Promise<RawPredictionCandidate[][]> {
request(task : string, minibatch : Example[], options : GenerationOptions) : Promise<RawPredictionCandidate[][]> {
const id = this._nextId ++;

return new Promise((resolve, reject) => {
this._requests.set(id, { resolve, reject });
//console.error(`${this._requests.size} pending requests`);

this._stream!.write({ id, task, instances: minibatch }, (err : Error | undefined | null) => {
this._stream!.write({ id, task, instances: minibatch, options: options }, (err : Error | undefined | null) => {
if (err) {
console.error(err);
reject(err);
Expand All @@ -179,10 +180,11 @@ class RemoteWorker extends events.EventEmitter {
start() {}
stop() {}

async request(task : string, minibatch : Example[]) : Promise<RawPredictionCandidate[][]> {
async request(task : string, minibatch : Example[], options : GenerationOptions) : Promise<RawPredictionCandidate[][]> {
const response = await Tp.Helpers.Http.post(this._url, JSON.stringify({
task,
instances: minibatch
instances: minibatch,
options: options
}), { dataContentType: 'application/json', accept: 'application/json' });
return JSON.parse(response).predictions.map((instance : any) : RawPredictionCandidate[] => {
if (instance.candidates) {
Expand All @@ -209,6 +211,7 @@ export default class Predictor {
private _maxLatency : number;

private _minibatchTask = '';
private _minitbatchOptions = {};
private _minibatch : Example[] = [];
private _minibatchStartTime = 0;

Expand All @@ -225,13 +228,14 @@ export default class Predictor {
private _flushRequest() {
const minibatch = this._minibatch;
const task = this._minibatchTask;
const options = this._minitbatchOptions;

this._minibatch = [];
this._minibatchTask = '';
this._minitbatchOptions = {};
this._minibatchStartTime = 0;

//console.error(`minibatch: ${minibatch.length} instances`);

this._worker!.request(task, minibatch).then((candidates) => {
this._worker!.request(task, minibatch, options).then((candidates) => {
assert(candidates.length === minibatch.length);
for (let i = 0; i < minibatch.length; i++)
minibatch[i].resolve(candidates[i]);
Expand All @@ -241,10 +245,11 @@ export default class Predictor {
});
}

private _startRequest(ex : Example, task : string, now : number) {
private _startRequest(ex : Example, task : string, options : GenerationOptions, now : number) {
assert(this._minibatch.length === 0);
this._minibatch.push(ex);
this._minibatchTask = task;
this._minitbatchOptions = options;
this._minibatchStartTime = now;

setTimeout(() => {
Expand All @@ -253,23 +258,21 @@ export default class Predictor {
}, this._maxLatency);
}

private _addRequest(ex : Example, task : string) {
private _addRequest(ex : Example, task : string, options : GenerationOptions) {
const now = Date.now();
if (this._minibatch.length === 0) {
this._startRequest(ex, task, now);
this._startRequest(ex, task, options, now);
} else if (this._minibatchTask === task &&
(now - this._minibatchStartTime < this._maxLatency) &&
this._minibatch.length < this._minibatchSize) {
this._minibatch.push(ex);
} else {
this._flushRequest();
this._startRequest(ex, task, now);
this._startRequest(ex, task, options, now);
}
}

predict(context : string, question = DEFAULT_QUESTION, answer ?: string, task = 'almond', example_id ?: string) : Promise<RawPredictionCandidate[]> {
assert(typeof context === 'string');
assert(typeof question === 'string');
predict(context : string, question : string = DEFAULT_QUESTION, answer ?: string, task = 'almond', example_id ?: string, options : GenerationOptions = {}) : Promise<RawPredictionCandidate[]> {

// ensure we have a worker, in case it recently died
if (!this._worker)
Expand All @@ -281,7 +284,7 @@ export default class Predictor {
resolve = _resolve;
reject = _reject;
});
this._addRequest({ context, question, answer, resolve, reject }, task);
this._addRequest({ context, question, answer, resolve, reject }, task, options);

return promise;
}
Expand Down
28 changes: 28 additions & 0 deletions lib/prediction/remoteparserclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import * as ThingTalk from 'thingtalk';
import * as Tp from 'thingpedia';
import * as Utils from '../utils/misc-utils';
import qs from 'qs';

import { EntityMap } from '../utils/entity-utils';
Expand All @@ -30,6 +31,7 @@ import {
PredictionResult,
GenerationResult,
ExactMatcher,
GenerationOptions,
} from './types';
import ExactMatcherBuilder from './exactbuilder';

Expand Down Expand Up @@ -168,4 +170,30 @@ export default class RemoteParserClient {

return parsed.candidates;
}

async translateUtterance(input : string[], entities : string[]|undefined, generationOptions : GenerationOptions) : Promise<GenerationResult[]> {
input = Utils.qpisEntities(input, entities);

const data = {
input: input.join(' '),
tgt_locale: generationOptions.tgt_locale,
entities: entities,
alignment: generationOptions.do_alignment,
src_locale: generationOptions.src_locale,
Comment thread
Mehrad0711 marked this conversation as resolved.
// always remove quotation marks in the output string used to mark entity boundaries
align_remove_output_quotation: true,
// always break input utterance into individual sentences before translation
translate_example_split: true
};

const response = await Tp.Helpers.Http.post(`${this._baseUrl}/translate`, JSON.stringify(data), {
dataContentType: 'application/json' //'
});
const parsed = JSON.parse(response);
if (parsed.error)
throw new Error('Error received from Genie server: ' + parsed.error);

return parsed.candidates;
}

}
20 changes: 20 additions & 0 deletions lib/prediction/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ export interface ExactMatcher {
get(tokens : string[]) : string[][]|null;
}

export interface GenerationOptions {
num_beams ?: number
num_beam_groups ?: number
diversity_penalty ?: number
num_outputs ?: number
no_repeat_ngram_size ?: number
top_p ?: number
top_k ?: number
repetition_penalty ?: number
temperature ?: number
max_output_length ?: number
src_locale ?: string
tgt_locale ?: string
do_alignment ?: boolean
align_preserve_input_quotation ?: boolean
align_remove_output_quotation ?: boolean
translate_example_split ?: boolean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need so many options? Seriously, let's cut this down to nothing, and we hardcode whatever is meaningful for Genie.

@Mehrad0711 Mehrad0711 Oct 7, 2021

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ignoring "yagni" here cause I foresee using translation api for translating po and other stuff too so it's better to add support for modifying all generation args in genienlp right now once for all.
This is just the interface. In (local|remote)_predictor and server I changed it to read only the necessary options.

}


export interface ParseOptions {
thingtalk_version ?: string;
store ?: string;
Expand Down
35 changes: 35 additions & 0 deletions lib/utils/misc-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,39 @@ function isHumanEntity(type : Type|string) : boolean {
return false;
}

function substringSpan(sequence : string[], substring : string[]) : [number, number] | null {
for (let i=0; i < sequence.length; i++) {
let found = true;
for (let j = 0; j < substring.length; j++) {
if (sequence[i+j] !== substring[j]) {
found = false;
break;
}
}
if (found)
return [i, i + substring.length];
}
return null;
}


function qpisEntities(input : string[], entities : string[]|undefined) : string[] {
if (entities) {
const entityTokens = entities.map((ent) => ent.split(' '));
for (const entity of entityTokens) {
const span = substringSpan(input, entity);
if (span) {
input.splice(span[0], 0, '"');

// add 1 cause previous splice shift tokens to the right
input.splice(span[1] + 1, 0, '"');
}
}
}
return input;
}


export {
splitParams,
split,
Expand All @@ -212,4 +245,6 @@ export {
makeDummyEntity,
makeDummyEntities,
renumberEntities,

qpisEntities
};
Loading