Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add condition asserting str is in Message.text #335

Merged
merged 7 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ import dff.script.conditions.std_conditions as cnd
script = {
GLOBAL: {
TRANSITIONS: {
("flow", "node_hi"): cnd.exact_match(Message("Hi")),
("flow", "node_hi"): cnd.has_text("Hi"),
("flow", "node_ok"): cnd.true()
}
},
Expand Down
1 change: 1 addition & 0 deletions dff/script/conditions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .std_conditions import (
exact_match,
has_text,
regexp,
check_cond_seq,
aggregate,
Expand Down
16 changes: 16 additions & 0 deletions dff/script/conditions/std_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ def exact_match_condition_handler(ctx: Context, pipeline: Pipeline) -> bool:
return exact_match_condition_handler


@validate_call
def has_text(text: str) -> Callable[[Context, Pipeline], bool]:
"""
Return function handler. This handler returns `True` only if the last user phrase
contains the phrase specified in :py:param:`text`.

:param text: A `str` variable to look for within the user request.
"""

def has_text_condition_handler(ctx: Context, pipeline: Pipeline) -> bool:
request = ctx.last_request
return text in request.text

return has_text_condition_handler


@validate_call
def regexp(pattern: Union[str, Pattern], flags: Union[int, re.RegexFlag] = 0) -> Callable[[Context, Pipeline], bool]:
"""
Expand Down
66 changes: 33 additions & 33 deletions dff/utils/testing/toy_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@
in tutorials.
"""

from dff.script.conditions import exact_match
from dff.script.conditions import has_text
from dff.script import TRANSITIONS, RESPONSE, Message

TOY_SCRIPT = {
"greeting_flow": {
"start_node": {
RESPONSE: Message(),
TRANSITIONS: {"node1": exact_match(Message("Hi"))},
TRANSITIONS: {"node1": has_text("Hi")},
},
"node1": {
RESPONSE: Message("Hi, how are you?"),
TRANSITIONS: {"node2": exact_match(Message("i'm fine, how are you?"))},
TRANSITIONS: {"node2": has_text("i'm fine, how are you?")},
},
"node2": {
RESPONSE: Message("Good. What do you want to talk about?"),
TRANSITIONS: {"node3": exact_match(Message("Let's talk about music."))},
TRANSITIONS: {"node3": has_text("Let's talk about music.")},
},
"node3": {
RESPONSE: Message("Sorry, I can not talk about music now."),
TRANSITIONS: {"node4": exact_match(Message("Ok, goodbye."))},
TRANSITIONS: {"node4": has_text("Ok, goodbye.")},
},
"node4": {RESPONSE: Message("bye"), TRANSITIONS: {"node1": exact_match(Message("Hi"))}},
"node4": {RESPONSE: Message("bye"), TRANSITIONS: {"node1": has_text("Hi")}},
"fallback_node": {
RESPONSE: Message("Ooops"),
TRANSITIONS: {"node1": exact_match(Message("Hi"))},
TRANSITIONS: {"node1": has_text("Hi")},
},
}
}
Expand Down Expand Up @@ -69,37 +69,37 @@
"start": {
RESPONSE: Message("Hi"),
TRANSITIONS: {
("small_talk", "ask_some_questions"): exact_match(Message("hi")),
("animals", "have_pets"): exact_match(Message("i like animals")),
("animals", "like_animals"): exact_match(Message("let's talk about animals")),
("news", "what_news"): exact_match(Message("let's talk about news")),
("small_talk", "ask_some_questions"): has_text("hi"),
("animals", "have_pets"): has_text("i like animals"),
("animals", "like_animals"): has_text("let's talk about animals"),
("news", "what_news"): has_text("let's talk about news"),
},
},
"fallback": {RESPONSE: Message("Oops")},
},
"animals": {
"have_pets": {
RESPONSE: Message("do you have pets?"),
TRANSITIONS: {"what_animal": exact_match(Message("yes"))},
TRANSITIONS: {"what_animal": has_text("yes")},
},
"like_animals": {
RESPONSE: Message("do you like it?"),
TRANSITIONS: {"what_animal": exact_match(Message("yes"))},
TRANSITIONS: {"what_animal": has_text("yes")},
},
"what_animal": {
RESPONSE: Message("what animals do you have?"),
TRANSITIONS: {
"ask_about_color": exact_match(Message("bird")),
"ask_about_breed": exact_match(Message("dog")),
"ask_about_color": has_text("bird"),
"ask_about_breed": has_text("dog"),
},
},
"ask_about_color": {RESPONSE: Message("what color is it")},
"ask_about_breed": {
RESPONSE: Message("what is this breed?"),
TRANSITIONS: {
"ask_about_breed": exact_match(Message("pereat")),
"tell_fact_about_breed": exact_match(Message("bulldog")),
"ask_about_training": exact_match(Message("I don't know")),
"ask_about_breed": has_text("pereat"),
"tell_fact_about_breed": has_text("bulldog"),
"ask_about_training": has_text("I don't know"),
},
},
"tell_fact_about_breed": {
Expand All @@ -111,53 +111,53 @@
"what_news": {
RESPONSE: Message("what kind of news do you prefer?"),
TRANSITIONS: {
"ask_about_science": exact_match(Message("science")),
"ask_about_sport": exact_match(Message("sport")),
"ask_about_science": has_text("science"),
"ask_about_sport": has_text("sport"),
},
},
"ask_about_science": {
RESPONSE: Message("i got news about science, do you want to hear?"),
TRANSITIONS: {
"science_news": exact_match(Message("yes")),
("small_talk", "ask_some_questions"): exact_match(Message("let's change the topic")),
"science_news": has_text("yes"),
("small_talk", "ask_some_questions"): has_text("let's change the topic"),
},
},
"science_news": {
RESPONSE: Message("This is science news"),
TRANSITIONS: {
"what_news": exact_match(Message("ok")),
("small_talk", "ask_some_questions"): exact_match(Message("let's change the topic")),
"what_news": has_text("ok"),
("small_talk", "ask_some_questions"): has_text("let's change the topic"),
},
},
"ask_about_sport": {
RESPONSE: Message("i got news about sport, do you want to hear?"),
TRANSITIONS: {
"sport_news": exact_match(Message("yes")),
("small_talk", "ask_some_questions"): exact_match(Message("let's change the topic")),
"sport_news": has_text("yes"),
("small_talk", "ask_some_questions"): has_text("let's change the topic"),
},
},
"sport_news": {
RESPONSE: Message("This is sport news"),
TRANSITIONS: {
"what_news": exact_match(Message("ok")),
("small_talk", "ask_some_questions"): exact_match(Message("let's change the topic")),
"what_news": has_text("ok"),
("small_talk", "ask_some_questions"): has_text("let's change the topic"),
},
},
},
"small_talk": {
"ask_some_questions": {
RESPONSE: Message("how are you"),
TRANSITIONS: {
"ask_talk_about": exact_match(Message("fine")),
("animals", "like_animals"): exact_match(Message("let's talk about animals")),
("news", "what_news"): exact_match(Message("let's talk about news")),
"ask_talk_about": has_text("fine"),
("animals", "like_animals"): has_text("let's talk about animals"),
("news", "what_news"): has_text("let's talk about news"),
},
},
"ask_talk_about": {
RESPONSE: Message("what do you want to talk about"),
TRANSITIONS: {
("animals", "like_animals"): exact_match(Message("dog")),
("news", "what_news"): exact_match(Message("let's talk about news")),
("animals", "like_animals"): has_text("dog"),
("news", "what_news"): has_text("let's talk about news"),
},
},
},
Expand Down
12 changes: 6 additions & 6 deletions docs/source/user_guides/basic_conceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ Example flow & script
RESPONSE: Message(), # the response of the initial node is skipped
TRANSITIONS: {
("greeting_flow", "greeting_node"):
cnd.exact_match(Message("/start")),
cnd.has_text("/start"),
},
},
"greeting_node": {
RESPONSE: Message("Hi!"),
TRANSITIONS: {
("ping_pong_flow", "game_start_node"):
cnd.exact_match(Message("Hello!"))
cnd.has_text("Hello!")
}
},
"fallback_node": {
Expand All @@ -111,14 +111,14 @@ Example flow & script
RESPONSE: Message("Let's play ping-pong!"),
TRANSITIONS: {
("ping_pong_flow", "response_node"):
cnd.exact_match(Message("Ping!")),
cnd.has_text("Ping!"),
},
},
"response_node": {
RESPONSE: Message("Pong!"),
TRANSITIONS: {
("ping_pong_flow", "response_node"):
cnd.exact_match(Message("Ping!")),
cnd.has_text("Ping!"),
},
},
},
Expand Down Expand Up @@ -151,7 +151,7 @@ Likewise, if additional scenarios need to be covered, additional flow objects ca
This is a dictionary that maps labels of other nodes to conditions, i.e. callback functions that
return `True` or `False`. These conditions determine whether respective nodes can be visited
in the next turn.
In the example script, we use standard transitions: ``exact_match`` requires the user request to
In the example script, we use standard transitions: ``has_text`` requires the user request to
fully match the provided text, while ``true`` always allows a transition. However, passing custom
callbacks that implement arbitrary logic is also an option.

