stealth 2.0.0.beta5 → 2.0.0.beta6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitbook.yaml +1 -0
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/ci.yml +50 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile.lock +36 -35
- data/MAINTENANCE.md +28 -0
- data/SECURITY.md +13 -0
- data/VERSION +1 -1
- data/docs/.gitbook/assets/2880px-Turnstile_state_machine_colored.svg.png +0 -0
- data/docs/.gitbook/assets/Torniqueterevolution.jpg +0 -0
- data/docs/.gitbook/assets/logo.svg +28 -0
- data/docs/.gitbook/assets/ruby.png +0 -0
- data/docs/README.md +35 -0
- data/docs/SUMMARY.md +99 -0
- data/docs/basics.md +77 -0
- data/docs/building-components/message-services.md +7 -0
- data/docs/building-components/nlp.md +7 -0
- data/docs/config/services.yml.md +2 -0
- data/docs/config/settings.md +2 -0
- data/docs/controllers/available-data.md +77 -0
- data/docs/controllers/catch-alls.md +135 -0
- data/docs/controllers/controller-overview.md +130 -0
- data/docs/controllers/dev-jumps.md +47 -0
- data/docs/controllers/get_match/README.md +2 -0
- data/docs/controllers/get_match/alpha-ordinals.md +2 -0
- data/docs/controllers/get_match/entity-match.md +2 -0
- data/docs/controllers/get_match/exact-match.md +2 -0
- data/docs/controllers/handle_message/README.md +54 -0
- data/docs/controllers/handle_message/alpha-ordinal-matcher.md +40 -0
- data/docs/controllers/handle_message/homophone-detection.md +36 -0
- data/docs/controllers/handle_message/nil-matcher.md +44 -0
- data/docs/controllers/handle_message/nlp-matcher.md +51 -0
- data/docs/controllers/handle_message/regex-matcher.md +41 -0
- data/docs/controllers/handle_message/string-mather.md +23 -0
- data/docs/controllers/interrupt-detection.md +2 -0
- data/docs/controllers/platform-errors.md +2 -0
- data/docs/controllers/route.md +70 -0
- data/docs/controllers/sessions/README.md +2 -0
- data/docs/controllers/sessions/do_nothing.md +17 -0
- data/docs/controllers/sessions/intro.md +37 -0
- data/docs/controllers/sessions/step_back.md +88 -0
- data/docs/controllers/sessions/step_to.md +47 -0
- data/docs/controllers/sessions/step_to_at.md +43 -0
- data/docs/controllers/sessions/step_to_in.md +43 -0
- data/docs/controllers/sessions/update_session_to.md +47 -0
- data/docs/controllers/unrecognized-messages.md +2 -0
- data/docs/deployment/heroku.md +2 -0
- data/docs/deployment/overview.md +2 -0
- data/docs/dev-environment/README.md +2 -0
- data/docs/dev-environment/booting-up.md +33 -0
- data/docs/dev-environment/environment-variables.md +54 -0
- data/docs/dev-environment/hot-code-reloading.md +52 -0
- data/docs/dev-environment/logs.md +74 -0
- data/docs/dev-environment/procfile.md +22 -0
- data/docs/dev-environment/tunnels.md +18 -0
- data/docs/flows/flowmap.md +40 -0
- data/docs/flows/overview.md +43 -0
- data/docs/flows/state-naming.md +53 -0
- data/docs/flows/state-options.md +70 -0
- data/docs/getting-started.md +19 -0
- data/docs/glossary.md +21 -0
- data/docs/models/activerecord.md +2 -0
- data/docs/models/mongoid.md +2 -0
- data/docs/models/overview.md +2 -0
- data/docs/nlp-nlu/microsoft-luis.md +2 -0
- data/docs/nlp-nlu/openai.md +2 -0
- data/docs/nlp-nlu/overview.md +2 -0
- data/docs/platforms/alexa-skills.md +2 -0
- data/docs/platforms/facebook-messenger.md +2 -0
- data/docs/platforms/overview.md +2 -0
- data/docs/platforms/sms-whatsapp.md +2 -0
- data/docs/platforms/voice.md +2 -0
- data/docs/replies/delays.md +2 -0
- data/docs/replies/erb.md +2 -0
- data/docs/replies/inline-replies.md +2 -0
- data/docs/replies/reply-overview.md +2 -0
- data/docs/replies/variants.md +2 -0
- data/docs/replies/yaml-replies.md +2 -0
- data/docs/testing/integration-testing.md +2 -0
- data/docs/testing/untitled.md +2 -0
- data/lib/stealth/server.rb +10 -1
- data/stealth.gemspec +1 -1
- metadata +81 -5
- data/.circleci/config.yml +0 -226
@@ -0,0 +1,135 @@
|
|
1
|
+
# Catch-Alls
|
2
|
+
|
3
|
+
Stealth Catch-Alls are designed to handle the error cases within bots. Either the bot doesn't understand what a user typed or an error occurred. If this were a webpage, we'd see the dreaded HTTP 500 error page. 
|
4
|
+
|
5
|
+
Catch-Alls are designed to move beyond simple "I don't understand" messages and help users get back on track. The better your CatchAlls, the better your bot.
|
6
|
+
|
7
|
+
### Triggering
|
8
|
+
|
9
|
+
The `catch_all` flow is automatically triggered when either of two things happens within a controller action:
|
10
|
+
|
11
|
+
1. The action [fails to progress a user](controller-overview.md#failing-to-progress-a-user).
|
12
|
+
2. An Exception is raised.
|
13
|
+
|
14
|
+
{% hint style="info" %}
|
15
|
+
If an action within `CatchAllsController` raises an exception, it won't fire another Catch-All to prevent loops.
|
16
|
+
{% endhint %}
|
17
|
+
|
18
|
+
### Multi-Level
|
19
|
+
|
20
|
+
Stealth keeps track of how many times a Catch-All is triggered for a given session. This allows you to build experiences in which the user is provided different responses for subsequent failures.
|
21
|
+
|
22
|
+
So for example, if in the `hello` flow and `say_hello` state an exception is raised, then Catch-All _Level 1_ will be called. If the user is return to that same flow and state and another exception is raised, Catch-All _Level 2_ will be called. This continues until you either run out of Catch-All states or if the Catch-All counter resets.
|
23
|
+
|
24
|
+
{% hint style="info" %}
|
25
|
+
The Catch-All counter currently resets after 15 minutes. This is per flow and state. So a user may encounter a Catch-All elsewhere in your bot and it will utilize a separate Catch-All counter.
|
26
|
+
{% endhint %}
|
27
|
+
|
28
|
+
### Retrying
|
29
|
+
|
30
|
+
By default, a Stealth bot comes with Catch-All Level 1 already defined. Here is the default `CatchAllsController` and associated reply:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class CatchAllsController < BotController
|
34
|
+
|
35
|
+
def level1
|
36
|
+
send_replies
|
37
|
+
|
38
|
+
if fail_session.present?
|
39
|
+
step_to session: fail_session
|
40
|
+
else
|
41
|
+
step_to session: previous_session - 2.states
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def fail_session
|
48
|
+
previous_session.flow.current_state.fails_to
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
```yaml
|
55
|
+
- reply_type: text
|
56
|
+
text: Oops. It looks like something went wrong. Let's try that again
|
57
|
+
```
|
58
|
+
|
59
|
+
In the controller action, we check if the `previous_session` (the one that failed) specified a `fails_to` state. If so, we send the user there. Otherwise, we send the user back 2 states.
|
60
|
+
|
61
|
+
Sending a user back two states is a pretty good generic action. Going back 1 state takes us back to the action that failed. Since the actions most likely to fail are `get` actions, or actions that deal with user responses, going back 2 states usually takes us back to the original "question".
|
62
|
+
|
63
|
+
{% hint style="info" %}
|
64
|
+
Where possible, it's better to specify a `fails_to` state so Stealth doesn't incorrectly guess where to send your user back.
|
65
|
+
{% endhint %}
|
66
|
+
|
67
|
+
### Adding More Levels
|
68
|
+
|
69
|
+
If you would like to extend the experience, add a `level2` controller action and associated reply (and update the `FlowMap`). You can go as far as you want. CatchAlls have no limit, just make sure you increment using the standardized method names of `level1`, `level2`, `level3`, `level4`, etc.
|
70
|
+
|
71
|
+
If a user has encountered the maximum number of CatchAll levels that have been defined, it won't attempt to call any more levels.
|
72
|
+
|
73
|
+
{% hint style="warning" %}
|
74
|
+
For the last Catch-All state, you'll probably want to prompt the user to contact support or send them to a special menu to choose from.
|
75
|
+
{% endhint %}
|
76
|
+
|
77
|
+
### Catch-All Reasons
|
78
|
+
|
79
|
+
As mentioned in the [Triggering](catch-alls.md#triggering) section above, there are two reasons a Catch-All triggers. Stealth will provide the `CatchAllsController` with that reason so you can customize your messages and take the appropriate action.
|
80
|
+
|
81
|
+
So if for example your bot just didn't recognize the message sent by the user, you may ask the user to repeat. If however, your database is down, you might other action.
|
82
|
+
|
83
|
+
Here is an example usage:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
class CatchAllsController < BotController
|
87
|
+
|
88
|
+
before_action :set_catch_all_reason
|
89
|
+
|
90
|
+
def level1
|
91
|
+
send_catch_all_replies('level1')
|
92
|
+
|
93
|
+
if fail_session.present?
|
94
|
+
step_to session: fail_session, pos: -1
|
95
|
+
else
|
96
|
+
step_to session: previous_session - 2.states, pos: -1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def level2
|
101
|
+
send_catch_all_replies('level2')
|
102
|
+
end
|
103
|
+
|
104
|
+
def level3
|
105
|
+
send_catch_all_replies('level3')
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def fail_session
|
111
|
+
previous_session.flow.current_state.fails_to
|
112
|
+
end
|
113
|
+
|
114
|
+
def send_catch_all_replies(level)
|
115
|
+
if @reason == :unrecognized_message
|
116
|
+
send_replies(custom_reply: "catch_alls/#{level}_unrecognized")
|
117
|
+
else
|
118
|
+
send_replies(custom_reply: "catch_alls/#{level}")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def set_catch_all_reason
|
123
|
+
@reason = case current_message.catch_all_reason[:err].to_s
|
124
|
+
when 'Stealth::Errors::UnrecognizedMessage'
|
125
|
+
:unrecognized_message
|
126
|
+
else
|
127
|
+
:system_error
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
```
|
134
|
+
|
135
|
+
In `CatchAllsController` we have two sets of Catch-All replies. One for when the message was unrecognized and another for when we've encountered a system error. We dynamically send the appropriate reply based on the `@reason` instance variable that we set with the `before_action` in the controller.
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Controller Overview
|
2
|
+
|
3
|
+
Controllers are responsible for handling incoming requests and getting a response back to the user via replies. They also perform all state transitions.
|
4
|
+
|
5
|
+
## Naming Conventions
|
6
|
+
|
7
|
+
The controller's methods, also referred to as actions, must be named after the flow's states. So for example, given the flow:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
flow :onboard do
|
11
|
+
state :say_welcome
|
12
|
+
state :ask_for_phone
|
13
|
+
state :get_phone, fails_to: :ask_for_phone
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
The corresponding controller would be:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class OnboardsController < BotController
|
21
|
+
def say_welcome
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def ask_for_phone
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_phone
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
## BotController
|
36
|
+
|
37
|
+
Every Stealth bot comes with a default `bot_controller.rb`. You don't have to know what each method does yet, we'll cover each in their respective doc sections.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# frozen_string_literal: true
|
41
|
+
|
42
|
+
class BotController < Stealth::Controller
|
43
|
+
|
44
|
+
helper :all
|
45
|
+
|
46
|
+
def route
|
47
|
+
if current_message.payload.present?
|
48
|
+
handle_payloads
|
49
|
+
# Clear out the payload to prevent duplicate handling
|
50
|
+
current_message.payload = nil
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
# Allow devs to jump around flows and states by typing:
|
55
|
+
# /flow_name/state_name or
|
56
|
+
# /flow_name (jumps to first state) or
|
57
|
+
# //state_name (jumps to state in current flow)
|
58
|
+
# (only works for bots in development)
|
59
|
+
return if dev_jump_detected?
|
60
|
+
|
61
|
+
if current_session.present?
|
62
|
+
step_to session: current_session
|
63
|
+
else
|
64
|
+
step_to flow: 'hello', state: 'say_hello'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Handle payloads globally since payload buttons remain in the chat
|
71
|
+
# and we cannot guess in which states they will be tapped.
|
72
|
+
def handle_payloads
|
73
|
+
case current_message.payload
|
74
|
+
when 'developer_restart', 'new_user'
|
75
|
+
step_to flow: 'hello', state: 'say_hello'
|
76
|
+
when 'goodbye'
|
77
|
+
step_to flow: 'goodbye'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Automatically called when clients receive an opt-out error from
|
82
|
+
# the platform. You can write your own steps for handling.
|
83
|
+
def handle_opt_out
|
84
|
+
do_nothing
|
85
|
+
end
|
86
|
+
|
87
|
+
# Automatically called when clients receive an invalid session_id error from
|
88
|
+
# the platform. For example, attempting to text a landline.
|
89
|
+
# You can write your own steps for handling.
|
90
|
+
def handle_invalid_session_id
|
91
|
+
do_nothing
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
```
|
97
|
+
|
98
|
+
All of your controllers will inherit from this `BotController`:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
class QuotesController < BotController
|
102
|
+
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
## Failing to Progress a User
|
107
|
+
|
108
|
+
One of the primary responsibilities of a controller is to update a user's session. The other responsibility is sending replies to a user. If you fail to do either of these things, essentially the user at the other end won't have any feedback.
|
109
|
+
|
110
|
+
If a controller action fails to update the state or send any replies, Stealth will automatically fire a [CatchAll](catch-alls.md). This is designed to catch errors during development. If you are certain you don't want to send any feedback to the user for a specific action you can call [do\_nothing](sessions/do\_nothing.md) to override Stealth's default behavior.  
|
111
|
+
|
112
|
+
## Before/After/Around Filters
|
113
|
+
|
114
|
+
Like Ruby on Rails controllers, Stealth controllers support `before_action`, `after_action`, and `around_action` filters.
|
115
|
+
|
116
|
+
Given a `BotController` that loads a user:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class BotController < Stealth::Controller
|
120
|
+
|
121
|
+
before_action :current_user
|
122
|
+
|
123
|
+
private def current_user
|
124
|
+
@current_user ||= User.find_by_session_id(current_session_id)
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
The `current_user` method will be run on all controllers that inherit from `BotController`. Similarly, if you add a `before_action` to a child controller, only that controller's actions will run that filter.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Dev Jumps
|
2
|
+
|
3
|
+
Dev Jumps are a feature of Stealth that makes you and your team more productive during development. It enables you to jump between flows and states while interacting with your bot. As you develop your bot, you can avoid having to restart the conversation each time.
|
4
|
+
|
5
|
+
{% hint style="warning" %}
|
6
|
+
Dev Jumps will only work while your bot is in the `development` environment. Dev jumps in other environments will be ignored.
|
7
|
+
{% endhint %}
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
You can specify Dev Jumps in one of three ways:
|
12
|
+
|
13
|
+
1. Flow and state.
|
14
|
+
2. Just a flow name.
|
15
|
+
3. Just a state name.
|
16
|
+
|
17
|
+
{% hint style="info" %}
|
18
|
+
You can text these at any time to your bot.
|
19
|
+
{% endhint %}
|
20
|
+
|
21
|
+
### Flow and State
|
22
|
+
|
23
|
+
```
|
24
|
+
/flow_name/state_name
|
25
|
+
```
|
26
|
+
|
27
|
+
This will immediately step to the `flow_name` and `state_name` that you specified.
|
28
|
+
|
29
|
+
### Flow Name
|
30
|
+
|
31
|
+
```
|
32
|
+
/flow_name
|
33
|
+
```
|
34
|
+
|
35
|
+
This will jump the the first state declared in the [FlowMap](../flows/flowmap.md) for the flow.
|
36
|
+
|
37
|
+
### State Name
|
38
|
+
|
39
|
+
```
|
40
|
+
//state_name
|
41
|
+
```
|
42
|
+
|
43
|
+
This will jump to specified `state_name` within the current flow.
|
44
|
+
|
45
|
+
{% hint style="info" %}
|
46
|
+
Note the double forward slash `//`. This is essentially because the `flow_name` has been explicitly omitted.
|
47
|
+
{% endhint %}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# handle\_message
|
2
|
+
|
3
|
+
The `handle_message` method is one of the most fundamental controller methods in Stealth. It enables you to succinctly handle messages without needing long `if/else` or `case` statements.
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
Here's an example of what it looks like:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
def get_call_response
|
11
|
+
handle_message(
|
12
|
+
:yes => proc { step_to state: :say_yes },
|
13
|
+
'Not available' => proc { step_to state: :say_no_problem },
|
14
|
+
:no => proc { step_to state: :say_no_problem },
|
15
|
+
:call => proc { step_to state: :say_call_later }
|
16
|
+
)
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
In this example, the `handle_message` method has four different "match arms". Each arm is just `key => value` pair since it's all just a hash. The keys are the `match expressions`. The values are `procs` that serve as the action to take if the match expression is matched.
|
21
|
+
|
22
|
+
Stealth accepts match expressions in five different ways:
|
23
|
+
|
24
|
+
1. [A string](string-mather.md) (like Line 4)
|
25
|
+
2. An [alpha ordinal](alpha-ordinal-matcher.md)
|
26
|
+
3. A [symbol](nlp-matcher.md) (like Lines 3, 5, and 6)
|
27
|
+
4. A [regex](regex-matcher.md)
|
28
|
+
5. [nil](nil-matcher.md)
|
29
|
+
|
30
|
+
{% hint style="info" %}
|
31
|
+
The `procs` can be multi-line and thus perform more than one action. In the docs for the match expressions we'll include some of those examples.
|
32
|
+
{% endhint %}
|
33
|
+
|
34
|
+
## Proc Scope
|
35
|
+
|
36
|
+
If you are new to Ruby, you might be wondering about `procs`. You can think of them as anonymous functions from Javascript or closures. 
|
37
|
+
|
38
|
+
The code inside of the `proc` is executed if the match expression is matched. That code has access to the same variables as the containing method's scope. So for example:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
def get_response
|
42
|
+
x = 15
|
43
|
+
handle_message(
|
44
|
+
'Buy' => proc {
|
45
|
+
x += 1
|
46
|
+
},
|
47
|
+
'Refinance' => proc {
|
48
|
+
x += 2
|
49
|
+
}
|
50
|
+
)
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
In this example if a user sends the message "Buy", the value of `x` will be `16`. Similarly, if the user sends the message "Refinance", the value of `x` will be `17`.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Alpha Ordinal Matcher
|
2
|
+
|
3
|
+
Alpha ordinals are a way of providing "quick replies" for messaging services that do not support them natively, such as SMS and Whatsapp. With `handle_message`, they are supported directly.
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
Imagine you send a user this reply:
|
8
|
+
|
9
|
+
```
|
10
|
+
What's your favorite color?
|
11
|
+
|
12
|
+
Reply with:
|
13
|
+
"A" for Red
|
14
|
+
"B" for Blue
|
15
|
+
"C" for Green
|
16
|
+
"D" for Yellow
|
17
|
+
```
|
18
|
+
|
19
|
+
{% hint style="info" %}
|
20
|
+
Stealth's Twilio component can automatically generate these for you. This is covered in depth in the [YAML Replies docs](../../replies/yaml-replies.md).
|
21
|
+
{% endhint %}
|
22
|
+
|
23
|
+
The corresponding `handle_message` method would look like this:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
def get_response
|
27
|
+
handle_message(
|
28
|
+
'Red' => proc { current_user.update_attributes!(favorite_color: 'red') },
|
29
|
+
'Blue' => proc { current_user.update_attributes!(favorite_color: 'blue') },
|
30
|
+
'Green' => proc { current_user.update_attributes!(favorite_color: 'green') },
|
31
|
+
'Yellow' => proc { current_user.update_attributes!(favorite_color: 'yellow') }
|
32
|
+
)
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
With alpha ordinals, if a user types "C", then the `Green` match arm is automatically selected since it is the 3rd match expression (Line 5) specified in `handle_message`. Similarly, the user could still reply with "green" directly and that will also match the 3rd match expression (Line 5).
|
37
|
+
|
38
|
+
{% hint style="warning" %}
|
39
|
+
If a user were to type, "E", then Stealth will still raise a `Stealth::Errors::UnrecognizedMessage` exception.
|
40
|
+
{% endhint %}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Homophone Detection
|
2
|
+
|
3
|
+
One of the problems that arises with [alpha ordinals](alpha-ordinal-matcher.md) is that some English letters sound like other words. So for example, pronouncing "C" sounds like "see" or "sea". The letter "B" sounds like "bee" or "be". These are called homophones.
|
4
|
+
|
5
|
+
If your user is using a voice assistant to dictate their responses to your bot, it's likely the voice assistant will substitute one of these homophones for the actual letter and your bot would fail to match the user's message.
|
6
|
+
|
7
|
+
This is where homophone detection helps.
|
8
|
+
|
9
|
+
{% hint style="info" %}
|
10
|
+
Homophone detection is currently only supported for the English language.
|
11
|
+
{% endhint %}
|
12
|
+
|
13
|
+
## Example
|
14
|
+
|
15
|
+
Given this code snippet from the [Alpha Ordinal Matcher docs](alpha-ordinal-matcher.md):
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
def get_response
|
19
|
+
handle_message(
|
20
|
+
'Red' => proc { current_user.update_attributes!(favorite_color: 'red') },
|
21
|
+
'Blue' => proc { current_user.update_attributes!(favorite_color: 'blue') },
|
22
|
+
'Green' => proc { current_user.update_attributes!(favorite_color: 'green') },
|
23
|
+
'Yellow' => proc { current_user.update_attributes!(favorite_color: 'yellow') }
|
24
|
+
)
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
If a user enters the letter "b", then the "Blue" match arm will automatically be selected as expected. However, Stealth will also natively accept the input "be" and "bee" from the user and the "Blue" match arm will still be selected. Similarly, "see" and "sea" will select the "Green" match arm.
|
29
|
+
|
30
|
+
{% hint style="info" %}
|
31
|
+
For a full list of homophones Stealth detects, you can inspect the array constant `Stealth::Controller::Messages::HOMOPHONES`
|
32
|
+
{% endhint %}
|
33
|
+
|
34
|
+
{% hint style="danger" %}
|
35
|
+
If you attempt to use a homophone as your match expression, Stealth will raise a `Stealth::Errors::ReservedHomophoneUsed` exception.
|
36
|
+
{% endhint %}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Nil Matcher
|
2
|
+
|
3
|
+
When none of the match expressions are matched in a `handle_message` method call, by default Stealth will raise a `Stealth::Errors::UnrecognizedMessage` exception. This is typically the desired behavior because it allows the [UnrecognizedMessagesController](../unrecognized-messages.md) to run.
|
4
|
+
|
5
|
+
In the event that you don't want to raise an error, like in the case where you want to just save what the user typed in and move on, you can use the nil matcher.
|
6
|
+
|
7
|
+
## Example
|
8
|
+
|
9
|
+
Given this reply to a user:
|
10
|
+
|
11
|
+
```
|
12
|
+
How much is your property worth?
|
13
|
+
|
14
|
+
Reply with:
|
15
|
+
"A" for $1 - $100
|
16
|
+
"B" for $101 - $999
|
17
|
+
"C" for $1000 - $9999
|
18
|
+
```
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
def get_response
|
22
|
+
handle_message(
|
23
|
+
'$1 - $100' => proc {
|
24
|
+
current_user.update_attributes!(property_value: '$1 - $100')
|
25
|
+
},
|
26
|
+
'$101 - $999' => proc {
|
27
|
+
current_user.update_attributes!(property_value: '$101 - $999')
|
28
|
+
},
|
29
|
+
'$1000 - $9999' => proc {
|
30
|
+
current_user.update_attributes!(property_value: '$1000 - $9999')
|
31
|
+
},
|
32
|
+
nil => proc {
|
33
|
+
amount = current_user.message
|
34
|
+
current_user.update_attributes!(property_value: amount)
|
35
|
+
}
|
36
|
+
)
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
In the above example, if a user enters a specific amount instead of choosing one of the ranges provided, we just store that amount and don't raise an error.
|
41
|
+
|
42
|
+
{% hint style="warning" %}
|
43
|
+
You may likely still want to verify the input they entered is a Numeric. There are also better ways to handle an example like this using [get\_match](../get\_match/).
|
44
|
+
{% endhint %}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# NLP Matcher
|
2
|
+
|
3
|
+
NLP is a very important part of creating powerful bots. Stealth seamlessly integrates with NLP services to provide NLP matching from within the same `handle_message` method.
|
4
|
+
|
5
|
+
{% hint style="info" %}
|
6
|
+
Check out the [NLP section](../../nlp-nlu/overview.md) for more information on how to configure your NLP service to work with Stealth's `handle_message`.
|
7
|
+
{% endhint %}
|
8
|
+
|
9
|
+
## Example
|
10
|
+
|
11
|
+
Let's pretend you've trained your NLP service with some examples of "Yes" and some examples of "No". These are two common intents that you'll likely want to train for your bot. Let's also assume that we've named the "Yes" intent as `yes` and the "No" intent as `no`
|
12
|
+
|
13
|
+
{% hint style="warning" %}
|
14
|
+
Make sure you name your intents using Ruby's `snake_casing` so they easily be used in `handle_message`.
|
15
|
+
{% endhint %}
|
16
|
+
|
17
|
+
Given this reply to the user:
|
18
|
+
|
19
|
+
```
|
20
|
+
Are you interested in learning more about Stealth?
|
21
|
+
|
22
|
+
Reply with:
|
23
|
+
"A" for Yes
|
24
|
+
"B" for Remind me later
|
25
|
+
"C" for No longer interested
|
26
|
+
```
|
27
|
+
|
28
|
+
Here is what our controller action that handles this message can look like with NLP matchers:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
def get_response
|
32
|
+
handle_message(
|
33
|
+
:yes => proc {
|
34
|
+
step_to state: :say_proceed
|
35
|
+
},
|
36
|
+
'Remind me later' => proc { step_to state: :say_no_problem },
|
37
|
+
:no => proc { step_to state: :say_goodbye },
|
38
|
+
:call => proc {
|
39
|
+
step_to state: :ask_when_to_call
|
40
|
+
}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Here, we are using NLP matchers on Lines 3, 7, and 8. NLP matchers have specify match expression as a Ruby symbol.
|
46
|
+
|
47
|
+
When Stealth encounters an NLP matcher as a match expression, it will perform NLP using your configured NLP service. The result is automatically cached so that subsequent NLP matchers don't trigger another NLP lookup. The raw result of the NLP query will be stored in `current_message.nlp_result`, but `handle_message` will automatically make use of that without you having to parse it yourself.
|
48
|
+
|
49
|
+
If the resulting NLP intent is `yes` then the `:yes` match arm will be matched. The same for `:no` and `:call`. So for example, if a user type "Nah, not interested" it's likely or `:no` match arm will be called. Similarly, if a user writes "Sure!!" the `:yes` match arm will be called.
|
50
|
+
|
51
|
+
Another interesting thing to note about this example is that we have a 4th option (`:call`) that isn't explicitly mentioned in the reply to the user. With NLP matchers, it's sometimes useful to do this when your data shows that a lot users are manually typing in a custom response for a specific question. So in this case, we've asked the user if they are interested in learning more about Stealth, but some users will ask to jump on a phone call. So we've trained an NLP intent for "calls" and it will match the cases where a user requests a call for this question.
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Regex Matcher
|
2
|
+
|
3
|
+
When using the [string matcher](string-mather.md), you might have a scenario where the option you present to user for selection is longer or contains more detail than the answer they type. In these cases, it's useful to be able to use a regex to match a just a part of a message.
|
4
|
+
|
5
|
+
{% hint style="info" %}
|
6
|
+
The regex matcher is not limited to this use case. You can use the full power of Ruby regexes.
|
7
|
+
{% endhint %}
|
8
|
+
|
9
|
+
## Example
|
10
|
+
|
11
|
+
Given this reply to a user:
|
12
|
+
|
13
|
+
```
|
14
|
+
What would you like to do?
|
15
|
+
|
16
|
+
Reply with:
|
17
|
+
"A" for I'd like to restart
|
18
|
+
"B" for Just stop
|
19
|
+
"C" for Repeat
|
20
|
+
```
|
21
|
+
|
22
|
+
We can take advantage of the regex matcher for options "A" and "B" since it's unlikely a user would type that entire string with the formatting we expect.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
def get_response
|
26
|
+
handle_message(
|
27
|
+
/restart/ => proc { step_to flow: :hello },
|
28
|
+
/stop/ => proc {
|
29
|
+
current_user.opt_out!
|
30
|
+
step_to flow: :opt_out
|
31
|
+
},
|
32
|
+
'Repeat' => proc { step_to session: previous_session }
|
33
|
+
)
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Now if a user types "restart" or "restart plz" it will still match the first match arm. Similarly, if they type "stop" it will match the second match arm. But just as before, typing "just stop" and "B" will also match the second match arm.
|
38
|
+
|
39
|
+
{% hint style="info" %}
|
40
|
+
Notice how we are able to mix matchers in the same `handle_message` method.
|
41
|
+
{% endhint %}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# String Matcher
|
2
|
+
|
3
|
+
The string matcher matches exact string responses. It will however automatically ignore case and also ignore blank padding preceding and trailing a string. These blank spaces occur frequently with texting apps and autocomplete.
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
def get_response
|
9
|
+
handle_message(
|
10
|
+
'Sure' => proc {
|
11
|
+
current_user.update_attributes!(interested: true)
|
12
|
+
step_to state: :say_yes
|
13
|
+
},
|
14
|
+
'Nope' => proc { step_to state: :say_no_problem }
|
15
|
+
)
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
In this example, if a user types in `SURE` or `SuRE` or `sure`, it will match the first match arm and the corresponding proc will be executed.
|
20
|
+
|
21
|
+
{% hint style="warning" %}
|
22
|
+
If none of the match expressions are matched, Stealth will raise a `Stealth::Errors::UnrecognizedMessage` exception unless the [nil matcher](nil-matcher.md) is included.
|
23
|
+
{% endhint %}
|