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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +1 -0
  3. data/.github/dependabot.yml +11 -0
  4. data/.github/workflows/ci.yml +50 -0
  5. data/CHANGELOG.md +1 -0
  6. data/Gemfile.lock +36 -35
  7. data/MAINTENANCE.md +28 -0
  8. data/SECURITY.md +13 -0
  9. data/VERSION +1 -1
  10. data/docs/.gitbook/assets/2880px-Turnstile_state_machine_colored.svg.png +0 -0
  11. data/docs/.gitbook/assets/Torniqueterevolution.jpg +0 -0
  12. data/docs/.gitbook/assets/logo.svg +28 -0
  13. data/docs/.gitbook/assets/ruby.png +0 -0
  14. data/docs/README.md +35 -0
  15. data/docs/SUMMARY.md +99 -0
  16. data/docs/basics.md +77 -0
  17. data/docs/building-components/message-services.md +7 -0
  18. data/docs/building-components/nlp.md +7 -0
  19. data/docs/config/services.yml.md +2 -0
  20. data/docs/config/settings.md +2 -0
  21. data/docs/controllers/available-data.md +77 -0
  22. data/docs/controllers/catch-alls.md +135 -0
  23. data/docs/controllers/controller-overview.md +130 -0
  24. data/docs/controllers/dev-jumps.md +47 -0
  25. data/docs/controllers/get_match/README.md +2 -0
  26. data/docs/controllers/get_match/alpha-ordinals.md +2 -0
  27. data/docs/controllers/get_match/entity-match.md +2 -0
  28. data/docs/controllers/get_match/exact-match.md +2 -0
  29. data/docs/controllers/handle_message/README.md +54 -0
  30. data/docs/controllers/handle_message/alpha-ordinal-matcher.md +40 -0
  31. data/docs/controllers/handle_message/homophone-detection.md +36 -0
  32. data/docs/controllers/handle_message/nil-matcher.md +44 -0
  33. data/docs/controllers/handle_message/nlp-matcher.md +51 -0
  34. data/docs/controllers/handle_message/regex-matcher.md +41 -0
  35. data/docs/controllers/handle_message/string-mather.md +23 -0
  36. data/docs/controllers/interrupt-detection.md +2 -0
  37. data/docs/controllers/platform-errors.md +2 -0
  38. data/docs/controllers/route.md +70 -0
  39. data/docs/controllers/sessions/README.md +2 -0
  40. data/docs/controllers/sessions/do_nothing.md +17 -0
  41. data/docs/controllers/sessions/intro.md +37 -0
  42. data/docs/controllers/sessions/step_back.md +88 -0
  43. data/docs/controllers/sessions/step_to.md +47 -0
  44. data/docs/controllers/sessions/step_to_at.md +43 -0
  45. data/docs/controllers/sessions/step_to_in.md +43 -0
  46. data/docs/controllers/sessions/update_session_to.md +47 -0
  47. data/docs/controllers/unrecognized-messages.md +2 -0
  48. data/docs/deployment/heroku.md +2 -0
  49. data/docs/deployment/overview.md +2 -0
  50. data/docs/dev-environment/README.md +2 -0
  51. data/docs/dev-environment/booting-up.md +33 -0
  52. data/docs/dev-environment/environment-variables.md +54 -0
  53. data/docs/dev-environment/hot-code-reloading.md +52 -0
  54. data/docs/dev-environment/logs.md +74 -0
  55. data/docs/dev-environment/procfile.md +22 -0
  56. data/docs/dev-environment/tunnels.md +18 -0
  57. data/docs/flows/flowmap.md +40 -0
  58. data/docs/flows/overview.md +43 -0
  59. data/docs/flows/state-naming.md +53 -0
  60. data/docs/flows/state-options.md +70 -0
  61. data/docs/getting-started.md +19 -0
  62. data/docs/glossary.md +21 -0
  63. data/docs/models/activerecord.md +2 -0
  64. data/docs/models/mongoid.md +2 -0
  65. data/docs/models/overview.md +2 -0
  66. data/docs/nlp-nlu/microsoft-luis.md +2 -0
  67. data/docs/nlp-nlu/openai.md +2 -0
  68. data/docs/nlp-nlu/overview.md +2 -0
  69. data/docs/platforms/alexa-skills.md +2 -0
  70. data/docs/platforms/facebook-messenger.md +2 -0
  71. data/docs/platforms/overview.md +2 -0
  72. data/docs/platforms/sms-whatsapp.md +2 -0
  73. data/docs/platforms/voice.md +2 -0
  74. data/docs/replies/delays.md +2 -0
  75. data/docs/replies/erb.md +2 -0
  76. data/docs/replies/inline-replies.md +2 -0
  77. data/docs/replies/reply-overview.md +2 -0
  78. data/docs/replies/variants.md +2 -0
  79. data/docs/replies/yaml-replies.md +2 -0
  80. data/docs/testing/integration-testing.md +2 -0
  81. data/docs/testing/untitled.md +2 -0
  82. data/lib/stealth/server.rb +10 -1
  83. data/stealth.gemspec +1 -1
  84. metadata +81 -5
  85. 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. &#x20;
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,2 @@
1
+ # get\_match
2
+
@@ -0,0 +1,2 @@
1
+ # Alpha Ordinals
2
+
@@ -0,0 +1,2 @@
1
+ # Entity Match
2
+
@@ -0,0 +1,2 @@
1
+ # Exact Match
2
+
@@ -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.&#x20;
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 %}
@@ -0,0 +1,2 @@
1
+ # Interrupt Detection
2
+
@@ -0,0 +1,2 @@
1
+ # Platform Errors
2
+