From dbaa1a1a2f394b44e5c54ca8fd33ecffced56c5f Mon Sep 17 00:00:00 2001 From: mehrad Date: Sat, 2 Oct 2021 23:15:57 -0700 Subject: [PATCH 1/3] Add support for translation API --- lib/prediction/localparserclient.ts | 37 +++++++++++++++ lib/prediction/predictor.ts | 32 +++++++------ tool/server.ts | 72 +++++++++++++++++++++++++++-- 3 files changed, 123 insertions(+), 18 deletions(-) diff --git a/lib/prediction/localparserclient.ts b/lib/prediction/localparserclient.ts index 399899638..5d74f797e 100644 --- a/lib/prediction/localparserclient.ts +++ b/lib/prediction/localparserclient.ts @@ -41,6 +41,7 @@ import { 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 { @@ -59,6 +60,22 @@ function compareScore(a : PredictionCandidate, b : PredictionCandidate) : number return b.score - a.score; } +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 + 1]; + } + return null; +} + + export default class LocalParserClient { private _locale : string; private _langPack : I18n.LanguagePack; @@ -262,4 +279,24 @@ export default class LocalParserClient { }; }); } + + async translateUtterance(input : string[], contextEntities : EntityMap|undefined, translationOptions : Record) : Promise { + if (contextEntities) { + const allEntities = Object.keys(contextEntities).map((ent) => ent.split(' ')); + for (const entity of allEntities) { + const span = substringSpan(input, entity); + if (span) { + input.splice(span[0], 0, '"'); + input.splice(span[1], 0, '"'); + } + } + } + const candidates = await this._predictor.predict('', input.join(' '), undefined, Translation_TASK, 'id-null', translationOptions); + return candidates.map((cand) => { + return { + answer: cand.answer, + score: cand.score.confidence ?? 1 + }; + }); + } } diff --git a/lib/prediction/predictor.ts b/lib/prediction/predictor.ts index 7bcde17d8..6980c3d79 100644 --- a/lib/prediction/predictor.ts +++ b/lib/prediction/predictor.ts @@ -151,14 +151,14 @@ class LocalWorker extends events.EventEmitter { this._requests.clear(); } - request(task : string, minibatch : Example[]) : Promise { + request(task : string, minibatch : Example[], options : Record) : Promise { 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); @@ -179,10 +179,11 @@ class RemoteWorker extends events.EventEmitter { start() {} stop() {} - async request(task : string, minibatch : Example[]) : Promise { + async request(task : string, minibatch : Example[], options : Record) : Promise { 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) { @@ -209,6 +210,7 @@ export default class Predictor { private _maxLatency : number; private _minibatchTask = ''; + private _minitbatchOptions = {}; private _minibatch : Example[] = []; private _minibatchStartTime = 0; @@ -225,13 +227,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]); @@ -241,10 +244,11 @@ export default class Predictor { }); } - private _startRequest(ex : Example, task : string, now : number) { + private _startRequest(ex : Example, task : string, options : Record, now : number) { assert(this._minibatch.length === 0); this._minibatch.push(ex); this._minibatchTask = task; + this._minitbatchOptions = options; this._minibatchStartTime = now; setTimeout(() => { @@ -253,23 +257,21 @@ export default class Predictor { }, this._maxLatency); } - private _addRequest(ex : Example, task : string) { + private _addRequest(ex : Example, task : string, options : Record) { 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 { - assert(typeof context === 'string'); - assert(typeof question === 'string'); + predict(context : string, question : string = DEFAULT_QUESTION, answer ?: string, task = 'almond', example_id ?: string, options : Record = {}) : Promise { // ensure we have a worker, in case it recently died if (!this._worker) @@ -281,7 +283,7 @@ export default class Predictor { resolve = _resolve; reject = _reject; }); - this._addRequest({ context, question, answer, resolve, reject }, task); + this._addRequest({ context, question, answer, example_id, resolve, reject }, task, options); return promise; } diff --git a/tool/server.ts b/tool/server.ts index 45865cb34..538fa8d42 100644 --- a/tool/server.ts +++ b/tool/server.ts @@ -39,6 +39,7 @@ interface Backend { tokenizer : I18n.BaseTokenizer; nlu : LocalParserClient; nlg ?: LocalParserClient; + translator ?: LocalParserClient; } declare global { @@ -148,6 +149,53 @@ async function queryNLG(params : Record, }); } + +interface TranslationData { + input : string; + tgt_locale : string + entities ?: EntityMap; + limit ?: string; + alignment ?: boolean; + src_locale ?: string; +} +const Translation_PARAMS = { + input: 'string', + tgt_locale: 'string', + entities: '?object', + limit: '?number', + alignment: '?boolean', + src_locale: '?string', +}; + +async function queryTranslate(params : Record, + data : TranslationData, + res : express.Response) { + const app = res.app; + + if (params.locale !== app.args.locale) { + res.status(400).json({ error: 'Unsupported language' }); + return; + } + + if (! data.src_locale) + data.src_locale = 'en-US'; + + const translationOptions : Record = { + 'do_alignment': data.alignment, + 'align_remove_output_quotation': true, + 'src_locale': data.src_locale, + 'tgt_locale': data.tgt_locale + }; + + const result = await res.app.backend.translator!.translateUtterance( + data.input.split(' '), data.entities, translationOptions); + res.json({ + candidates: result.slice(0, data.limit ? parseInt(data.limit) : undefined), + }); +} + + + export function initArgparse(subparsers : argparse.SubParser) { const parser = subparsers.add_parser('server', { add_help: true, @@ -159,13 +207,17 @@ export function initArgparse(subparsers : argparse.SubParser) { default: 8400, }); parser.add_argument('--nlu-model', { - required: true, + required: false, help: "Path to the NLU model, pointing to a model directory.", }); parser.add_argument('--nlg-model', { required: false, help: "Path to the NLG model, pointing to a model directory.", }); + parser.add_argument('--translation-model', { + required: false, + help: "Path to the Translation model, pointing to a model directory.", + }); parser.add_argument('--thingpedia', { required: true, help: 'Path to ThingTalk file containing class definitions.' @@ -199,7 +251,9 @@ export async function execute(args : any) { tokenizer: i18n.getTokenizer(), nlu: new LocalParserClient(args.nlu_model, args.locale, undefined, undefined, tpClient) }; - app.backend.nlu.start(); + + if (args.nlu_model) + app.backend.nlu.start(); if (args.nlg_model && args.nlg_model !== args.nlu_model) { app.backend.nlg = new LocalParserClient(args.nlg_model, args.locale, undefined, undefined, tpClient); app.backend.nlg.start(); @@ -207,6 +261,11 @@ export async function execute(args : any) { app.backend.nlg = app.backend.nlu; } + if (args.translation_model) { + app.backend.translator = new LocalParserClient(args.translation_model, args.locale, undefined, undefined, tpClient); + app.backend.translator.start(); + } + app.args = args; app.set('port', args.port); @@ -227,6 +286,10 @@ export async function execute(args : any) { queryNLG(req.params, req.body, res).catch(next); }); + app.post('/:locale/translate', qv.validatePOST(Translation_PARAMS, { accept: 'application/json' }), (req, res, next) => { + queryTranslate(req.params, req.body, res).catch(next); + }); + app.post('/:locale/tokenize', qv.validatePOST({ q: 'string', entities: '?object' }, { accept: 'application/json' }), (req, res, next) => { tokenize(req.params, req.body, res).catch(next); }); @@ -246,8 +309,11 @@ export async function execute(args : any) { process.on('SIGTERM', resolve); }); - await app.backend.nlu.stop(); + if (app.backend.nlu) + await app.backend.nlu.stop(); if (app.backend.nlg !== app.backend.nlu) await app.backend.nlg.stop(); + if (app.backend.translator) + await app.backend.translator.stop(); server.close(); } From 58edbfbeb21165f770d3f6511c293ad38b935d18 Mon Sep 17 00:00:00 2001 From: mehrad Date: Wed, 6 Oct 2021 17:04:00 -0700 Subject: [PATCH 2/3] Address PR comments - fix syntax - add transaltion interface for remoteParserClient --- lib/prediction/localparserclient.ts | 33 +++---------------------- lib/prediction/predictor.ts | 12 ++++----- lib/prediction/remoteparserclient.ts | 24 ++++++++++++++++++ lib/utils/misc-utils.ts | 34 +++++++++++++++++++++++++ tool/server.ts | 37 +++++++++++++++++++++++----- 5 files changed, 99 insertions(+), 41 deletions(-) diff --git a/lib/prediction/localparserclient.ts b/lib/prediction/localparserclient.ts index 5d74f797e..36b3df007 100644 --- a/lib/prediction/localparserclient.ts +++ b/lib/prediction/localparserclient.ts @@ -41,7 +41,7 @@ import { const SEMANTIC_PARSING_TASK = 'almond'; const NLU_TASK = 'almond_dialogue_nlu'; const NLG_TASK = 'almond_dialogue_nlg'; -const Translation_TASK = 'almond_translate'; +const TRANSLATION_TASK = 'almond_translate'; const NLG_QUESTION = 'what should the agent say ?'; export interface LocalParserOptions { @@ -60,22 +60,6 @@ function compareScore(a : PredictionCandidate, b : PredictionCandidate) : number return b.score - a.score; } -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 + 1]; - } - return null; -} - - export default class LocalParserClient { private _locale : string; private _langPack : I18n.LanguagePack; @@ -280,18 +264,9 @@ export default class LocalParserClient { }); } - async translateUtterance(input : string[], contextEntities : EntityMap|undefined, translationOptions : Record) : Promise { - if (contextEntities) { - const allEntities = Object.keys(contextEntities).map((ent) => ent.split(' ')); - for (const entity of allEntities) { - const span = substringSpan(input, entity); - if (span) { - input.splice(span[0], 0, '"'); - input.splice(span[1], 0, '"'); - } - } - } - const candidates = await this._predictor.predict('', input.join(' '), undefined, Translation_TASK, 'id-null', translationOptions); + async translateUtterance(input : string[], contextEntities : EntityMap|undefined, translationOptions : Record) : Promise { + input = Utils.qpisEntities(input, contextEntities); + const candidates = await this._predictor.predict('', input.join(' '), undefined, TRANSLATION_TASK, 'id-null', translationOptions); return candidates.map((cand) => { return { answer: cand.answer, diff --git a/lib/prediction/predictor.ts b/lib/prediction/predictor.ts index 6980c3d79..c46f6c638 100644 --- a/lib/prediction/predictor.ts +++ b/lib/prediction/predictor.ts @@ -151,7 +151,7 @@ class LocalWorker extends events.EventEmitter { this._requests.clear(); } - request(task : string, minibatch : Example[], options : Record) : Promise { + request(task : string, minibatch : Example[], options : Record) : Promise { const id = this._nextId ++; return new Promise((resolve, reject) => { @@ -179,7 +179,7 @@ class RemoteWorker extends events.EventEmitter { start() {} stop() {} - async request(task : string, minibatch : Example[], options : Record) : Promise { + async request(task : string, minibatch : Example[], options : Record) : Promise { const response = await Tp.Helpers.Http.post(this._url, JSON.stringify({ task, instances: minibatch, @@ -244,7 +244,7 @@ export default class Predictor { }); } - private _startRequest(ex : Example, task : string, options : Record, now : number) { + private _startRequest(ex : Example, task : string, options : Record, now : number) { assert(this._minibatch.length === 0); this._minibatch.push(ex); this._minibatchTask = task; @@ -257,7 +257,7 @@ export default class Predictor { }, this._maxLatency); } - private _addRequest(ex : Example, task : string, options : Record) { + private _addRequest(ex : Example, task : string, options : Record) { const now = Date.now(); if (this._minibatch.length === 0) { this._startRequest(ex, task, options, now); @@ -271,7 +271,7 @@ export default class Predictor { } } - predict(context : string, question : string = DEFAULT_QUESTION, answer ?: string, task = 'almond', example_id ?: string, options : Record = {}) : Promise { + predict(context : string, question : string = DEFAULT_QUESTION, answer ?: string, task = 'almond', example_id ?: string, options : Record = {}) : Promise { // ensure we have a worker, in case it recently died if (!this._worker) @@ -283,7 +283,7 @@ export default class Predictor { resolve = _resolve; reject = _reject; }); - this._addRequest({ context, question, answer, example_id, resolve, reject }, task, options); + this._addRequest({ context, question, answer, resolve, reject }, task, options); return promise; } diff --git a/lib/prediction/remoteparserclient.ts b/lib/prediction/remoteparserclient.ts index ad744b7c5..bab93ee4d 100644 --- a/lib/prediction/remoteparserclient.ts +++ b/lib/prediction/remoteparserclient.ts @@ -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'; @@ -168,4 +169,27 @@ export default class RemoteParserClient { return parsed.candidates; } + + async translateUtterance(input : string[], contextEntities : EntityMap|undefined, translationOptions : Record) : Promise { + input = Utils.qpisEntities(input, contextEntities); + + const data = { + input: input.join(' '), + tgt_locale: translationOptions.tgt_locale, + entities: contextEntities, + alignment: translationOptions.alignment, + src_locale: translationOptions.src_locale, + align_remove_output_quotation: translationOptions.align_remove_output_quotation + }; + + 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; + } + } diff --git a/lib/utils/misc-utils.ts b/lib/utils/misc-utils.ts index 3939c6ede..26d38a58a 100644 --- a/lib/utils/misc-utils.ts +++ b/lib/utils/misc-utils.ts @@ -31,6 +31,7 @@ import { makeDummyEntity, makeDummyEntities, renumberEntities, + EntityMap, } from './entity-utils'; class ValidationError extends Error { @@ -200,6 +201,37 @@ 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 + 1]; + } + return null; +} + + +function qpisEntities(input : string[], contextEntities : EntityMap|undefined) : string[] { + if (contextEntities) { + const allEntities = Object.keys(contextEntities).map((ent) => ent.split(' ')); + for (const entity of allEntities) { + const span = substringSpan(input, entity); + if (span) { + input.splice(span[0], 0, '"'); + input.splice(span[1], 0, '"'); + } + } + } + return input; +} + + export { splitParams, split, @@ -212,4 +244,6 @@ export { makeDummyEntity, makeDummyEntities, renumberEntities, + + qpisEntities }; diff --git a/tool/server.ts b/tool/server.ts index 538fa8d42..4e70056e6 100644 --- a/tool/server.ts +++ b/tool/server.ts @@ -157,6 +157,7 @@ interface TranslationData { limit ?: string; alignment ?: boolean; src_locale ?: string; + align_remove_output_quotation ?: boolean } const Translation_PARAMS = { input: 'string', @@ -165,8 +166,31 @@ const Translation_PARAMS = { limit: '?number', alignment: '?boolean', src_locale: '?string', + align_remove_output_quotation: '?boolean' }; + +// const VALID_PARSER_OPTIONS = new Set([ +// "num_beams", +// "num_beam_groups", +// "diversity_penalty", +// "num_outputs", +// "no_repeat_ngram_size", +// "top_p", +// "top_k", +// "repetition_penalty", +// "temperature", +// "max_output_length", +// "reduce_metrics", +// "database_dir", +// "do_alignment", +// "align_preserve_input_quotation", +// "align_remove_output_quotation", +// "src_locale", +// "tgt_locale", +// "translate_example_split" +// ]); + async function queryTranslate(params : Record, data : TranslationData, res : express.Response) { @@ -177,14 +201,15 @@ async function queryTranslate(params : Record, return; } - if (! data.src_locale) + if (!data.src_locale) data.src_locale = 'en-US'; - const translationOptions : Record = { - 'do_alignment': data.alignment, - 'align_remove_output_quotation': true, - 'src_locale': data.src_locale, - 'tgt_locale': data.tgt_locale + const translationOptions : Record = { + 'src_locale': data.src_locale, + 'tgt_locale': data.tgt_locale, + 'do_alignment': data.alignment, + 'align_remove_output_quotation': data.align_remove_output_quotation, + }; const result = await res.app.backend.translator!.translateUtterance( From 417d7d2d922176e70594ae5964116d4ece9aba57 Mon Sep 17 00:00:00 2001 From: mehrad Date: Wed, 6 Oct 2021 21:12:13 -0700 Subject: [PATCH 3/3] Address PR comments (2) - declare a proper type for generation arguments that user can override when calling the genienlp parser - some refactoring for better reading --- lib/prediction/localparserclient.ts | 9 ++++---- lib/prediction/predictor.ts | 11 +++++----- lib/prediction/remoteparserclient.ts | 18 +++++++++------- lib/prediction/types.ts | 20 ++++++++++++++++++ lib/utils/misc-utils.ts | 15 +++++++------- tool/server.ts | 31 +++++----------------------- 6 files changed, 55 insertions(+), 49 deletions(-) diff --git a/lib/prediction/localparserclient.ts b/lib/prediction/localparserclient.ts index 36b3df007..1a3142453 100644 --- a/lib/prediction/localparserclient.ts +++ b/lib/prediction/localparserclient.ts @@ -35,7 +35,8 @@ import { PredictionCandidate, PredictionResult, GenerationResult, - ExactMatcher + ExactMatcher, + GenerationOptions } from './types'; const SEMANTIC_PARSING_TASK = 'almond'; @@ -264,9 +265,9 @@ export default class LocalParserClient { }); } - async translateUtterance(input : string[], contextEntities : EntityMap|undefined, translationOptions : Record) : Promise { - input = Utils.qpisEntities(input, contextEntities); - const candidates = await this._predictor.predict('', input.join(' '), undefined, TRANSLATION_TASK, 'id-null', translationOptions); + async translateUtterance(input : string[], entities : string[]|undefined, generationOptions : GenerationOptions) : Promise { + 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, diff --git a/lib/prediction/predictor.ts b/lib/prediction/predictor.ts index c46f6c638..15e525068 100644 --- a/lib/prediction/predictor.ts +++ b/lib/prediction/predictor.ts @@ -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'; @@ -151,7 +152,7 @@ class LocalWorker extends events.EventEmitter { this._requests.clear(); } - request(task : string, minibatch : Example[], options : Record) : Promise { + request(task : string, minibatch : Example[], options : GenerationOptions) : Promise { const id = this._nextId ++; return new Promise((resolve, reject) => { @@ -179,7 +180,7 @@ class RemoteWorker extends events.EventEmitter { start() {} stop() {} - async request(task : string, minibatch : Example[], options : Record) : Promise { + async request(task : string, minibatch : Example[], options : GenerationOptions) : Promise { const response = await Tp.Helpers.Http.post(this._url, JSON.stringify({ task, instances: minibatch, @@ -244,7 +245,7 @@ export default class Predictor { }); } - private _startRequest(ex : Example, task : string, options : Record, now : number) { + private _startRequest(ex : Example, task : string, options : GenerationOptions, now : number) { assert(this._minibatch.length === 0); this._minibatch.push(ex); this._minibatchTask = task; @@ -257,7 +258,7 @@ export default class Predictor { }, this._maxLatency); } - private _addRequest(ex : Example, task : string, options : Record) { + private _addRequest(ex : Example, task : string, options : GenerationOptions) { const now = Date.now(); if (this._minibatch.length === 0) { this._startRequest(ex, task, options, now); @@ -271,7 +272,7 @@ export default class Predictor { } } - predict(context : string, question : string = DEFAULT_QUESTION, answer ?: string, task = 'almond', example_id ?: string, options : Record = {}) : Promise { + predict(context : string, question : string = DEFAULT_QUESTION, answer ?: string, task = 'almond', example_id ?: string, options : GenerationOptions = {}) : Promise { // ensure we have a worker, in case it recently died if (!this._worker) diff --git a/lib/prediction/remoteparserclient.ts b/lib/prediction/remoteparserclient.ts index bab93ee4d..0d9798666 100644 --- a/lib/prediction/remoteparserclient.ts +++ b/lib/prediction/remoteparserclient.ts @@ -31,6 +31,7 @@ import { PredictionResult, GenerationResult, ExactMatcher, + GenerationOptions, } from './types'; import ExactMatcherBuilder from './exactbuilder'; @@ -170,16 +171,19 @@ export default class RemoteParserClient { return parsed.candidates; } - async translateUtterance(input : string[], contextEntities : EntityMap|undefined, translationOptions : Record) : Promise { - input = Utils.qpisEntities(input, contextEntities); + async translateUtterance(input : string[], entities : string[]|undefined, generationOptions : GenerationOptions) : Promise { + input = Utils.qpisEntities(input, entities); const data = { input: input.join(' '), - tgt_locale: translationOptions.tgt_locale, - entities: contextEntities, - alignment: translationOptions.alignment, - src_locale: translationOptions.src_locale, - align_remove_output_quotation: translationOptions.align_remove_output_quotation + tgt_locale: generationOptions.tgt_locale, + entities: entities, + alignment: generationOptions.do_alignment, + src_locale: generationOptions.src_locale, + // 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), { diff --git a/lib/prediction/types.ts b/lib/prediction/types.ts index c59f675df..fd18eb77c 100644 --- a/lib/prediction/types.ts +++ b/lib/prediction/types.ts @@ -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 +} + + export interface ParseOptions { thingtalk_version ?: string; store ?: string; diff --git a/lib/utils/misc-utils.ts b/lib/utils/misc-utils.ts index 26d38a58a..f5f74ca0e 100644 --- a/lib/utils/misc-utils.ts +++ b/lib/utils/misc-utils.ts @@ -31,7 +31,6 @@ import { makeDummyEntity, makeDummyEntities, renumberEntities, - EntityMap, } from './entity-utils'; class ValidationError extends Error { @@ -211,20 +210,22 @@ function substringSpan(sequence : string[], substring : string[]) : [number, num } } if (found) - return [i, i + substring.length + 1]; + return [i, i + substring.length]; } return null; } -function qpisEntities(input : string[], contextEntities : EntityMap|undefined) : string[] { - if (contextEntities) { - const allEntities = Object.keys(contextEntities).map((ent) => ent.split(' ')); - for (const entity of allEntities) { +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, '"'); - input.splice(span[1], 0, '"'); + + // add 1 cause previous splice shift tokens to the right + input.splice(span[1] + 1, 0, '"'); } } } diff --git a/tool/server.ts b/tool/server.ts index 4e70056e6..f93ed1d1b 100644 --- a/tool/server.ts +++ b/tool/server.ts @@ -32,6 +32,7 @@ import * as Utils from '../lib/utils/misc-utils'; import { EntityMap } from '../lib/utils/entity-utils'; import LocalParserClient from '../lib/prediction/localparserclient'; import * as I18n from '../lib/i18n'; +import {GenerationOptions} from "../lib/prediction/types"; interface Backend { schemas : ThingTalk.SchemaRetriever; @@ -153,7 +154,7 @@ async function queryNLG(params : Record, interface TranslationData { input : string; tgt_locale : string - entities ?: EntityMap; + entities ?: string[]; limit ?: string; alignment ?: boolean; src_locale ?: string; @@ -162,35 +163,13 @@ interface TranslationData { const Translation_PARAMS = { input: 'string', tgt_locale: 'string', - entities: '?object', + entities: '?array', limit: '?number', alignment: '?boolean', src_locale: '?string', align_remove_output_quotation: '?boolean' }; - -// const VALID_PARSER_OPTIONS = new Set([ -// "num_beams", -// "num_beam_groups", -// "diversity_penalty", -// "num_outputs", -// "no_repeat_ngram_size", -// "top_p", -// "top_k", -// "repetition_penalty", -// "temperature", -// "max_output_length", -// "reduce_metrics", -// "database_dir", -// "do_alignment", -// "align_preserve_input_quotation", -// "align_remove_output_quotation", -// "src_locale", -// "tgt_locale", -// "translate_example_split" -// ]); - async function queryTranslate(params : Record, data : TranslationData, res : express.Response) { @@ -204,7 +183,7 @@ async function queryTranslate(params : Record, if (!data.src_locale) data.src_locale = 'en-US'; - const translationOptions : Record = { + const generationOptions : GenerationOptions = { 'src_locale': data.src_locale, 'tgt_locale': data.tgt_locale, 'do_alignment': data.alignment, @@ -213,7 +192,7 @@ async function queryTranslate(params : Record, }; const result = await res.app.backend.translator!.translateUtterance( - data.input.split(' '), data.entities, translationOptions); + data.input.split(' '), data.entities, generationOptions); res.json({ candidates: result.slice(0, data.limit ? parseInt(data.limit) : undefined), });