Expand Down Expand Up @@ -384,4 +384,4 @@ Further reading
* `Guide on Context <../user_guides/context_guide.html>`_
* `Tutorial on global transitions <../tutorials/tutorials.script.core.5_global_transitions.html>`_
* `Tutorial on context serialization <../tutorials/tutorials.script.core.6_context_serialization.html>`_
* `Tutorial on script MISC <../tutorials/tutorials.script.core.8_misc.html>`_
* `Tutorial on script MISC <../tutorials/tutorials.script.core.8_misc.html>`_
6 changes: 3 additions & 3 deletions tests/pipeline/test_messenger_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
RESPONSE: {
"text": "",
},
TRANSITIONS: {"node1": cnd.exact_match(Message("Ping"))},
TRANSITIONS: {"node1": cnd.has_text("Ping")},
},
"node1": {
RESPONSE: {
"text": "Pong",
},
TRANSITIONS: {"node1": cnd.exact_match(Message("Ping"))},
TRANSITIONS: {"node1": cnd.has_text("Ping")},
},
"fallback_node": {
RESPONSE: {
"text": "Ooops",
},
TRANSITIONS: {"node1": cnd.exact_match(Message("Ping"))},
TRANSITIONS: {"node1": cnd.has_text("Ping")},
},
}
}
Expand Down
20 changes: 12 additions & 8 deletions tests/script/conditions/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,25 @@ def test_conditions():
assert cnd.exact_match(Message())(ctx, pipeline)
assert not cnd.exact_match(Message(), skip_none=False)(ctx, pipeline)

