Skip to content

Commit

Permalink
Merge branch 'dm2' into ATO-1530-add-validation-of-flows-to-rasa-data…
Browse files Browse the repository at this point in the history
…-validate
  • Loading branch information
ancalita committed Sep 27, 2023
2 parents a1714b0 + 5247210 commit 2548504
Show file tree
Hide file tree
Showing 28 changed files with 628 additions and 276 deletions.
8 changes: 4 additions & 4 deletions rasa/cli/project_templates/dm2/data/flows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ flows:
description: greet the user and ask how they are doing. cheer them up if needed.
steps:
- id: "0"
collect_information: good_mood
collect: good_mood
description: "can be true or false"
next:
- if: good_mood
Expand All @@ -30,13 +30,13 @@ flows:
description: This flow recommends a restaurant
steps:
- id: "0"
collect_information: cuisine
collect: cuisine
next: "1"
- id: "1"
collect_information: price_range
collect: price_range
next: "2"
- id: "2"
collect_information: city
collect: city
next: "3"
- id: "3"
action: utter_recommend_restaurant
Expand Down
4 changes: 2 additions & 2 deletions rasa/cli/project_templates/tutorial/data/flows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ flows:
description: This flow lets users send money to friends and family.
steps:
- id: "ask_recipient"
collect_information: recipient
collect: recipient
next: "ask_amount"
- id: "ask_amount"
collect_information: amount
collect: amount
next: "transfer_successful"
- id: "transfer_successful"
action: utter_transfer_complete
4 changes: 2 additions & 2 deletions rasa/core/actions/action_run_slot_rejections.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async def run(
if not top_frame.rejections:
return []

slot_name = top_frame.collect_information
slot_name = top_frame.collect
slot_instance = tracker.slots.get(slot_name)
if slot_instance and not slot_instance.has_been_set:
# this is the first time the assistant asks for the slot value,
Expand Down Expand Up @@ -101,7 +101,7 @@ async def run(
return []

# reset slot value that was initially filled with an invalid value
events.append(SlotSet(top_frame.collect_information, None))
events.append(SlotSet(top_frame.collect, None))

if internal_error:
utterance = "utter_internal_error_rasa"
Expand Down
52 changes: 34 additions & 18 deletions rasa/core/channels/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -337,51 +337,67 @@ <h2>Happy chatting!</h2>
title: ${flow?.id ?? "No Flow"}
---
flowchart TD
classDef collect_information stroke-width:1px
classDef collect stroke-width:1px
classDef action fill:#FBFCFD,stroke:#A0B8CF
classDef link fill:#f43
classDef slot fill:#aaa
classDef slot fill:#e8f3db,stroke:#c5e1a5
classDef endstep fill:#ccc,stroke:#444
classDef active stroke:#5A17ED,stroke-width:3px,stroke-dasharray: 5 3
`;

if (flow) {
flow.steps.forEach((step) => {
if (step.collect_information) {
var slotValue = slots[step.collect_information]
? `'${slots[step.collect_information]}'`
function renderStepSequence(steps) {
var mermaidTextFragment = "";
steps.forEach((step) => {
if (step.collect) {
var slotValue = slots[step.collect]
? `'${slots[step.collect]}'`
: "\uD83D\uDCAC";
mermaidText += `${step.id}["${toHtmlEntities(keepShort(inject(step.collect_information, currentContext)))}\n${keepShort(slotValue)}"]:::collect_information\n`;
mermaidTextFragment += `${step.id}["${toHtmlEntities(keepShort(inject(step.collect, currentContext)))}\n${keepShort(slotValue)}"]:::collect\n`;
}
if (step.action) {
mermaidText += `${step.id}["${toHtmlEntities(keepShort(inject(step.action, currentContext)))}"]:::action\n`;
mermaidTextFragment += `${step.id}["${toHtmlEntities(keepShort(inject(step.action, currentContext)))}"]:::action\n`;
}
if (step.link) {
mermaidText += `${step.id}["\uD83D\uDD17 ${step.link}"]:::link\n`;
mermaidTextFragment += `${step.id}["\uD83D\uDD17 ${step.link}"]:::link\n`;
}
if (step.set_slots) {
mermaidText += `${step.id}["\uD83E\uDEA3 ${step.id}"]:::slot\n`;
mermaidTextFragment += `${step.id}["\uD83E\uDEA3 ${step.id}"]:::slot\n`;
}

if (step.id === activeStep) {
mermaidText += `class ${step.id} active\n`;
mermaidTextFragment += `class ${step.id} active\n`;
}

// if next is an id, then it is a link
if (step.next && typeof step.next === "string") {
mermaidText += `${step.id} --> ${step.next}\n`;
mermaidTextFragment += `${step.id} --> ${step.next}\n`;
}
// if next is an array, then it is a list of conditions
if (step.next && Array.isArray(step.next)) {
step.next.forEach((condition) => {
if (condition.then) {
mermaidText += `${step.id} -->|${toHtmlEntities(inject(condition.if, currentContext))}| ${condition.then}\n`;
if (condition.then && typeof condition.then === "string") {
mermaidTextFragment += `${step.id} -->|${toHtmlEntities(inject(condition.if, currentContext))}| ${condition.then}\n`;
}
else if (condition.then) {
mermaidTextFragment += `${step.id} -->|${toHtmlEntities(inject(condition.if, currentContext))}| ${condition.then[0].id}\n`;
mermaidTextFragment += renderStepSequence(condition.then);
}
if (condition.else) {
mermaidText += `${step.id} -->|else| ${condition.else}\n`;

if (condition.else && typeof condition.else === "string") {
mermaidTextFragment += `${step.id} -->|else| ${condition.else}\n`;
} else if(condition.else) {
mermaidTextFragment += `${step.id} -->|else| ${condition.else[0].id}\n`;
mermaidTextFragment += renderStepSequence(condition.else);
}
})
}
})
});
mermaidTextFragment += `END["\uD83C\uDFC1 END"]:::endstep\n`;
return mermaidTextFragment;
}

if (flow) {
mermaidText += renderStepSequence(flow.steps);
}
let flowEl = document.getElementById("flow");
console.log(mermaidText);
Expand Down
44 changes: 19 additions & 25 deletions rasa/core/policies/flow_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
from rasa.engine.storage.storage import ModelStorage
from rasa.shared.core.domain import Domain
from rasa.shared.core.generator import TrackerWithCachedStates
from rasa.shared.core.slots import Slot
from rasa.shared.core.trackers import (
DialogueStateTracker,
)
Expand Down Expand Up @@ -342,12 +341,18 @@ def _select_next_step_id(
tracker=tracker,
)
return None
if current.id != END_STEP:
# we've reached the end of the user defined steps in the flow.
# every flow should end with an end step, so we add it here.
if current.id == END_STEP:
# we are already at the very end of the flow. There is no next step.
return None
elif isinstance(current, LinkFlowStep):
# link steps don't have a next step, so we'll return the end step
return END_STEP
else:
# we are already at the very end of the flow. There is no next step.
structlogger.error(
"flow.step.failed_to_select_next_step",
step=current,
tracker=tracker,
)
return None

def _select_next_step(
Expand All @@ -372,23 +377,12 @@ def render_template_variables(text: str, context: Dict[Text, Any]) -> str:
"""Replace context variables in a text."""
return Template(text).render(context)

def _slot_for_collect_information(self, collect_information: Text) -> Slot:
"""Find the slot for the collect information step."""
for slot in self.domain.slots:
if slot.name == collect_information:
return slot
else:
raise FlowException(
f"Collect Information '{collect_information}' does not map to "
f"an existing slot."
)

def _is_step_completed(
self, step: FlowStep, tracker: "DialogueStateTracker"
) -> bool:
"""Check if a step is completed."""
if isinstance(step, CollectInformationFlowStep):
return tracker.get_slot(step.collect_information) is not None
return tracker.get_slot(step.collect) is not None
else:
return True

Expand Down Expand Up @@ -522,9 +516,9 @@ def _reset_scoped_slots(
isinstance(step, CollectInformationFlowStep)
and step.reset_after_flow_ends
):
slot = tracker.slots.get(step.collect_information, None)
slot = tracker.slots.get(step.collect, None)
initial_value = slot.initial_value if slot else None
events.append(SlotSet(step.collect_information, initial_value))
events.append(SlotSet(step.collect, initial_value))
return events

def _run_step(
Expand All @@ -551,17 +545,17 @@ def _run_step(
A result of running the step describing where to transition to.
"""
if isinstance(step, CollectInformationFlowStep):
structlogger.debug("flow.step.run.collect_information")
structlogger.debug("flow.step.run.collect")
self.trigger_pattern_ask_collect_information(
step.collect_information, step.rejections, step.utter
step.collect, step.rejections, step.utter
)

# reset the slot if its already filled and the collect information shouldn't
# be skipped
slot = tracker.slots.get(step.collect_information, None)
slot = tracker.slots.get(step.collect, None)

if slot and slot.has_been_set and step.ask_before_filling:
events = [SlotSet(step.collect_information, slot.initial_value)]
events = [SlotSet(step.collect, slot.initial_value)]
else:
events = []

Expand Down Expand Up @@ -683,14 +677,14 @@ def trigger_pattern_completed(self, current_frame: DialogueStackFrame) -> None:

def trigger_pattern_ask_collect_information(
self,
collect_information: str,
collect: str,
rejections: List[SlotRejection],
utter: str,
) -> None:
"""Trigger the pattern to ask for a slot value."""
self.dialogue_stack.push(
CollectInformationPatternFlowStackFrame(
collect_information=collect_information,
collect=collect,
utter=utter,
rejections=rejections,
)
Expand Down
10 changes: 3 additions & 7 deletions rasa/core/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,13 +652,9 @@ async def trigger_external_user_uttered(
@staticmethod
def _log_slots(tracker: DialogueStateTracker) -> None:
# Log currently set slots
slot_values = "\n".join(
[f"\t{s.name}: {s.value}" for s in tracker.slots.values()]
)
if slot_values.strip():
structlogger.debug(
"processor.slots.log", slot_values=copy.deepcopy(slot_values)
)
slots = {s.name: s.value for s in tracker.slots.values() if s.value is not None}

structlogger.debug("processor.slots.log", slots=slots)

def _check_for_unseen_features(self, parse_data: Dict[Text, Any]) -> None:
"""Warns the user if the NLU parse data contains unrecognized features.
Expand Down
16 changes: 8 additions & 8 deletions rasa/dialogue_understanding/commands/correct_slots_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def are_all_slots_reset_only(
) -> bool:
"""Checks if all slots are reset only.
A slot is reset only if the `collect_information` step it gets filled by
A slot is reset only if the `collect` step it gets filled by
has the `ask_before_filling` flag set to `True`. This means, the slot
shouldn't be filled if the question isn't asked.
Expand All @@ -83,10 +83,10 @@ def are_all_slots_reset_only(
`True` if all slots are reset only, `False` otherwise.
"""
return all(
collect_information_step.collect_information not in proposed_slots
or collect_information_step.ask_before_filling
collect_step.collect not in proposed_slots
or collect_step.ask_before_filling
for flow in all_flows.underlying_flows
for collect_information_step in flow.get_collect_information_steps()
for collect_step in flow.get_collect_steps()
)

@staticmethod
Expand Down Expand Up @@ -123,11 +123,11 @@ def find_earliest_updated_collect_info(
# The way to get the exact set of slots would probably simulate the
# flow forwards from the starting step. Given the current slots you
# could chart the path to the current step id.
asked_collect_info_steps = flow.previous_collect_information_steps(step.id)
asked_collect_steps = flow.previous_collect_steps(step.id)

for collect_info_step in reversed(asked_collect_info_steps):
if collect_info_step.collect_information in updated_slots:
return collect_info_step
for collect_step in reversed(asked_collect_steps):
if collect_step.collect in updated_slots:
return collect_step
return None

def corrected_slots_dict(self, tracker: DialogueStateTracker) -> Dict[str, Any]:
Expand Down
4 changes: 2 additions & 2 deletions rasa/dialogue_understanding/commands/set_slot_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ def run_command_on_tracker(
if self.name not in slots_so_far:
# only fill slots that belong to a collect infos that can be asked
use_slot_fill = any(
step.collect_information == self.name and not step.ask_before_filling
step.collect == self.name and not step.ask_before_filling
for flow in all_flows.underlying_flows
for step in flow.get_collect_information_steps()
for step in flow.get_collect_steps()
)

if not use_slot_fill:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Here is what happened previously in the conversation:
===
{% if current_flow != None %}
You are currently in the flow "{{ current_flow }}", which {{ current_flow.description }}
You have just asked the user for the slot "{{ collect_information }}"{% if collect_information_description %} ({{ collect_information_description }}){% endif %}.
You have just asked the user for the slot "{{ collect }}"{% if collect_description %} ({{ collect_description }}){% endif %}.

{% if flow_slots|length > 0 %}
Here are the slots of the currently active flow:
Expand Down
Loading

0 comments on commit 2548504

Please sign in to comment.