forked from nyaruka/goflow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
call_classifier.go
133 lines (110 loc) · 4.39 KB
/
call_classifier.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package actions
import (
"github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/goflow/assets"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/flows/events"
)
func init() {
registerType(TypeCallClassifier, func() flows.Action { return &CallClassifierAction{} })
}
var classificationCategories = []string{CategorySuccess, CategorySkipped, CategoryFailure}
// TypeCallClassifier is the type for the call classifier action
const TypeCallClassifier string = "call_classifier"
// CallClassifierAction can be used to classify the intent and entities from a given input using an NLU classifier. It always
// saves a result indicating whether the classification was successful, skipped or failed, and what the extracted intents
// and entities were.
//
// {
// "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9",
// "type": "call_classifier",
// "classifier": {
// "uuid": "1c06c884-39dd-4ce4-ad9f-9a01cbe6c000",
// "name": "Booking"
// },
// "input": "@input.text",
// "result_name": "Intent"
// }
//
// @action call_classifier
type CallClassifierAction struct {
baseAction
onlineAction
Classifier *assets.ClassifierReference `json:"classifier" validate:"required"`
Input string `json:"input" validate:"required" engine:"evaluated"`
ResultName string `json:"result_name" validate:"required"`
}
// NewCallClassifier creates a new call classifier action
func NewCallClassifier(uuid flows.ActionUUID, classifier *assets.ClassifierReference, input string, resultName string) *CallClassifierAction {
return &CallClassifierAction{
baseAction: newBaseAction(TypeCallClassifier, uuid),
Classifier: classifier,
Input: input,
ResultName: resultName,
}
}
// Execute runs this action
func (a *CallClassifierAction) Execute(run flows.Run, step flows.Step, logModifier flows.ModifierCallback, logEvent flows.EventCallback) error {
classifiers := run.Session().Assets().Classifiers()
classifier := classifiers.Get(a.Classifier.UUID)
// substitute any variables in our input
input, err := run.EvaluateTemplate(a.Input)
if err != nil {
logEvent(events.NewError(err))
}
classification, skipped := a.classify(run, step, input, classifier, logEvent)
if classification != nil {
a.saveSuccess(run, step, input, classification, logEvent)
} else if skipped {
a.saveSkipped(run, step, input, logEvent)
} else {
a.saveFailure(run, step, input, logEvent)
}
return nil
}
func (a *CallClassifierAction) classify(run flows.Run, step flows.Step, input string, classifier *flows.Classifier, logEvent flows.EventCallback) (*flows.Classification, bool) {
if input == "" {
logEvent(events.NewErrorf("can't classify empty input, skipping classification"))
return nil, true
}
if classifier == nil {
logEvent(events.NewDependencyError(a.Classifier))
return nil, false
}
svc, err := run.Session().Engine().Services().Classification(classifier)
if err != nil {
logEvent(events.NewError(err))
return nil, false
}
httpLogger := &flows.HTTPLogger{}
classification, err := svc.Classify(run.Environment(), input, httpLogger.Log)
if len(httpLogger.Logs) > 0 {
logEvent(events.NewClassifierCalled(classifier.Reference(), httpLogger.Logs))
}
if err != nil {
logEvent(events.NewError(err))
return nil, false
}
return classification, false
}
func (a *CallClassifierAction) saveSuccess(run flows.Run, step flows.Step, input string, classification *flows.Classification, logEvent flows.EventCallback) {
// result value is name of top ranked intent if there is one
value := ""
if len(classification.Intents) > 0 {
value = classification.Intents[0].Name
}
extra, _ := jsonx.Marshal(classification)
a.saveResult(run, step, a.ResultName, value, CategorySuccess, "", input, extra, logEvent)
}
func (a *CallClassifierAction) saveSkipped(run flows.Run, step flows.Step, input string, logEvent flows.EventCallback) {
a.saveResult(run, step, a.ResultName, "0", CategorySkipped, "", input, nil, logEvent)
}
func (a *CallClassifierAction) saveFailure(run flows.Run, step flows.Step, input string, logEvent flows.EventCallback) {
a.saveResult(run, step, a.ResultName, "0", CategoryFailure, "", input, nil, logEvent)
}
// Results enumerates any results generated by this flow object
func (a *CallClassifierAction) Results(include func(*flows.ResultInfo)) {
if a.ResultName != "" {
include(flows.NewResultInfo(a.ResultName, classificationCategories))
}
}