16
16
__author__ = 'seanfitz'
17
17
18
18
import itertools
19
-
20
- CLIENT_ENTITY_NAME = 'Client'
19
+ from ovos_workshop .intents import Intent , IntentBuilder
21
20
22
21
23
22
def is_entity (tag , entity_name ):
@@ -44,15 +43,7 @@ def find_first_tag(tags, entity_type, after_index=-1):
44
43
confidence(float): is a measure of accuracy. 1 is full confidence
45
44
and 0 is none.
46
45
"""
47
- for tag in tags :
48
- for entity in tag .get ('entities' ):
49
- for v , t in entity .get ('data' ):
50
- if t .lower () == entity_type .lower () and \
51
- (tag .get ('start_token' , 0 ) > after_index or \
52
- tag .get ('from_context' , False )):
53
- return tag , v , entity .get ('confidence' )
54
-
55
- return None , None , None
46
+ return Intent ._find_first_tag (tags , entity_type , after_index )
56
47
57
48
58
49
def find_next_tag (tags , end_index = 0 ):
@@ -93,237 +84,5 @@ def resolve_one_of(tags, at_least_one):
93
84
object:
94
85
returns None if no match is found but returns any match as an object
95
86
"""
87
+ return Intent ._resolve_one_of (tags , at_least_one )
96
88
97
- for possible_resolution in choose_1_from_each (at_least_one ):
98
- resolution = {}
99
- pr = possible_resolution [:]
100
- for entity_type in pr :
101
- last_end_index = - 1
102
- if entity_type in resolution :
103
- last_end_index = resolution [entity_type ][- 1 ].get ('end_token' )
104
- tag , value , c = find_first_tag (tags , entity_type ,
105
- after_index = last_end_index )
106
- if not tag :
107
- break
108
- else :
109
- if entity_type not in resolution :
110
- resolution [entity_type ] = []
111
- resolution [entity_type ].append (tag )
112
- # Check if this is a valid resolution (all one_of rules matched)
113
- if len (resolution ) == len (possible_resolution ):
114
- return resolution
115
-
116
- return None
117
-
118
-
119
- class Intent (object ):
120
- def __init__ (self , name , requires , at_least_one , optional , excludes = None ):
121
- """Create Intent object
122
-
123
- Args:
124
- name(str): Name for Intent
125
- requires(list): Entities that are required
126
- at_least_one(list): One of these Entities are required
127
- optional(list): Optional Entities used by the intent
128
- """
129
- self .name = name
130
- self .requires = requires
131
- self .at_least_one = at_least_one
132
- self .optional = optional
133
- self .excludes = excludes or []
134
-
135
- def validate (self , tags , confidence ):
136
- """Using this method removes tags from the result of validate_with_tags
137
-
138
- Returns:
139
- intent(intent): Results from validate_with_tags
140
- """
141
- intent , tags = self .validate_with_tags (tags , confidence )
142
- return intent
143
-
144
- def validate_with_tags (self , tags , confidence ):
145
- """Validate whether tags has required entites for this intent to fire
146
-
147
- Args:
148
- tags(list): Tags and Entities used for validation
149
- confidence(float): The weight associate to the parse result,
150
- as indicated by the parser. This is influenced by a parser
151
- that uses edit distance or context.
152
-
153
- Returns:
154
- intent, tags: Returns intent and tags used by the intent on
155
- failure to meat required entities then returns intent with
156
- confidence
157
- of 0.0 and an empty list for tags.
158
- """
159
- result = {'intent_type' : self .name }
160
- intent_confidence = 0.0
161
- local_tags = tags [:]
162
- used_tags = []
163
-
164
- # Check excludes first
165
- for exclude_type in self .excludes :
166
- exclude_tag , _canonical_form , _tag_confidence = \
167
- find_first_tag (local_tags , exclude_type )
168
- if exclude_tag :
169
- result ['confidence' ] = 0.0
170
- return result , []
171
-
172
- for require_type , attribute_name in self .requires :
173
- required_tag , canonical_form , tag_confidence = \
174
- find_first_tag (local_tags , require_type )
175
- if not required_tag :
176
- result ['confidence' ] = 0.0
177
- return result , []
178
-
179
- result [attribute_name ] = canonical_form
180
- if required_tag in local_tags :
181
- local_tags .remove (required_tag )
182
- used_tags .append (required_tag )
183
- intent_confidence += tag_confidence
184
-
185
- if len (self .at_least_one ) > 0 :
186
- best_resolution = resolve_one_of (local_tags , self .at_least_one )
187
- if not best_resolution :
188
- result ['confidence' ] = 0.0
189
- return result , []
190
- else :
191
- for key in best_resolution :
192
- # TODO: at least one should support aliases
193
- result [key ] = best_resolution [key ][0 ].get ('key' )
194
- intent_confidence += \
195
- 1.0 * best_resolution [key ][0 ]['entities' ][0 ]\
196
- .get ('confidence' , 1.0 )
197
- used_tags .append (best_resolution [key ][0 ])
198
- if best_resolution in local_tags :
199
- local_tags .remove (best_resolution [key ][0 ])
200
-
201
- for optional_type , attribute_name in self .optional :
202
- optional_tag , canonical_form , tag_confidence = \
203
- find_first_tag (local_tags , optional_type )
204
- if not optional_tag or attribute_name in result :
205
- continue
206
- result [attribute_name ] = canonical_form
207
- if optional_tag in local_tags :
208
- local_tags .remove (optional_tag )
209
- used_tags .append (optional_tag )
210
- intent_confidence += tag_confidence
211
-
212
- total_confidence = (intent_confidence / len (tags ) * confidence ) \
213
- if tags else 0.0
214
-
215
- target_client , canonical_form , confidence = \
216
- find_first_tag (local_tags , CLIENT_ENTITY_NAME )
217
-
218
- result ['target' ] = target_client .get ('key' ) if target_client else None
219
- result ['confidence' ] = total_confidence
220
-
221
- return result , used_tags
222
-
223
-
224
- class IntentBuilder (object ):
225
- """
226
- IntentBuilder, used to construct intent parsers.
227
-
228
- Attributes:
229
- at_least_one(list): A list of Entities where one is required.
230
- These are separated into lists so you can have one of (A or B) and
231
- then require one of (D or F).
232
- requires(list): A list of Required Entities
233
- optional(list): A list of optional Entities
234
- name(str): Name of intent
235
-
236
- Notes:
237
- This is designed to allow construction of intents in one line.
238
-
239
- Example:
240
- IntentBuilder("Intent")\
241
- .requires("A")\
242
- .one_of("C","D")\
243
- .optional("G").build()
244
- """
245
- def __init__ (self , intent_name ):
246
- """
247
- Constructor
248
-
249
- Args:
250
- intent_name(str): the name of the intents that this parser
251
- parses/validates
252
- """
253
- self .at_least_one = []
254
- self .requires = []
255
- self .excludes = []
256
- self .optional = []
257
- self .name = intent_name
258
-
259
- def one_of (self , * args ):
260
- """
261
- The intent parser should require one of the provided entity types to
262
- validate this clause.
263
-
264
- Args:
265
- args(args): *args notation list of entity names
266
-
267
- Returns:
268
- self: to continue modifications.
269
- """
270
- self .at_least_one .append (args )
271
- return self
272
-
273
- def require (self , entity_type , attribute_name = None ):
274
- """
275
- The intent parser should require an entity of the provided type.
276
-
277
- Args:
278
- entity_type(str): an entity type
279
- attribute_name(str): the name of the attribute on the parsed intent.
280
- Defaults to match entity_type.
281
-
282
- Returns:
283
- self: to continue modifications.
284
- """
285
- if not attribute_name :
286
- attribute_name = entity_type
287
- self .requires += [(entity_type , attribute_name )]
288
- return self
289
-
290
- def exclude (self , entity_type ):
291
- """
292
- The intent parser must not contain an entity of the provided type.
293
-
294
- Args:
295
- entity_type(str): an entity type
296
-
297
- Returns:
298
- self: to continue modifications.
299
- """
300
- self .excludes .append (entity_type )
301
- return self
302
-
303
- def optionally (self , entity_type , attribute_name = None ):
304
- """
305
- Parsed intents from this parser can optionally include an entity of the
306
- provided type.
307
-
308
- Args:
309
- entity_type(str): an entity type
310
- attribute_name(str): the name of the attribute on the parsed intent.
311
- Defaults to match entity_type.
312
-
313
- Returns:
314
- self: to continue modifications.
315
- """
316
- if not attribute_name :
317
- attribute_name = entity_type
318
- self .optional += [(entity_type , attribute_name )]
319
- return self
320
-
321
- def build (self ):
322
- """
323
- Constructs an intent from the builder's specifications.
324
-
325
- :return: an Intent instance.
326
- """
327
- return Intent (self .name , self .requires ,
328
- self .at_least_one , self .optional ,
329
- self .excludes )
0 commit comments