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

[ENG-876] Feature completeness #26

Merged
merged 15 commits into from
Jun 17, 2024
55 changes: 35 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ This project is released under the Rasa's [Early Release Software Access Terms](

## Demo Bot

The demo bot's business logic is implemented as a set of [flows](https://rasa.com/docs/rasa-pro/concepts/flows),
The demo bot's business logic is implemented as a set of [flows](https://rasa.com/docs/rasa-pro/concepts/flows), [rules](https://rasa.com/docs/rasa/rules), and [stories](https://rasa.com/docs/rasa/stories),
which are organized into three main skill groups: Contacts, Transactions,
and Others/Misc.

The skill groups `Contacts` and `Transactions` are implemented using CALM, e.g. are defined in flows.
The skill group `Others/Misc` is implemented via the [nlu-based approach](https://rasa.com/docs/rasa/training-data-format).
[Coexistence](https://rasa.com/docs/rasa-pro/building-assistants/coexistence) allows you to run a single assistant that uses both the Conversational AI with Language Models (CALM)
approach and an NLU-based system in parallel.

Each flow consists of a `yaml` file and a [domain definition](https://rasa.com/docs/rasa-pro/concepts/domain),
which includes [actions](https://rasa.com/docs/rasa-pro/concepts/domain#actions),
[slots](https://rasa.com/docs/rasa-pro/concepts/domain#slots), and
[bot ressponses](https://rasa.com/docs/rasa-pro/concepts/domain#responses).
The table below shows all the skills implemented in the bot, along with the flow and
domain definitions for each:

The table below shows all the skills implemented in the bot:

<table border="1">
<tr>
Expand All @@ -41,21 +46,21 @@ domain definitions for each:
<td>Add new contact</td>
<td>Adds a new contact to the user's list.</td>
<td><a href="data/flows/add_contact.yml">Link</a></td>
<td><a href="domain/add_contact.yml">Link</a></td>
<td><a href="domain/flows/add_contact.yml">Link</a></td>
</tr>

<tr>
<td>Remove contact</td>
<td>Removes selected contact from the user's list.</td>
<td><a href="data/flows/remove_contact.yml">Link</a></td>
<td><a href="domain/remove_contact.yml">Link</a></td>
<td><a href="domain/flows/remove_contact.yml">Link</a></td>
</tr>

<tr>
<td>List contacts</td>
<td>List all of user's saved contacts.</td>
<td><a href="data/flows/list_contacts.yml">Link</a></td>
<td><a href="domain/list_contacts.yml">Link</a></td>
<td><a href="domain/flows/list_contacts.yml">Link</a></td>
</tr>


Expand All @@ -66,35 +71,35 @@ domain definitions for each:
<td>Check account balance</td>
<td>Allows users to check their current account balance.</td>
<td><a href="data/flows/check_balance.yml">Link</a></td>
<td><a href="domain/check_balance.yml">Link</a></td>
<td><a href="domain/flows/check_balance.yml">Link</a></td>
</tr>

<tr>
<td>Transfer money</td>
<td>Facilitates the transfer of funds to user's contacts.</td>
<td><a href="data/flows/transfer_money.yml">Link</a></td>
<td><a href="domain/transfer_money.yml">Link</a></td>
<td><a href="domain/flows/transfer_money.yml">Link</a></td>
</tr>

<tr>
<td>Setup recurrent payment</td>
<td>Sets up recurring payments which can either be a direct debit or a standing order.</td>
<td><a href="data/flows/setup_recurrent_payment.yml">Link</a></td>
<td><a href="domain/setup_recurrent_payment.yml">Link</a></td>
<td><a href="domain/flows/setup_recurrent_payment.yml">Link</a></td>
</tr>

<tr>
<td>List transactions</td>
<td>List the last user's transactions.</td>
<td><a href="data/flows/transaction_search.yml">Link</a></td>
<td><a href="domain/transaction_search.yml">Link</a></td>
<td><a href="domain/flows/transaction_search.yml">Link</a></td>
</tr>

<tr>
<td>Replace card</td>
<td>Replace the user's card.</td>
<td><a href="data/flows/replace_card.yml">Link</a></td>
<td><a href="domain/replace_card.yml">Link</a></td>
<td><a href="domain/flows/replace_card.yml">Link</a></td>
</tr>

<tr>
Expand All @@ -108,40 +113,50 @@ domain definitions for each:
<td>Verify account</td>
<td>Verify an account for higher transfer limits.</td>
<td><a href="data/flows/verify_account.yml">Link</a></td>
<td><a href="domain/verify_account.yml">Link</a></td>
<td><a href="domain/flows/verify_account.yml">Link</a></td>
</tr>

</table>


<table border="1">
<tr>
<th>Skill Group</th>
<th>Description</th>
<th>Link to story, rules, nlu data</th>
<th>Link to domain</th>
</tr>

<!-- Others / Misc -->

<tr>
<td rowspan="5">Others / Misc</td>
<td>Book Restaurant</td>
<td>Make a reservation at a restaurant.</td>
<td><a href="data/flows/book_restaurant.yml">Link</a></td>
<td><a href="domain/book_restaurant.yml">Link</a></td>
<td><a href="data/nlu-based">Link</a></td>
<td><a href="domain/nlu-based/restaurant.yml">Link</a></td>
</tr>

<tr>
<td>Health Advice</td>
<td>Detects an out-of-scope topic: health advice.</td>
<td><a href="data/flows/health_advice.yml">Link</a></td>
<td><a href="domain/health_advice.yml">Link</a></td>
<td><a href="data/nlu-based">Link</a></td>
<td><a href="domain/nlu-based/health_advice.yml">Link</a></td>
</tr>

<tr>
<td>Hotel search</td>
<td>Search for a hotel and show hotel rating.</td>
<td><a href="data/flows/hotel_search.yml">Link</a></td>
<td><a href="domain/hotel_search.yml">Link</a></td>
<td><a href="data/nlu-based">Link</a></td>
<td><a href="domain/nlu-based/hotel_search.yml">Link</a></td>
</tr>

</table>

Rasa ships with a default behavior for every [conversation repair case](https://rasa.com/docs/rasa-pro/concepts/conversation-repair/#conversation-repair-cases)
Rasa ships with a default behavior in CALM for every [conversation repair case](https://rasa.com/docs/rasa-pro/concepts/conversation-repair/#conversation-repair-cases)
which is handled through a [default pattern flow](https://rasa.com/docs/rasa-pro/concepts/conversation-repair/#conversation-repair-cases).
In addition to its core functionality, the demo bot also includes an examples of
pattern overriding in [`data/flows/patterns.yml`](./data/flows/patterns.yml).
pattern overriding in [`data/flows/patterns.yml`](data/flows/patterns.yml).

## Running the project

Expand Down
25 changes: 25 additions & 0 deletions actions/action_ask_remove_contact_handle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Dict, Text

from rasa_sdk import Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk import Action

from actions.db import get_contacts


class AskForRemoveContactHandle(Action):
def name(self) -> Text:
return "action_ask_remove_contact_handle"

def run(
self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
):
contacts = get_contacts(tracker.sender_id)

dispatcher.utter_button_message(
text="What's the handle of the user you want to remove?",
buttons=[
{"title": f"{c.handle} ({c.name})", "payload": c.handle}
for c in contacts
]
)
25 changes: 25 additions & 0 deletions actions/action_check_portfolio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from rasa_sdk import Action, Tracker
from rasa_sdk.events import SlotSet
from rasa_sdk.executor import CollectingDispatcher

from actions.db import get_portfolio_options


class ActionCheckPortfolio(Action):

def name(self) -> str:
return "action_check_portfolio"

def run(self, dispatcher: CollectingDispatcher, tracker: Tracker,
domain: dict) -> list:
# Retrieve the portfolio type from slots
portfolio_type = tracker.get_slot("portfolio_type")

portfolio_db = get_portfolio_options(tracker.sender_id)

portfolio_exists = [p for p in portfolio_db if p.type == portfolio_type]

if portfolio_exists:
return [SlotSet("portfolio_exists", True)]
else:
return [SlotSet("portfolio_exists", False)]
26 changes: 26 additions & 0 deletions actions/action_show_portfolio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from rasa_sdk import Action, Tracker
from rasa_sdk.events import SlotSet
from rasa_sdk.executor import CollectingDispatcher

from actions.db import get_portfolio_options


class ActionShowPortfolio(Action):

def name(self) -> str:
return "action_show_portfolio"

tabergma marked this conversation as resolved.
Show resolved Hide resolved
def run(self, dispatcher: CollectingDispatcher, tracker: Tracker,
domain: dict) -> list:
# Retrieve the portfolio type from slots
portfolio_type = tracker.get_slot("portfolio_type")

# Placeholder for portfolio data
portfolio_db = get_portfolio_options(tracker.sender_id)

portfolio = [p.options for p in portfolio_db if p.type == portfolio_type]

if portfolio:
return [SlotSet("portfolio_options", portfolio[0])]
else:
return [SlotSet("portfolio_options", [])]
58 changes: 58 additions & 0 deletions actions/ask_for_slot_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import Dict, Text

from rasa_sdk import Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk import Action

from actions.db import get_restaurants


class AskForRestaurantFormCuisine(Action):
def name(self) -> Text:
return "action_ask_restaurant_form_cuisine"

def run(
self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
):
restaurants = get_restaurants(tracker.sender_id)
cuisine_list = set([
r.cuisine
for r in restaurants
if r.city.lower() == tracker.get_slot("city").lower()
])

dispatcher.utter_button_message(
text="What cuisine are you looking for?",
buttons=[
{"title": c, "payload": f'/inform{{"cuisine":"{c}"}}'}
for c in cuisine_list
]
)


class AskForRestaurantFormRestaurantName(Action):
def name(self) -> Text:
return "action_ask_restaurant_form_restaurant_name"

def run(
self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
):
restaurants = get_restaurants(tracker.sender_id)

restaurant_names = set([
r.name for r in restaurants
if r.city.lower() == tracker.get_slot("city").lower() and
r.cuisine.lower() == tracker.get_slot("cuisine").lower()
])

if len(restaurant_names) > 0:
dispatcher.utter_button_message(
text="Do you know which restaurant you would like me to reverse a table at?",
buttons=[
{"title": r, "payload": f'/inform{{"restaurant_name":"{r}"}}'}
for r in restaurant_names
]
)
else:
dispatcher.utter_message("I'm sorry I could not find any suitable restaurant "
"for you.")
25 changes: 25 additions & 0 deletions actions/authenticate_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from rasa_sdk import Action, Tracker
from rasa_sdk.events import SlotSet
from rasa_sdk.executor import CollectingDispatcher


class ActionAuthenticateUser(Action):

def name(self) -> str:
return "action_authenticate_user"

def run(self, dispatcher: CollectingDispatcher, tracker: Tracker,
domain: dict) -> list:
# Retrieve the user credentials from slots
user_name = tracker.get_slot("user_name")
user_password = tracker.get_slot("user_password")

# Dummy authentication.
# Placeholder for user authentication, in real scenarios the username and
# password should be checked against a database.
authenticated = True

if authenticated:
return [SlotSet("is_user_logged_in", True)]
else:
return [SlotSet("is_user_logged_in", False)]
25 changes: 24 additions & 1 deletion actions/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
CONTACTS = "contacts.json"
TRANSACTIONS = "transactions.json"
MY_ACCOUNT = "my_account.json"
RESTAURANTS = "restaurants.json"
PORTFOLIO_OPTIONS = "portfolio_options.json"


class MyAccount(BaseModel):
Expand All @@ -35,9 +37,22 @@ class Contact(BaseModel):
handle: str


class Restaurant(BaseModel):
name: str
address: str
city: str
cuisine: str
capacity: int


class Portfolio(BaseModel):
type: str
options: List[str]


def get_session_db_path(session_id: str) -> str:
tempdir = tempfile.gettempdir()
project_name = "financial_demo_flows_llms"
project_name = "rasa-calm-demo"
return os.path.join(tempdir, project_name, session_id)


Expand Down Expand Up @@ -91,3 +106,11 @@ def add_transaction(session_id: str, transaction: Transaction) -> None:

def write_contacts(session_id: str, contacts: List[Contact]) -> None:
write_db(session_id, CONTACTS, [c.dict() for c in contacts])


def get_restaurants(session_id: str) -> List[Restaurant]:
return [Restaurant(**item) for item in read_db(session_id, RESTAURANTS)]


def get_portfolio_options(session_id: str) -> List[Portfolio]:
return [Portfolio(**item) for item in read_db(session_id, PORTFOLIO_OPTIONS)]
2 changes: 1 addition & 1 deletion actions/list_contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rasa_sdk import Action, Tracker
from rasa_sdk.events import SlotSet
from rasa_sdk.executor import CollectingDispatcher
from actions.db import get_contacts, add_contact, Contact
from actions.db import get_contacts


class ListContacts(Action):
Expand Down
Loading
Loading