aws-lex-conversation 0.5.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 196357c0326b8a475650f1be193292f99818ca79b98a9957234f01b5e228caf9
4
- data.tar.gz: 4acba1d9e6647752ecb57d6fed79746214fbf5dbaba2f495085a77ebb0aa2cde
3
+ metadata.gz: bb6d1313f30041f9896bccc30fe2511d7eb0f377f505dc3c3c8e153e2838151d
4
+ data.tar.gz: aa0804ca414e0c68d982ae8ba601a7f46a729678130babb56d7eb87edf119cd3
5
5
  SHA512:
6
- metadata.gz: b66d860466c3514f7e4122dccc9545eaf98b3d0fd8a6535873f323c73366071197c6b545f67eef71d25f86ea1d4ddd02a334660087a7ec3c84e85dce00f9f82d
7
- data.tar.gz: d02d3473ef14600f09d78a94bf839c80dce492943956e1afe565261eca22b33dac50c1d6699687e57d97c69e95955a81a415cc122d122f05aab9393e662bd3a1
6
+ metadata.gz: 3471ce6eddb122e71cb81aad093e663165bae469b8317c045eddb4f3ba21412ed3815e3f9e895aea121a6af61ae143abb15c0cff1520c10243bfe51a807b09b8
7
+ data.tar.gz: 906124b34ad1218de0c2725942620fc530120b855cf8e12552c8fc9b841fa18894a7327d92e908ff02e8e3bf276da7d1c4a951efcb87bb2c8f4dd19ecf057bc0
data/README.md CHANGED
@@ -58,13 +58,13 @@ The first handler that returns `true` for the `will_respond?` method will provid
58
58
  ```ruby
59
59
  class SayHello < Aws::Lex::Conversation::Handler::Base
60
60
  def will_respond?(conversation)
61
- conversation.lex.incovation_source.dialog_code_hook? && # callback is for DialogCodeHook (i.e. validation)
61
+ conversation.lex.invocation_source.dialog_code_hook? && # callback is for DialogCodeHook (i.e. validation)
62
62
  conversation.lex.current_intent.name == 'SayHello' && # Lex has routed to the 'SayHello' intent
63
- conversation.slots[:name] # our expected slot value is set
63
+ conversation.slots[:name].filled? # our expected slot value is set
64
64
  end
65
65
 
66
66
  def response(conversation)
67
- name = conversation.slots[:name]
67
+ name = conversation.slots[:name].value
68
68
 
69
69
  # NOTE: you can use the Type::* classes if you wish. The final output
70
70
  # will be normalized to a value that complies with the Lex response format.
@@ -5,7 +5,7 @@ require_relative 'conversation/base'
5
5
  module Aws
6
6
  module Lex
7
7
  class Conversation
8
- include Support::Responses
8
+ include Support::Mixins::Responses
9
9
 
10
10
  attr_accessor :event, :context, :lex
11
11
 
@@ -10,8 +10,11 @@ require_relative 'response/confirm_intent'
10
10
  require_relative 'response/delegate'
11
11
  require_relative 'response/elicit_intent'
12
12
  require_relative 'response/elicit_slot'
13
- require_relative 'support/responses'
13
+ require_relative 'support/mixins/responses'
14
+ require_relative 'support/mixins/slot_elicitation'
14
15
  require_relative 'support/inflector'
16
+ require_relative 'slot/elicitation'
17
+ require_relative 'slot/elicitor'
15
18
  require_relative 'type/base'
16
19
  require_relative 'type/enumeration'
17
20
  require_relative 'type/sentiment_label'
@@ -22,6 +25,7 @@ require_relative 'type/dialog_action_type'
22
25
  require_relative 'type/confirmation_status'
23
26
  require_relative 'type/fulfillment_state'
24
27
  require_relative 'type/recent_intent_summary_view'
28
+ require_relative 'type/slot'
25
29
  require_relative 'type/slot_resolution'
26
30
  require_relative 'type/slot_detail'
27
31
  require_relative 'type/current_intent'
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Slot
7
+ class Elicitation
8
+ attr_accessor :name, :elicit, :message, :follow_up_message,
9
+ :content_type, :fallback, :maximum_elicitations,
10
+ :conversation
11
+
12
+ def initialize(opts = {})
13
+ self.name = opts.fetch(:name)
14
+ self.elicit = opts.fetch(:elicit) { ->(_c) { true } }
15
+ self.message = opts.fetch(:message)
16
+ self.follow_up_message = opts.fetch(:follow_up_message) { opts.fetch(:message) }
17
+ self.content_type = opts.fetch(:content_type) do
18
+ Aws::Lex::Conversation::Type::Message::ContentType.new('PlainText')
19
+ end
20
+ self.fallback = opts.fetch(:fallback) { ->(_c) {} }
21
+ self.maximum_elicitations = opts.fetch(:maximum_elicitations) { 0 }
22
+ end
23
+
24
+ def elicit!
25
+ return fallback.call(conversation) if maximum_elicitations?
26
+
27
+ increment_slot_elicitations!
28
+ conversation.elicit_slot(
29
+ slot_to_elicit: name,
30
+ message: {
31
+ contentType: content_type,
32
+ content: elicitation_content
33
+ }
34
+ )
35
+ end
36
+
37
+ def elicit?
38
+ elicit.call(conversation) && !slot.filled?
39
+ end
40
+
41
+ private
42
+
43
+ def slot
44
+ conversation.slots[name.to_sym]
45
+ end
46
+
47
+ def elicitation_content
48
+ first_elicitation? ? compose_message(message) : compose_message(follow_up_message)
49
+ end
50
+
51
+ def compose_message(msg)
52
+ msg.is_a?(Proc) ? msg.call(conversation) : msg
53
+ end
54
+
55
+ def increment_slot_elicitations!
56
+ conversation.session[session_key] = elicitation_attempts + 1
57
+ end
58
+
59
+ def maximum_elicitations?
60
+ return false if maximum_elicitations.zero?
61
+
62
+ elicitation_attempts > maximum_elicitations
63
+ end
64
+
65
+ def first_elicitation?
66
+ elicitation_attempts == 1
67
+ end
68
+
69
+ def session_key
70
+ :"SlotElicitations_#{name}"
71
+ end
72
+
73
+ def elicitation_attempts
74
+ conversation.session[session_key].to_i
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Slot
7
+ class Elicitor
8
+ attr_accessor :conversation, :elicitations
9
+
10
+ def initialize(opts = {})
11
+ self.conversation = opts.fetch(:conversation)
12
+ self.elicitations = opts.fetch(:elicitations) { [] }
13
+ elicitations.each do |elicitation|
14
+ elicitation.conversation = conversation
15
+ end
16
+ end
17
+
18
+ def elicit?
19
+ incomplete_elicitations.any?
20
+ end
21
+
22
+ def elicit!
23
+ incomplete_elicitations.first.elicit! if elicit?
24
+ end
25
+
26
+ private
27
+
28
+ def incomplete_elicitations
29
+ elicitations.select(&:elicit?)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Support
7
+ module Mixins
8
+ module Responses
9
+ def close(opts = {})
10
+ params = {
11
+ session_attributes: lex.session_attributes,
12
+ recent_intent_summary_view: lex.recent_intent_summary_view
13
+ }.merge(opts)
14
+ Response::Close.new(params).to_lex
15
+ end
16
+
17
+ def confirm_intent(opts = {})
18
+ params = {
19
+ session_attributes: lex.session_attributes,
20
+ recent_intent_summary_view: lex.recent_intent_summary_view,
21
+ intent_name: lex.current_intent.name,
22
+ slots: lex.current_intent.slots
23
+ }.merge(opts)
24
+ Response::ConfirmIntent.new(params).to_lex
25
+ end
26
+
27
+ def delegate(opts = {})
28
+ params = {
29
+ session_attributes: lex.session_attributes,
30
+ recent_intent_summary_view: lex.recent_intent_summary_view,
31
+ slots: lex.current_intent.slots
32
+ }.merge(opts)
33
+ Response::Delegate.new(params).to_lex
34
+ end
35
+
36
+ def elicit_intent(opts = {})
37
+ params = {
38
+ session_attributes: lex.session_attributes,
39
+ recent_intent_summary_view: lex.recent_intent_summary_view
40
+ }.merge(opts)
41
+ Response::ElicitIntent.new(params).to_lex
42
+ end
43
+
44
+ def elicit_slot(opts = {})
45
+ params = {
46
+ session_attributes: lex.session_attributes,
47
+ recent_intent_summary_view: lex.recent_intent_summary_view,
48
+ slots: lex.current_intent.slots,
49
+ intent_name: lex.current_intent.name
50
+ }.merge(opts)
51
+ Response::ElicitSlot.new(params).to_lex
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Support
7
+ module Mixins
8
+ module SlotElicitation
9
+ def self.included(base)
10
+ base.include(InstanceMethods)
11
+ base.extend(ClassMethods)
12
+ base.attr_accessor(:conversation)
13
+ base.class_attribute(:slots_to_elicit)
14
+ end
15
+
16
+ module InstanceMethods
17
+ def elicit_slots!
18
+ slot_elicitor.elicit!
19
+ end
20
+
21
+ def slots_elicitable?
22
+ slot_elicitor.elicit?
23
+ end
24
+
25
+ private
26
+
27
+ def slot_elicitor
28
+ @slot_elicitor ||= Aws::Lex::Conversation::Slot::Elicitor.new(
29
+ conversation: conversation,
30
+ elicitations: self.class.slots_to_elicit
31
+ )
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ def slot(opts = {})
37
+ self.slots_to_elicit ||= []
38
+ self.slots_to_elicit.push(
39
+ Aws::Lex::Conversation::Slot::Elicitation.new(opts)
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -19,7 +19,8 @@ module Aws
19
19
 
20
20
  module InstanceMethods
21
21
  def assign_attributes!(opts = {})
22
- self.class.attributes.each do |attribute|
22
+ attributes = self.class.attributes | self.class.virtual_attributes
23
+ attributes.each do |attribute|
23
24
  instance_variable_set("@#{attribute}", opts[attribute])
24
25
  end
25
26
  end
@@ -60,6 +61,17 @@ module Aws
60
61
  ->(v) { v.transform_keys(&:to_sym) }
61
62
  end
62
63
 
64
+ def computed_property(attribute, callable)
65
+ mapping[attribute] = attribute
66
+ attr_writer(attribute)
67
+
68
+ # dynamically memoize the result
69
+ define_method(attribute) do
70
+ instance_variable_get("@#{attribute}") ||
71
+ instance_variable_set("@#{attribute}", callable.call(self))
72
+ end
73
+ end
74
+
63
75
  def required(attribute, opts = {})
64
76
  property(attribute, opts.merge(allow_nil: false))
65
77
  end
@@ -76,7 +88,12 @@ module Aws
76
88
 
77
89
  attr_accessor(attribute)
78
90
 
79
- mapping[attribute] = from
91
+ if opts.fetch(:virtual) { false }
92
+ virtual_attributes << attribute
93
+ else
94
+ mapping[attribute] = from
95
+ end
96
+
80
97
  translate(attribute => params)
81
98
  end
82
99
 
@@ -84,6 +101,10 @@ module Aws
84
101
  @attributes ||= mapping.keys
85
102
  end
86
103
 
104
+ def virtual_attributes
105
+ @virtual_attributes ||= []
106
+ end
107
+
87
108
  def mapping
88
109
  @mapping ||= {}
89
110
  end
@@ -8,22 +8,35 @@ module Aws
8
8
  include Base
9
9
 
10
10
  required :name
11
- required :slots
11
+ required :raw_slots, from: :slots, virtual: true
12
12
  required :slot_details
13
13
  required :confirmation_status
14
14
 
15
+ computed_property :slots, ->(instance) do
16
+ instance.raw_slots.each_with_object({}) do |(key, value), hash|
17
+ hash[key.to_sym] = Slot.shrink_wrap(
18
+ name: key,
19
+ value: value,
20
+ # pass a reference to the parent down to the slot so that each slot
21
+ # instance can view a broader scope such as slot_details/resolutions
22
+ current_intent: instance
23
+ )
24
+ end
25
+ end
26
+
15
27
  class << self
16
28
  def slot_details!
17
- ->(v) do
18
- v.each_with_object({}) do |(key, value), hash|
19
- hash[key.to_sym] = SlotDetail.shrink_wrap(value)
20
- end
29
+ ->(val) do
30
+ val
31
+ .reject { |_, v| v.nil? }
32
+ .each_with_object({}) do |(key, value), hash|
33
+ hash[key.to_sym] = SlotDetail.shrink_wrap(value)
34
+ end
21
35
  end
22
36
  end
23
37
  end
24
38
 
25
39
  coerce(
26
- slots: symbolize_hash!,
27
40
  slot_details: slot_details!,
28
41
  confirmation_status: ConfirmationStatus
29
42
  )
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Type
7
+ class Slot
8
+ include Base
9
+
10
+ required :current_intent, from: :current_intent, virtual: true
11
+ required :name
12
+ required :value
13
+
14
+ def as_json(_opts = {})
15
+ to_lex
16
+ end
17
+
18
+ def to_lex
19
+ value
20
+ end
21
+
22
+ def filled?
23
+ value.to_s != ''
24
+ end
25
+
26
+ def resolve!(index: 0)
27
+ self.value = resolved(index: index)
28
+ end
29
+
30
+ def resolved(index: 0)
31
+ details.resolutions.fetch(index) { SlotResolution.new(value: value) }.value
32
+ end
33
+
34
+ def original_value
35
+ details.original_value
36
+ end
37
+
38
+ def resolvable?
39
+ details.resolutions.any?
40
+ end
41
+
42
+ def details
43
+ @details ||= current_intent.slot_details.fetch(name.to_sym) do
44
+ SlotDetail.new(name: name, resolutions: [], original_value: value)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -3,7 +3,7 @@
3
3
  module Aws
4
4
  module Lex
5
5
  class Conversation
6
- VERSION = '0.5.0'
6
+ VERSION = '1.2.0'
7
7
  end
8
8
  end
9
9
  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: 0.5.0
4
+ version: 1.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: 2020-06-23 00:00:00.000000000 Z
15
+ date: 2020-07-22 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: shrink_wrap
@@ -64,8 +64,11 @@ files:
64
64
  - lib/aws/lex/conversation/response/delegate.rb
65
65
  - lib/aws/lex/conversation/response/elicit_intent.rb
66
66
  - lib/aws/lex/conversation/response/elicit_slot.rb
67
+ - lib/aws/lex/conversation/slot/elicitation.rb
68
+ - lib/aws/lex/conversation/slot/elicitor.rb
67
69
  - lib/aws/lex/conversation/support/inflector.rb
68
- - lib/aws/lex/conversation/support/responses.rb
70
+ - lib/aws/lex/conversation/support/mixins/responses.rb
71
+ - lib/aws/lex/conversation/support/mixins/slot_elicitation.rb
69
72
  - lib/aws/lex/conversation/type/base.rb
70
73
  - lib/aws/lex/conversation/type/bot.rb
71
74
  - lib/aws/lex/conversation/type/confirmation_status.rb
@@ -87,6 +90,7 @@ files:
87
90
  - lib/aws/lex/conversation/type/sentiment_label.rb
88
91
  - lib/aws/lex/conversation/type/sentiment_response.rb
89
92
  - lib/aws/lex/conversation/type/sentiment_score.rb
93
+ - lib/aws/lex/conversation/type/slot.rb
90
94
  - lib/aws/lex/conversation/type/slot_detail.rb
91
95
  - lib/aws/lex/conversation/type/slot_resolution.rb
92
96
  - lib/aws/lex/conversation/version.rb
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Aws
4
- module Lex
5
- class Conversation
6
- module Support
7
- module Responses
8
- def close(opts = {})
9
- params = {
10
- session_attributes: lex.session_attributes,
11
- recent_intent_summary_view: lex.recent_intent_summary_view
12
- }.merge(opts)
13
- Response::Close.new(params).to_lex
14
- end
15
-
16
- def confirm_intent(opts = {})
17
- params = {
18
- session_attributes: lex.session_attributes,
19
- recent_intent_summary_view: lex.recent_intent_summary_view,
20
- intent_name: lex.current_intent.name,
21
- slots: lex.current_intent.slots
22
- }.merge(opts)
23
- Response::ConfirmIntent.new(params).to_lex
24
- end
25
-
26
- def delegate(opts = {})
27
- params = {
28
- session_attributes: lex.session_attributes,
29
- recent_intent_summary_view: lex.recent_intent_summary_view,
30
- slots: lex.current_intent.slots
31
- }.merge(opts)
32
- Response::Delegate.new(params).to_lex
33
- end
34
-
35
- def elicit_intent(opts = {})
36
- params = {
37
- session_attributes: lex.session_attributes,
38
- recent_intent_summary_view: lex.recent_intent_summary_view
39
- }.merge(opts)
40
- Response::ElicitIntent.new(params).to_lex
41
- end
42
-
43
- def elicit_slot(opts = {})
44
- params = {
45
- session_attributes: lex.session_attributes,
46
- recent_intent_summary_view: lex.recent_intent_summary_view,
47
- slots: lex.current_intent.slots,
48
- intent_name: lex.current_intent.name
49
- }.merge(opts)
50
- Response::ElicitSlot.new(params).to_lex
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end