diff --git a/docs/docs/start-here.mdx b/docs/docs/start-here.mdx new file mode 100644 index 000000000000..428ffecc02a4 --- /dev/null +++ b/docs/docs/start-here.mdx @@ -0,0 +1,211 @@ +--- +id: start-here +sidebar_label: Start Here +title: Start Here +hide_table_of_contents: true +--- + + +## Getting Started with Rasa + +This page provides an introduction to Rasa. It's intended for newcomers and for people +familiar with earlier versions of Rasa who want to understand the new DM2 approach (name TBD). +DM2 gives you the best of both worlds: the time-to-value and generality of LLMs, and the +controllability of intent-based approaches. + +### Command Line Interface Basics + +Once you have installed Rasa, you can run the `init` command to create a starter project: + +```bash +rasa init --dm2 +``` + +Run the `train` command at any time to build an assistant from the current state of your project: + +```bash +rasa train +``` + +:::note Model Training + +While the command is called `rasa train`, building a new version of your assistant doesn't always +require training a model. Rasa uses a cache and only trains when necessary. +More information on the [`rasa train` command reference](./command-line-interface.mdx#rasa-train). + +::: + + +Run the `shell` command to talk to your assistant on the command line: + +```bash +rasa shell +``` + +To stop the conversation, type `/stop` or use `control+C`. + + +:::note Debugging + +Adding the flag `rasa shell --debug` can be very helpful if you want to understand what is happening in detail. +More information on the [`rasa shell` command reference](./command-line-interface.mdx#rasa-shell) + +::: + + +### Exploring what your assistant can do + +Assistants often have to collect a few pieces of information from the user in order to complete a task. +This starter project contains an example flow which recommends restaurants. To do so, it asks the user +for their preferred cuisine, city, and price range. + +To try it out, start a conversation using `rasa shell`, and say something like "I'm looking for a restaurant". + +Out of the box, this assistant can already handle a variety of conversations. The first is what we call the "happy path", +where the user always provides the information the assistant requests. +But if users change their mind, answer indirectly, or interject with questions, this assistant can handle those cases as well. +Try out some of these conversations yourself to get a feel for things. +If you want your assistant to sound a bit more natural, you can activate [contextual rephrasing](./llms/llm-nlg.mdx) of responses. + + + + + Can you recommend somewhere to eat? + What kind of food are you looking for? + Indian food + in what price range? + cheap + and in which city? + Berlin + Here's a recommendation: ... + + + + + I'm looking for a cheap Chinese restaurant in Amsterdam? + Here's a recommendation: ... + + + + + Can you recommend somewhere to eat? + What kind of food are you looking for? + Indian food + in what price range? + actually no I want Italian. A cheap spot + and in which city? + Berlin + Here's a recommendation: ... + + + + + Can you recommend somewhere to eat? + What kind of food are you looking for? + wait are you a bot? + I am a bot, powered by Rasa. + What kind of food are you looking for? + + + + + +### Understanding flows + +Given the range of conversations this assistant can handle, you might expect the implementation to be complex. +In fact, there are only two small files that provide Rasa what it needs. + +The `data/flows/restaurants.yml` file defines the logic for this flow. In this example the logic is linear +and walks the user through each of the steps in order. Each of the `question` steps fills the corresponding slot. + +```yaml +flows: + recommend_restaurant: + description: This flow recommends a restaurant + steps: + - id: "0" + question: cuisine + skip_if_filled: true + next: "1" + - id: "1" + question: price_range + next: "2" + - id: "2" + question: part_of_town + next: "3" + - id: "3" + action: search_restaurants +``` + +To build more advanced flows, you can add conditional logic, link to other flows, and more. Read more on how to handle [Business Logic with Flows](./flows.mdx) + +In addition to the flows, the `domain.yml` file contains definitions of the slots and responses used in this flow. + +### Understanding DM2 + +Rasa uses a new approach to building AI assistants called DM2 (name TBD). +If you've built AI assistants before, you might look at your project and think that many things are missing. + +* There is no NLU data with intents and entities +* There is no logic mapping an intent like "restaurant_search" to the start of the restaurant flow +* There are no slot mappings +* There is no explicit logic handling corrections, interruptions, or other unhappy paths. + +DM2 doesn't need any of these things to be able to handle the example conversations above. So, how does that work? + +Many developers are familiar with dialogue systems made up of separate NLU, dialogue, and NLG components +(this is also how Rasa worked previously). + +In DM2, we have a different set of modules. + +* The *conversation handling* component (name TBD) interprets the conversation so far and predicts +a series of commands to progress the state of the conversation. +* The *business logic* component executes those commands and the logic of your flows. + +The NLU systems you might be familiar with take a single user message as input, and aim to represent +the meaning of that message by predicting intents and entities. +Instead, *conversation handling* considers the conversation as a whole (not just one message), +and predicts the *intended effect* of the user's message. + +As an example, let's look at using a yes/no question to fill a slot called `late_delivery`: + + +Has it been more than 10 business days since you placed your order? + + + +When a user answers "yes" or "no", a traditional NLU model predicts an intent like `affirm` or `deny`. +A second step (usually handled by the dialogue manager) then maps the intents to +the `True/False` values of the `late_delivery` slot. + +Instead, the *conversation handling* component directly outputs a command to set the `late_delivery` slot to `True`. + +The *conversation handling* approach requires much less work to set up, since you don't need to worry about +intents and entities and slot mappings. It is also more powerful because it allows us to +[break free from intents](https://rasa.com/blog/its-about-time-we-get-rid-of-intents/). + +For example, intent-based approaches struggle when context is required to understand what the user means: + + +Has it been more than 10 business days since you placed your order? +sadly + + +This kind of conversation illustrates the limitations of working with intents. It's perfectly clear +what the user means in this context, but in general the word "sadly" does not mean `affirm`. +The *conversation handling* component will correctly output `SetSlot("late_delivery", True)`. + +The example project above includes a definition of the core business logic for recommending a restaurant +and not much else. Yet, it can handle a number of advanced conversations right away. +The advantage of DM2 is that it makes chatbots much smarter and much easier to build, while still giving +you full control over your business logic, and the ability to override and customize all behavior. + +Learn more about how to use DM2 to build advanced assistants: + +* Advanced flow logic +* Search-based question-answering +* Context switching +* Disambiguation +* Contextual understanding and negation +* Chitchat and digressions + diff --git a/docs/sidebars.js b/docs/sidebars.js index 738bae1a6983..3b44f4a1aa2f 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -265,6 +265,7 @@ module.exports = { }, ], "llms": [ + 'start-here', 'llms/large-language-models', 'llms/llm-setup', { diff --git a/rasa/cdu/command_generator/command_prompt_template.jinja2 b/rasa/cdu/command_generator/command_prompt_template.jinja2 index d4aefd05afe9..5ce4e603827c 100644 --- a/rasa/cdu/command_generator/command_prompt_template.jinja2 +++ b/rasa/cdu/command_generator/command_prompt_template.jinja2 @@ -25,7 +25,7 @@ Here are the slots of the currently active flow with their names and values: {% endif %} {% else %} You are currently not in any flow and so there are no active slots. -In order to fill a slot, you first have to start the flow and then fill the slot. +This means you can only set a slot if you first start a flow that requires that slot. {% endif %} If you start a flow, first start the flow and then optionally fill that flow's slots with information the user provided in their message. diff --git a/rasa/cdu/command_generator/llm_command_generator.py b/rasa/cdu/command_generator/llm_command_generator.py index a3711f0df9b7..f07b6ed0c205 100644 --- a/rasa/cdu/command_generator/llm_command_generator.py +++ b/rasa/cdu/command_generator/llm_command_generator.py @@ -21,8 +21,6 @@ from rasa.engine.storage.resource import Resource from rasa.engine.storage.storage import ModelStorage from rasa.shared.core.constants import ( - MAPPING_TYPE, - SlotMappingType, MAPPING_CONDITIONS, ACTIVE_LOOP, ) @@ -245,15 +243,14 @@ def is_extractable(q: QuestionFlowStep, tracker: DialogueStateTracker) -> bool: return False for mapping in slot.mappings: - if mapping.get(MAPPING_TYPE) == str(SlotMappingType.FROM_ENTITY): - conditions = mapping.get(MAPPING_CONDITIONS, []) - if len(conditions) == 0: - return True - else: - for condition in conditions: - active_loop = condition.get(ACTIVE_LOOP) - if active_loop and active_loop == tracker.active_loop_name: - return True + conditions = mapping.get(MAPPING_CONDITIONS, []) + if len(conditions) == 0: + return True + else: + for condition in conditions: + active_loop = condition.get(ACTIVE_LOOP) + if active_loop and active_loop == tracker.active_loop_name: + return True return False def render_template( diff --git a/rasa/cli/initial_project_dm2/actions/__init__.py b/rasa/cli/initial_project_dm2/actions/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/rasa/cli/initial_project_dm2/actions/actions.py b/rasa/cli/initial_project_dm2/actions/actions.py new file mode 100644 index 000000000000..8bf1f757f851 --- /dev/null +++ b/rasa/cli/initial_project_dm2/actions/actions.py @@ -0,0 +1,27 @@ +# This files contains your custom actions which can be used to run +# custom Python code. +# +# See this guide on how to implement these action: +# https://rasa.com/docs/rasa/custom-actions + + +# This is a simple example for a custom action which utters "Hello World!" + +# from typing import Any, Text, Dict, List +# +# from rasa_sdk import Action, Tracker +# from rasa_sdk.executor import CollectingDispatcher +# +# +# class ActionHelloWorld(Action): +# +# def name(self) -> Text: +# return "action_hello_world" +# +# def run(self, dispatcher: CollectingDispatcher, +# tracker: Tracker, +# domain: Dict[Text, Any]) -> List[Dict[Text, Any]]: +# +# dispatcher.utter_message(text="Hello World!") +# +# return [] diff --git a/rasa/cli/initial_project_dm2/config.yml b/rasa/cli/initial_project_dm2/config.yml new file mode 100644 index 000000000000..b6638aca4dc7 --- /dev/null +++ b/rasa/cli/initial_project_dm2/config.yml @@ -0,0 +1,13 @@ +recipe: default.v1 +language: en +pipeline: + - name: LLMCommandGenerator + # llm: + # model_name: gpt-4 + +policies: + - name: rasa.core.policies.flow_policy.FlowPolicy +# - name: rasa_plus.ml.DocsearchPolicy +# - name: RulePolicy + +assistant_id: 20230405-114328-tranquil-mustard diff --git a/rasa/cli/initial_project_dm2/credentials.yml b/rasa/cli/initial_project_dm2/credentials.yml new file mode 100644 index 000000000000..e9f12911e3cf --- /dev/null +++ b/rasa/cli/initial_project_dm2/credentials.yml @@ -0,0 +1,33 @@ +# This file contains the credentials for the voice & chat platforms +# which your bot is using. +# https://rasa.com/docs/rasa/messaging-and-voice-channels + +rest: +# # you don't need to provide anything here - this channel doesn't +# # require any credentials + + +#facebook: +# verify: "" +# secret: "" +# page-access-token: "" + +#slack: +# slack_token: "" +# slack_channel: "" +# slack_signing_secret: "" + +#socketio: +# user_message_evt: +# bot_message_evt: +# session_persistence: + +#mattermost: +# url: "https:///api/v4" +# token: "" +# webhook_url: "" + +# This entry is needed if you are using Rasa Enterprise. The entry represents credentials +# for the Rasa Enterprise "channel", i.e. Talk to your bot and Share with guest testers. +rasa: + url: "http://localhost:5002/api" diff --git a/rasa/cli/initial_project_dm2/data/flows.yml b/rasa/cli/initial_project_dm2/data/flows.yml new file mode 100644 index 000000000000..f7a4f7e6b5f4 --- /dev/null +++ b/rasa/cli/initial_project_dm2/data/flows.yml @@ -0,0 +1,46 @@ +flows: + say_goodbye: + description: say goodbye to the user + steps: + - id: "0" + action: utter_goodbye + bot_challenge: + description: explain to the user that they are talking to a bot, if they ask + steps: + - id: "0" + action: utter_iamabot + greet: + description: greet the user and ask how they are doing. cheer them up if needed. + steps: + - id: "0" + question: good_mood + description: "can be true or false" + next: + - if: good_mood + then: "doing_great" + - else: "cheer_up" + - id: "doing_great" + action: utter_happy + - id: "cheer_up" + action: utter_cheer_up + next: "did_that_help" + - id: "did_that_help" + action: utter_did_that_help + recommend_restaurant: + description: This flow recommends a restaurant + steps: + - id: "0" + question: cuisine + skip_if_filled: true + next: "1" + - id: "1" + question: price_range + skip_if_filled: true + next: "2" + - id: "2" + question: city + skip_if_filled: true + next: "3" + - id: "3" + action: utter_recommend_restaurant + diff --git a/rasa/cli/initial_project_dm2/data/nlu.yml b/rasa/cli/initial_project_dm2/data/nlu.yml new file mode 100644 index 000000000000..edbbf36d42ad --- /dev/null +++ b/rasa/cli/initial_project_dm2/data/nlu.yml @@ -0,0 +1,11 @@ +version: "3.1" + +nlu: + - intent: affirm + examples: | + - yes + - yup + - intent: deny + examples: | + - no + - nope \ No newline at end of file diff --git a/rasa/cli/initial_project_dm2/domain.yml b/rasa/cli/initial_project_dm2/domain.yml new file mode 100644 index 000000000000..eb308f751812 --- /dev/null +++ b/rasa/cli/initial_project_dm2/domain.yml @@ -0,0 +1,64 @@ +version: "3.1" + +slots: + good_mood: + type: bool + mappings: + - type: custom + cuisine: + type: text + mappings: + - type: custom + price_range: + type: text + mappings: + - type: custom + city: + type: text + mappings: + - type: custom + + +responses: + utter_greet: + - text: "Hey!" + + utter_ask_good_mood: + - text: "How are you?" + + utter_ask_cuisine: + - text: "What kind of food are you looking for?" + + utter_ask_price_range: + - text: "in what price range?" + + utter_ask_city: + - text: "and in which city?" + + utter_recommend_restaurant: + - text: "Here's a recommendation ..." + + utter_cheer_up: + - text: "Here is something to cheer you up:" + image: "https://i.imgur.com/nGF1K8f.jpg" + + utter_did_that_help: + - text: "Did that help you?" + + utter_flow_continue_interrupted: + - text: Let's continue with the previous topic {flow_name}. + metadata: {allow_variation: True} + + utter_happy: + - text: "Great, carry on!" + + utter_goodbye: + - text: "Bye" + + utter_iamabot: + - text: "I am a bot, powered by Rasa." + + +session_config: + session_expiration_time: 60 + carry_over_slots_to_new_session: true diff --git a/rasa/cli/initial_project_dm2/e2e_tests/complete_request.yml b/rasa/cli/initial_project_dm2/e2e_tests/complete_request.yml new file mode 100644 index 000000000000..b0a93f5bfe37 --- /dev/null +++ b/rasa/cli/initial_project_dm2/e2e_tests/complete_request.yml @@ -0,0 +1,5 @@ +test_cases: + - test_case: user corrects recipient in the next message + steps: + - user: I'm looking for a cheap Chinese restaurant in Amsterdam + - utter: utter_recommend_restaurant \ No newline at end of file diff --git a/rasa/cli/initial_project_dm2/e2e_tests/happy.yml b/rasa/cli/initial_project_dm2/e2e_tests/happy.yml new file mode 100644 index 000000000000..2181e9d2bb7d --- /dev/null +++ b/rasa/cli/initial_project_dm2/e2e_tests/happy.yml @@ -0,0 +1,11 @@ +test_cases: + - test_case: user corrects recipient in the next message + steps: + - user: Please recommend a restaurant + - utter: utter_ask_cuisine + - user: Indian food + - utter: utter_ask_price_range + - user: cheap + - utter: utter_ask_city + - user: Berlin + - utter: utter_recommend_restaurant \ No newline at end of file diff --git a/rasa/cli/initial_project_dm2/endpoints.yml b/rasa/cli/initial_project_dm2/endpoints.yml new file mode 100644 index 000000000000..5f65275b8802 --- /dev/null +++ b/rasa/cli/initial_project_dm2/endpoints.yml @@ -0,0 +1,42 @@ +# This file contains the different endpoints your bot can use. + +# Server where the models are pulled from. +# https://rasa.com/docs/rasa/model-storage#fetching-models-from-a-server + +#models: +# url: http://my-server.com/models/default_core@latest +# wait_time_between_pulls: 10 # [optional](default: 100) + +# Server which runs your custom actions. +# https://rasa.com/docs/rasa/custom-actions + +action_endpoint: + url: "http://localhost:5055/webhook" + +# Tracker store which is used to store the conversations. +# By default the conversations are stored in memory. +# https://rasa.com/docs/rasa/tracker-stores + +#tracker_store: +# type: redis +# url: +# port: +# db: +# password: +# use_ssl: + +#tracker_store: +# type: mongod +# url: +# db: +# username: +# password: + +# Event broker which all conversation events should be streamed to. +# https://rasa.com/docs/rasa/event-brokers + +#event_broker: +# url: localhost +# username: username +# password: password +# queue: queue diff --git a/rasa/cli/initial_project_dm2/tests/test_stories.yml b/rasa/cli/initial_project_dm2/tests/test_stories.yml new file mode 100644 index 000000000000..d46e39b3ea06 --- /dev/null +++ b/rasa/cli/initial_project_dm2/tests/test_stories.yml @@ -0,0 +1,91 @@ +#### This file contains tests to evaluate that your bot behaves as expected. +#### If you want to learn more, please see the docs: https://rasa.com/docs/rasa/testing-your-assistant + +stories: +- story: happy path 1 + steps: + - user: | + hello there! + intent: greet + - action: utter_greet + - user: | + amazing + intent: mood_great + - action: utter_happy + +- story: happy path 2 + steps: + - user: | + hello there! + intent: greet + - action: utter_greet + - user: | + amazing + intent: mood_great + - action: utter_happy + - user: | + bye-bye! + intent: goodbye + - action: utter_goodbye + +- story: sad path 1 + steps: + - user: | + hello + intent: greet + - action: utter_greet + - user: | + not good + intent: mood_unhappy + - action: utter_cheer_up + - action: utter_did_that_help + - user: | + yes + intent: affirm + - action: utter_happy + +- story: sad path 2 + steps: + - user: | + hello + intent: greet + - action: utter_greet + - user: | + not good + intent: mood_unhappy + - action: utter_cheer_up + - action: utter_did_that_help + - user: | + not really + intent: deny + - action: utter_goodbye + +- story: sad path 3 + steps: + - user: | + hi + intent: greet + - action: utter_greet + - user: | + very terrible + intent: mood_unhappy + - action: utter_cheer_up + - action: utter_did_that_help + - user: | + no + intent: deny + - action: utter_goodbye + +- story: say goodbye + steps: + - user: | + bye-bye! + intent: goodbye + - action: utter_goodbye + +- story: bot challenge + steps: + - user: | + are you a bot? + intent: bot_challenge + - action: utter_iamabot diff --git a/rasa/cli/scaffold.py b/rasa/cli/scaffold.py index b5f0f1e50f56..b6715519b896 100644 --- a/rasa/cli/scaffold.py +++ b/rasa/cli/scaffold.py @@ -42,7 +42,11 @@ def add_subparser( default=None, help="Directory where your project should be initialized.", ) - + scaffold_parser.add_argument( + "--dm2", + action="store_true", + help="Temporary. Whether to create a DM2 project or a classic one", + ) scaffold_parser.set_defaults(func=run) @@ -127,21 +131,23 @@ def print_run_or_instructions(args: argparse.Namespace) -> None: def init_project(args: argparse.Namespace, path: Text) -> None: """Inits project.""" os.chdir(path) - create_initial_project(".") + create_initial_project(".", args.dm2) print(f"Created project directory at '{os.getcwd()}'.") print_train_or_instructions(args) -def create_initial_project(path: Text) -> None: +def create_initial_project(path: Text, is_dm2: bool = False) -> None: """Creates directory structure and templates for initial project.""" from distutils.dir_util import copy_tree - copy_tree(scaffold_path(), path) + copy_tree(scaffold_path(is_dm2), path) -def scaffold_path() -> Text: +def scaffold_path(is_dm2: bool = False) -> Text: import pkg_resources + if is_dm2: + return pkg_resources.resource_filename(__name__, "initial_project_dm2") return pkg_resources.resource_filename(__name__, "initial_project") diff --git a/tests/cli/test_rasa_init.py b/tests/cli/test_rasa_init.py index b7f45cb856da..62932d5039e3 100644 --- a/tests/cli/test_rasa_init.py +++ b/tests/cli/test_rasa_init.py @@ -46,7 +46,7 @@ def test_init_help(run: Callable[..., RunResult]): help_text = f"""usage: {RASA_EXE} init [-h] [-v] [-vv] [--quiet] [--logging-config-file LOGGING_CONFIG_FILE] [--no-prompt] - [--init-dir INIT_DIR]""" + [--init-dir INIT_DIR] [--dm2]""" lines = help_text.split("\n") # expected help text lines should appear somewhere in the output