aws-lex-conversation 5.1.1 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +104 -0
- data/README.md +164 -0
- data/lib/aws/lex/conversation/simulator.rb +188 -0
- data/lib/aws/lex/conversation/slot/elicitation.rb +3 -5
- data/lib/aws/lex/conversation/spec/matchers.rb +236 -0
- data/lib/aws/lex/conversation/spec.rb +21 -0
- data/lib/aws/lex/conversation/type/base.rb +8 -3
- data/lib/aws/lex/conversation/type/bot.rb +1 -0
- data/lib/aws/lex/conversation/type/checkpoint.rb +28 -0
- data/lib/aws/lex/conversation/type/context.rb +1 -1
- data/lib/aws/lex/conversation/type/event.rb +5 -5
- data/lib/aws/lex/conversation/type/intent.rb +1 -1
- data/lib/aws/lex/conversation/type/slot.rb +16 -5
- data/lib/aws/lex/conversation/version.rb +1 -1
- data/lib/aws/lex/conversation.rb +55 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a4ab092d321df0c71e5f16cf1021e87358b1b39ab025eaf5555e3e3da7894c1
|
4
|
+
data.tar.gz: a0332c4f2f73b35972c83a0740e1adfa4d724fe5d9861087d348e905e3e15633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 734b1ec8921c8982a3e123520b08b0d0b9e655159c781f7df18d106b8de635815bc026862fd6a4ec7a3d2665d47393bb8d977cf530a1331e2f3c2bea543b4a94
|
7
|
+
data.tar.gz: 2c4c43bafd522791d6f5f39596c7b594d3788098876b51fc8294cf2304a23c42a06fd6635e15e791410bc824273599f9afab3935fd853981f81b732672d03d96
|
data/.rubocop.yml
CHANGED
@@ -20,12 +20,17 @@ Metrics/AbcSize:
|
|
20
20
|
Max: 20
|
21
21
|
Metrics/ClassLength:
|
22
22
|
Max: 150
|
23
|
+
Exclude:
|
24
|
+
- lib/aws/lex/conversation/simulator.rb
|
23
25
|
Metrics/BlockLength:
|
24
26
|
Exclude:
|
25
27
|
- 'spec/**/*'
|
26
28
|
- '*.gemspec'
|
27
29
|
Metrics/MethodLength:
|
28
30
|
Max: 25
|
31
|
+
Metrics/ModuleLength:
|
32
|
+
Exclude:
|
33
|
+
- lib/aws/lex/conversation/spec/matchers.rb
|
29
34
|
Naming/FileName:
|
30
35
|
Enabled: true
|
31
36
|
Exclude:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,107 @@
|
|
1
|
+
# 6.2.0 - Sept 28, 2021
|
2
|
+
|
3
|
+
* 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.
|
4
|
+
* Make the `dialog_action_type` parameter on `Aws::Lex::Conversation#checkpoint!` default to `Delegate` if not specified as a developer convenience.
|
5
|
+
* Allow developers to pass an optional `intent` override parameter on `Aws::Lex::Conversation#checkpoint!` for convenience.
|
6
|
+
* Update the README with advanced examples for the conversation stash and checkpoints.
|
7
|
+
|
8
|
+
# 6.1.1 - Sept 22, 2021
|
9
|
+
|
10
|
+
* 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
|
11
|
+
|
12
|
+
# 6.1.0 - Sept 7, 2021
|
13
|
+
|
14
|
+
Added helper methods for clearing active contexts
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
conversation.clear_context!(name: 'test') # clears this specific active context
|
18
|
+
conversation.clear_all_contexts! # clears all current active contexts
|
19
|
+
```
|
20
|
+
|
21
|
+
# 6.0.0 - Sept 7, 2021
|
22
|
+
|
23
|
+
* **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.
|
24
|
+
* **breaking change** - Add a required `alias_name` attribute on `Aws::Lex::Conversation::Type::Bot` instances. Please note that the Version 2 of AWS Lex correctly returns this value as input to lambda functions. Therefore no application-level changes are necessary.
|
25
|
+
* Implement a new set of test helpers to make it easier to modify state and match test expectations. You can use the test helpers as follows:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# we must explicitly require the test helpers
|
29
|
+
require 'aws/lex/conversation/spec'
|
30
|
+
|
31
|
+
# optional: include the custom matchers if you're using RSpec
|
32
|
+
RSpec.configure do |config|
|
33
|
+
config.include(Aws::Lex::Conversation::Spec)
|
34
|
+
end
|
35
|
+
|
36
|
+
# we can now simulate state in a test somewhere
|
37
|
+
it 'simulates a conversation' do
|
38
|
+
conversation # given we have an instance of Aws::Lex::Conversation
|
39
|
+
.simulate! # simulation modifies the underlying instance
|
40
|
+
.transcript('My age is 21') # optionally set an input transcript
|
41
|
+
.intent(name: 'MyCoolIntent') # route to the intent named "MyCoolIntent"
|
42
|
+
.slot(name: 'Age', value: '21') # add a slot named "Age" with a corresponding value
|
43
|
+
|
44
|
+
expect(conversation).to have_transcript('My age is 21')
|
45
|
+
expect(conversation).to route_to_intent('MyCoolIntent')
|
46
|
+
expect(conversation).to have_slot(name: 'Age', value: '21')
|
47
|
+
end
|
48
|
+
|
49
|
+
# if you'd rather create your own event from scratch
|
50
|
+
it 'creates an event' do
|
51
|
+
simulator = Aws::Lex::Conversation::Simulator.new
|
52
|
+
simulator
|
53
|
+
.transcript('I am 21 years old.')
|
54
|
+
.input_mode('Speech')
|
55
|
+
.context(name: 'WelcomeGreetingCompleted')
|
56
|
+
.invocation_source('FulfillmentCodeHook')
|
57
|
+
.session(username: 'jane.doe')
|
58
|
+
.intent(
|
59
|
+
name: 'GuessZodiacSign',
|
60
|
+
state: 'ReadyForFulfillment',
|
61
|
+
slots: {
|
62
|
+
age: {
|
63
|
+
value: '21'
|
64
|
+
}
|
65
|
+
}
|
66
|
+
)
|
67
|
+
event = simulator.event
|
68
|
+
|
69
|
+
expect(event).to have_transcript('I am 21 years old.')
|
70
|
+
expect(event).to have_input_mode('Speech')
|
71
|
+
expect(event).to have_active_context(name: 'WelcomeGreetingCompleted')
|
72
|
+
expect(event).to have_invocation_source('FulfillmentCodeHook')
|
73
|
+
expect(event).to route_to_intent('GuessZodiacSign')
|
74
|
+
expect(event).to have_slot(name: 'age', value: '21')
|
75
|
+
expect(event).to include_session_values(username: 'jane.doe')
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
* Add a few convenience methods to `Aws::Lex::Conversation` instances for dealing with active contexts:
|
80
|
+
- `#active_context(name:)`:
|
81
|
+
|
82
|
+
Returns the active context instance that matches the name parameter.
|
83
|
+
|
84
|
+
- `#active_context?(name:)`:
|
85
|
+
|
86
|
+
Returns true/false depending on if an active context matching
|
87
|
+
the name parameter is found.
|
88
|
+
|
89
|
+
- `#active_context!(name:, turns:, seconds:, attributes:)`:
|
90
|
+
|
91
|
+
Creates or updates an existing active context instance for
|
92
|
+
the conversation.
|
93
|
+
|
94
|
+
# 5.1.0 - Sept 2, 2021
|
95
|
+
|
96
|
+
* Allow the intent to be specified when returning a response such as `elicit_slot`.
|
97
|
+
|
98
|
+
# 5.0.0 - August 30, 2021
|
99
|
+
|
100
|
+
* **breaking change** - `Aws::Lex::Conversation::Support::Mixins::SlotElicitation`
|
101
|
+
- Rename the `message` attribute to `messages`. This attribute must be a callable that returns and array of `Aws::Lex::Conversation::Type::Message` instances.
|
102
|
+
- rename the `follow_up_message` attribute to `follow_up_messages`. This must also be a callable that returns an array of message instances.
|
103
|
+
* Allow the `fallback` callable in `SlotElicitation` to be nilable. The slot value will not be elicited if the value is nil and maximum attempts have been exceeded.
|
104
|
+
|
1
105
|
# 4.3.0 - August 25, 2021
|
2
106
|
|
3
107
|
* Slot elicitor can now be passed an Aws::Lex::Conversation::Type::Message as part of the DSL/callback and properly formats the response as such
|
data/README.md
CHANGED
@@ -191,6 +191,170 @@ conversation.handlers = [
|
|
191
191
|
conversation.respond # => { dialogAction: { type: 'Delegate' } }
|
192
192
|
```
|
193
193
|
|
194
|
+
## Advanced Concepts
|
195
|
+
|
196
|
+
This library provides a few constructs to help manage complex interactions:
|
197
|
+
|
198
|
+
### Data Stash
|
199
|
+
|
200
|
+
`Aws::Lex::Conversation` instances implement a `stash` method that can be used to store temporary data within a single invocation.
|
201
|
+
|
202
|
+
A conversation's stashed data will not be persisted between multiple invocations of your lambda function.
|
203
|
+
|
204
|
+
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.
|
205
|
+
|
206
|
+
This example illustrates how the stash can be used to store deserialized data from the session:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
# given we have JSON-serialized data in as a persisted session value
|
210
|
+
conversation.session[:user_data] = '{"name":"Jane","id":1234,"email":"test@example.com"}'
|
211
|
+
# we can deserialize the data into a Hash that we store in the conversation stash
|
212
|
+
conversation.stash[:user] = JSON.parse(conversation.session[:user_data])
|
213
|
+
# later on we can reference our stashed data (within the same invocation)
|
214
|
+
conversation.stash[:user] # => {"name"=>"Jane", "id"=>1234, "email"=>"test@example.com"}
|
215
|
+
```
|
216
|
+
|
217
|
+
### Checkpoints
|
218
|
+
|
219
|
+
A conversation may transition between many different topics as the interaction progresses. This type of state transition can be easily handled with checkpoints.
|
220
|
+
|
221
|
+
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.
|
222
|
+
|
223
|
+
You can create a checkpoint as follows:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
# we're ready to fulfill the OrderFlowers intent, but we want to elicit another intent first
|
227
|
+
conversation.checkpoint!(
|
228
|
+
label: 'order_flowers',
|
229
|
+
dialog_action_type: 'Close' # defaults to 'Delegate' if not specified
|
230
|
+
)
|
231
|
+
conversation.elicit_intent(
|
232
|
+
messages: [
|
233
|
+
{
|
234
|
+
content: 'Thanks! Before I place your order, is there anything else I can help with?',
|
235
|
+
contentType: 'PlainText'
|
236
|
+
}
|
237
|
+
]
|
238
|
+
)
|
239
|
+
```
|
240
|
+
|
241
|
+
You can restore the checkpoint in one of two ways:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
# in a future invocation, we can fetch an instance of the checkpoint and easily
|
245
|
+
# restore the conversation to the previous state
|
246
|
+
checkpoint = conversation.checkpoint(label: 'order_flowers')
|
247
|
+
checkpoint.restore!(
|
248
|
+
fulfillment_state: 'Fulfilled',
|
249
|
+
messages: [
|
250
|
+
{
|
251
|
+
content: 'Okay, your flowers have been ordered! Thanks!',
|
252
|
+
contentType: 'PlainText'
|
253
|
+
}
|
254
|
+
]
|
255
|
+
) # => our response object to Lex is returned
|
256
|
+
```
|
257
|
+
|
258
|
+
It's also possible to restore state from a checkpoint and utilize the conversation's handler chain:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
class AnotherIntent < Aws::Lex::Conversation::Handler::Base
|
262
|
+
def will_respond?(conversation)
|
263
|
+
conversation.intent_name == 'AnotherIntent' &&
|
264
|
+
conversation.checkpoint?(label: 'order_flowers')
|
265
|
+
end
|
266
|
+
|
267
|
+
def response(conversation)
|
268
|
+
checkpoint = conversation.checkpoint(label: 'order_flowers')
|
269
|
+
# replace the conversation's current resolved intent/slot data with the saved checkpoint data
|
270
|
+
conversation.restore_from!(checkpoint)
|
271
|
+
# call the next handler in the chain to produce a response
|
272
|
+
successor.handle(conversation)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class OrderFlowers < Aws::Lex::Conversation::Handler::Base
|
277
|
+
def will_respond?(conversation)
|
278
|
+
conversation.intent_name == 'OrderFlowers'
|
279
|
+
end
|
280
|
+
|
281
|
+
def response(conversation)
|
282
|
+
conversation.close(
|
283
|
+
fulfillment_state: 'Fulfilled',
|
284
|
+
messages: [
|
285
|
+
{
|
286
|
+
content: 'Okay, your flowers have been ordered! Thanks!',
|
287
|
+
contentType: 'PlainText'
|
288
|
+
}
|
289
|
+
]
|
290
|
+
)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
conversation = Aws::Lex::Conversation.new(event: event, context: context)
|
295
|
+
conversation.handlers = [
|
296
|
+
{ handler: AnotherIntent },
|
297
|
+
{ handler: OrderFlowers }
|
298
|
+
]
|
299
|
+
conversation.respond # => returns a Lex response object
|
300
|
+
```
|
301
|
+
|
302
|
+
## Test Helpers
|
303
|
+
|
304
|
+
This library provides convenience methods to make testing easy! You can use the test helpers as follows:
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
# we must explicitly require the test helpers
|
308
|
+
require 'aws/lex/conversation/spec'
|
309
|
+
|
310
|
+
# optional: include the custom matchers if you're using RSpec
|
311
|
+
RSpec.configure do |config|
|
312
|
+
config.include(Aws::Lex::Conversation::Spec)
|
313
|
+
end
|
314
|
+
|
315
|
+
# we can now simulate state in a test somewhere
|
316
|
+
it 'simulates a conversation' do
|
317
|
+
conversation # given we have an instance of Aws::Lex::Conversation
|
318
|
+
.simulate! # simulation modifies the underlying instance
|
319
|
+
.transcript('My age is 21') # optionally set an input transcript
|
320
|
+
.intent(name: 'MyCoolIntent') # route to the intent named "MyCoolIntent"
|
321
|
+
.slot(name: 'Age', value: '21') # add a slot named "Age" with a corresponding value
|
322
|
+
|
323
|
+
expect(conversation).to have_transcript('My age is 21')
|
324
|
+
expect(conversation).to route_to_intent('MyCoolIntent')
|
325
|
+
expect(conversation).to have_slot(name: 'Age', value: '21')
|
326
|
+
end
|
327
|
+
|
328
|
+
# if you'd rather create your own event from scratch
|
329
|
+
it 'creates an event' do
|
330
|
+
simulator = Aws::Lex::Conversation::Simulator.new
|
331
|
+
simulator
|
332
|
+
.transcript('I am 21 years old.')
|
333
|
+
.input_mode('Speech')
|
334
|
+
.context(name: 'WelcomeGreetingCompleted')
|
335
|
+
.invocation_source('FulfillmentCodeHook')
|
336
|
+
.session(username: 'jane.doe')
|
337
|
+
.intent(
|
338
|
+
name: 'GuessZodiacSign',
|
339
|
+
state: 'ReadyForFulfillment',
|
340
|
+
slots: {
|
341
|
+
age: {
|
342
|
+
value: '21'
|
343
|
+
}
|
344
|
+
}
|
345
|
+
)
|
346
|
+
event = simulator.event
|
347
|
+
|
348
|
+
expect(event).to have_transcript('I am 21 years old.')
|
349
|
+
expect(event).to have_input_mode('Speech')
|
350
|
+
expect(event).to have_active_context(name: 'WelcomeGreetingCompleted')
|
351
|
+
expect(event).to have_invocation_source('FulfillmentCodeHook')
|
352
|
+
expect(event).to route_to_intent('GuessZodiacSign')
|
353
|
+
expect(event).to have_slot(name: 'age', value: '21')
|
354
|
+
expect(event).to include_session_values(username: 'jane.doe')
|
355
|
+
end
|
356
|
+
```
|
357
|
+
|
194
358
|
## Development
|
195
359
|
|
196
360
|
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.
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Lex
|
5
|
+
class Conversation
|
6
|
+
class Simulator
|
7
|
+
attr_accessor :lex
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
self.lex = opts.fetch(:lex) do
|
11
|
+
Type::Event.shrink_wrap(
|
12
|
+
bot: default_bot,
|
13
|
+
inputMode: 'Text',
|
14
|
+
inputTranscript: '',
|
15
|
+
interpretations: [],
|
16
|
+
invocationSource: 'DialogCodeHook',
|
17
|
+
messageVersion: '1.0',
|
18
|
+
requestAttributes: {},
|
19
|
+
responseContentType: 'text/plain; charset=utf-8',
|
20
|
+
sessionId: '1234567890000',
|
21
|
+
sessionState: default_session_state
|
22
|
+
)
|
23
|
+
end
|
24
|
+
interpretation(name: 'SIMULATION')
|
25
|
+
end
|
26
|
+
|
27
|
+
def event
|
28
|
+
lex.to_lex
|
29
|
+
end
|
30
|
+
|
31
|
+
def bot(opts = {})
|
32
|
+
changes = {
|
33
|
+
aliasName: opts[:alias_name],
|
34
|
+
aliasId: opts[:alias_id],
|
35
|
+
name: opts[:name],
|
36
|
+
version: opts[:version],
|
37
|
+
localeId: opts[:locale_id],
|
38
|
+
id: opts[:id]
|
39
|
+
}.compact
|
40
|
+
lex.bot = Type::Bot.shrink_wrap(lex.bot.to_lex.merge(changes))
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def transcript(message)
|
45
|
+
lex.input_transcript = message
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def intent(opts = {})
|
50
|
+
data = default_intent(opts)
|
51
|
+
intent = Type::Intent.shrink_wrap(data)
|
52
|
+
lex.session_state.intent = intent
|
53
|
+
interpretation(data)
|
54
|
+
end
|
55
|
+
|
56
|
+
# rubocop:disable Metrics/AbcSize
|
57
|
+
def interpretation(opts = {})
|
58
|
+
name = opts.fetch(:name)
|
59
|
+
slots = opts.fetch(:slots) { {} }
|
60
|
+
sentiment_score = opts.dig(:sentiment_response, :sentiment_score)
|
61
|
+
sentiment = opts.dig(:sentiment_response, :sentiment)
|
62
|
+
sentiment_response = opts[:sentiment_response] && {
|
63
|
+
sentiment: sentiment,
|
64
|
+
sentimentScore: sentiment_score
|
65
|
+
}
|
66
|
+
data = {
|
67
|
+
intent: default_intent(opts),
|
68
|
+
sentimentResponse: sentiment_response,
|
69
|
+
nluConfidence: opts[:nlu_confidence]
|
70
|
+
}.compact
|
71
|
+
lex.interpretations.delete_if { |i| i.intent.name == name }
|
72
|
+
lex.interpretations << Type::Interpretation.shrink_wrap(data)
|
73
|
+
slots.each do |key, value|
|
74
|
+
slot_data = { name: key }.merge(value)
|
75
|
+
slot(slot_data)
|
76
|
+
end
|
77
|
+
reset_computed_properties!
|
78
|
+
self
|
79
|
+
end
|
80
|
+
# rubocop:enable Metrics/AbcSize
|
81
|
+
|
82
|
+
def context(opts = {})
|
83
|
+
data = {
|
84
|
+
name: opts.fetch(:name),
|
85
|
+
contextAttributes: opts.fetch(:context_attributes) { {} },
|
86
|
+
timeToLive: {
|
87
|
+
timeToLiveInSeconds: opts.fetch(:seconds) { 600 },
|
88
|
+
turnsToLive: opts.fetch(:turns) { 100 }
|
89
|
+
}
|
90
|
+
}
|
91
|
+
context = Type::Context.shrink_wrap(data)
|
92
|
+
lex.session_state.active_contexts.delete_if { |c| c.name == context.name }
|
93
|
+
lex.session_state.active_contexts << context
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def slot(opts = {})
|
98
|
+
name = opts.fetch(:name).to_sym
|
99
|
+
raw_slots = {
|
100
|
+
shape: opts.fetch(:shape) { 'Scalar' },
|
101
|
+
value: {
|
102
|
+
originalValue: opts.fetch(:original_value) { opts.fetch(:value) },
|
103
|
+
resolvedValues: opts.fetch(:resolved_values) { [opts.fetch(:value)] },
|
104
|
+
interpretedValue: opts.fetch(:interpreted_value) { opts.fetch(:value) }
|
105
|
+
}
|
106
|
+
}
|
107
|
+
lex.session_state.intent.raw_slots[name] = raw_slots
|
108
|
+
current_interpretation.intent.raw_slots[name] = raw_slots
|
109
|
+
reset_computed_properties!
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def invocation_source(source)
|
114
|
+
lex.invocation_source = Type::InvocationSource.new(source)
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
def input_mode(mode)
|
119
|
+
lex.input_mode = Type::InputMode.new(mode)
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
def session(data)
|
124
|
+
lex.session_state.session_attributes.merge!(Type::SessionAttributes[data])
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def current_interpretation
|
131
|
+
lex.interpretations.find { |i| i.intent.name == lex.session_state.intent.name }
|
132
|
+
end
|
133
|
+
|
134
|
+
# computed properties are memoized using instance variables, so we must
|
135
|
+
# uncache the values when things change
|
136
|
+
def reset_computed_properties!
|
137
|
+
%w[
|
138
|
+
@alternate_intents
|
139
|
+
@current_intent
|
140
|
+
@intents
|
141
|
+
].each do |variable|
|
142
|
+
lex.instance_variable_set(variable, nil)
|
143
|
+
end
|
144
|
+
|
145
|
+
lex.session_state.intent.instance_variable_set('@slots', nil)
|
146
|
+
current_interpretation.intent.instance_variable_set('@slots', nil)
|
147
|
+
end
|
148
|
+
|
149
|
+
def default_bot
|
150
|
+
{
|
151
|
+
aliasId: 'TSTALIASID',
|
152
|
+
aliasName: 'TestBotAlias',
|
153
|
+
id: 'BOT_ID',
|
154
|
+
localeId: 'en_US',
|
155
|
+
name: 'SIMULATOR',
|
156
|
+
version: 'DRAFT'
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def default_session_state
|
161
|
+
{
|
162
|
+
activeContexts: [],
|
163
|
+
sessionAttributes: {},
|
164
|
+
intent: {
|
165
|
+
confirmationState: 'None',
|
166
|
+
name: 'SIMULATION',
|
167
|
+
slots: {},
|
168
|
+
state: 'InProgress',
|
169
|
+
originatingRequestId: SecureRandom.uuid
|
170
|
+
}
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
def default_intent(opts = {})
|
175
|
+
{
|
176
|
+
confirmationState: opts.fetch(:confirmation_state) { 'None' },
|
177
|
+
kendraResponse: opts[:kendra_response],
|
178
|
+
name: opts.fetch(:name),
|
179
|
+
nluConfidence: opts[:nlu_confidence],
|
180
|
+
originatingRequestId: opts.fetch(:originating_request_id) { SecureRandom.uuid },
|
181
|
+
slots: opts.fetch(:slots) { {} },
|
182
|
+
state: opts.fetch(:state) { 'InProgress' }
|
183
|
+
}.compact
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
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, :
|
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.
|
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
|
-
|
57
|
-
|
58
|
-
elicitation_attempts > maximum_elicitations
|
56
|
+
elicitation_attempts > max_retries
|
59
57
|
end
|
60
58
|
|
61
59
|
def first_elicitation?
|
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Lex
|
5
|
+
class Conversation
|
6
|
+
module Spec
|
7
|
+
module Matchers
|
8
|
+
extend RSpec::Matchers::DSL
|
9
|
+
|
10
|
+
# :nocov:
|
11
|
+
def build_event(input)
|
12
|
+
case input
|
13
|
+
when Aws::Lex::Conversation
|
14
|
+
input.lex.to_lex
|
15
|
+
when Hash
|
16
|
+
input
|
17
|
+
else
|
18
|
+
raise ArgumentError, \
|
19
|
+
'expected instance of Aws::Lex::Conversation ' \
|
20
|
+
"or Hash, got: #{input.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# :nocov:
|
24
|
+
|
25
|
+
matcher(:have_transcript) do |expected|
|
26
|
+
match do |actual|
|
27
|
+
build_event(actual).fetch(:inputTranscript) == expected
|
28
|
+
end
|
29
|
+
|
30
|
+
# :nocov:
|
31
|
+
failure_message do |actual|
|
32
|
+
response = build_event(actual)
|
33
|
+
"expected a transcript of '#{expected}', got '#{response.fetch(:inputTranscript)}'"
|
34
|
+
end
|
35
|
+
|
36
|
+
failure_message_when_negated do |actual|
|
37
|
+
response = build_event(actual)
|
38
|
+
"expected transcript to not equal '#{expected}', got '#{response.fetch(:inputTranscript)}'"
|
39
|
+
end
|
40
|
+
# :nocov:
|
41
|
+
end
|
42
|
+
|
43
|
+
matcher(:route_to_intent) do |expected|
|
44
|
+
match do |actual|
|
45
|
+
build_event(actual).dig(:sessionState, :intent, :name) == expected
|
46
|
+
end
|
47
|
+
|
48
|
+
# :nocov:
|
49
|
+
failure_message do |actual|
|
50
|
+
response = build_event(actual)
|
51
|
+
"expected intent to be '#{expected}', got '#{response.dig(:sessionState, :intent, :name)}'"
|
52
|
+
end
|
53
|
+
|
54
|
+
failure_message_when_negated do |actual|
|
55
|
+
response = build_event(actual)
|
56
|
+
"expected intent to not be '#{expected}', got '#{response.dig(:sessionState, :intent, :name)}'"
|
57
|
+
end
|
58
|
+
# :nocov:
|
59
|
+
end
|
60
|
+
|
61
|
+
matcher(:have_active_context) do |expected|
|
62
|
+
match do |actual|
|
63
|
+
build_event(actual).dig(:sessionState, :activeContexts).any? { |c| c[:name] == expected }
|
64
|
+
end
|
65
|
+
|
66
|
+
# :nocov:
|
67
|
+
failure_message do |actual|
|
68
|
+
response = build_event(actual)
|
69
|
+
names = response.dig(:sessionState, :activeContexts).map { |c| c[:name] }.inspect
|
70
|
+
"expected active context of `#{expected}` in #{names}"
|
71
|
+
end
|
72
|
+
|
73
|
+
failure_message_when_negated do |actual|
|
74
|
+
response = build_event(actual)
|
75
|
+
names = response.dig(:sessionState, :activeContexts).map { |c| c[:name] }.inspect
|
76
|
+
"expected active contexts to not include `#{expected}`, got: #{names}"
|
77
|
+
end
|
78
|
+
# :nocov:
|
79
|
+
end
|
80
|
+
|
81
|
+
matcher(:have_invocation_source) do |expected|
|
82
|
+
match do |actual|
|
83
|
+
build_event(actual).fetch(:invocationSource) == expected
|
84
|
+
end
|
85
|
+
|
86
|
+
# :nocov:
|
87
|
+
failure_message do |actual|
|
88
|
+
response = build_event(actual)
|
89
|
+
"expected invocationSource of `#{expected}`, got: #{response[:invocationSource]}"
|
90
|
+
end
|
91
|
+
|
92
|
+
failure_message_when_negated do |actual|
|
93
|
+
response = build_event(actual)
|
94
|
+
"expected invocationSource to not be `#{expected}`, got: #{response[:invocationSource]}"
|
95
|
+
end
|
96
|
+
# :nocov:
|
97
|
+
end
|
98
|
+
|
99
|
+
matcher(:have_input_mode) do |expected|
|
100
|
+
match do |actual|
|
101
|
+
build_event(actual).fetch(:inputMode) == expected
|
102
|
+
end
|
103
|
+
|
104
|
+
# :nocov:
|
105
|
+
failure_message do |actual|
|
106
|
+
response = build_event(actual)
|
107
|
+
"expected inputMode of `#{expected}`, got: #{response[:inputMode]}"
|
108
|
+
end
|
109
|
+
|
110
|
+
failure_message_when_negated do |actual|
|
111
|
+
response = build_event(actual)
|
112
|
+
"expected inputMode to not be `#{expected}`, got: #{response[:inputMode]}"
|
113
|
+
end
|
114
|
+
# :nocov:
|
115
|
+
end
|
116
|
+
|
117
|
+
matcher(:have_interpretation) do |expected|
|
118
|
+
match do |actual|
|
119
|
+
build_event(actual).fetch(:interpretations).any? { |i| i.dig(:intent, :name) == expected }
|
120
|
+
end
|
121
|
+
|
122
|
+
# :nocov:
|
123
|
+
failure_message do |actual|
|
124
|
+
response = build_event(actual)
|
125
|
+
names = response[:interpretations].map { |i| i.dig(:intent, :name) }.inspect
|
126
|
+
"expected interpretation of `#{expected}` in #{names}"
|
127
|
+
end
|
128
|
+
|
129
|
+
failure_message_when_negated do |actual|
|
130
|
+
response = build_event(actual)
|
131
|
+
names = response[:interpretations].map { |_i| c.dig(:intent, :name) }.inspect
|
132
|
+
"expected interpretations to not include `#{expected}`, got: #{names}"
|
133
|
+
end
|
134
|
+
# :nocov:
|
135
|
+
end
|
136
|
+
|
137
|
+
matcher(:have_slot) do |opts|
|
138
|
+
name = opts.fetch(:name)
|
139
|
+
value = opts[:value]
|
140
|
+
values = value && [value]
|
141
|
+
expected_slot = {
|
142
|
+
shape: opts.fetch(:shape) { 'Scalar' },
|
143
|
+
value: {
|
144
|
+
originalValue: opts.fetch(:original_value, value),
|
145
|
+
interpretedValue: opts.fetch(:interpreted_value, value),
|
146
|
+
resolvedValues: opts.fetch(:resolved_values, values)
|
147
|
+
}.compact
|
148
|
+
}
|
149
|
+
|
150
|
+
match do |actual|
|
151
|
+
slot = build_event(actual).dig(:sessionState, :intent, :slots, name.to_sym)
|
152
|
+
slot[:shape] == expected_slot[:shape] &&
|
153
|
+
slot[:value].slice(*expected_slot[:value].keys) == expected_slot[:value]
|
154
|
+
end
|
155
|
+
|
156
|
+
# :nocov:
|
157
|
+
failure_message do |actual|
|
158
|
+
slot = build_event(actual).dig(:sessionState, :intent, :slots, name.to_sym)
|
159
|
+
"expected #{expected_slot.inspect} to equal #{slot.inspect}"
|
160
|
+
end
|
161
|
+
|
162
|
+
failure_message_when_negated do |actual|
|
163
|
+
slot = build_event(actual).dig(:sessionState, :intent, :slots, name.to_sym)
|
164
|
+
"expected #{expected_slot.inspect} to not equal #{slot.inspect}"
|
165
|
+
end
|
166
|
+
# :nocov:
|
167
|
+
end
|
168
|
+
|
169
|
+
matcher(:have_action) do |expected|
|
170
|
+
match do |actual|
|
171
|
+
build_event(actual).dig(:sessionState, :dialogAction, :type) == expected
|
172
|
+
end
|
173
|
+
|
174
|
+
# :nocov:
|
175
|
+
failure_message do |actual|
|
176
|
+
"expected #{build_event(actual).dig(:sessionState, :dialogAction, :type)} to equal #{expected}"
|
177
|
+
end
|
178
|
+
|
179
|
+
failure_message_when_negated do |actual|
|
180
|
+
"expected #{build_event(actual).dig(:sessionState, :dialogAction, :type)} to not equal #{expected}"
|
181
|
+
end
|
182
|
+
# :nocov:
|
183
|
+
end
|
184
|
+
|
185
|
+
matcher(:have_intent_state) do |expected|
|
186
|
+
match do |actual|
|
187
|
+
build_event(actual).dig(:sessionState, :intent, :state) == expected
|
188
|
+
end
|
189
|
+
|
190
|
+
# :nocov:
|
191
|
+
failure_message do |actual|
|
192
|
+
"expected #{build_event(actual).dig(:sessionState, :intent, :state)} to equal #{expected}"
|
193
|
+
end
|
194
|
+
|
195
|
+
failure_message_when_negated do |actual|
|
196
|
+
"expected #{build_event(actual).dig(:sessionState, :intent, :state)} to not equal #{expected}"
|
197
|
+
end
|
198
|
+
# :nocov:
|
199
|
+
end
|
200
|
+
|
201
|
+
matcher(:elicit_slot) do |expected|
|
202
|
+
match do |actual|
|
203
|
+
response = build_event(actual)
|
204
|
+
|
205
|
+
response.dig(:sessionState, :dialogAction, :type) == 'ElicitSlot' &&
|
206
|
+
response.dig(:sessionState, :dialogAction, :slotToElicit) == expected
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
matcher(:include_session_values) do |expected|
|
211
|
+
match do |actual|
|
212
|
+
response = build_event(actual)
|
213
|
+
response.dig(:sessionState, :sessionAttributes).slice(*expected.keys) == expected
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
matcher(:have_message) do |expected|
|
218
|
+
match do |actual|
|
219
|
+
build_event(actual)[:messages].any? { |m| m.slice(*expected.keys) == expected }
|
220
|
+
end
|
221
|
+
|
222
|
+
# :nocov:
|
223
|
+
failure_message do |actual|
|
224
|
+
"expected matching message of #{expected.inspect} in #{build_event(actual)[:messages].inspect}"
|
225
|
+
end
|
226
|
+
|
227
|
+
failure_message_when_negated do |actual|
|
228
|
+
"found matching message of #{expected.inspect} in #{build_event(actual)[:messages].inspect}"
|
229
|
+
end
|
230
|
+
# :nocov:
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
require_relative 'simulator'
|
5
|
+
require_relative 'spec/matchers'
|
6
|
+
|
7
|
+
module Aws
|
8
|
+
module Lex
|
9
|
+
class Conversation
|
10
|
+
module Spec
|
11
|
+
def self.included(base)
|
12
|
+
base.include(Matchers)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def simulate!
|
17
|
+
@simulate ||= Simulator.new(lex: lex)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -71,14 +71,19 @@ module Aws
|
|
71
71
|
->(v) { v.transform_keys(&:to_sym) }
|
72
72
|
end
|
73
73
|
|
74
|
-
def computed_property(attribute,
|
75
|
-
mapping[attribute] = attribute
|
74
|
+
def computed_property(attribute, opts = {}, &block)
|
76
75
|
attr_writer(attribute)
|
77
76
|
|
77
|
+
if opts.fetch(:virtual) { false }
|
78
|
+
virtual_attributes << attribute
|
79
|
+
else
|
80
|
+
mapping[attribute] = attribute
|
81
|
+
end
|
82
|
+
|
78
83
|
# dynamically memoize the result
|
79
84
|
define_method(attribute) do
|
80
85
|
instance_variable_get("@#{attribute}") ||
|
81
|
-
instance_variable_set("@#{attribute}",
|
86
|
+
instance_variable_set("@#{attribute}", block.call(self))
|
82
87
|
end
|
83
88
|
end
|
84
89
|
|
@@ -20,6 +20,34 @@ 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
|
+
|
45
|
+
# restore the checkpoint AND remove it from session
|
46
|
+
def restore!(conversation, opts = {})
|
47
|
+
conversation.checkpoints.delete_if { |c| c.label == label }
|
48
|
+
restore(conversation, opts)
|
49
|
+
end
|
50
|
+
|
23
51
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
24
52
|
def restore(conversation, opts = {})
|
25
53
|
case dialog_action_type.raw
|
@@ -18,21 +18,21 @@ module Aws
|
|
18
18
|
required :session_id
|
19
19
|
required :session_state
|
20
20
|
|
21
|
-
computed_property
|
21
|
+
computed_property(:current_intent, virtual: true) do |instance|
|
22
22
|
instance.session_state.intent.tap do |intent|
|
23
|
-
intent.nlu_confidence = instance.interpretations.find { |i| i.intent.name == intent.name }
|
23
|
+
intent.nlu_confidence = instance.interpretations.find { |i| i.intent.name == intent.name }&.nlu_confidence
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
computed_property
|
27
|
+
computed_property(:intents, virutal: true) do |instance|
|
28
28
|
instance.interpretations.map(&:intent).tap do |intents|
|
29
29
|
intents.map do |intent|
|
30
|
-
intent.nlu_confidence = instance.interpretations.find { |i| i.intent.name == intent.name }
|
30
|
+
intent.nlu_confidence = instance.interpretations.find { |i| i.intent.name == intent.name }&.nlu_confidence
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
computed_property
|
35
|
+
computed_property(:alternate_intents, virtual: true) do |instance|
|
36
36
|
instance.intents.reject { |intent| intent.name == instance.current_intent.name }
|
37
37
|
end
|
38
38
|
|
@@ -15,7 +15,7 @@ module Aws
|
|
15
15
|
optional :originating_request_id
|
16
16
|
optional :nlu_confidence
|
17
17
|
|
18
|
-
computed_property
|
18
|
+
computed_property(:slots) do |instance|
|
19
19
|
# any keys indexed without a value will return an empty Slot instance
|
20
20
|
default_hash = Hash.new do |_hash, key|
|
21
21
|
Slot.shrink_wrap(active: false, name: key.to_sym, value: nil, shape: 'Scalar')
|
@@ -20,10 +20,7 @@ module Aws
|
|
20
20
|
)
|
21
21
|
|
22
22
|
def to_lex
|
23
|
-
super.merge(
|
24
|
-
value: transform_to_lex(lex_value),
|
25
|
-
values: transform_to_lex(lex_values)
|
26
|
-
)
|
23
|
+
super.merge(extra_response_attributes)
|
27
24
|
end
|
28
25
|
|
29
26
|
def value=(val)
|
@@ -35,7 +32,7 @@ module Aws
|
|
35
32
|
def value
|
36
33
|
raise TypeError, 'use values for List-type slots' if shape.list?
|
37
34
|
|
38
|
-
lex_value
|
35
|
+
lex_value&.interpreted_value
|
39
36
|
end
|
40
37
|
|
41
38
|
# takes an array of slot values
|
@@ -87,6 +84,20 @@ module Aws
|
|
87
84
|
def requestable?
|
88
85
|
active? && blank?
|
89
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def extra_response_attributes
|
91
|
+
if shape.list?
|
92
|
+
{
|
93
|
+
values: transform_to_lex(lex_values)
|
94
|
+
}
|
95
|
+
else
|
96
|
+
{
|
97
|
+
value: transform_to_lex(lex_value)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
90
101
|
end
|
91
102
|
end
|
92
103
|
end
|
data/lib/aws/lex/conversation.rb
CHANGED
@@ -62,9 +62,9 @@ module Aws
|
|
62
62
|
label = opts.fetch(:label)
|
63
63
|
params = {
|
64
64
|
label: label,
|
65
|
-
dialog_action_type: opts.fetch(:dialog_action_type),
|
65
|
+
dialog_action_type: opts.fetch(:dialog_action_type) { 'Delegate' },
|
66
66
|
fulfillment_state: opts[:fulfillment_state],
|
67
|
-
intent: lex.current_intent,
|
67
|
+
intent: opts.fetch(:intent) { lex.current_intent },
|
68
68
|
slot_to_elicit: opts[:slot_to_elicit]
|
69
69
|
}.compact
|
70
70
|
|
@@ -72,9 +72,8 @@ module Aws
|
|
72
72
|
# update the existing checkpoint
|
73
73
|
checkpoint(label: label).assign_attributes!(params)
|
74
74
|
else
|
75
|
-
# push a new checkpoint to the recent_intent_summary_view
|
76
75
|
checkpoints.unshift(
|
77
|
-
Type::Checkpoint.
|
76
|
+
Type::Checkpoint.build(params)
|
78
77
|
)
|
79
78
|
end
|
80
79
|
end
|
@@ -91,6 +90,58 @@ module Aws
|
|
91
90
|
lex.session_state.session_attributes.checkpoints
|
92
91
|
end
|
93
92
|
|
93
|
+
def restore_from!(checkpoint)
|
94
|
+
# we're done with the stored checkpoint once it's been restored
|
95
|
+
checkpoints.delete_if { |c| c.label == checkpoint.label }
|
96
|
+
# remove any memoized intent data
|
97
|
+
lex.current_intent = nil
|
98
|
+
# replace the intent with data from the checkpoint
|
99
|
+
lex.session_state.intent = checkpoint.intent
|
100
|
+
dialog_action = Type::DialogAction.new(
|
101
|
+
type: checkpoint.dialog_action_type,
|
102
|
+
slot_to_elicit: checkpoint.slot_to_elicit
|
103
|
+
)
|
104
|
+
lex.session_state.dialog_action = dialog_action
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def active_context?(name:)
|
109
|
+
!active_context(name: name).nil?
|
110
|
+
end
|
111
|
+
|
112
|
+
def active_context(name:)
|
113
|
+
lex.session_state.active_contexts.find { |c| c.name == name }
|
114
|
+
end
|
115
|
+
|
116
|
+
def active_context!(name:, turns: 10, seconds: 300, attributes: {})
|
117
|
+
# look for an existing active context if present
|
118
|
+
instance = active_context(name: name)
|
119
|
+
|
120
|
+
if instance
|
121
|
+
clear_context!(name: name)
|
122
|
+
else
|
123
|
+
instance = Type::Context.new
|
124
|
+
end
|
125
|
+
|
126
|
+
# update attributes as requested
|
127
|
+
instance.name = name
|
128
|
+
instance.context_attributes = attributes
|
129
|
+
instance.time_to_live = Type::TimeToLive.new(
|
130
|
+
turns_to_live: turns,
|
131
|
+
time_to_live_in_seconds: seconds
|
132
|
+
)
|
133
|
+
lex.session_state.active_contexts << instance
|
134
|
+
instance
|
135
|
+
end
|
136
|
+
|
137
|
+
def clear_context!(name:)
|
138
|
+
lex.session_state.active_contexts.delete_if { |c| c.name == name }
|
139
|
+
end
|
140
|
+
|
141
|
+
def clear_all_contexts!
|
142
|
+
lex.session_state.active_contexts = []
|
143
|
+
end
|
144
|
+
|
94
145
|
def stash
|
95
146
|
@stash ||= {}
|
96
147
|
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:
|
4
|
+
version: 6.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesse Doyle
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: exe
|
14
14
|
cert_chain: []
|
15
|
-
date: 2021-09-
|
15
|
+
date: 2021-09-28 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: shrink_wrap
|
@@ -67,8 +67,11 @@ files:
|
|
67
67
|
- lib/aws/lex/conversation/response/delegate.rb
|
68
68
|
- lib/aws/lex/conversation/response/elicit_intent.rb
|
69
69
|
- lib/aws/lex/conversation/response/elicit_slot.rb
|
70
|
+
- lib/aws/lex/conversation/simulator.rb
|
70
71
|
- lib/aws/lex/conversation/slot/elicitation.rb
|
71
72
|
- lib/aws/lex/conversation/slot/elicitor.rb
|
73
|
+
- lib/aws/lex/conversation/spec.rb
|
74
|
+
- lib/aws/lex/conversation/spec/matchers.rb
|
72
75
|
- lib/aws/lex/conversation/support/inflector.rb
|
73
76
|
- lib/aws/lex/conversation/support/mixins/responses.rb
|
74
77
|
- lib/aws/lex/conversation/support/mixins/slot_elicitation.rb
|