kwargs) {
205 | messages.put(MSG_IMAGE_KEY, image);
206 | if(kwargs != null) {
207 | messages.putAll(kwargs);
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/VersionChecker.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk;
2 |
3 | import io.github.rbajek.rasa.sdk.util.StringUtils;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | /**
8 | * @author Rafał Bajek
9 | */
10 | class VersionChecker {
11 |
12 | private static final Logger LOGGER = LoggerFactory.getLogger(VersionChecker.class);
13 |
14 | public static final String SUPPORTED_VERSION = "1.4.0";
15 |
16 | /**
17 | * Check if the version of rasa and rasa_sdk are compatible.
18 | *
19 | * The version check relies on the version string being formatted as
20 | * 'x.y.z' and compares whether the numbers x and y are the same for both
21 | * rasa and rasa_sdk.
22 | *
23 | * Currently, only warning is logging
24 | *
25 | * @param rasaVersion A string containing the version of rasa that is making the call to the action server.
26 | */
27 | public static void checkVersionCompatibility(String rasaVersion) {
28 |
29 | // check for versions of Rasa that are too old to report their version number
30 | if(StringUtils.isNullOrEmpty(rasaVersion)) {
31 | LOGGER.warn("You are using an old version of rasa which might not be compatible with this version of rasa_sdk ({}).\n" +
32 | "To ensure compatibility use the same version for both, modulo the last number, i.e. using version A.B.x " +
33 | "the numbers A and B should be identical for both rasa and rasa_sdk.", SUPPORTED_VERSION);
34 | return;
35 | }
36 |
37 | String[] rasa = rasaVersion.split("\\.");
38 | String[] sdk = SUPPORTED_VERSION.split("\\.");
39 |
40 | if(rasa[0].equals(sdk[0]) == false || rasa[1].equals(sdk[1]) == false) {
41 | LOGGER.warn("Your versions of rasa and rasa_sdk might not be compatible. You are currently running rasa version {} " +
42 | "and rasa_sdk version {}.\nTo ensure compatibility use the same version for both, modulo the last number, " +
43 | "i.e. using version A.B.x the numbers A and B should be identical for both rasa and rasa_sdk.", rasaVersion, SUPPORTED_VERSION );
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/action/Action.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.action;
2 |
3 | import io.github.rbajek.rasa.sdk.CollectingDispatcher;
4 | import io.github.rbajek.rasa.sdk.dto.Domain;
5 | import io.github.rbajek.rasa.sdk.dto.Tracker;
6 | import io.github.rbajek.rasa.sdk.dto.event.AbstractEvent;
7 |
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | /**
12 | * Next action to be taken in response to a dialogue state.
13 | *
14 | * @author Rafał Bajek
15 | */
16 | public interface Action {
17 |
18 | /**
19 | * Unique identifier of this action.
20 | *
21 | * @return a name of this action
22 | */
23 | String name();
24 |
25 | /**
26 | * Execute the side effects of this action
27 | *
28 | * @param dispatcher the dispatcher which is used to send messages back to the user.
29 | * Use {@link CollectingDispatcher#utterMessage(String, Map)} or any other method.
30 | * @param tracker the state tracker for the current user. You can access slot values using tracker.getSlots().get(slotName)
(see: {@link Tracker}),
31 | * the most recent user message is tracker.getLatestMessage().getText()
(see {@link Tracker}) and any other property.
32 | * @param domain the bot's domain
33 | * @return A list of {@link AbstractEvent} instances that is returned through the endpoint
34 | */
35 | List run(CollectingDispatcher dispatcher, Tracker tracker, Domain domain);
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/action/form/AbstractFormAction.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.action.form;
2 |
3 | import io.github.rbajek.rasa.sdk.CollectingDispatcher;
4 | import io.github.rbajek.rasa.sdk.action.Action;
5 | import io.github.rbajek.rasa.sdk.action.form.slot.mapper.AbstractSlotMapping;
6 | import io.github.rbajek.rasa.sdk.action.form.slot.mapper.EntitySlotMapping;
7 | import io.github.rbajek.rasa.sdk.action.form.slot.mapper.IntentSlotMapping;
8 | import io.github.rbajek.rasa.sdk.action.form.slot.mapper.TriggerIntentSlotMapping;
9 | import io.github.rbajek.rasa.sdk.dto.Domain;
10 | import io.github.rbajek.rasa.sdk.dto.Tracker;
11 | import io.github.rbajek.rasa.sdk.dto.event.AbstractEvent;
12 | import io.github.rbajek.rasa.sdk.dto.event.Form;
13 | import io.github.rbajek.rasa.sdk.dto.event.SlotSet;
14 | import io.github.rbajek.rasa.sdk.exception.ActionExecutionRejectionException;
15 | import io.github.rbajek.rasa.sdk.exception.RasaException;
16 | import io.github.rbajek.rasa.sdk.util.CollectionsUtils;
17 | import io.github.rbajek.rasa.sdk.util.SerializationUtils;
18 | import org.slf4j.Logger;
19 | import org.slf4j.LoggerFactory;
20 |
21 | import java.util.*;
22 |
23 | /**
24 | * An abstract form action class
25 | *
26 | * @author Rafał Bajek
27 | */
28 | public abstract class AbstractFormAction implements Action {
29 |
30 | private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFormAction.class);
31 |
32 | /**
33 | * This slot is used to store information needed to do the form handling
34 | */
35 | protected static final String REQUESTED_SLOT = "requested_slot";
36 |
37 | /**
38 | * Unique identifier of the form
39 | */
40 | private final String formName;
41 |
42 | /**
43 | * Map of slot validators
44 | */
45 | private final Map slotValidatorMap = new HashMap<>();
46 |
47 | public AbstractFormAction(String formName) {
48 | this.formName = formName;
49 | registerSlotsValidators(slotValidatorMap);
50 | }
51 |
52 | /**
53 | * A list of required slots that the form has to fill.
54 | *
55 | * Use "tracker" to request different list of slots
56 | * depending on the state of the dialogue
57 | *
58 | * @param tracker a {@link Tracker} object
59 | * @return list of required slots
60 | */
61 | protected abstract List requiredSlots(Tracker tracker);
62 |
63 | /**
64 | * Define what the form has to do
65 | * after all required slots are filled
66 | *
67 | * @param dispatcher a {@link CollectingDispatcher} object
68 | * @return list of events
69 | */
70 | protected abstract List submit(CollectingDispatcher dispatcher);
71 |
72 | /**
73 | * Register a slot validator
74 | *
75 | * @param slotValidatorMap map of slot validators which should be filled out in a particular class
76 | */
77 | protected abstract void registerSlotsValidators(Map slotValidatorMap);
78 |
79 | /**
80 | * A Map to mapping required slots.
81 | *
82 | * Options:
83 | *
84 | * - an extracted entity
85 | * - intent: value pairs
86 | * - trigger_intent: value pairs
87 | * - a whole message
88 | *
89 | *
90 | * or a list of them, where the first match will be picked
91 | *
92 | * Empty map is converted to a mapping of
93 | * the slot to the extracted entity with the same name
94 | *
95 | * @return Map of slots with mappings
96 | */
97 | protected Map> slotMappings() {
98 | return Collections.emptyMap();
99 | }
100 |
101 | /**
102 | * Check whether user intent matches intent conditions
103 | *
104 | * @param requestedSlotMapping requested slot mapping
105 | * @param tracker a tracker object
106 | * @return true
- if the user intent matches intent conditions. Otherwise - false
107 | */
108 | private boolean intentIsDesired(AbstractSlotMapping requestedSlotMapping, Tracker tracker) {
109 | List mappingIntents = requestedSlotMapping.getIntent();
110 | List mappingNotIntents = requestedSlotMapping.getNotIntent();
111 | String intent = tracker.getLatestMessage().getIntent() != null ? tracker.getLatestMessage().getIntent().getName() : null;
112 |
113 | boolean intentNotBlacklisted = CollectionsUtils.isEmpty(mappingIntents) && mappingNotIntents.contains(intent) == false;
114 |
115 | return intentNotBlacklisted || mappingIntents.contains(intent);
116 | }
117 |
118 | /**
119 | * Logs the values of all required slots before submitting the form.
120 | *
121 | * @param tracker a {@link Tracker} object
122 | */
123 | private void logFormSlots(Tracker tracker) {
124 | List requiredSlots = requiredSlots(tracker);
125 | if(CollectionsUtils.isNotEmpty(requiredSlots) && tracker.hasSlots()) {
126 | StringBuilder slotValues = new StringBuilder();
127 | requiredSlots.forEach(slotName -> {
128 | slotValues.append("\n").append("\t").append(slotName).append(": ").append(tracker.getSlotValue(slotName));
129 | });
130 | LOGGER.debug("No slots left to request, all required slots are filled:{}", slotValues);
131 | }
132 | LOGGER.debug("There are no any slots to requests");
133 | }
134 |
135 | /**
136 | * Activate form if the form is called for the first time
137 | *
138 | * If activating, validate any required slots that were filled before
139 | * form activation and return "Form" event with the name of the form, as well
140 | *
141 | * @param dispatcher a {@link CollectingDispatcher} object
142 | * @param tracker a {@link Tracker} object
143 | * @param domain a {@link Domain} object
144 | * @return list of events
145 | */
146 | List activateFormIfRequired(CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
147 | List events = new ArrayList<>();
148 |
149 | if(tracker.hasActiveForm()){
150 | LOGGER.debug("The form '{}' is active", tracker.getActiveForm().getName());
151 | } else {
152 | LOGGER.debug("There is no active form");
153 | }
154 |
155 | if(tracker.hasActiveForm() && this.name().equals(tracker.getActiveForm().getName())) {
156 | return new ArrayList<>();
157 | } else {
158 | LOGGER.debug("Activated the form '{}'", this.name());
159 | events.add(new Form(this.name()));
160 |
161 | //collect values of required slots filled before activation
162 | Map preFilledSlots = new HashMap<>();
163 | List requiredSlots = requiredSlots(tracker);
164 | if(CollectionsUtils.isNotEmpty(requiredSlots)) {
165 | requiredSlots.forEach(slotName -> {
166 | if (tracker.hasSlots() && !shouldRequestSlot(tracker, slotName)) {
167 | preFilledSlots.put(slotName, tracker.getSlotValue(slotName));
168 | }
169 | });
170 | }
171 |
172 | if(!preFilledSlots.isEmpty()) {
173 | LOGGER.debug("Validating pre-filled required slots: {}", preFilledSlots);
174 | events.addAll(validateSlots(preFilledSlots, dispatcher, tracker, domain));
175 | } else {
176 | LOGGER.debug("No pre-filled required slots to validate.");
177 | }
178 | }
179 | return events;
180 | }
181 |
182 | /**
183 | * Return a list of events from "validate(...)" method if validation is required:
184 | * - the form is active
185 | * - the form is called after "action_listen"
186 | * - form validation was not cancelled
187 | *
188 | * @param dispatcher a {@link CollectingDispatcher} object
189 | * @param tracker a {@link Tracker} object
190 | * @param domain a {@link Domain} object
191 | * @return list of events
192 | */
193 | List validateIfRequired(CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
194 | if("action_listen".equals(tracker.getLatestActionName()) && (tracker.getActiveForm() == null || tracker.getActiveForm().shouldValidate(true))) {
195 | LOGGER.debug("Validating user input '{}'", tracker.getLatestMessage());
196 | return validate(dispatcher, tracker, domain);
197 | }
198 | LOGGER.debug("Skipping validation");
199 | return Collections.emptyList();
200 | }
201 |
202 | /**
203 | * Extract and validate value of requested slot.
204 | *
205 | * If nothing was extracted reject execution of the form action.
206 | * Subclass this method to add custom validation and rejection logic
207 | *
208 | * @param dispatcher a {@link CollectingDispatcher} object
209 | * @param tracker a {@link Tracker} object
210 | * @param domain a {@link Domain} object
211 | * @return list of events
212 | */
213 | List validate(CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
214 | // extract other slots that were not requested
215 | // but set by corresponding entity or trigger intent mapping
216 |
217 | Map slotValues = extractOtherSlots(dispatcher, tracker, domain);
218 |
219 | // extract requested slot
220 | if(tracker.hasSlotValue(REQUESTED_SLOT)) {
221 | Object slotToFill = tracker.getSlotValue(REQUESTED_SLOT);
222 | slotValues.putAll(extractRequestedSlot(dispatcher, tracker, domain));
223 |
224 | if(CollectionsUtils.isEmpty(slotValues)) {
225 | // reject to execute the form action
226 | // if some slot was requested but nothing was extracted
227 | // it will allow other policies to predict another action
228 | throw new ActionExecutionRejectionException("Failed to extract slot '" + slotToFill + "' with action '" + name() + "'");
229 | }
230 | }
231 |
232 | LOGGER.debug("Validating extracted slots: {}", slotValues);
233 | return validateSlots(slotValues, dispatcher, tracker, domain);
234 | }
235 |
236 | /**
237 | * Request the next slot and utter template if needed
238 | *
239 | * @param dispatcher a {@link CollectingDispatcher} object
240 | * @param tracker a {@link Tracker} object
241 | * @param domain a {@link Domain} object
242 | * @return an event
243 | */
244 | private AbstractEvent requestNextSlot(CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
245 | List requiredSlots = requiredSlots(tracker);
246 | if(requiredSlots != null) {
247 | for (String slotName : requiredSlots) {
248 | if(shouldRequestSlot(tracker, slotName)) {
249 | LOGGER.debug("Request next slot '{}'", slotName);
250 | dispatcher.utterTemplate("utter_ask_" + slotName, tracker.hasSlots() ? tracker.getSlots() : Collections.emptyMap());
251 | return new SlotSet(REQUESTED_SLOT, slotName);
252 | }
253 | };
254 | }
255 | // no more required slots to fill
256 | return null;
257 | }
258 |
259 | /**
260 | * Return "Form" event with null as name to deactivate the form and reset the requested slot
261 | *
262 | * @return list of events
263 | */
264 | protected List deactivate() {
265 | LOGGER.debug("Deactivating the form '{}'", name());
266 | return Arrays.asList(new Form(null), new SlotSet(REQUESTED_SLOT, null));
267 | }
268 |
269 | /**
270 | * Extract entities for given name
271 | *
272 | * @param entityName an entity name
273 | * @return an extracted value of the given entity name
274 | */
275 | private String getEntityValue(String entityName, Tracker tracker) {
276 | List entityValues = tracker.getLatestEntityValues(entityName);
277 | return CollectionsUtils.isNotEmpty(entityValues) ? entityValues.get(0) : null;
278 | }
279 |
280 | /**
281 | * Extract the values of the other slots if they are set by corresponding entities from the user input
282 | * else return None
283 | *
284 | * @param dispatcher a {@link CollectingDispatcher} object
285 | * @param tracker a {@link Tracker} object
286 | * @param domain a {@link Domain} object
287 | * @return map of the other slots with values
288 | */
289 | Map extractOtherSlots(CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
290 | Map slotValues = new HashMap<>();
291 |
292 | List requiredSlots = requiredSlots(tracker);
293 | if(requiredSlots != null && !requiredSlots.isEmpty()) {
294 | // look for other slots
295 | for (String slotName : requiredSlots) {
296 | if(!slotName.equals(tracker.getSlotValue(REQUESTED_SLOT))) {
297 | List otherSlotMappings = getMappingsForSlot(slotName);
298 | for (AbstractSlotMapping otherSlotMapping : otherSlotMappings) {
299 | // check whether the slot should be filled by entity with the same name
300 |
301 | boolean shouldFillEntitySlot = otherSlotMapping.isEntitySlotMappingType() &&
302 | ((EntitySlotMapping) otherSlotMapping).getEntity().equals(slotName) &&
303 | intentIsDesired(otherSlotMapping, tracker);
304 |
305 | // check whether the slot should be filled from trigger intent mapping
306 | boolean shouldFillTriggerSlot = (!tracker.hasActiveForm() || name().equals(tracker.getActiveForm().getName()) == false) &&
307 | otherSlotMapping.isTriggerIntentSlotMappingType() &&
308 | intentIsDesired(otherSlotMapping, tracker);
309 |
310 | Object value = null;
311 | if (shouldFillEntitySlot) {
312 | value = getEntityValue(slotName, tracker);
313 | } else if (shouldFillTriggerSlot) {
314 | value = ((TriggerIntentSlotMapping) otherSlotMapping).getValue();
315 | }
316 |
317 | if(value != null) {
318 | LOGGER.debug("Extracted '{}' for extra slot '{}'", value, slotName);
319 | slotValues.put(slotName, value);
320 | return slotValues;
321 | }
322 |
323 | }
324 | }
325 | }
326 | }
327 | return slotValues;
328 | }
329 |
330 | /**
331 | * Extract the value of requested slot from a user input
332 | *
333 | * @param dispatcher a {@link CollectingDispatcher} object
334 | * @param tracker a {@link Tracker} object
335 | * @param domain a {@link Domain} object
336 | * @return map of the requested slot with extracted value
337 | */
338 | protected Map extractRequestedSlot(CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
339 | if(tracker.hasSlotValue(REQUESTED_SLOT) == false) {
340 | return Collections.emptyMap();
341 | }
342 |
343 | String slotToFill = tracker.getSlotValue(REQUESTED_SLOT, String.class);
344 | LOGGER.debug("Trying to extract requested slot '{}' ...", slotToFill);
345 |
346 | //get mapping for requested slot
347 | List requestedSlotMappings = getMappingsForSlot(slotToFill);
348 | for (AbstractSlotMapping requestedSlotMapping : requestedSlotMappings) {
349 | LOGGER.debug("Got mapping '{}'", requestedSlotMapping);
350 | if(intentIsDesired(requestedSlotMapping, tracker)) {
351 | Object value = null;
352 | switch(requestedSlotMapping.getType()) {
353 | case ENTITY:
354 | value = getEntityValue(EntitySlotMapping.class.cast(requestedSlotMapping).getEntity(), tracker);
355 | break;
356 |
357 | case INTENT:
358 | value = IntentSlotMapping.class.cast(requestedSlotMapping).getValue();
359 | break;
360 |
361 | case TEXT:
362 | value = tracker.getLatestMessage().getText();
363 | break;
364 |
365 | default:
366 | throw new RasaException("Provided slot mapping type ('" + requestedSlotMapping.getType() + "') is not supported");
367 | }
368 |
369 | if(value != null) {
370 | LOGGER.debug("Successfully extracted '{}' for requested slot '{}'", value, slotToFill);
371 | Map resultMap = new HashMap<>();
372 | resultMap.put(slotToFill, value);
373 | return resultMap;
374 | }
375 | }
376 | }
377 | LOGGER.debug("Failed to extract requested slot '{}'", slotToFill);
378 | return Collections.emptyMap();
379 | }
380 |
381 | /**
382 | * Get mappings for requested slot.
383 | *
384 | * If None, map requested slot to an entity with the same name
385 | *
386 | * @param slotToFill a slot which should be filled
387 | * @return list of slot mappings
388 | */
389 | private List getMappingsForSlot(String slotToFill) {
390 | List requestedSlotMappings = slotMappings().getOrDefault(slotToFill, Arrays.asList(EntitySlotMapping.builder(slotToFill).build()));
391 |
392 | // check provided slot mappings
393 | requestedSlotMappings.forEach(requestedSlotMapping -> {
394 | if(requestedSlotMapping.getType() == null) {
395 | throw new RasaException("Provided incompatible slot mapping");
396 | }
397 | });
398 |
399 | return requestedSlotMappings;
400 | }
401 |
402 | /**
403 | * Validate slots using helper validation functions.
404 | *
405 | * Call particular validator for each slot, value pair to be validated.
406 | * If the particular validator is not implemented, set the slot to the value.
407 | *
408 | * @param slotsMap map of slots with values
409 | * @param dispatcher a {@link CollectingDispatcher} object
410 | * @param tracker a {@link Tracker} object
411 | * @param domain a {@link Domain} object
412 | * @return list of event
413 | */
414 | private List validateSlots(Map slotsMap, CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
415 | Map validationOutput = new HashMap<>();
416 | slotsMap.forEach((slotName, slotValue) -> {
417 | if(this.slotValidatorMap.containsKey(slotName)) {
418 | Map result = this.slotValidatorMap.get(slotName).validateAndConvert(slotValue, dispatcher, tracker, domain);
419 | validationOutput.putAll(result);
420 | }
421 | });
422 |
423 | slotsMap.putAll(validationOutput);
424 |
425 | // validation succeed, set slots to extracted values
426 | List events = new ArrayList<>();
427 | slotsMap.forEach((slotName, slotValue) -> {
428 | events.add(new SlotSet(slotName, slotValue));
429 | });
430 | return events;
431 | }
432 |
433 | /**
434 | * Check whether form action should request given slot
435 | *
436 | * @param tracker a {@link Tracker} object
437 | * @param slotName a slot name
438 | * @return true
- if the given slot should be requested. Otherwise - false
439 | */
440 | private boolean shouldRequestSlot(Tracker tracker, String slotName) {
441 | return tracker.hasSlotValue(slotName) == false;
442 | }
443 |
444 | @Override
445 | public String name() {
446 | return this.formName;
447 | }
448 |
449 | /**
450 | * Execute the side effects of this form.
451 | *
452 | * Steps:
453 | * - activate if needed
454 | * - validate user input if needed
455 | * - set validated slots
456 | * - utter_ask_{slot} template with the next required slot
457 | * - submit the form if all required slots are set
458 | * - deactivate the form
459 | * @return list of events
460 | */
461 | @Override
462 | public List run(CollectingDispatcher dispatcher, Tracker tracker, Domain domain) {
463 | // activate the form
464 | List events = activateFormIfRequired(dispatcher, tracker, domain);
465 |
466 | // validate user input
467 | events.addAll(validateIfRequired(dispatcher, tracker, domain));
468 |
469 | // check that the form wasn't deactivated in validation
470 | if(events.stream().filter(event -> event instanceof Form).noneMatch(event -> Form.class.cast(event).isNotActive())) {
471 | // create temp tracker with populated slots from `validate` method
472 |
473 | // deep clone using JSON serialization
474 | Tracker tempTracker = SerializationUtils.deepClone(tracker); //JsonParser.parse(JsonParser.jsonAsString(tracker), Tracker.class);
475 | events.forEach(event -> {
476 | if(SlotSet.class.isInstance(event)) {
477 | SlotSet slotSetEvent = SlotSet.class.cast(event);
478 | tempTracker.addSlot(slotSetEvent.getName(), slotSetEvent.getValue());
479 | }
480 | });
481 |
482 | AbstractEvent nextSlotEvent = requestNextSlot(dispatcher, tempTracker, domain);
483 | if(nextSlotEvent != null) {
484 | // request next slot
485 | events.add(nextSlotEvent);
486 | } else {
487 | // there is nothing more to request, so we can submit
488 | logFormSlots(tempTracker);
489 | LOGGER.debug("Submitting the form '{}'", name());
490 | List submitEvents = submit(dispatcher);
491 | if(CollectionsUtils.isNotEmpty(submitEvents)) {
492 | events.addAll(submitEvents);
493 | }
494 | // deactivate the form after submission
495 | events.addAll(deactivate());
496 | }
497 | }
498 | return events;
499 | }
500 |
501 | /**
502 | * A validator slot interface
503 | *
504 | * @author Rafał Bajek
505 | */
506 | public interface ValidateSlot {
507 |
508 | /**
509 | * Validate slot value and set a new value if required
510 | *
511 | * @param value current slot value
512 | * @param dispatcher dispatcher object
513 | * @param tracker tracker object
514 | * @param domain domain object
515 | * @return Map of slots value, where: key=slotName, value=slotValue
516 | */
517 | Map validateAndConvert(Object value, CollectingDispatcher dispatcher, Tracker tracker, Domain domain);
518 | }
519 | }
520 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/action/form/slot/mapper/AbstractSlotMapping.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.action.form.slot.mapper;
2 |
3 | import io.github.rbajek.rasa.sdk.util.CollectionsUtils;
4 | import io.github.rbajek.rasa.sdk.exception.RasaException;
5 | import lombok.Getter;
6 | import lombok.ToString;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | @Getter
13 | @ToString
14 | public abstract class AbstractSlotMapping {
15 |
16 | //=================================================
17 | // Class fields
18 | //=================================================
19 |
20 | protected final SlotMappingType type;
21 | protected List intent;
22 | protected List notIntent;
23 |
24 | //=================================================
25 | // Constructors
26 | //=================================================
27 |
28 | public AbstractSlotMapping(SlotMappingType type) {
29 | this.type = type;
30 | }
31 |
32 | //=================================================
33 | // Class methods
34 | //=================================================
35 |
36 | public boolean isEntitySlotMappingType() {
37 | return SlotMappingType.ENTITY == type;
38 | }
39 |
40 | public boolean isTriggerIntentSlotMappingType() {
41 | return SlotMappingType.TRIGGER_INTENT == type;
42 | }
43 |
44 | //=================================================
45 | // Builder
46 | //=================================================
47 |
48 | public abstract static class AbstractBuilder {
49 | protected final T instance;
50 |
51 | public AbstractBuilder(T instance) {
52 | this.instance = instance;
53 | }
54 |
55 | public B intent(String intent) {
56 | if(this.instance.intent == null) {
57 | this.instance.intent = new ArrayList<>();
58 | }
59 | this.instance.intent.add(intent);
60 | return (B) this;
61 | }
62 |
63 | public B intent(List intents) {
64 | if(this.instance.intent == null) {
65 | this.instance.intent = new ArrayList<>();
66 | }
67 | this.instance.intent.addAll(intents);
68 | return (B) this;
69 | }
70 |
71 | public B notIntent(String notIntent) {
72 | if(this.instance.notIntent == null) {
73 | this.instance.notIntent = new ArrayList<>();
74 | }
75 | this.instance.notIntent.add(notIntent);
76 | return (B) this;
77 | }
78 |
79 | public B notIntent(List notIntents) {
80 | if(this.instance.notIntent == null) {
81 | this.instance.notIntent = new ArrayList<>();
82 | }
83 | this.instance.notIntent.addAll(notIntents);
84 | return (B) this;
85 | }
86 |
87 | public T build() {
88 | if(CollectionsUtils.isNotEmpty(this.instance.intent) && CollectionsUtils.isNotEmpty(this.instance.notIntent)) {
89 | throw new RasaException("Providing both intent '" + this.instance.intent + "' and notIntent '" + this.instance.notIntent + "' is not supported");
90 | }
91 |
92 | if(this.instance.intent == null) {
93 | this.instance.intent = Collections.emptyList();
94 | }
95 | if(this.instance.notIntent == null) {
96 | this.instance.notIntent = Collections.emptyList();
97 | }
98 | return this.instance;
99 | }
100 | }
101 |
102 | //=================================================
103 | // Inner Types
104 | //=================================================
105 |
106 | @Getter
107 | public enum SlotMappingType {
108 | ENTITY ("from_entity"),
109 | INTENT ("from_intent"),
110 | TEXT ("from_text"),
111 | TRIGGER_INTENT ("from_trigger_intent");
112 |
113 | private final String value;
114 |
115 | SlotMappingType(String value) {
116 | this.value = value;
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/action/form/slot/mapper/EntitySlotMapping.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.action.form.slot.mapper;
2 |
3 | import lombok.Getter;
4 | import lombok.ToString;
5 |
6 | @Getter
7 | @ToString(callSuper = true)
8 | public class EntitySlotMapping extends AbstractSlotMapping {
9 |
10 | private final String entity;
11 |
12 | private EntitySlotMapping(String entity) {
13 | super(SlotMappingType.ENTITY);
14 | this.entity = entity;
15 | }
16 |
17 | public static Builder builder(String entity) {
18 | return new Builder(entity);
19 | }
20 |
21 | public static class Builder extends AbstractSlotMapping.AbstractBuilder {
22 |
23 | public Builder(String entity) {
24 | super(new EntitySlotMapping(entity));
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/action/form/slot/mapper/IntentSlotMapping.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.action.form.slot.mapper;
2 |
3 | import io.github.rbajek.rasa.sdk.exception.RasaException;
4 | import lombok.Getter;
5 | import lombok.ToString;
6 |
7 | @Getter
8 | @ToString(callSuper = true)
9 | public class IntentSlotMapping extends AbstractSlotMapping {
10 |
11 | protected T value;
12 |
13 | public IntentSlotMapping() {
14 | this(SlotMappingType.INTENT);
15 | }
16 |
17 | public IntentSlotMapping(SlotMappingType type) {
18 | super(type);
19 | if(SlotMappingType.INTENT != type && SlotMappingType.TRIGGER_INTENT != type) {
20 | throw new RasaException("Slot mapping type should be one of the: " + SlotMappingType.INTENT.getValue() + " or " + SlotMappingType.TRIGGER_INTENT + " but is: " + type);
21 | }
22 | }
23 |
24 | public static Builder builder() {
25 | return new Builder(new IntentSlotMapping());
26 | }
27 |
28 | public static class Builder extends AbstractSlotMapping.AbstractBuilder, Builder> {
29 |
30 | public Builder(IntentSlotMapping intentSlotMapping) {
31 | super(intentSlotMapping);
32 | }
33 |
34 | public Builder value(T value) {
35 | this.instance.value = value;
36 | return this;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/action/form/slot/mapper/TextSlotMapping.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.action.form.slot.mapper;
2 |
3 | import lombok.ToString;
4 |
5 | @ToString(callSuper = true)
6 | public class TextSlotMapping extends AbstractSlotMapping {
7 |
8 | public TextSlotMapping() {
9 | super(SlotMappingType.TEXT);
10 | }
11 |
12 | public static Builder builder() {
13 | return new Builder();
14 | }
15 |
16 | public static class Builder extends AbstractSlotMapping.AbstractBuilder {
17 |
18 | public Builder() {
19 | super(new TextSlotMapping());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/action/form/slot/mapper/TriggerIntentSlotMapping.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.action.form.slot.mapper;
2 |
3 | import lombok.ToString;
4 |
5 | @ToString(callSuper = true)
6 | public class TriggerIntentSlotMapping extends IntentSlotMapping {
7 |
8 | public TriggerIntentSlotMapping() {
9 | super(SlotMappingType.TRIGGER_INTENT);
10 | }
11 |
12 | public static Builder builder() {
13 | return new Builder(new TriggerIntentSlotMapping());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/dto/ActionRequest.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.dto;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | @Getter @Setter @ToString
9 | public class ActionRequest {
10 |
11 | @JsonProperty("next_action")
12 | private String nextAction;
13 |
14 | @JsonProperty("sender_id")
15 | private String senderId;
16 |
17 | private Tracker tracker;
18 |
19 | private Domain domain;
20 |
21 | private String version;
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/io/github/rbajek/rasa/sdk/dto/ActionResponse.java:
--------------------------------------------------------------------------------
1 | package io.github.rbajek.rasa.sdk.dto;
2 |
3 | import io.github.rbajek.rasa.sdk.dto.event.AbstractEvent;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | @Getter @Setter @ToString
12 | public class ActionResponse {
13 |
14 | private List events;
15 | private List