aws-lex-conversation 6.1.0 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bbaa290608e0035207d3fbf6b53c862f36c95e0a07012a562e18ff21c6b3f3f
4
- data.tar.gz: a79cff690a0d2419de8f859688f43881cf9d67e40756972afde597196ba7ac64
3
+ metadata.gz: 61c4edf66e617becb5f2c7aa3611e32c7eeef58ab2cb9533f1816abcaf675ab8
4
+ data.tar.gz: 485e85d4c07d25527752e2451e99c02af1eae808dfd9e048efa147a8042d76aa
5
5
  SHA512:
6
- metadata.gz: 22e3b48931497c44e65d71834379ccd37e9f5c08ffc8713b4bfd7dfefdbc2e02f4d46aea8cd7e2a180b5537a80c9dbeee53eb3817f82755b37d4b1698d730a58
7
- data.tar.gz: 71a0fe45f60e828b51cefd0ab52901ca06691377428eb19ff0c82623cfa85a603454b318f483fed138694889a3dd77dbf15b4c9a525f8338a8c379bf26345074
6
+ metadata.gz: 03f8814a82e0f6cea94033ef13d4f59ade4bb74a1cee26fe554151bff1b0785f32f0c3aef17e2a2293a1c71dd4442679ee516a9d16bd1bc94069e0e00dccfb3f
7
+ data.tar.gz: 360cb550a21e90e6063722c676071ed59d083e6a03eb44281f2517b79b8e2a70ccfeb435ad0df4b37b1c46a8e6e9e4258505622b6396f3168a140eb9e2998524
data/.rubocop.yml CHANGED
@@ -45,6 +45,8 @@ Style/Lambda:
45
45
  EnforcedStyle: literal
46
46
  Style/GuardClause:
47
47
  Enabled: false
48
+ Style/OpenStructUse:
49
+ Enabled: false
48
50
  Style/RedundantFetchBlock:
49
51
  Enabled: false
50
52
  Style/SlicingWithRange:
