package edu.neu.ccs.task.agent;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import edu.neu.ccs.task.DecompositionPlan;
import edu.neu.ccs.task.Plan;
import edu.neu.ccs.task.Priority;
import edu.neu.ccs.task.Script;
import edu.neu.ccs.task.StepPlan;
import edu.neu.ccs.task.Task;
import edu.neu.ccs.task.TaskEngine;
import edu.neu.ccs.task.TaskModelSet;
import edu.neu.ccs.task.TaskPlan;
import edu.neu.ccs.task.dialogue.Display;
import edu.neu.ccs.task.dialogue.Output;
import edu.neu.ccs.task.dialogue.Turn;
import edu.neu.ccs.task.dialogue.WidgetUserAction;
import edu.neu.ccs.task.util.Bool;
import edu.neu.ccs.task.util.CollectingVisitor;
import edu.neu.ccs.task.util.Pair;
import edu.neu.ccs.task.util.Visitor;
/**
* A very simple dialogue agent designed for system-directed dialogue with
* multiple choice input.
* <p>
* The agent tracks its intentional state using a plan tree, and maintains
* dialogue state by keeping track of a <em>focus</em> within that tree.
* At each turn, it tries to find an elaboration that adds to the plan
* tree as near the focus as possible, and has a primitive task as a
* leaf. It then adds this to the tree and executes the task (which
* becomes the new focus).
* <p>
* This class provides no UI; see {@link AgentConsole} and
* {@link AgentServer}, which both build (different) UIs on top of it.
*
* @author Dan Schulman
* @version $Id: Agent.java 1009 2011-01-21 21:02:36Z schulman $
*/
public class Agent {
private final TaskEngine engine;
private Plan focus;
private Turn currentTurn;
private Output currentOutput;
private Output currentAltOutput;
private List<Pair<String, Integer>> currentInputMenu;
private String currentInputPrompt;
private Pair<String, Map<String, String>> currentInputWidget;
public static final String VERSION = "0.17 internal";
public Agent() {
this(null);
}
public Agent(TaskEngine engine) {
this.engine = (engine!=null) ? engine :
new TaskEngine(new TaskModelSet());
}
/**
* @return the task engine we are using
*/
public TaskEngine getEngine() {
return engine;
}
public TaskModelSet getModelSet() {
return engine.getModelSet();
}
/**
* @return whether the agent is working on a live top-level goal.
*/
public boolean isLive() {
return (focus != null) && focus.getTop().isLive();
}
/**
* @return the current focus (null if no top-level goal).
*/
public Plan getFocus() {
return focus;
}
/**
* @return the current focus (null if no top-level goal).
*/
public Task getFocusTask() {
return (focus == null) ? null : focus.getThisTask();
}
/**
* @return the current top-level plan (may be null)
*/
public Plan getTop() {
return (focus == null) ? null : focus.getTop();
}
/**
* @param focus set the focus (null means no top-level plan)
*/
public void setFocus(Plan focus) {
this.focus = focus;
focus.attach();
setCurrentTurn(null);
}
private void setCurrentTurn(Turn turn) {
currentTurn = turn;
currentOutput = null;
currentAltOutput = null;
currentInputMenu = null;
currentInputPrompt = null;
currentInputWidget = null;
}
public boolean hasCurrentTurn() {
return currentTurn != null;
}
public Display getCurrentDisplay() {
if (currentTurn!=null && currentTurn.hasDisplay())
return currentTurn.getDisplay();
return null;
}
private Output getCurrentOutput() {
if (currentOutput==null && currentTurn!=null && currentTurn.hasOutputText())
currentOutput = currentTurn.getOutputText();
return currentOutput;
}
public String getCurrentPlainOutput() {
Output o = getCurrentOutput();
return o==null ? null : o.getPlain(getFocusTask());
}
public String getCurrentAnnotatedOutput() {
Output o = getCurrentOutput();
return o==null ? null : o.getAnnotated(getFocusTask());
}
private Output getCurrentAltOutput() {
if (currentAltOutput==null && currentTurn!=null && currentTurn.hasOutputText())
currentAltOutput = currentTurn.getAltOutputText();
return currentAltOutput;
}
public String getCurrentPlainAltOutput() {
Output o = getCurrentAltOutput();
return o==null ? null : o.getPlain(getFocusTask());
}
public String getCurrentAnnotatedAltOutput() {
Output o = getCurrentAltOutput();
return o==null ? null : o.getAnnotated(getFocusTask());
}
public List<Pair<String, Integer>> getCurrentInputs() {
if (currentInputMenu==null && currentTurn!=null && currentTurn.hasUserMenu())
currentInputMenu = currentTurn.getUserMenu().getInputs(
getFocusTask(), 6, currentTurn.hasOutputText());
return currentInputMenu;
}
public String getCurrentInputPrompt() {
if (currentInputPrompt==null && currentTurn!=null && currentTurn.hasUserText())
currentInputPrompt = currentTurn.getUserText().getPrompt(getFocusTask());
return currentInputPrompt;
}
public Pair<String, Map<String, String>> getCurrentInputWidget() {
if (currentInputWidget==null && currentTurn!=null && currentTurn.hasUserWidget()) {
WidgetUserAction wua = currentTurn.getUserWidget();
currentInputWidget = Pair.create(
wua.getUrl(),
wua.getParameters(getFocusTask()));
}
return currentInputWidget;
}
private boolean getElaboratedLive(final Visitor<Plan> v) {
Plan plan = focus;
while (!plan.isLive())
plan = plan.getParent();
return plan.getBottommostLive(new Visitor<Plan> () {
public boolean visit(Plan p) {
return getElaboratedLive(p, v);
}
});
}
private boolean getElaboratedLive(Plan p, final Visitor<Plan> v) {
if ( ! p.isLive())
return false;
for (Plan child : p.getChildren())
if (child.isLive())
if (getElaboratedLive(child, v))
return true;
Visitor<Plan> v2 = new Visitor<Plan>() {
public boolean visit(Plan p2) {
return getElaboratedLive(p2, v);
}
};
return p.elaborate(v2) || v.visit(p);
}
private boolean performPlan(Plan p) {
return skipSufficientPostcondition(p) || performAtomic(p);
}
private boolean closePlan(List<Plan> agenda) {
return closeWithSuccess(agenda) || closeAny(agenda);
}
private boolean skipSufficientPostcondition(Plan p) {
if (p instanceof TaskPlan) {
TaskPlan task = (TaskPlan) p;
if (Bool.isTrue(task.getTask().isAchieved())) {
setFocus(task);
task.close();
return true;
}
}
return false;
}
private boolean performAtomic(Plan p) {
if (p instanceof TaskPlan) {
TaskPlan plan = (TaskPlan) p;
Task task = plan.getTask();
// First try to do it as a dialogue turn
List<Turn> turns = getModelSet().getTurns(plan.getQName());
for (Turn turn : Priority.copyShuffleSort(turns))
if (Bool.maybeTrue(turn.isApplicable(task))) {
setFocus(plan);
setCurrentTurn(turn);
return true;
}
// If not, try to execute a script
List<Script> scripts = getModelSet().getScripts(plan.getQName());
for (Script script : Priority.copyShuffleSort(scripts))
if (Bool.maybeTrue(script.isApplicable(task))) {
setFocus(plan);
script.apply(task, true);
performedFocus(null);
return true;
}
}
return false;
}
private boolean closeWithSuccess(List<Plan> agenda) {
for (Plan p : agenda)
if (p.computeSuccess()) {
setFocus(p);
p.close();
return true;
}
return false;
}
private boolean closeAny(List<Plan> agenda) {
if (! agenda.isEmpty()) {
setFocus(agenda.get(0));
focus.close();
return true;
}
return false;
}
/**
* Advance (by repeatedly generated elaborations for the plan
* tree) until we have a primitive task to execute that is
* realized by a dialogue turn.
*
* @return true if there is a next turn (false if done)
*/
public boolean nextTurn() {
if ((currentTurn == null) && isLive()) {
CollectingVisitor<Plan> cv = new CollectingVisitor<Plan>() {
public boolean visit(Plan p) {
super.visit(p);
return performPlan(p);
}
};
if (getElaboratedLive(cv) || closePlan(cv.items()))
engine.update();
else
throw new RuntimeException("no viable actions");
}
return currentTurn != null;
}
private void performedFocus(Boolean success) {
Task task = getFocusTask();
task.setWhen(System.currentTimeMillis());
if (success != null)
task.setSuccess(success);
focus.close();
setCurrentTurn(null);
engine.update();
}
public void applyCurrentTurn() {
currentTurn.apply(getFocusTask());
performedFocus(true);
}
public void applyCurrentTurn(int choice) {
currentTurn.applyChoice(getFocusTask(), choice);
performedFocus(true);
}
public void applyCurrentTurn(String input) {
currentTurn.applyInput(getFocusTask(), input);
performedFocus(true);
}
public void failCurrentTurn() {
if (currentTurn != null)
performedFocus(false);
}
public boolean status(PrintWriter out, String type) throws XMLStreamException {
if ("plan".equals(type)) {
planTree(out);
return true;
} else
return false;
}
private void planTree(PrintWriter out) throws XMLStreamException {
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter xw = xof.createXMLStreamWriter(out);
xw.writeStartDocument("UTF-8", "1.0");
planTree(xw, getTop());
xw.writeEndDocument();
xw.flush();
}
private void planTree(XMLStreamWriter xw, Plan plan) throws XMLStreamException {
if (plan instanceof TaskPlan) {
TaskPlan tp = (TaskPlan) plan;
xw.writeStartElement("TASK");
xw.writeAttribute("ID", tp.getTask().toString());
} else if (plan instanceof DecompositionPlan) {
DecompositionPlan dp = (DecompositionPlan) plan;
xw.writeStartElement("RECIPE");
xw.writeAttribute("ID", dp.getType().toString());
} else if (plan instanceof StepPlan) {
StepPlan sp = (StepPlan) plan;
xw.writeStartElement("STEP");
xw.writeAttribute("NAME", sp.getName());
} else
return;
if (plan == focus)
xw.writeAttribute("STATUS", "FOCUS");
else if (plan.isFailed())
xw.writeAttribute("STATUS", "FAILED");
else if (plan.isSuccessful())
xw.writeAttribute("STATUS", "DONE");
else if (plan.isLive())
xw.writeAttribute("STATUS", "LIVE");
else
xw.writeAttribute("STATUS", "UNSTARTED"); // always correct??
for (Plan child : plan.getChildren())
planTree(xw, child);
xw.writeEndElement();
}
public static void main(String[] args) throws Exception {
AgentMain.main(args);
}
}