Core: 2. Conditions#

This tutorial shows different options for setting transition conditions from one node to another.

Here, conditions for script transitions are shown.

[1]:
# installing dependencies
%pip install -q chatsky
Note: you may need to restart the kernel to use updated packages.
[2]:
import re

from chatsky import (
    Context,
    TRANSITIONS,
    RESPONSE,
    Message,
    Pipeline,
    BaseCondition,
    Transition as Tr,
    conditions as cnd,
)

from chatsky.utils.testing.common import (
    check_happy_path,
    is_interactive_mode,
)

The transition condition is determined by BaseCondition.

If this function returns True, then the corresponding transition is considered possible.

Condition functions have signature

class MyCondition(BaseCondition):
    async def call(self, ctx: Context) -> bool:

This script covers the following pre-defined conditions:

  • ExactMatch returns True if the user’s request completely matches the value passed to the function.

  • Regexp returns True if the pattern matches the user’s request. Regexp has same signature as re.compile function.

  • Any returns True if one element of input sequence of conditions is True.

  • All returns True if All elements of input sequence of conditions are True.

For a full list of available conditions see here.

The cnd field of Transition may also be a constant bool value.

[3]:
class HiLowerCase(BaseCondition):
    """
    Return True if `hi` in both uppercase and lowercase
    letters is contained in the user request.
    """

    async def call(self, ctx: Context) -> bool:
        request = ctx.last_request
        return "hi" in request.text.lower()

Conditions are subclasses of pydantic.BaseModel.

You can define custom fields to make them more customizable:

[4]:
class ComplexUserAnswer(BaseCondition):
    """
    Checks if the misc field of the last message is of a certain value.

    Messages are more complex than just strings.
    The misc field can be used to store metadata about the message.
    More on that in the next tutorial.
    """

    value: dict

    async def call(self, ctx: Context) -> bool:
        request = ctx.last_request
        return request.misc == self.value


customized_condition = ComplexUserAnswer(value={"some_key": "some_value"})
[5]:
toy_script = {
    "greeting_flow": {
        "start_node": {
            TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))],
            # If "Hi" == request of user then we make the transition
        },
        "node1": {
            RESPONSE: "Hi, how are you?",
            TRANSITIONS: [
                Tr(
                    dst="node2",
                    cnd=cnd.Regexp(r".*how are you", flags=re.IGNORECASE),
                )
            ],
            # pattern matching
        },
        "node2": {
            RESPONSE: "Good. What do you want to talk about?",
            TRANSITIONS: [
                Tr(
                    dst="node3",
                    cnd=cnd.All(
                        cnd.Regexp(r"talk"), cnd.Regexp(r"about.*music")
                    ),
                )
            ],
            # Combine sequences of conditions with `cnd.All`
        },
        "node3": {
            RESPONSE: "Sorry, I can not talk about music now.",
            TRANSITIONS: [
                Tr(dst="node4", cnd=cnd.Regexp(re.compile(r"Ok, goodbye.")))
            ],
        },
        "node4": {
            RESPONSE: "bye",
            TRANSITIONS: [
                Tr(
                    dst="node1",
                    cnd=cnd.Any(
                        HiLowerCase(),
                        cnd.ExactMatch("hello"),
                    ),
                )
            ],
            # Combine sequences of conditions with `cnd.Any`
        },
        "fallback_node": {  # We get to this node
            # if no suitable transition was found
            RESPONSE: "Ooops",
            TRANSITIONS: [
                Tr(dst="node1", cnd=customized_condition),
                # use a previously instantiated condition here
                Tr(dst="start_node", cnd=False),
                # This transition will never be made
                Tr(dst="fallback_node"),
                # `True` is the default value of `cnd`
                # this transition will always be valid
            ],
        },
    }
}

# testing
happy_path = (
    (
        "Hi",
        "Hi, how are you?",
    ),  # start_node -> node1
    (
        "i'm fine, how are you?",
        "Good. What do you want to talk about?",
    ),  # node1 -> node2
    (
        "Let's talk about music.",
        "Sorry, I can not talk about music now.",
    ),  # node2 -> node3
    ("Ok, goodbye.", "bye"),  # node3 -> node4
    ("Hi", "Hi, how are you?"),  # node4 -> node1
    ("stop", "Ooops"),  # node1 -> fallback_node
    (
        "one",
        "Ooops",
    ),  # fallback_node -> fallback_node
    (
        "help",
        "Ooops",
    ),  # fallback_node -> fallback_node
    (
        "nope",
        "Ooops",
    ),  # fallback_node -> fallback_node
    (
        Message(misc={"some_key": "some_value"}),
        "Hi, how are you?",
    ),  # fallback_node -> node1
    (
        "i'm fine, how are you?",
        "Good. What do you want to talk about?",
    ),  # node1 -> node2
    (
        "Let's talk about music.",
        "Sorry, I can not talk about music now.",
    ),  # node2 -> node3
    ("Ok, goodbye.", "bye"),  # node3 -> node4
)
[6]:
pipeline = Pipeline(
    script=toy_script,
    start_label=("greeting_flow", "start_node"),
    fallback_label=("greeting_flow", "fallback_node"),
)

if __name__ == "__main__":
    check_happy_path(pipeline, happy_path, printout=True)
    if is_interactive_mode():
        pipeline.run()
USER: text='Hi'
BOT : text='Hi, how are you?'
USER: text='i'm fine, how are you?'
BOT : text='Good. What do you want to talk about?'
USER: text='Let's talk about music.'
BOT : text='Sorry, I can not talk about music now.'
USER: text='Ok, goodbye.'
BOT : text='bye'
USER: text='Hi'
BOT : text='Hi, how are you?'
USER: text='stop'
BOT : text='Ooops'
USER: text='one'
BOT : text='Ooops'
USER: text='help'
BOT : text='Ooops'
USER: text='nope'
BOT : text='Ooops'
USER: misc='{'some_key': 'some_value'}'
BOT : text='Hi, how are you?'
USER: text='i'm fine, how are you?'
BOT : text='Good. What do you want to talk about?'
USER: text='Let's talk about music.'
BOT : text='Sorry, I can not talk about music now.'
USER: text='Ok, goodbye.'
BOT : text='bye'