data/CHANGELOG.md CHANGED
@@ -1,11 +1,45 @@
1
+ # 6.4.0 - Feb 10, 2022
2
+
3
+ * Add support for the new protocol properties of `proposedNextState` and `transcriptions`. This enables support for [transcription confidence scores](https://docs.aws.amazon.com/lexv2/latest/dg/using-transcript-confidence-scores.html).
4
+ * Expose the following methods on `Aws::Lex::Conversation` instances:
5
+ - `proposed_next_state?`: Returns `true` if a `proposedNextState` value is present or `false` otherwise.
6
+ - `proposed_next_state`: Returns an `Aws::Lex::Conversation::Type::ProposedNextState` instance. May return `nil`.
7
+ - `transcriptions`: Returns an array of `Aws::Lex::Conversation::Type::Transcription` instances. Defaults to a empty array if the `transcriptions` property is not present.
8
+ * Make the `content_type` attribute in the constructor for `Aws::Lex::Conversation::Type::Message` default to `PlainText` when not present.
9
+
10
+ # 6.3.0 - Nov 22, 2021
11
+
12
+ * Add support for the recently added `slotElicitationStyle` property when generating an `ElicitSlot` repsonse ([documentation](https://docs.aws.amazon.com/lexv2/latest/dg/using-spelling.html)).
13
+
14
+ You can generate an `ElicitSlot` response now with an optional `slot_elicitation_style` property to allow for spelling support:
15
+
16
+ ```ruby
17
+ conversation.elicit_slot(
18
+ slot_to_elicit: 'LastName',
19
+ slot_elicitation_style: 'SpellByWord' # one of Default, SpellByLetter, or SpellByWord
20
+ )
21
+ ```
22
+
23
+ # 6.2.0 - Sept 28, 2021
24
+
25
+ * Add a `Aws::Lex::Conversation#restore_from!` method that accepts a checkpoint parameter. This method modifies the underlying conversation state to match the data from the saved checkpoint.
26
+ * Make the `dialog_action_type` parameter on `Aws::Lex::Conversation#checkpoint!` default to `Delegate` if not specified as a developer convenience.
27
+ * Allow developers to pass an optional `intent` override parameter on `Aws::Lex::Conversation#checkpoint!` for convenience.
28
+ * Update the README with advanced examples for the conversation stash and checkpoints.
29
+
30
+ # 6.1.1 - Sept 22, 2021
31
+
32
+ * renamed `maximum_elicitations` to `max_retries` and made it backwards compatible to make the param name clear, by default this value is zero, allowing each slot to elicit only once
33
+
1
34
  # 6.1.0 - Sept 7, 2021
35
+
2
36
  Added helper methods for clearing active contexts
37
+
3
38
  ```ruby
4
39
  conversation.clear_context!(name: 'test') # clears this specific active context
5
40
  conversation.clear_all_contexts! # clears all current active contexts
6
41
  ```
7
42
 
8
-
9
43
  # 6.0.0 - Sept 7, 2021
10
44
 
11
45
  * **breaking change** - Modify `Aws::Lex::Conversation::Type::Base#computed_property` to accept a block instead of a callable argument. This is an internal class and should not require any application-level changes.
@@ -63,6 +97,7 @@ it 'creates an event' do
63
97
  expect(event).to include_session_values(username: 'jane.doe')
64
98
  end
65
99
  ```
100
+
66
101
  * Add a few convenience methods to `Aws::Lex::Conversation` instances for dealing with active contexts:
67
102
  - `#active_context(name:)`:
68
103
 
data/README.md CHANGED
@@ -1,19 +1,15 @@
1
- # Aws::Lex::Conversation
1
+ # `Aws::Lex::Conversation`
2
2
 
3
- Have you played around with [AWS Lex](https://aws.amazon.com/lex/) and quickly realized you were duplicating code to read and generate the [Lex Lambda input event and response format](https://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html)?
3
+ Building a chatbot with [AWS Lex](https://aws.amazon.com/lex/) is really fun! Unfortunately implementing your bot's behaviour with the [Lex/Lambda event protocol](https://docs.aws.amazon.com/lexv2/latest/dg/lambda.html) is less fun.
4
4
 
5
- `Aws::Lex::Conversation` provides a mechanism to define the flow of a Lex conversation with a user easily!
5
+ But don't worry - we've done the hard work for you!
6
6
 
7
- ## Installation
8
-
9
- ### Lex V1
10
-
11
- Version 3.x is the last major version of this gem that will support Lex V1.
7
+ `Aws::Lex::Conversation` makes it simple to build dynamic, conversational chatbots with AWS Lex and AWS Lambda!
12
8
 
13
- Add this line to your application's Gemfile:
9
+ ## Installation
14
10
 
15
11
  ```ruby
16
- gem 'aws-lex-conversation', '~> 3.0'
12
+ gem 'aws-lex-conversation'
17
13
  ```
18
14
 
19
15
  And then execute:
@@ -22,96 +18,162 @@ And then execute:
22
18
  bundle install
23
19
  ```
24
20
 
25
- ### Lex V2
21
+ **Please Note:** This library currently supports [AWS Lex Version 2](https://docs.aws.amazon.com/lexv2/latest/dg/lambda.html). If you're looking for Lex V1 support, lock `aws-lex-conversation` to `~> 3.0` in your `Gemfile`.
22
+
23
+ ## Core Concepts
24
+
25
+ ### The Conversation Instance
26
26
 
27
- Version 4.x and higher support Lex V2.
27
+ Instances of `Aws::Lex::Conversation` wrap the Lex input/output event format and make it easy to manage conversation dialog.
28
28
 
29
- Add this line to your application's Gemfile:
29
+ Imagine you have a `ButlerBot` configured with a `ServeBreakfast` intent and `BreakfastFood` slot.
30
+
31
+ The backing lambda handler for your bot might look something like:
30
32
 
31
33
  ```ruby
32
- gem 'aws-lex-conversation', '>= 4.0'
34
+ require "aws-lex-conversation"
35
+
36
+ def lambda_handler(event:, context:)
37
+ # The conversation instance validates and wraps the Lex input event
38
+ conversation = Aws::Lex::Conversation.new(event: event, context: context)
39
+
40
+ # Return a Close dialog action to our Lex bot and end the conversation
41
+ conversation.close(
42
+ fulfillment_state: "Fulfilled",
43
+ messages: [
44
+ # We can construct response messages using wrapper classes.
45
+ Aws::Lex::Conversation::Type::Message.new(
46
+ content: "Hi - I'm a 🤖!"
47
+ ),
48
+ # Or we can pass a Hash that directly maps to the Lex response format
49
+ {
50
+ content: "Your intent is: #{conversation.intent_name}",
51
+ contentType: "PlainText"
52
+ },
53
+ {
54
+ content: "Here's your #{conversation.slots[:BreakfastFood].value}!",
55
+ contentType: "PlainText"
56
+ }
57
+ ]
58
+ )
59
+ end
33
60
  ```
34
61
 
35
- And then execute:
62
+ This lambda handler would generate the following dialog:
36
63
 
37
- ```bash
38
- bundle install
64
+ ![ButlerBot Dialog](https://raw.github.com/amaabca/aws-lex-conversation/master/data/butler_bot.png)
65
+
66
+ All data from the Lex input event is exposed via the `lex` attribute. By convention, the `lex` attribute directly translates input event attributes from `camelCase` to `snake_case`.
67
+
68
+ We also provide some helpers to help manage the conversation. For example:
69
+
70
+ ```ruby
71
+ # returns true if the lambda function was invoked as a DialogCodeHook
72
+ conversation.lex.invocation_source.dialog_code_hook?
73
+
74
+ # returns true if the InputMode is Speech
75
+ conversation.lex.input_mode.speech?
76
+
77
+ # you can easily set or retrieve a session values
78
+ conversation.session[:name] = "Jane"
79
+ conversation.session[:name] # => "Jane"
80
+
81
+ # dealing with slot data is simple
82
+ conversation.slots[:BreakfastFood].filled? # returns true if a slot value is present
83
+ conversation.slots[:Hometown].blank? # returns true if a slot value is nil/empty
84
+ conversation.slots[:FirstName].value # => "John"
39
85
  ```
40
86
 
41
- ## Usage
87
+ ### The Handler Chain
88
+
89
+ Conversational dialog gets complex quickly! Conversation instances include a handler chain that can help manage this complexity.
42
90
 
43
- At a high level, you must create a new instance of `Aws::Lex::Conversation`.
91
+ Each handler in the chain defines the prerequisites necessary for the handler to generate a response.
44
92
 
45
- Generally the conversation instance will be initialized inside your Lambda handler method as follows:
93
+ You can configure the handler chain as follows:
46
94
 
47
95
  ```ruby
48
- def my_lambda_handler(event:, context:)
96
+ def lambda_handler(event:, context:)
49
97
  conversation = Aws::Lex::Conversation.new(event: event, context: context)
50
98
 
51
- # define a chain of your own Handler classes
52
99
  conversation.handlers = [
100
+ # You need to define custom handler classes yourself
101
+ { handler: ServeBreakfast },
102
+ { handler: FallbackIntent },
103
+ # We offer a few "built in" handlers
53
104
  {
54
105
  handler: Aws::Lex::Conversation::Handler::Delegate,
55
106
  options: {
56
- respond_on: ->(conversation) { conversation.current_intent.name == 'MyIntent' }
107
+ # If we get this far, always return a Delegate action
108
+ respond_on: ->(_) { true }
57
109
  }
58
110
  }
59
111
  ]
60
112
 
61
- # return a response object to indicate Lex's next action
113
+ # The respond method will execute each handler sequentially and return a Lex response
62
114
  conversation.respond
63
115
  end
64
116
  ```
65
117
 
66
- Any custom behaviour in your flow is achieved by defining a Handler class. Handler classes must provide the following:
118
+ ### Writing Your Own Handler
119
+
120
+ Generally, custom behaviour in your flow is achieved by defining your own handler class. Handler classes must:
67
121
 
68
122
  1. Inherit from `Aws::Lex::Conversation::Handler::Base`.
69
123
  2. Define a `will_respond?(conversation)` method that returns a boolean value.
70
124
  3. Define a `response(conversation)` method to return final response to Lex. This method is called if `will_respond?` returns `true`.
71
125
 
72
- The handlers defined on an `Aws::Lex::Conversation` instance will be called one-by-one in the order defined.
126
+ Handlers in the chain are invoked sequentially in the order defined.
73
127
 
74
- The first handler that returns `true` for the `will_respond?` method will provide the final Lex response action.
128
+ The first handler in the chain that returns `true` for the `will_respond?` method will provide the final Lex response action.
75
129
 
76
- ### Custom Handler Example
130
+ Here's an example for the `ServeBreakfast` and `FallbackIntent` handlers above:
77
131
 
78
132
  ```ruby
79
- class SayHello < Aws::Lex::Conversation::Handler::Base
133
+ class ServeBreakfast < Aws::Lex::Conversation::Handler::Base
80
134
  def will_respond?(conversation)
81
- conversation.lex.invocation_source.dialog_code_hook? && # callback is for DialogCodeHook (i.e. validation)
82
- conversation.lex.current_intent.name == 'SayHello' && # Lex has routed to the 'SayHello' intent
83
- conversation.slots[:name].filled? # our expected slot value is set
135
+ conversation.intent_name == "ServeBreakfast" &&
136
+ conversation.slots[:BreakfastFood].filled?
84
137
  end
85
138
 
86
139
  def response(conversation)
87
- name = conversation.slots[:name].value
88
-
89
- # NOTE: you can use the Type::* classes if you wish. The final output
90
- # will be normalized to a value that complies with the Lex response format.
91
- #
92
- # You can also specify raw values for the response:
93
- #
94
- # conversation.close(
95
- # fulfillment_state: 'Fulfilled',
96
- # messages: [{ content: "Hello, #{name}!", contentType: 'PlainText' }]
97
- # )
98
- #
140
+ food = conversation.slots[:BreakfastFood].value
141
+ emoji = food == "waffle" ? "🧇" : "🥓"
142
+
99
143
  conversation.close(
100
- fulfillment_state: Aws::Lex::Conversation::Type::FulfillmentState.new('Fulfilled'),
144
+ fulfillment_state: "Fulfilled",
101
145
  messages: [
102
- Aws::Lex::Conversation::Type::Message.new(
103
- content: "Hello, #{name}!",
104
- content_type: Aws::Lex::Conversation::Type::Message::ContentType.new('PlainText')
105
- )
146
+ {
147
+ content: "Here's your #{emoji}!",
148
+ contentType: "PlainText"
149
+ }
150
+ ]
151
+ )
152
+ end
153
+ end
154
+
155
+ class FallbackIntent < Aws::Lex::Conversation::Handler::Base
156
+ def will_respond?(conversation)
157
+ conversation.intent_name == "FallbackIntent"
158
+ end
159
+
160
+ def response(conversation)
161
+ conversation.close(
162
+ fulfillment_state: 'Failed',
163
+ messages: [
164
+ {
165
+ content: "Sorry - I'm not sure what you said!",
166
+ contentType: "PlainText"
167
+ }
106
168
  ]
107
169
  )
108
170
  end
109
171
  end
110
172
  ```
111
173
 
112
- ## Built-In Handlers
174
+ ### Built-In Handlers
113
175
 
114
- ### `Aws::Lex::Conversation::Handler::Echo`
176
+ #### `Aws::Lex::Conversation::Handler::Echo`
115
177
 
116
178
  This handler simply returns a close response with a message that matches the `inputTranscript` property of the input event.
117
179
 
@@ -140,7 +202,7 @@ conversation.handlers = [
140
202
  conversation.respond # => { dialogAction: { type: 'Close' } ... }
141
203
  ```
142
204
 
143
- ### `Aws::Lex::Conversation::Handler::Delegate`
205
+ #### `Aws::Lex::Conversation::Handler::Delegate`
144
206
 
145
207
  This handler returns a `Delegate` response to the Lex bot (i.e. "do the next bot action").
146
208
 
@@ -163,7 +225,7 @@ conversation.handlers = [
163
225
  conversation.respond # => { dialogAction: { type: 'Delegate' } }
164
226
  ```
165
227
 
166
- ### `Aws::Lex::Conversation::Handler::SlotResolution`
228
+ #### `Aws::Lex::Conversation::Handler::SlotResolution`
167
229
 
168
230
  This handler will set all slot values equal to their top resolution in the input event. The handler then calls the next handler in the chain for a response.
169
231
 
@@ -191,6 +253,114 @@ conversation.handlers = [
191
253
  conversation.respond # => { dialogAction: { type: 'Delegate' } }
192
254
  ```
193
255
 
256
+ ## Advanced Concepts
257
+
258
+ This library provides a few constructs to help manage complex interactions:
259
+
260
+ ### Data Stash
261
+
262
+ `Aws::Lex::Conversation` instances implement a `stash` method that can be used to store temporary data within a single invocation.
263
+
264
+ A conversation's stashed data will not be persisted between multiple invocations of your lambda function.
265
+
266
+ The conversation stash is a great spot to store deserialized data from the session, or invocation-specific state that needs to be shared between handler classes.
267
+
268
+ This example illustrates how the stash can be used to store deserialized data from the session:
269
+
270
+ ```ruby
271
+ # given we have JSON-serialized data in as a persisted session value
272
+ conversation.session[:user_data] = '{"name":"Jane","id":1234,"email":"test@example.com"}'
273
+ # we can deserialize the data into a Hash that we store in the conversation stash
274
+ conversation.stash[:user] = JSON.parse(conversation.session[:user_data])
275
+ # later on we can reference our stashed data (within the same invocation)
276
+ conversation.stash[:user] # => {"name"=>"Jane", "id"=>1234, "email"=>"test@example.com"}
277
+ ```
278
+
279
+ ### Checkpoints
280
+
281
+ A conversation may transition between many different topics as the interaction progresses. This type of state transition can be easily handled with checkpoints.
282
+
283
+ When a checkpoint is created, all intent and slot data is encoded and stored into a `checkpoints` session value. This data persists between invocations, and is not removed until the checkpoint is restored.
284
+
285
+ You can create a checkpoint as follows:
286
+
287
+ ```ruby
288
+ # we're ready to fulfill the OrderFlowers intent, but we want to elicit another intent first
289
+ conversation.checkpoint!(
290
+ label: 'order_flowers',
291
+ dialog_action_type: 'Close' # defaults to 'Delegate' if not specified
292
+ )
293
+ conversation.elicit_intent(
294
+ messages: [
295
+ {
296
+ content: 'Thanks! Before I place your order, is there anything else I can help with?',
297
+ contentType: 'PlainText'
298
+ }
299
+ ]
300
+ )
301
+ ```
302
+
303
+ You can restore the checkpoint in one of two ways:
304
+
305
+ ```ruby
306
+ # in a future invocation, we can fetch an instance of the checkpoint and easily
307
+ # restore the conversation to the previous state
308
+ checkpoint = conversation.checkpoint(label: 'order_flowers')
309
+ checkpoint.restore!(
310
+ fulfillment_state: 'Fulfilled',
311
+ messages: [
312
+ {
313
+ content: 'Okay, your flowers have been ordered! Thanks!',
314
+ contentType: 'PlainText'
315
+ }
316
+ ]
317
+ ) # => our response object to Lex is returned
318
+ ```
319
+
320
+ It's also possible to restore state from a checkpoint and utilize the conversation's handler chain:
321
+
322
+ ```ruby
323
+ class AnotherIntent < Aws::Lex::Conversation::Handler::Base
324
+ def will_respond?(conversation)
325
+ conversation.intent_name == 'AnotherIntent' &&
326
+ conversation.checkpoint?(label: 'order_flowers')
327
+ end
328
+
329
+ def response(conversation)
330
+ checkpoint = conversation.checkpoint(label: 'order_flowers')
331
+ # replace the conversation's current resolved intent/slot data with the saved checkpoint data
332
+ conversation.restore_from!(checkpoint)
333
+ # call the next handler in the chain to produce a response
334
+ successor.handle(conversation)
335
+ end
336
+ end
337
+
338
+ class OrderFlowers < Aws::Lex::Conversation::Handler::Base
339
+ def will_respond?(conversation)
340
+ conversation.intent_name == 'OrderFlowers'
341
+ end
342
+
343
+ def response(conversation)
344
+ conversation.close(
345
+ fulfillment_state: 'Fulfilled',
346
+ messages: [
347
+ {
348
+ content: 'Okay, your flowers have been ordered! Thanks!',
349
+ contentType: 'PlainText'
350
+ }
351
+ ]
352
+ )
353
+ end
354
+ end
355
+
356
+ conversation = Aws::Lex::Conversation.new(event: event, context: context)
357
+ conversation.handlers = [
358
+ { handler: AnotherIntent },
359
+ { handler: OrderFlowers }
360
+ ]
361
+ conversation.respond # => returns a Lex response object
362
+ ```
363
+
194
364
  ## Test Helpers
195
365
 
196
366
  This library provides convenience methods to make testing easy! You can use the test helpers as follows:
@@ -251,8 +421,6 @@ end
251
421
 
252
422
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
253
423
 
254
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
255
-
256
424
  ## Contributing
257
425
 
258
426
  Bug reports and pull requests are welcome on GitHub at https://github.com/amaabca/aws-lex-conversation. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/amaabca/aws-lex-conversation/blob/master/CODE_OF_CONDUCT.md).
@@ -263,4 +431,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
263
431
 
264
432
  ## Code of Conduct
265
433
 
266
- Everyone interacting in the Aws::Lex::Conversation project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/amaabca/aws-lex-conversation/blob/master/CODE_OF_CONDUCT.md).
434
+ Everyone interacting in the `Aws::Lex::Conversation` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/amaabca/aws-lex-conversation/blob/master/CODE_OF_CONDUCT.md).
@@ -26,9 +26,10 @@ Gem::Specification.new do |spec|
26
26
  spec.license = 'MIT'
27
27
  spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
28
28
  spec.metadata['homepage_uri'] = spec.homepage
29
+ spec.metadata['rubygems_mfa_required'] = 'true'
29
30
 
30
31
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
31
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|data)/}) }
32
33
  end
33
34
 
34
35
  spec.bindir = 'exe'
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'base64'
4
+ require 'forwardable'
4
5
  require 'json'
5
6
  require 'shrink/wrap'
6
7
 
@@ -25,6 +26,7 @@ require_relative 'type/sentiment_score'
25
26
  require_relative 'type/sentiment_response'
26
27
  require_relative 'type/invocation_source'
27
28
  require_relative 'type/dialog_action_type'
29
+ require_relative 'type/slot_elicitation_style'
28
30
  require_relative 'type/dialog_action'
29
31
  require_relative 'type/confirmation_state'
30
32
  require_relative 'type/fulfillment_state'
@@ -35,11 +37,14 @@ require_relative 'type/slot_value'
35
37
  require_relative 'type/slot'
36
38
  require_relative 'type/context'
37
39
  require_relative 'type/intent'
40
+ require_relative 'type/proposed_next_state'
38
41
  require_relative 'type/checkpoint'
39
42
  require_relative 'type/session_attributes'
40
43
  require_relative 'type/session_state'
41
44
  require_relative 'type/interpretation'
42
45
  require_relative 'type/bot'
46
+ require_relative 'type/transcription/resolved_context'
47
+ require_relative 'type/transcription'
43
48
  require_relative 'type/response_card/button'
44
49
  require_relative 'type/response_card'
45
50
  require_relative 'type/message/content_type'
@@ -5,7 +5,7 @@ module Aws
5
5
  class Conversation
6
6
  module Response
7
7
  class ElicitSlot < Base
8
- attr_accessor :slot_to_elicit
8
+ attr_accessor :slot_to_elicit, :slot_elicitation_style
9
9
 
10
10
  def initialize(opts = {})
11
11
  super
@@ -16,7 +16,8 @@ module Aws
16
16
  def dialog_action
17
17
  Aws::Lex::Conversation::Type::DialogAction.shrink_wrap(
18
18
  type: 'ElicitSlot',
19
- slotToElicit: slot_to_elicit
19
+ slotToElicit: slot_to_elicit,
20
+ slotElicitationStyle: slot_elicitation_style
20
21
  )
21
22
  end
22
23
  end
@@ -120,13 +120,41 @@ module Aws
120
120
  self
121
121
  end
122
122
 
123
+ def proposed_next_state(opts = {})
124
+ data = {
125
+ dialogAction: lex_attributes(opts.fetch(:dialog_action)),
126
+ intent: lex_attributes(opts.fetch(:intent))
127
+ }
128
+ lex.proposed_next_state = Type::ProposedNextState.shrink_wrap(data)
129
+ self
130
+ end
131
+
123
132
  def session(data)
124
133
  lex.session_state.session_attributes.merge!(Type::SessionAttributes[data])
125
134
  self
126
135
  end
127
136
 
137
+ def transcription(opts = {})
138
+ data = {
139
+ transcription: opts.fetch(:transcription),
140
+ transcriptionConfidence: opts.fetch(:confidence, 1),
141
+ resolvedContext: {
142
+ intent: opts.fetch(:intent) { 'FallbackIntent' }
143
+ },
144
+ resolvedSlots: opts.fetch(:resolved_slots) { {} }
145
+ }
146
+ lex.transcriptions.push(Type::Transcription.shrink_wrap(data))
147
+ self
148
+ end
149
+
128
150
  private
129
151
 
152
+ def lex_attributes(instance)
153
+ return instance unless instance.respond_to?(:to_lex)
154
+
155
+ instance.to_lex
156
+ end
157
+
130
158
  def current_interpretation
131
159
  lex.interpretations.find { |i| i.intent.name == lex.session_state.intent.name }
132
160
  end
@@ -6,7 +6,7 @@ module Aws
6
6
  module Slot
7
7
  class Elicitation
8
8
  attr_accessor :name, :elicit, :messages, :follow_up_messages,
9
- :fallback, :maximum_elicitations, :conversation
9
+ :fallback, :max_retries, :conversation
10
10
 
11
11
  def initialize(opts = {})
12
12
  self.name = opts.fetch(:name)
@@ -14,7 +14,7 @@ module Aws
14
14
  self.messages = opts.fetch(:messages)
15
15
  self.follow_up_messages = opts.fetch(:follow_up_messages) { opts.fetch(:messages) }
16
16
  self.fallback = opts[:fallback]
17
- self.maximum_elicitations = opts.fetch(:maximum_elicitations) { 0 }
17
+ self.max_retries = opts[:max_retries] || opts[:maximum_elicitations] || 0
18
18
  end
19
19
 
20
20
  def elicit!
@@ -53,9 +53,7 @@ module Aws
53
53
  end
54
54
 
55
55
  def maximum_elicitations?
56
- return false if maximum_elicitations.zero?
57
-
58
- elicitation_attempts > maximum_elicitations
56
+ elicitation_attempts > max_retries
59
57
  end
60
58
 
61
59
  def first_elicitation?
@@ -20,6 +20,28 @@ module Aws
20
20
  fulfillment_state: FulfillmentState
21
21
  )
22
22
 
23
+ class << self
24
+ def build(opts = {})
25
+ new(normalize_parameters(opts))
26
+ end
27
+
28
+ private
29
+
30
+ def normalize_parameters(opts)
31
+ params = opts.dup # we don't want to mutate our arguments
32
+
33
+ if params[:dialog_action_type].is_a?(String)
34
+ params[:dialog_action_type] = DialogActionType.new(params[:dialog_action_type])
35
+ end
36
+
37
+ if params[:fulfillment_state].is_a?(String)
38
+ params[:fulfillment_state] = FulfillmentState.new(params[:fulfillment_state])
39
+ end
40
+
41
+ params
42
+ end
43
+ end
44
+
23
45
  # restore the checkpoint AND remove it from session
24
46
  def restore!(conversation, opts = {})
25
47
  conversation.checkpoints.delete_if { |c| c.label == label }
@@ -8,10 +8,12 @@ module Aws
8
8
  include Base
9
9
 
10
10
  optional :slot_to_elicit
11
+ optional :slot_elicitation_style, default: -> { 'Default' }
11
12
  required :type
12
13
 
13
14
  coerce(
14
- type: DialogActionType
15
+ type: DialogActionType,
16
+ slot_elicitation_style: SlotElicitationStyle
15
17
  )
16
18
  end
17
19
  end
@@ -17,6 +17,8 @@ module Aws
17
17
  required :response_content_type
18
18
  required :session_id
19
19
  required :session_state
20
+ required :transcriptions, default: -> { [] }
21
+ optional :proposed_next_state
20
22
 
21
23
  computed_property(:current_intent, virtual: true) do |instance|
22
24
  instance.session_state.intent.tap do |intent|
@@ -41,9 +43,11 @@ module Aws
41
43
  input_mode: InputMode,
42
44
  interpretations: Array[Interpretation],
43
45
  invocation_source: InvocationSource,
46
+ proposed_next_state: ProposedNextState,
44
47
  request_attributes: symbolize_hash!,
45
48
  response_content_type: Message::ContentType,
46
- session_state: SessionState
49
+ session_state: SessionState,
50
+ transcriptions: Array[Transcription]
47
51
  )
48
52
  end
49
53
  end
@@ -15,6 +15,11 @@ module Aws
15
15
  content_type: Message::ContentType,
16
16
  image_response_card: Aws::Lex::Conversation::Type::ResponseCard
17
17
  )
18
+
19
+ def initialize(opts = {})
20
+ assign_attributes!(opts)
21
+ self.content_type ||= ContentType.new('PlainText')
22
+ end
18
23
  end
19
24
  end
20
25
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Type
7
+ class ProposedNextState
8
+ include Base
9
+
10
+ required :dialog_action
11
+ required :intent
12
+
13
+ coerce(
14
+ dialog_action: DialogAction,
15
+ intent: Intent
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -32,7 +32,7 @@ module Aws
32
32
  def value
33
33
  raise TypeError, 'use values for List-type slots' if shape.list?
34
34
 
35
- lex_value.interpreted_value
35
+ lex_value&.interpreted_value
36
36
  end
37
37
 
38
38
  # takes an array of slot values
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Type
7
+ class SlotElicitationStyle
8
+ include Enumeration
9
+
10
+ enumeration('Default')
11
+ enumeration('SpellByLetter')
12
+ enumeration('SpellByWord')
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Type
7
+ class Transcription
8
+ class ResolvedContext
9
+ include Base
10
+
11
+ required :intent
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Type
7
+ class Transcription
8
+ include Base
9
+
10
+ required :transcription
11
+ required :transcription_confidence
12
+ optional :resolved_context
13
+ optional :resolved_slots
14
+ alias_method :confidence, :transcription_confidence
15
+
16
+ coerce(
17
+ resolved_context: Transcription::ResolvedContext,
18
+ resolved_slots: Array[Slot]
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,7 +3,7 @@
3
3
  module Aws
4
4
  module Lex
5
5
  class Conversation
6
- VERSION = '6.1.0'
6
+ VERSION = '6.4.0'
7
7
  end
8
8
  end
9
9
  end
@@ -5,10 +5,13 @@ require_relative 'conversation/base'
5
5
  module Aws
6
6
  module Lex
7
7
  class Conversation
8
+ extend Forwardable
8
9
  include Support::Mixins::Responses
9
10
 
10
11
  attr_accessor :event, :context, :lex
11
12
 
13
+ def_delegators :@lex, :proposed_next_state, :transcriptions
14
+
12
15
  def initialize(opts = {})
13
16
  self.event = opts.fetch(:event)
14
17
  self.context = opts.fetch(:context)
@@ -62,9 +65,9 @@ module Aws
62
65
  label = opts.fetch(:label)
63
66
  params = {
64
67
  label: label,
65
- dialog_action_type: opts.fetch(:dialog_action_type),
68
+ dialog_action_type: opts.fetch(:dialog_action_type) { 'Delegate' },
66
69
  fulfillment_state: opts[:fulfillment_state],
67
- intent: lex.current_intent,
70
+ intent: opts.fetch(:intent) { lex.current_intent },
68
71
  slot_to_elicit: opts[:slot_to_elicit]
69
72
  }.compact
70
73
 
@@ -72,9 +75,8 @@ module Aws
72
75
  # update the existing checkpoint
73
76
  checkpoint(label: label).assign_attributes!(params)
74
77
  else
75
- # push a new checkpoint to the recent_intent_summary_view
76
78
  checkpoints.unshift(
77
- Type::Checkpoint.new(params)
79
+ Type::Checkpoint.build(params)
78
80
  )
79
81
  end
80
82
  end
@@ -91,6 +93,21 @@ module Aws
91
93
  lex.session_state.session_attributes.checkpoints
92
94
  end
93
95
 
96
+ def restore_from!(checkpoint)
97
+ # we're done with the stored checkpoint once it's been restored
98
+ checkpoints.delete_if { |c| c.label == checkpoint.label }
99
+ # remove any memoized intent data
100
+ lex.current_intent = nil
101
+ # replace the intent with data from the checkpoint
102
+ lex.session_state.intent = checkpoint.intent
103
+ dialog_action = Type::DialogAction.new(
104
+ type: checkpoint.dialog_action_type,
105
+ slot_to_elicit: checkpoint.slot_to_elicit
106
+ )
107
+ lex.session_state.dialog_action = dialog_action
108
+ self
109
+ end
110
+
94
111
  def active_context?(name:)
95
112
  !active_context(name: name).nil?
96
113
  end
@@ -104,7 +121,7 @@ module Aws
104
121
  instance = active_context(name: name)
105
122
 
106
123
  if instance
107
- lex.session_state.active_contexts.delete_if { |c| c.name == name }
124
+ clear_context!(name: name)
108
125
  else
109
126
  instance = Type::Context.new
110
127
  end
@@ -128,6 +145,10 @@ module Aws
128
145
  lex.session_state.active_contexts = []
129
146
  end
130
147
 
148
+ def proposed_next_state?
149
+ !proposed_next_state.nil?
150
+ end
151
+
131
152
  def stash
132
153
  @stash ||= {}
133
154
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-lex-conversation
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0
4
+ version: 6.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesse Doyle
@@ -9,10 +9,10 @@ authors:
9
9
  - Darko Dosenovic
10
10
  - Zoie Carnegie
11
11
  - Carlos Mejia Castelo
12
- autorequire:
12
+ autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2021-09-08 00:00:00.000000000 Z
15
+ date: 2022-02-10 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: shrink_wrap
@@ -92,6 +92,7 @@ files:
92
92
  - lib/aws/lex/conversation/type/invocation_source.rb
93
93
  - lib/aws/lex/conversation/type/message.rb
94
94
  - lib/aws/lex/conversation/type/message/content_type.rb
95
+ - lib/aws/lex/conversation/type/proposed_next_state.rb
95
96
  - lib/aws/lex/conversation/type/response.rb
96
97
  - lib/aws/lex/conversation/type/response_card.rb
97
98
  - lib/aws/lex/conversation/type/response_card/button.rb
@@ -101,16 +102,20 @@ files:
101
102
  - lib/aws/lex/conversation/type/session_attributes.rb
102
103
  - lib/aws/lex/conversation/type/session_state.rb
103
104
  - lib/aws/lex/conversation/type/slot.rb
105
+ - lib/aws/lex/conversation/type/slot_elicitation_style.rb
104
106
  - lib/aws/lex/conversation/type/slot_shape.rb
105
107
  - lib/aws/lex/conversation/type/slot_value.rb
106
108
  - lib/aws/lex/conversation/type/time_to_live.rb
109
+ - lib/aws/lex/conversation/type/transcription.rb
110
+ - lib/aws/lex/conversation/type/transcription/resolved_context.rb
107
111
  - lib/aws/lex/conversation/version.rb
108
112
  homepage: https://github.com/amaabca/aws-lex-conversation
109
113
  licenses:
110
114
  - MIT
111
115
  metadata:
112
116
  homepage_uri: https://github.com/amaabca/aws-lex-conversation
113
- post_install_message:
117
+ rubygems_mfa_required: 'true'
118
+ post_install_message:
114
119
  rdoc_options: []
115
120
  require_paths:
116
121
  - lib
@@ -126,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
131
  version: '0'
127
132
  requirements: []
128
133
  rubygems_version: 3.1.2
129
- signing_key:
134
+ signing_key:
130
135
  specification_version: 4
131
136
  summary: AWS Lex Conversation Dialog Management
132
137
  test_files: []