assert cnd.has_text("text")(ctx, pipeline)
assert not cnd.has_text("text1")(ctx, pipeline)
assert cnd.has_text("")(ctx, pipeline)

assert cnd.regexp("t.*t")(ctx, pipeline)
assert not cnd.regexp("t.*t1")(ctx, pipeline)
assert not cnd.regexp("t.*t1")(failed_ctx, pipeline)

assert cnd.agg([cnd.regexp("t.*t"), cnd.exact_match(Message("text"))], aggregate_func=all)(ctx, pipeline)
assert not cnd.agg([cnd.regexp("t.*t1"), cnd.exact_match(Message("text"))], aggregate_func=all)(ctx, pipeline)
assert cnd.agg([cnd.regexp("t.*t"), cnd.has_text("text")], aggregate_func=all)(ctx, pipeline)
assert not cnd.agg([cnd.regexp("t.*t1"), cnd.has_text("text")], aggregate_func=all)(ctx, pipeline)

assert cnd.any([cnd.regexp("t.*t1"), cnd.exact_match(Message("text"))])(ctx, pipeline)
assert not cnd.any([cnd.regexp("t.*t1"), cnd.exact_match(Message("text1"))])(ctx, pipeline)
assert cnd.any([cnd.regexp("t.*t1"), cnd.has_text("text")])(ctx, pipeline)
assert not cnd.any([cnd.regexp("t.*t1"), cnd.has_text("text1")])(ctx, pipeline)

assert cnd.all([cnd.regexp("t.*t"), cnd.exact_match(Message("text"))])(ctx, pipeline)
assert not cnd.all([cnd.regexp("t.*t1"), cnd.exact_match(Message("text"))])(ctx, pipeline)
assert cnd.all([cnd.regexp("t.*t"), cnd.has_text("text")])(ctx, pipeline)
assert not cnd.all([cnd.regexp("t.*t1"), cnd.has_text("text")])(ctx, pipeline)

