/**
*
* Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com
*
* This file is part of Freedomotic
*
* This Program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* This Program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* Freedomotic; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
package com.freedomotic.nlp;
import com.freedomotic.api.AbstractConsumer;
import com.freedomotic.api.EventTemplate;
import com.freedomotic.bus.BusService;
import com.freedomotic.core.Resolver;
import com.freedomotic.events.GenericEvent;
import com.freedomotic.exceptions.NoResultsException;
import com.freedomotic.exceptions.UnableToExecuteException;
import com.freedomotic.exceptions.VariableResolutionException;
import com.freedomotic.reactions.Command;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
/**
* Listen for free-form (natural language) text commands on channel
* <strong>"app.commands.interpreter.nlp"</strong> and using NLP tools execute
* the most similar command that the framework has in memory. For example a
* speech recognition utility may return a free-form text that can be
* interpreted by this module as an executable command. Another example is a
* chat bot that executes text commands.
*
* It expects a command with the following properties
* <ul>
* <li>text = A_STRING</li>
* </ul>
*
* It replies back if the received command cannot be interpreted, so you can
* send the command and expect a reply in a given timeout. Read the see section
* for more info.
*
* @see BusService#send(com.freedomotic.reactions.Command)
* @author Enrico Nicoletti
*/
public class CommandsNlpService extends AbstractConsumer {
private static final Logger LOG = LoggerFactory.getLogger(CommandsNlpService.class.getName());
private static final String MESSAGING_CHANNEL = "app.commands.interpreter.nlp";
// Messaging related parameters
public static final String PARAM_NLP_TEXT = "text";
// Dependencies
private final NlpCommand nlpCommands;
@Inject
public CommandsNlpService(NlpCommand nlpCommands, BusService busService) {
super(busService);
this.nlpCommands = nlpCommands;
}
@Override
public void onCommand(final Command command) throws UnableToExecuteException {
String text = command.getProperty(PARAM_NLP_TEXT);
// Use NLP to find the most similar Command using the given free-form text
Command mostSimilar;
try {
mostSimilar = findMostSimilarCommand(text);
// Generate an almost empty event used to resolve commands properites (eg: current time and date)
GenericEvent event = new GenericEvent(this);
Resolver resolver = new Resolver();
resolver.addContext("event.", event.getPayload());
// Try to resolve the commands properties against the created event
try {
mostSimilar = resolver.resolve(mostSimilar);
mostSimilar.setReplyTimeout(-1);
} catch (CloneNotSupportedException | VariableResolutionException ex) {
LOG.error(ex.getMessage());
}
// Schedule the command for execution
getBusService().send(mostSimilar);
// Report back which cammand was executed
command.setProperty("result", mostSimilar.getName());
} catch (NoResultsException ex) {
throw new UnableToExecuteException("The given natural language text '"
+ text + "' cannot be recognized as a valid framework command");
}
}
/**
* Creates a similarity ranking between the string in input and the Commands
* registered in the system. Elements are ordered from the most similar
* (index zero) to the less similar. It allows elements with similarity
* equals to zero, so the first element may be not similar at all
*
* @param phrase The text representing a natural language command
* @return a ranking of of commands similarity
* @throws NoResultsException
*/
public Command findMostSimilarCommand(String phrase) throws NoResultsException {
// Compute the commands ranking
List<Nlp.Rank<Command>> ranking = nlpCommands.computeSimilarity(phrase, 10);
// Avoid returning a command with zero similarity
if (ranking.isEmpty() || ranking.get(0).getSimilarity() <= 0) {
throw new NoResultsException("No command is similar enough to '" + phrase + "'");
}
// Get the most similar command (it is in the top of the list)
return ranking.get(0).getElement();
}
@Override
public void onEvent(EventTemplate eventTemplate) {
throw new UnsupportedOperationException("This modules doesn't handle events");
}
@Override
public String getMessagingChannel() {
return MESSAGING_CHANNEL;
}
}