package edu.stanford.nlp.sempre.interactive; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.testng.collections.Lists; import com.google.common.collect.ImmutableList; import edu.stanford.nlp.sempre.ActionFormula; import edu.stanford.nlp.sempre.BeamParser; import edu.stanford.nlp.sempre.Derivation; import edu.stanford.nlp.sempre.Example; import edu.stanford.nlp.sempre.Formula; import edu.stanford.nlp.sempre.Formulas; import edu.stanford.nlp.sempre.IdentityFn; import edu.stanford.nlp.sempre.Json; import edu.stanford.nlp.sempre.Master; import edu.stanford.nlp.sempre.Params; import edu.stanford.nlp.sempre.Parser; import edu.stanford.nlp.sempre.Rule; import edu.stanford.nlp.sempre.SemanticFn; import fig.basic.LispTree; import fig.basic.LogInfo; import fig.basic.Option; import fig.basic.Ref; /** * Utilities for interactive learning * * @author sidaw */ public final class InteractiveUtils { public static class Options { @Option(gloss = "use the best formula when no match or not provided") public boolean useBestFormula = false; @Option(gloss = "path to the citations") public String citationPath; @Option(gloss = "verbose") public int verbose = 0; } public static Options opts = new Options(); private InteractiveUtils() { } // dont spam my log when reading things in the beginning... public static boolean fakeLog = false; public static Derivation stripDerivation(Derivation deriv) { while (deriv.rule.sem instanceof IdentityFn) { deriv = deriv.child(0); } return deriv; } public static Derivation stripBlock(Derivation deriv) { if (opts.verbose > 0) LogInfo.logs("StripBlock %s %s %s", deriv, deriv.rule, deriv.cat); while ((deriv.rule.sem instanceof BlockFn || deriv.rule.sem instanceof IdentityFn) && deriv.children.size() == 1) { deriv = deriv.child(0); } return deriv; } public static List<Derivation> derivsfromJson(String jsonDef, Parser parser, Params params, Ref<Master.Response> refResponse) { @SuppressWarnings("unchecked") List<Object> body = Json.readValueHard(jsonDef, List.class); // string together the body definition List<Derivation> allDerivs = new ArrayList<>(); int numFailed = 0; for (Object obj : body) { @SuppressWarnings("unchecked") List<String> pair = (List<String>) obj; String utt = pair.get(0); String formula = pair.get(1); if (formula.equals("()")) { LogInfo.logs("Error: Got empty formula"); continue; } Example.Builder b = new Example.Builder(); // b.setId("session:" + sessionId); b.setUtterance(utt); Example ex = b.createExample(); ex.preprocess(); LogInfo.logs("Parsing body: %s", ex.utterance); ((InteractiveBeamParser)parser).parseWithoutExecuting(params, ex, false); boolean found = false; Formula targetFormula = Formulas.fromLispTree(LispTree.proto.parseFromString(formula)); for (Derivation d : ex.predDerivations) { // LogInfo.logs("considering: %s", d.formula.toString()); if (d.formula.equals(targetFormula)) { found = true; allDerivs.add(stripDerivation(d)); break; } } if (!found && !formula.equals("?")) { LogInfo.errors("matching formula not found: %s :: %s", utt, formula); numFailed++; } // just making testing easier, use top derivation when we formula is not // given if (!found && ex.predDerivations.size() > 0 && (formula.equals("?") || formula == null || opts.useBestFormula)) allDerivs.add(stripDerivation(ex.predDerivations.get(0))); else if (!found) { Derivation res = new Derivation.Builder().formula(targetFormula) // setting start to -1 is important, // which grammarInducer interprets to mean we do not want partial // rules .withCallable(new SemanticFn.CallInfo("$Action", -1, -1, null, new ArrayList<>())).createDerivation(); allDerivs.add(res); } } if (refResponse != null) { refResponse.value.stats.put("num_failed", numFailed); refResponse.value.stats.put("num_body", body.size()); } // LogInfo.logs("returning deriv list %s, \n %s", allDerivs.toString(), // jsonDef); return allDerivs; } public static List<String> utterancefromJson(String jsonDef, boolean tokenize) { @SuppressWarnings("unchecked") List<Object> body = Json.readValueHard(jsonDef, List.class); // string together the body definition List<String> utts = new ArrayList<>(); for (int i = 0; i < body.size(); i++) { Object obj = body.get(i); @SuppressWarnings("unchecked") List<String> pair = (List<String>) obj; String utt = pair.get(0); Example.Builder b = new Example.Builder(); // b.setId("session:" + sessionId); b.setUtterance(utt); Example ex = b.createExample(); ex.preprocess(); if (tokenize) { utts.addAll(ex.getTokens()); if (i != body.size() - 1 && !utts.get(utts.size() - 1).equals(";")) utts.add(";"); } else { utts.add(String.join(" ", ex.getTokens())); } } return utts; } public static synchronized void addRuleInteractive(Rule rule, Parser parser) { LogInfo.logs("addRuleInteractive: %s", rule); if (parser instanceof InteractiveBeamParser) { parser.addRule(rule); } else { throw new RuntimeException("interactively adding rule not supported for paser " + parser.getClass().toString()); } } static Rule blockRule(ActionFormula.Mode mode) { BlockFn b = new BlockFn(mode); b.init(LispTree.proto.parseFromString("(BlockFn sequential)")); return new Rule("$Action", Lists.newArrayList("$Action", "$Action"), b); } public static Derivation combine(List<Derivation> children) { ActionFormula.Mode mode = ActionFormula.Mode.sequential; if (children.size() == 1) { return children.get(0); } Formula f = new ActionFormula(mode, children.stream().map(d -> d.formula).collect(Collectors.toList())); Derivation res = new Derivation.Builder().formula(f) // setting start to -1 is important, // which grammarInducer interprets to mean we do not want partial rules .withCallable(new SemanticFn.CallInfo("$Action", -1, -1, blockRule(mode), ImmutableList.copyOf(children))) .createDerivation(); return res; } public static String getParseStatus(Example ex) { return GrammarInducer.getParseStatus(ex).toString(); } public static void cite(Derivation match, Example ex) { CitationTracker tracker = new CitationTracker(ex.id, ex); tracker.citeAll(match); } }