assert cnd.neg(cnd.exact_match(Message("text1")))(ctx, pipeline)
assert not cnd.neg(cnd.exact_match(Message("text")))(ctx, pipeline)
assert cnd.neg(cnd.has_text("text1"))(ctx, pipeline)
assert not cnd.neg(cnd.has_text("text"))(ctx, pipeline)

assert cnd.has_last_labels(flow_labels=["flow"])(ctx, pipeline)
assert not cnd.has_last_labels(flow_labels=["flow1"])(ctx, pipeline)
Expand Down
4 changes: 2 additions & 2 deletions tutorials/messengers/telegram/1_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ class and [telebot](https://pytba.readthedocs.io/en/latest/index.html)
script = {
"greeting_flow": {
"start_node": {
TRANSITIONS: {"greeting_node": cnd.exact_match(Message("/start"))},
TRANSITIONS: {"greeting_node": cnd.has_text("/start")},
},
"greeting_node": {
RESPONSE: Message("Hi"),
TRANSITIONS: {lbl.repeat(): cnd.true()},
},
"fallback_node": {
RESPONSE: Message("Please, repeat the request"),
TRANSITIONS: {"greeting_node": cnd.exact_match(Message("/start"))},
TRANSITIONS: {"greeting_node": cnd.has_text("/start")},
},
}
}
Expand Down
16 changes: 6 additions & 10 deletions tutorials/script/core/1_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,37 +57,33 @@
"start_node": { # This is the initial node,
# it doesn't contain a `RESPONSE`.
RESPONSE: Message(),
TRANSITIONS: {"node1": cnd.exact_match(Message("Hi"))},
TRANSITIONS: {"node1": cnd.has_text("Hi")},
# If "Hi" == request of the user then we make the transition.
},
"node1": {
RESPONSE: Message(
text="Hi, how are you?"
), # When the agent enters node1,
# return "Hi, how are you?".
TRANSITIONS: {
"node2": cnd.exact_match(Message("I'm fine, how are you?"))
},
TRANSITIONS: {"node2": cnd.has_text("I'm fine, how are you?")},
},
"node2": {
RESPONSE: Message("Good. What do you want to talk about?"),
TRANSITIONS: {
"node3": cnd.exact_match(Message("Let's talk about music."))
},
TRANSITIONS: {"node3": cnd.has_text("Let's talk about music.")},
},
"node3": {
RESPONSE: Message("Sorry, I can not talk about music now."),
TRANSITIONS: {"node4": cnd.exact_match(Message("Ok, goodbye."))},
TRANSITIONS: {"node4": cnd.has_text("Ok, goodbye.")},
},
"node4": {
RESPONSE: Message("Bye"),
TRANSITIONS: {"node1": cnd.exact_match(Message("Hi"))},
TRANSITIONS: {"node1": cnd.has_text("Hi")},
},
"fallback_node": {
# We get to this node if the conditions
# for switching to other nodes are not performed.
RESPONSE: Message("Ooops"),
TRANSITIONS: {"node1": cnd.exact_match(Message("Hi"))},
TRANSITIONS: {"node1": cnd.has_text("Hi")},
},
}
}
Expand Down
5 changes: 3 additions & 2 deletions tutorials/script/core/2_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def func(ctx: Context, pipeline: Pipeline) -> bool

* `exact_match` returns `True` if the user's request completely
matches the value passed to the function.
* `has_text` returns `True` if the specified text matches the user's request.
* `regexp` returns `True` if the pattern matches the user's request,
while the user's request must be a string.
`regexp` has same signature as `re.compile` function.
Expand Down Expand Up @@ -107,7 +108,7 @@ def internal_condition_function(ctx: Context, _: Pipeline) -> bool:
"start_node": { # This is the initial node,
# it doesn't contain a `RESPONSE`.
RESPONSE: Message(),
TRANSITIONS: {"node1": cnd.exact_match(Message("Hi"))},
TRANSITIONS: {"node1": cnd.has_text("Hi")},
# If "Hi" == request of user then we make the transition
},
"node1": {
Expand Down Expand Up @@ -137,7 +138,7 @@ def internal_condition_function(ctx: Context, _: Pipeline) -> bool:
"node1": cnd.any(
[
hi_lower_case_condition,
cnd.exact_match(Message("hello")),
cnd.has_text("hello"),
]
)
},
Expand Down
Loading