Dialog.run refactor (#1059)
* Refactor for Issue 1046 * Update to push the build again.
This commit is contained in:
Родитель
44969e6b71
Коммит
3736b2656a
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -5,6 +5,7 @@ package com.microsoft.bot.dialogs;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.microsoft.bot.builder.BotAdapter;
|
||||
import com.microsoft.bot.builder.BotTelemetryClient;
|
||||
import com.microsoft.bot.builder.NullBotTelemetryClient;
|
||||
|
@ -12,14 +13,17 @@ import com.microsoft.bot.builder.StatePropertyAccessor;
|
|||
import com.microsoft.bot.builder.TurnContext;
|
||||
import com.microsoft.bot.builder.skills.SkillConversationReference;
|
||||
import com.microsoft.bot.builder.skills.SkillHandler;
|
||||
import com.microsoft.bot.connector.Async;
|
||||
import com.microsoft.bot.connector.authentication.AuthenticationConstants;
|
||||
import com.microsoft.bot.connector.authentication.ClaimsIdentity;
|
||||
import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants;
|
||||
import com.microsoft.bot.connector.authentication.SkillValidation;
|
||||
import com.microsoft.bot.dialogs.memory.DialogStateManager;
|
||||
import com.microsoft.bot.schema.Activity;
|
||||
import com.microsoft.bot.schema.ActivityTypes;
|
||||
import com.microsoft.bot.schema.EndOfConversationCodes;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
@ -29,11 +33,10 @@ import org.apache.commons.lang3.StringUtils;
|
|||
public abstract class Dialog {
|
||||
|
||||
/**
|
||||
* A {@link DialogTurnResult} that indicates that the current dialog is still active and waiting
|
||||
* for input from the user next turn.
|
||||
* A {@link DialogTurnResult} that indicates that the current dialog is still
|
||||
* active and waiting for input from the user next turn.
|
||||
*/
|
||||
public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(
|
||||
DialogTurnStatus.WAITING);
|
||||
public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(DialogTurnStatus.WAITING);
|
||||
|
||||
@JsonIgnore
|
||||
private BotTelemetryClient telemetryClient;
|
||||
|
@ -94,8 +97,8 @@ public abstract class Dialog {
|
|||
* Called when the dialog is started and pushed onto the dialog stack.
|
||||
*
|
||||
* @param dc The {@link DialogContext} for the current turn of conversation.
|
||||
* @return If the task is successful, the result indicates whether the dialog is still active
|
||||
* after the turn has been processed by the dialog.
|
||||
* @return If the task is successful, the result indicates whether the dialog is
|
||||
* still active after the turn has been processed by the dialog.
|
||||
*/
|
||||
public CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc) {
|
||||
return beginDialog(dc, null);
|
||||
|
@ -104,25 +107,27 @@ public abstract class Dialog {
|
|||
/**
|
||||
* Called when the dialog is started and pushed onto the dialog stack.
|
||||
*
|
||||
* @param dc The {@link DialogContext} for the current turn of conversation.
|
||||
* @param dc The {@link DialogContext} for the current turn of
|
||||
* conversation.
|
||||
* @param options Initial information to pass to the dialog.
|
||||
* @return If the task is successful, the result indicates whether the dialog is still active
|
||||
* after the turn has been processed by the dialog.
|
||||
* @return If the task is successful, the result indicates whether the dialog is
|
||||
* still active after the turn has been processed by the dialog.
|
||||
*/
|
||||
public abstract CompletableFuture<DialogTurnResult> beginDialog(
|
||||
DialogContext dc, Object options
|
||||
);
|
||||
public abstract CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc, Object options);
|
||||
|
||||
/**
|
||||
* Called when the dialog is _continued_, where it is the active dialog and the user replies
|
||||
* with a new activity.
|
||||
* Called when the dialog is _continued_, where it is the active dialog and the
|
||||
* user replies with a new activity.
|
||||
*
|
||||
* <p>If this method is *not* overridden, the dialog automatically ends when the user
|
||||
* replies.</p>
|
||||
* <p>
|
||||
* If this method is *not* overridden, the dialog automatically ends when the
|
||||
* user replies.
|
||||
* </p>
|
||||
*
|
||||
* @param dc The {@link DialogContext} for the current turn of conversation.
|
||||
* @return If the task is successful, the result indicates whether the dialog is still active
|
||||
* after the turn has been processed by the dialog. The result may also contain a return value.
|
||||
* @return If the task is successful, the result indicates whether the dialog is
|
||||
* still active after the turn has been processed by the dialog. The
|
||||
* result may also contain a return value.
|
||||
*/
|
||||
public CompletableFuture<DialogTurnResult> continueDialog(DialogContext dc) {
|
||||
// By default just end the current dialog.
|
||||
|
@ -130,46 +135,54 @@ public abstract class Dialog {
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when a child dialog completed this turn, returning control to this dialog.
|
||||
* Called when a child dialog completed this turn, returning control to this
|
||||
* dialog.
|
||||
*
|
||||
* <p>Generally, the child dialog was started with a call to
|
||||
* {@link #beginDialog(DialogContext, Object)} However, if the {@link
|
||||
* DialogContext#replaceDialog(String)} method is called, the logical child dialog may be
|
||||
* different than the original.</p>
|
||||
* <p>
|
||||
* Generally, the child dialog was started with a call to
|
||||
* {@link #beginDialog(DialogContext, Object)} However, if the
|
||||
* {@link DialogContext#replaceDialog(String)} method is called, the logical
|
||||
* child dialog may be different than the original.
|
||||
* </p>
|
||||
*
|
||||
* <p>If this method is *not* overridden, the dialog automatically ends when the user
|
||||
* replies.</p>
|
||||
* <p>
|
||||
* If this method is *not* overridden, the dialog automatically ends when the
|
||||
* user replies.
|
||||
* </p>
|
||||
*
|
||||
* @param dc The dialog context for the current turn of the conversation.
|
||||
* @param reason Reason why the dialog resumed.
|
||||
* @return If the task is successful, the result indicates whether this dialog is still active
|
||||
* after this dialog turn has been processed.
|
||||
* @return If the task is successful, the result indicates whether this dialog
|
||||
* is still active after this dialog turn has been processed.
|
||||
*/
|
||||
public CompletableFuture<DialogTurnResult> resumeDialog(DialogContext dc, DialogReason reason) {
|
||||
return resumeDialog(dc, reason, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child dialog completed this turn, returning control to this dialog.
|
||||
* Called when a child dialog completed this turn, returning control to this
|
||||
* dialog.
|
||||
*
|
||||
* <p>Generally, the child dialog was started with a call to
|
||||
* {@link #beginDialog(DialogContext, Object)} However, if the {@link
|
||||
* DialogContext#replaceDialog(String, Object)} method is called, the logical child dialog may
|
||||
* be different than the original.</p>
|
||||
* <p>
|
||||
* Generally, the child dialog was started with a call to
|
||||
* {@link #beginDialog(DialogContext, Object)} However, if the
|
||||
* {@link DialogContext#replaceDialog(String, Object)} method is called, the
|
||||
* logical child dialog may be different than the original.
|
||||
* </p>
|
||||
*
|
||||
* <p>If this method is *not* overridden, the dialog automatically ends when the user
|
||||
* replies.</p>
|
||||
* <p>
|
||||
* If this method is *not* overridden, the dialog automatically ends when the
|
||||
* user replies.
|
||||
* </p>
|
||||
*
|
||||
* @param dc The dialog context for the current turn of the conversation.
|
||||
* @param reason Reason why the dialog resumed.
|
||||
* @param result Optional, value returned from the dialog that was called. The type of the value
|
||||
* returned is dependent on the child dialog.
|
||||
* @return If the task is successful, the result indicates whether this dialog is still active
|
||||
* after this dialog turn has been processed.
|
||||
* @param result Optional, value returned from the dialog that was called. The
|
||||
* type of the value returned is dependent on the child dialog.
|
||||
* @return If the task is successful, the result indicates whether this dialog
|
||||
* is still active after this dialog turn has been processed.
|
||||
*/
|
||||
public CompletableFuture<DialogTurnResult> resumeDialog(
|
||||
DialogContext dc, DialogReason reason, Object result
|
||||
) {
|
||||
public CompletableFuture<DialogTurnResult> resumeDialog(DialogContext dc, DialogReason reason, Object result) {
|
||||
// By default just end the current dialog and return result to parent.
|
||||
return dc.endDialog(result);
|
||||
}
|
||||
|
@ -181,9 +194,7 @@ public abstract class Dialog {
|
|||
* @param instance State information for this dialog.
|
||||
* @return A CompletableFuture representing the asynchronous operation.
|
||||
*/
|
||||
public CompletableFuture<Void> repromptDialog(
|
||||
TurnContext turnContext, DialogInstance instance
|
||||
) {
|
||||
public CompletableFuture<Void> repromptDialog(TurnContext turnContext, DialogInstance instance) {
|
||||
// No-op by default
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
@ -192,24 +203,23 @@ public abstract class Dialog {
|
|||
* Called when the dialog is ending.
|
||||
*
|
||||
* @param turnContext The context object for this turn.
|
||||
* @param instance State information associated with the instance of this dialog on the
|
||||
* dialog stack.
|
||||
* @param instance State information associated with the instance of this
|
||||
* dialog on the dialog stack.
|
||||
* @param reason Reason why the dialog ended.
|
||||
* @return A CompletableFuture representing the asynchronous operation.
|
||||
*/
|
||||
public CompletableFuture<Void> endDialog(
|
||||
TurnContext turnContext, DialogInstance instance, DialogReason reason
|
||||
) {
|
||||
public CompletableFuture<Void> endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) {
|
||||
// No-op by default
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique String which represents the version of this dialog. If the version changes
|
||||
* between turns the dialog system will emit a DialogChanged event.
|
||||
* Gets a unique String which represents the version of this dialog. If the
|
||||
* version changes between turns the dialog system will emit a DialogChanged
|
||||
* event.
|
||||
*
|
||||
* @return Unique String which should only change when dialog has changed in a way that should
|
||||
* restart the dialog.
|
||||
* @return Unique String which should only change when dialog has changed in a
|
||||
* way that should restart the dialog.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getVersion() {
|
||||
|
@ -217,46 +227,47 @@ public abstract class Dialog {
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when an event has been raised, using `DialogContext.emitEvent()`, by either the
|
||||
* current dialog or a dialog that the current dialog started.
|
||||
* Called when an event has been raised, using `DialogContext.emitEvent()`, by
|
||||
* either the current dialog or a dialog that the current dialog started.
|
||||
*
|
||||
* @param dc The dialog context for the current turn of conversation.
|
||||
* @param e The event being raised.
|
||||
* @return True if the event is handled by the current dialog and bubbling should stop.
|
||||
* @return True if the event is handled by the current dialog and bubbling
|
||||
* should stop.
|
||||
*/
|
||||
public CompletableFuture<Boolean> onDialogEvent(DialogContext dc, DialogEvent e) {
|
||||
// Before bubble
|
||||
return onPreBubbleEvent(dc, e)
|
||||
.thenCompose(handled -> {
|
||||
// Bubble as needed
|
||||
if (!handled && e.shouldBubble() && dc.getParent() != null) {
|
||||
return dc.getParent().emitEvent(e.getName(), e.getValue(), true, false);
|
||||
}
|
||||
return onPreBubbleEvent(dc, e).thenCompose(handled -> {
|
||||
// Bubble as needed
|
||||
if (!handled && e.shouldBubble() && dc.getParent() != null) {
|
||||
return dc.getParent().emitEvent(e.getName(), e.getValue(), true, false);
|
||||
}
|
||||
|
||||
// just pass the handled value to the next stage
|
||||
return CompletableFuture.completedFuture(handled);
|
||||
})
|
||||
.thenCompose(handled -> {
|
||||
if (!handled) {
|
||||
// Post bubble
|
||||
return onPostBubbleEvent(dc, e);
|
||||
}
|
||||
// just pass the handled value to the next stage
|
||||
return CompletableFuture.completedFuture(handled);
|
||||
}).thenCompose(handled -> {
|
||||
if (!handled) {
|
||||
// Post bubble
|
||||
return onPostBubbleEvent(dc, e);
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(handled);
|
||||
});
|
||||
return CompletableFuture.completedFuture(handled);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before an event is bubbled to its parent.
|
||||
*
|
||||
* <p>This is a good place to perform interception of an event as returning `true` will prevent
|
||||
* any further bubbling of the event to the dialogs parents and will also prevent any child
|
||||
* dialogs from performing their default processing.</p>
|
||||
* <p>
|
||||
* This is a good place to perform interception of an event as returning `true`
|
||||
* will prevent any further bubbling of the event to the dialogs parents and
|
||||
* will also prevent any child dialogs from performing their default processing.
|
||||
* </p>
|
||||
*
|
||||
* @param dc The dialog context for the current turn of conversation.
|
||||
* @param e The event being raised.
|
||||
* @return Whether the event is handled by the current dialog and further processing should
|
||||
* stop.
|
||||
* @return Whether the event is handled by the current dialog and further
|
||||
* processing should stop.
|
||||
*/
|
||||
protected CompletableFuture<Boolean> onPreBubbleEvent(DialogContext dc, DialogEvent e) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
|
@ -265,14 +276,15 @@ public abstract class Dialog {
|
|||
/**
|
||||
* Called after an event was bubbled to all parents and wasn't handled.
|
||||
*
|
||||
* <p>This is a good place to perform default processing logic for an event. Returning `true`
|
||||
* will
|
||||
* prevent any processing of the event by child dialogs.</p>
|
||||
* <p>
|
||||
* This is a good place to perform default processing logic for an event.
|
||||
* Returning `true` will prevent any processing of the event by child dialogs.
|
||||
* </p>
|
||||
*
|
||||
* @param dc The dialog context for the current turn of conversation.
|
||||
* @param e The event being raised.
|
||||
* @return Whether the event is handled by the current dialog and further processing should
|
||||
* stop.
|
||||
* @return Whether the event is handled by the current dialog and further
|
||||
* processing should stop.
|
||||
*/
|
||||
protected CompletableFuture<Boolean> onPostBubbleEvent(DialogContext dc, DialogEvent e) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
|
@ -292,82 +304,139 @@ public abstract class Dialog {
|
|||
*
|
||||
* @param dialog The dialog to start.
|
||||
* @param turnContext The context for the current turn of the conversation.
|
||||
* @param accessor The StatePropertyAccessor accessor with which to manage the state of the
|
||||
* dialog stack.
|
||||
* @param accessor The StatePropertyAccessor accessor with which to manage
|
||||
* the state of the dialog stack.
|
||||
* @return A Task representing the asynchronous operation.
|
||||
*/
|
||||
public static CompletableFuture<Void> run(
|
||||
Dialog dialog,
|
||||
TurnContext turnContext,
|
||||
StatePropertyAccessor<DialogState> accessor
|
||||
) {
|
||||
public static CompletableFuture<Void> run(Dialog dialog, TurnContext turnContext,
|
||||
StatePropertyAccessor<DialogState> accessor) {
|
||||
DialogSet dialogSet = new DialogSet(accessor);
|
||||
dialogSet.add(dialog);
|
||||
dialogSet.setTelemetryClient(dialog.getTelemetryClient());
|
||||
|
||||
// return dialogSet.createContext(turnContext)
|
||||
// .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog,
|
||||
// turnContext))
|
||||
// .thenApply(result -> null);
|
||||
return dialogSet.createContext(turnContext)
|
||||
.thenCompose(dialogContext -> continueOrStart(dialogContext, dialog, turnContext))
|
||||
.thenApply(result -> null);
|
||||
.thenAccept(dialogContext -> innerRun(turnContext, dialog.getId(), dialogContext));
|
||||
}
|
||||
|
||||
private static CompletableFuture<Void> continueOrStart(
|
||||
DialogContext dialogContext, Dialog dialog, TurnContext turnContext
|
||||
) {
|
||||
/**
|
||||
* Shared implementation of run with Dialog and DialogManager.
|
||||
*
|
||||
* @param turnContext The turnContext.
|
||||
* @param dialogId The Id of the Dialog.
|
||||
* @param dialogContext The DialogContext.
|
||||
* @return A DialogTurnResult.
|
||||
*/
|
||||
protected static CompletableFuture<DialogTurnResult> innerRun(TurnContext turnContext, String dialogId,
|
||||
DialogContext dialogContext) {
|
||||
for (Entry<String, Object> entry : turnContext.getTurnState().getTurnStateServices().entrySet()) {
|
||||
dialogContext.getServices().replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
DialogStateManager dialogStateManager = new DialogStateManager(dialogContext);
|
||||
return dialogStateManager.loadAllScopes().thenCompose(result -> {
|
||||
dialogContext.getContext().getTurnState().add(dialogStateManager);
|
||||
DialogTurnResult dialogTurnResult = null;
|
||||
boolean endOfTurn = false;
|
||||
while (!endOfTurn) {
|
||||
try {
|
||||
dialogTurnResult = continueOrStart(dialogContext, dialogId, turnContext).join();
|
||||
endOfTurn = true;
|
||||
} catch (Exception err) {
|
||||
// fire error event, bubbling from the leaf.
|
||||
boolean handled = dialogContext.emitEvent(DialogEvents.ERROR, err, true, true).join();
|
||||
|
||||
if (!handled) {
|
||||
// error was NOT handled, return a result that signifies that the call was unsuccssfull
|
||||
// (This will trigger the Adapter.OnError handler and end the entire dialog stack)
|
||||
return Async.completeExceptionally(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(dialogTurnResult);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static CompletableFuture<DialogTurnResult> continueOrStart(DialogContext dialogContext, String dialogId,
|
||||
TurnContext turnContext) {
|
||||
if (DialogCommon.isFromParentToSkill(turnContext)) {
|
||||
// Handle remote cancellation request from parent.
|
||||
if (turnContext.getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION)) {
|
||||
if (dialogContext.getStack().size() == 0) {
|
||||
// No dialogs to cancel, just return.
|
||||
return CompletableFuture.completedFuture(null);
|
||||
return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY));
|
||||
}
|
||||
|
||||
DialogContext activeDialogContext = getActiveDialogContext(dialogContext);
|
||||
|
||||
// Send cancellation message to the top dialog in the stack to ensure all the parents
|
||||
// Send cancellation message to the top dialog in the stack to ensure all the
|
||||
// parents
|
||||
// are canceled in the right order.
|
||||
return activeDialogContext.cancelAllDialogs(true, null, null).thenApply(result -> null);
|
||||
return activeDialogContext.cancelAllDialogs(true, null, null);
|
||||
}
|
||||
|
||||
// Handle a reprompt event sent from the parent.
|
||||
if (turnContext.getActivity().getType().equals(ActivityTypes.EVENT)
|
||||
&& turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) {
|
||||
&& turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) {
|
||||
if (dialogContext.getStack().size() == 0) {
|
||||
// No dialogs to reprompt, just return.
|
||||
return CompletableFuture.completedFuture(null);
|
||||
return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY));
|
||||
}
|
||||
|
||||
return dialogContext.repromptDialog();
|
||||
return dialogContext.repromptDialog()
|
||||
.thenApply(result -> new DialogTurnResult(DialogTurnStatus.WAITING));
|
||||
}
|
||||
}
|
||||
return dialogContext.continueDialog()
|
||||
.thenCompose(result -> {
|
||||
if (result.getStatus() == DialogTurnStatus.EMPTY) {
|
||||
return dialogContext.beginDialog(dialog.getId(), null).thenCompose(finalResult -> {
|
||||
return processEOC(finalResult, turnContext);
|
||||
});
|
||||
}
|
||||
return processEOC(result, turnContext);
|
||||
});
|
||||
return dialogContext.continueDialog().thenCompose(result -> {
|
||||
if (result.getStatus() == DialogTurnStatus.EMPTY) {
|
||||
return dialogContext.beginDialog(dialogId, null).thenCompose(finalResult -> {
|
||||
return processEOC(dialogContext, finalResult, turnContext);
|
||||
});
|
||||
}
|
||||
return processEOC(dialogContext, result, turnContext);
|
||||
});
|
||||
}
|
||||
|
||||
private static CompletableFuture<Void> processEOC(DialogTurnResult result, TurnContext turnContext) {
|
||||
if (result.getStatus() == DialogTurnStatus.COMPLETE
|
||||
|| result.getStatus() == DialogTurnStatus.CANCELLED
|
||||
&& sendEoCToParent(turnContext)) {
|
||||
private static CompletableFuture<DialogTurnResult> processEOC(DialogContext dialogContext, DialogTurnResult result,
|
||||
TurnContext turnContext) {
|
||||
return sendStateSnapshotTrace(dialogContext).thenCompose(snapshotResult -> {
|
||||
if ((result.getStatus() == DialogTurnStatus.COMPLETE
|
||||
|| result.getStatus() == DialogTurnStatus.CANCELLED) && sendEoCToParent(turnContext)) {
|
||||
EndOfConversationCodes code = result.getStatus() == DialogTurnStatus.COMPLETE
|
||||
? EndOfConversationCodes.COMPLETED_SUCCESSFULLY
|
||||
: EndOfConversationCodes.USER_CANCELLED;
|
||||
? EndOfConversationCodes.COMPLETED_SUCCESSFULLY
|
||||
: EndOfConversationCodes.USER_CANCELLED;
|
||||
Activity activity = new Activity(ActivityTypes.END_OF_CONVERSATION);
|
||||
activity.setValue(result.getResult());
|
||||
activity.setLocale(turnContext.getActivity().getLocale());
|
||||
activity.setCode(code);
|
||||
return turnContext.sendActivity(activity).thenApply(finalResult -> null);
|
||||
turnContext.sendActivity(activity).join();
|
||||
}
|
||||
return CompletableFuture.completedFuture(result);
|
||||
});
|
||||
}
|
||||
|
||||
private static CompletableFuture<Void> sendStateSnapshotTrace(DialogContext dialogContext) {
|
||||
String traceLabel = "";
|
||||
Object identity = dialogContext.getContext().getTurnState().get(BotAdapter.BOT_IDENTITY_KEY);
|
||||
if (identity instanceof ClaimsIdentity) {
|
||||
traceLabel = SkillValidation.isSkillClaim(((ClaimsIdentity) identity).claims()) ? "Skill State"
|
||||
: "Bot State";
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
// send trace of memory
|
||||
JsonNode snapshot = getActiveDialogContext(dialogContext).getState().getMemorySnapshot();
|
||||
Activity traceActivity = Activity.createTraceActivity("BotState",
|
||||
"https://www.botframework.com/schemas/botState", snapshot, traceLabel);
|
||||
return dialogContext.getContext().sendActivity(traceActivity).thenApply(result -> null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine if we should send an EoC to the parent or not.
|
||||
*
|
||||
* @param turnContext
|
||||
* @return
|
||||
*/
|
||||
|
@ -376,17 +445,19 @@ public abstract class Dialog {
|
|||
ClaimsIdentity claimsIdentity = turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY);
|
||||
|
||||
if (claimsIdentity != null && SkillValidation.isSkillClaim(claimsIdentity.claims())) {
|
||||
// EoC Activities returned by skills are bounced back to the bot by SkillHandler.
|
||||
// EoC Activities returned by skills are bounced back to the bot by
|
||||
// SkillHandler.
|
||||
// In those cases we will have a SkillConversationReference instance in state.
|
||||
SkillConversationReference skillConversationReference =
|
||||
turnContext.getTurnState().get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY);
|
||||
SkillConversationReference skillConversationReference = turnContext.getTurnState()
|
||||
.get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY);
|
||||
if (skillConversationReference != null) {
|
||||
// If the skillConversationReference.OAuthScope is for one of the supported channels,
|
||||
// If the skillConversationReference.OAuthScope is for one of the supported
|
||||
// channels,
|
||||
// we are at the root and we should not send an EoC.
|
||||
return skillConversationReference.getOAuthScope()
|
||||
!= AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
|
||||
&& skillConversationReference.getOAuthScope()
|
||||
!= GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE;
|
||||
return skillConversationReference
|
||||
.getOAuthScope() != AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
|
||||
&& skillConversationReference
|
||||
.getOAuthScope() != GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -9,26 +9,14 @@ import java.util.Collection;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.microsoft.bot.builder.BotAdapter;
|
||||
import com.microsoft.bot.builder.BotStateSet;
|
||||
import com.microsoft.bot.builder.ConversationState;
|
||||
import com.microsoft.bot.builder.StatePropertyAccessor;
|
||||
import com.microsoft.bot.builder.TurnContext;
|
||||
import com.microsoft.bot.builder.TurnContextStateCollection;
|
||||
import com.microsoft.bot.builder.UserState;
|
||||
import com.microsoft.bot.builder.skills.SkillConversationReference;
|
||||
import com.microsoft.bot.builder.skills.SkillHandler;
|
||||
import com.microsoft.bot.connector.Async;
|
||||
import com.microsoft.bot.connector.authentication.AuthenticationConstants;
|
||||
import com.microsoft.bot.connector.authentication.ClaimsIdentity;
|
||||
import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants;
|
||||
import com.microsoft.bot.connector.authentication.SkillValidation;
|
||||
import com.microsoft.bot.dialogs.memory.DialogStateManager;
|
||||
import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration;
|
||||
import com.microsoft.bot.schema.Activity;
|
||||
import com.microsoft.bot.schema.ActivityTypes;
|
||||
import com.microsoft.bot.schema.EndOfConversationCodes;
|
||||
|
||||
/**
|
||||
* Class which runs the dialog system.
|
||||
|
@ -266,175 +254,13 @@ public class DialogManager {
|
|||
// Create DialogContext
|
||||
DialogContext dc = new DialogContext(dialogs, context, dialogState);
|
||||
|
||||
dc.getServices().getTurnStateServices().forEach((key, value) -> {
|
||||
dc.getServices().add(key, value);
|
||||
return Dialog.innerRun(context, rootDialogId, dc).thenCompose(turnResult -> {
|
||||
return botStateSet.saveAllChanges(dc.getContext(), false).thenCompose(saveResult -> {
|
||||
DialogManagerResult result = new DialogManagerResult();
|
||||
result.setTurnResult(turnResult);
|
||||
return CompletableFuture.completedFuture(result);
|
||||
});
|
||||
});
|
||||
|
||||
// map TurnState into root dialog context.services
|
||||
context.getTurnState().getTurnStateServices().forEach((key, value) -> {
|
||||
dc.getServices().add(key, value);
|
||||
});
|
||||
|
||||
// get the DialogStateManager configuration
|
||||
DialogStateManager dialogStateManager = new DialogStateManager(dc, stateManagerConfiguration);
|
||||
dialogStateManager.loadAllScopesAsync().join();
|
||||
dc.getContext().getTurnState().add(dialogStateManager);
|
||||
|
||||
DialogTurnResult turnResult = null;
|
||||
|
||||
// Loop as long as we are getting valid OnError handled we should continue
|
||||
// executing the
|
||||
// actions for the turn.
|
||||
// NOTE: We loop around this block because each pass through we either complete
|
||||
// the turn and
|
||||
// break out of the loop
|
||||
// or we have had an exception AND there was an OnError action which captured
|
||||
// the error.
|
||||
// We need to continue the turn based on the actions the OnError handler
|
||||
// introduced.
|
||||
Boolean endOfTurn = false;
|
||||
while (!endOfTurn) {
|
||||
try {
|
||||
ClaimsIdentity claimIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY);
|
||||
if (claimIdentity != null && SkillValidation.isSkillClaim(claimIdentity.claims())) {
|
||||
// The bot is running as a skill.
|
||||
turnResult = handleSkillOnTurn(dc).join();
|
||||
} else {
|
||||
// The bot is running as root bot.
|
||||
turnResult = handleBotOnTurn(dc).join();
|
||||
}
|
||||
|
||||
// turn successfully completed, break the loop
|
||||
endOfTurn = true;
|
||||
} catch (Exception err) {
|
||||
// fire error event, bubbling from the leaf.
|
||||
Boolean handled = dc.emitEvent(DialogEvents.ERROR, err, true, true).join();
|
||||
|
||||
if (!handled) {
|
||||
// error was NOT handled, throw the exception and end the turn. (This will
|
||||
// trigger
|
||||
// the Adapter.OnError handler and end the entire dialog stack)
|
||||
return Async.completeExceptionally(new RuntimeException(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save all state scopes to their respective botState locations.
|
||||
dialogStateManager.saveAllChanges();
|
||||
|
||||
// save BotState changes
|
||||
botStateSet.saveAllChanges(dc.getContext(), false);
|
||||
|
||||
DialogManagerResult result = new DialogManagerResult();
|
||||
result.setTurnResult(turnResult);
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to send a trace activity with a memory snapshot of the active dialog
|
||||
/// DC.
|
||||
/// </summary>
|
||||
private static CompletableFuture<Void> sendStateSnapshotTrace(DialogContext dc, String traceLabel) {
|
||||
// send trace of memory
|
||||
JsonNode snapshot = getActiveDialogContext(dc).getState().getMemorySnapshot();
|
||||
Activity traceActivity = (Activity) Activity.createTraceActivity("Bot State",
|
||||
"https://www.botframework.com/schemas/botState", snapshot, traceLabel);
|
||||
dc.getContext().sendActivity(traceActivity).join();
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively walk up the DC stack to find the active DC.
|
||||
*/
|
||||
private static DialogContext getActiveDialogContext(DialogContext dialogContext) {
|
||||
DialogContext child = dialogContext.getChild();
|
||||
if (child == null) {
|
||||
return dialogContext;
|
||||
} else {
|
||||
return getActiveDialogContext(child);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<DialogTurnResult> handleSkillOnTurn(DialogContext dc) {
|
||||
// the bot instanceof running as a skill.
|
||||
TurnContext turnContext = dc.getContext();
|
||||
|
||||
// Process remote cancellation
|
||||
if (turnContext.getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION)
|
||||
&& dc.getActiveDialog() != null
|
||||
&& DialogCommon.isFromParentToSkill(turnContext)) {
|
||||
// Handle remote cancellation request from parent.
|
||||
DialogContext activeDialogContext = getActiveDialogContext(dc);
|
||||
|
||||
// Send cancellation message to the top dialog in the stack to ensure all the
|
||||
// parents are canceled in the right order.
|
||||
return activeDialogContext.cancelAllDialogs();
|
||||
}
|
||||
|
||||
// Handle reprompt
|
||||
// Process a reprompt event sent from the parent.
|
||||
if (turnContext.getActivity().getType().equals(ActivityTypes.EVENT)
|
||||
&& turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) {
|
||||
if (dc.getActiveDialog() == null) {
|
||||
return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY));
|
||||
}
|
||||
|
||||
dc.repromptDialog();
|
||||
return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING));
|
||||
}
|
||||
|
||||
// Continue execution
|
||||
// - This will apply any queued up interruptions and execute the current/next step(s).
|
||||
DialogTurnResult turnResult = dc.continueDialog().join();
|
||||
if (turnResult.getStatus().equals(DialogTurnStatus.EMPTY)) {
|
||||
// restart root dialog
|
||||
turnResult = dc.beginDialog(rootDialogId).join();
|
||||
}
|
||||
|
||||
sendStateSnapshotTrace(dc, "Skill State");
|
||||
|
||||
if (shouldSendEndOfConversationToParent(turnContext, turnResult)) {
|
||||
// Send End of conversation at the end.
|
||||
EndOfConversationCodes code = turnResult.getStatus().equals(DialogTurnStatus.COMPLETE)
|
||||
? EndOfConversationCodes.COMPLETED_SUCCESSFULLY
|
||||
: EndOfConversationCodes.USER_CANCELLED;
|
||||
Activity activity = new Activity(ActivityTypes.END_OF_CONVERSATION);
|
||||
activity.setValue(turnResult.getResult());
|
||||
activity.setLocale(turnContext.getActivity().getLocale());
|
||||
activity.setCode(code);
|
||||
turnContext.sendActivity(activity).join();
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(turnResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine if we should send an EndOfConversation to the parent
|
||||
* or not.
|
||||
*/
|
||||
private static boolean shouldSendEndOfConversationToParent(TurnContext context, DialogTurnResult turnResult) {
|
||||
if (!(turnResult.getStatus().equals(DialogTurnStatus.COMPLETE)
|
||||
|| turnResult.getStatus().equals(DialogTurnStatus.CANCELLED))) {
|
||||
// The dialog instanceof still going, don't return EoC.
|
||||
return false;
|
||||
}
|
||||
ClaimsIdentity claimsIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY);
|
||||
if (claimsIdentity != null && SkillValidation.isSkillClaim(claimsIdentity.claims())) {
|
||||
// EoC Activities returned by skills are bounced back to the bot by SkillHandler.
|
||||
// In those cases we will have a SkillConversationReference instance in state.
|
||||
SkillConversationReference skillConversationReference =
|
||||
context.getTurnState().get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY);
|
||||
if (skillConversationReference != null) {
|
||||
// If the skillConversationReference.OAuthScope instanceof for one of the supported channels,
|
||||
// we are at the root and we should not send an EoC.
|
||||
return skillConversationReference.getOAuthScope()
|
||||
!= AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
|
||||
&& skillConversationReference.getOAuthScope()
|
||||
!= GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -459,28 +285,4 @@ public class DialogManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<DialogTurnResult> handleBotOnTurn(DialogContext dc) {
|
||||
DialogTurnResult turnResult;
|
||||
|
||||
// the bot is running as a root bot.
|
||||
if (dc.getActiveDialog() == null) {
|
||||
// start root dialog
|
||||
turnResult = dc.beginDialog(rootDialogId).join();
|
||||
} else {
|
||||
// Continue execution
|
||||
// - This will apply any queued up interruptions and execute the current/next
|
||||
// step(s).
|
||||
turnResult = dc.continueDialog().join();
|
||||
|
||||
if (turnResult.getStatus().equals(DialogTurnStatus.EMPTY)) {
|
||||
// restart root dialog
|
||||
turnResult = dc.beginDialog(rootDialogId).join();
|
||||
}
|
||||
}
|
||||
|
||||
sendStateSnapshotTrace(dc, "BotState").join();
|
||||
|
||||
return CompletableFuture.completedFuture(turnResult);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,17 @@ public class DialogStateManager implements Map<String, Object> {
|
|||
|
||||
private ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the
|
||||
* {@link com.microsoft.bot.dialogs.memory.DialogStateManager} class.
|
||||
*
|
||||
* @param dc The dialog context for the current turn of the
|
||||
* conversation.
|
||||
*/
|
||||
public DialogStateManager(DialogContext dc) {
|
||||
this(dc, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the
|
||||
* {@link com.microsoft.bot.dialogs.memory.DialogStateManager} class.
|
||||
|
@ -455,7 +466,7 @@ public class DialogStateManager implements Map<String, Object> {
|
|||
*
|
||||
* @return A Completed Future.
|
||||
*/
|
||||
public CompletableFuture<Void> loadAllScopesAsync() {
|
||||
public CompletableFuture<Void> loadAllScopes() {
|
||||
configuration.getMemoryScopes().forEach((scope) -> {
|
||||
scope.load(dialogContext, false).join();
|
||||
});
|
||||
|
|
|
@ -104,7 +104,7 @@ public class DialogManagerTests {
|
|||
|
||||
Dialog adaptiveDialog = CreateTestDialog("conversation.name");
|
||||
|
||||
CreateFlow(adaptiveDialog, storage, firstConversationId, "dialogState", null, null)
|
||||
CreateFlow(adaptiveDialog, storage, firstConversationId, "DialogState", null, null)
|
||||
.send("hi")
|
||||
.assertReply("Hello, what is your name?")
|
||||
.send("Carlos")
|
||||
|
|
Загрузка…
Ссылка в новой задаче