snips-skill 0.1.29__py3-none-any.whl
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.
- snips_skill/__init__.py +59 -0
- snips_skill/__main__.py +28 -0
- snips_skill/dialogue.py +60 -0
- snips_skill/exceptions.py +34 -0
- snips_skill/expr.py +172 -0
- snips_skill/i18n.py +83 -0
- snips_skill/intent.py +136 -0
- snips_skill/locale/de/LC_MESSAGES/snips_skill.mo +0 -0
- snips_skill/locale/de/LC_MESSAGES/snips_skill.po +210 -0
- snips_skill/locale/snips_skill.pot +208 -0
- snips_skill/log.py +57 -0
- snips_skill/mqtt.py +286 -0
- snips_skill/multi_room.py +112 -0
- snips_skill/parsetab.py +44 -0
- snips_skill/recorder.py +202 -0
- snips_skill/skill.py +154 -0
- snips_skill/snips.py +311 -0
- snips_skill/state.py +149 -0
- snips_skill/tasks.py +210 -0
- snips_skill/test_expr.py +158 -0
- snips_skill/test_schema.py +30 -0
- snips_skill/test_tasks.py +86 -0
- snips_skill-0.1.29.dist-info/METADATA +166 -0
- snips_skill-0.1.29.dist-info/RECORD +28 -0
- snips_skill-0.1.29.dist-info/WHEEL +5 -0
- snips_skill-0.1.29.dist-info/entry_points.txt +4 -0
- snips_skill-0.1.29.dist-info/licenses/LICENSE.txt +6 -0
- snips_skill-0.1.29.dist-info/top_level.txt +1 -0
snips_skill/__init__.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from .exceptions import SnipsClarificationError, SnipsError
|
|
2
|
+
from .i18n import CONFIRMATIONS, get_translations
|
|
3
|
+
from .log import LoggingMixin
|
|
4
|
+
from .mqtt import CommandLineClient, MqttClient, topic
|
|
5
|
+
from .multi_room import ROOMS, MultiRoomConfig, room_with_article, room_with_preposition
|
|
6
|
+
from .skill import PARDON, Skill, intent, min_confidence, require_slot
|
|
7
|
+
from .snips import (
|
|
8
|
+
SnipsClient,
|
|
9
|
+
debug_json,
|
|
10
|
+
on_continue_session,
|
|
11
|
+
on_end_session,
|
|
12
|
+
on_hotword_detected,
|
|
13
|
+
on_intent,
|
|
14
|
+
on_play_finished,
|
|
15
|
+
on_session_ended,
|
|
16
|
+
on_session_started,
|
|
17
|
+
on_start_session,
|
|
18
|
+
)
|
|
19
|
+
from .state import StateAwareMixin, conditional, when
|
|
20
|
+
from .tasks import Scheduler, cron, delay, now
|
|
21
|
+
|
|
22
|
+
__version__ = "0.1.29"
|
|
23
|
+
|
|
24
|
+
__all__ = (
|
|
25
|
+
"CommandLineClient",
|
|
26
|
+
"conditional",
|
|
27
|
+
"CONFIRMATIONS",
|
|
28
|
+
"cron",
|
|
29
|
+
"debug_json",
|
|
30
|
+
"delay",
|
|
31
|
+
"get_translations",
|
|
32
|
+
"intent",
|
|
33
|
+
"LoggingMixin",
|
|
34
|
+
"min_confidence",
|
|
35
|
+
"MqttClient",
|
|
36
|
+
"MultiRoomConfig",
|
|
37
|
+
"now",
|
|
38
|
+
"on_continue_session",
|
|
39
|
+
"on_end_session",
|
|
40
|
+
"on_hotword_detected",
|
|
41
|
+
"on_intent",
|
|
42
|
+
"on_play_finished",
|
|
43
|
+
"on_session_ended",
|
|
44
|
+
"on_session_started",
|
|
45
|
+
"on_start_session",
|
|
46
|
+
"PARDON",
|
|
47
|
+
"require_slot",
|
|
48
|
+
"room_with_article",
|
|
49
|
+
"room_with_preposition",
|
|
50
|
+
"ROOMS",
|
|
51
|
+
"Scheduler",
|
|
52
|
+
"Skill",
|
|
53
|
+
"SnipsClarificationError",
|
|
54
|
+
"SnipsClient",
|
|
55
|
+
"SnipsError",
|
|
56
|
+
"StateAwareMixin",
|
|
57
|
+
"topic",
|
|
58
|
+
"when",
|
|
59
|
+
)
|
snips_skill/__main__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from basecmd import BaseCmd
|
|
4
|
+
|
|
5
|
+
from .intent import IntentPayload
|
|
6
|
+
from .log import LoggingMixin
|
|
7
|
+
from .snips import SnipsClient, on_continue_session, on_end_session, on_intent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Logger(BaseCmd, LoggingMixin, SnipsClient):
|
|
11
|
+
@on_intent("#")
|
|
12
|
+
# Do not use @intent here because it ends the session,
|
|
13
|
+
# and thus interferes with other intent handlers
|
|
14
|
+
def intent_logger(self, userdata, msg):
|
|
15
|
+
self.log_intent(IntentPayload.model_validate(msg.payload), level=logging.INFO)
|
|
16
|
+
|
|
17
|
+
@on_end_session()
|
|
18
|
+
@on_continue_session()
|
|
19
|
+
def response_logger(self, userdata, msg):
|
|
20
|
+
self.log_response(msg.payload.get("text"), level=logging.INFO)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
Logger().run()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__": # demo code
|
|
28
|
+
main()
|
snips_skill/dialogue.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic.alias_generators import to_camel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ActionInit(
|
|
8
|
+
BaseModel, alias_generator=to_camel, serialize_by_alias=True, validate_by_name=True
|
|
9
|
+
):
|
|
10
|
+
"Start a session with an action"
|
|
11
|
+
|
|
12
|
+
# See: https://docs.snips.ai/reference/dialogue#session-initialization-action
|
|
13
|
+
type: Literal["action"] = "action"
|
|
14
|
+
text: str | None = None
|
|
15
|
+
intent_filter: list[str] | None = None
|
|
16
|
+
can_be_enqueued: bool = True
|
|
17
|
+
send_intent_not_recognized: bool = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NotififationInit(BaseModel):
|
|
21
|
+
"Start a session with a notification"
|
|
22
|
+
|
|
23
|
+
# See: https://docs.snips.ai/reference/dialogue#session-initialization-notification
|
|
24
|
+
type: Literal["notification"] = "notification"
|
|
25
|
+
text: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StartSession(
|
|
29
|
+
BaseModel, alias_generator=to_camel, serialize_by_alias=True, validate_by_name=True
|
|
30
|
+
):
|
|
31
|
+
"Start a session with an optional message"
|
|
32
|
+
|
|
33
|
+
# See: https://docs.snips.ai/reference/dialogue#start-session
|
|
34
|
+
site_id: str
|
|
35
|
+
init: ActionInit | NotififationInit = Field(discriminator="type")
|
|
36
|
+
custom_data: str | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EndSession(
|
|
40
|
+
BaseModel, alias_generator=to_camel, serialize_by_alias=True, validate_by_name=True
|
|
41
|
+
):
|
|
42
|
+
"End the session with an optional message"
|
|
43
|
+
|
|
44
|
+
# See: https://docs.snips.ai/reference/dialogue#end-session
|
|
45
|
+
session_id: str
|
|
46
|
+
text: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ContinueSession(
|
|
50
|
+
BaseModel, alias_generator=to_camel, serialize_by_alias=True, validate_by_name=True
|
|
51
|
+
):
|
|
52
|
+
"Continue the session with a question"
|
|
53
|
+
|
|
54
|
+
# See: https://docs.snips.ai/reference/dialogue#continue-session
|
|
55
|
+
session_id: str
|
|
56
|
+
text: str
|
|
57
|
+
intent_filter: list[str] | None = None
|
|
58
|
+
slot: str | None = None
|
|
59
|
+
send_intent_not_recognized: bool = False
|
|
60
|
+
custom_data: str | None = None
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class SnipsError(Exception):
|
|
2
|
+
"Signal that an intent cannot be handled"
|
|
3
|
+
|
|
4
|
+
def __str__(self):
|
|
5
|
+
return str(self.args[0])
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SnipsClarificationError(SnipsError):
|
|
9
|
+
"Signal that some clarification is needed from the user."
|
|
10
|
+
|
|
11
|
+
def __init__(self, msg, intent=None, slot=None, custom_data=None):
|
|
12
|
+
"""
|
|
13
|
+
Create a clarification request.
|
|
14
|
+
:param msg: Question for the user
|
|
15
|
+
:param intent: Optional expected intent
|
|
16
|
+
:param slot: Optional slot name
|
|
17
|
+
See: https://docs.snips.ai/reference/dialogue#continue-session
|
|
18
|
+
"""
|
|
19
|
+
super().__init__(msg, intent, slot, custom_data)
|
|
20
|
+
|
|
21
|
+
def __str__(self):
|
|
22
|
+
return str(self.args[0])
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def intent(self):
|
|
26
|
+
return self.args[1]
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def slot(self):
|
|
30
|
+
return self.args[2]
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def custom_data(self):
|
|
34
|
+
return self.args[3]
|
snips_skill/expr.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
|
|
4
|
+
import ply.lex as lex
|
|
5
|
+
import ply.yacc as yacc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Parser:
|
|
9
|
+
"Parse very simple expressions into callable boolean conditions"
|
|
10
|
+
|
|
11
|
+
class Expr(namedtuple("Expr", "keys expr")):
|
|
12
|
+
"""
|
|
13
|
+
Container for a callable boolean predicate
|
|
14
|
+
along with the topic names being evaluated
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
last_state = None
|
|
18
|
+
|
|
19
|
+
def __call__(self, *args, **kw):
|
|
20
|
+
"Syntactic sugar"
|
|
21
|
+
self.last_state = self.expr(*args, **kw)
|
|
22
|
+
return self.last_state
|
|
23
|
+
|
|
24
|
+
def __hash__(self):
|
|
25
|
+
return hash(self.expr)
|
|
26
|
+
|
|
27
|
+
tokens = (
|
|
28
|
+
"AND",
|
|
29
|
+
"EQUAL",
|
|
30
|
+
"GREATER_EQUAL",
|
|
31
|
+
"GREATER",
|
|
32
|
+
"LESS_EQUAL",
|
|
33
|
+
"LESS",
|
|
34
|
+
"LPAREN",
|
|
35
|
+
"NOT_EQUAL",
|
|
36
|
+
"NOT",
|
|
37
|
+
"NUMBER",
|
|
38
|
+
"OR",
|
|
39
|
+
"REGEX_MATCH",
|
|
40
|
+
"RPAREN",
|
|
41
|
+
"STRING",
|
|
42
|
+
"TOPIC",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
t_AND = r"and"
|
|
46
|
+
t_EQUAL = r"=="
|
|
47
|
+
t_GREATER = r">"
|
|
48
|
+
t_GREATER_EQUAL = r">="
|
|
49
|
+
t_LESS = r"<"
|
|
50
|
+
t_LESS_EQUAL = r"<="
|
|
51
|
+
t_LPAREN = r"\("
|
|
52
|
+
t_NOT = r"not"
|
|
53
|
+
t_NOT_EQUAL = r"!="
|
|
54
|
+
t_OR = r"or"
|
|
55
|
+
t_REGEX_MATCH = r"~="
|
|
56
|
+
t_RPAREN = r"\)"
|
|
57
|
+
t_TOPIC = r"[\w_-]+(/[\w_-]+)+"
|
|
58
|
+
|
|
59
|
+
# Ignored characters
|
|
60
|
+
t_ignore = " \t\r\n"
|
|
61
|
+
|
|
62
|
+
def t_NUMBER(self, t):
|
|
63
|
+
r"(\+|-)?\d+(\.\d+)?"
|
|
64
|
+
t.value = float(t.value)
|
|
65
|
+
return t
|
|
66
|
+
|
|
67
|
+
def t_STRING(self, t):
|
|
68
|
+
r"'[^']*'"
|
|
69
|
+
t.value = t.value[1:-1]
|
|
70
|
+
return t
|
|
71
|
+
|
|
72
|
+
# Track line numbers
|
|
73
|
+
def t_newline(self, t):
|
|
74
|
+
r"\n+"
|
|
75
|
+
t.lexer.lineno += len(t.value)
|
|
76
|
+
|
|
77
|
+
def t_error(self, t):
|
|
78
|
+
assert False, f"Illegal character '{t.value[0]}'"
|
|
79
|
+
|
|
80
|
+
# -#-# Grammar rules below #-#-#
|
|
81
|
+
|
|
82
|
+
precedence = (
|
|
83
|
+
("left", "OR"),
|
|
84
|
+
("left", "AND"),
|
|
85
|
+
("right", "NOT"),
|
|
86
|
+
(
|
|
87
|
+
"left",
|
|
88
|
+
"GREATER",
|
|
89
|
+
"LESS",
|
|
90
|
+
"GREATER_EQUAL",
|
|
91
|
+
"LESS_EQUAL",
|
|
92
|
+
"EQUAL",
|
|
93
|
+
"NOT_EQUAL",
|
|
94
|
+
"REGEX_MATCH",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def p_expr_term(self, p):
|
|
99
|
+
"expr : term"
|
|
100
|
+
p[0] = p[1]
|
|
101
|
+
|
|
102
|
+
def p_expr_and(self, p):
|
|
103
|
+
"expr : expr AND expr"
|
|
104
|
+
lhs, rhs = p[1], p[3]
|
|
105
|
+
p[0] = self.Expr(lhs.keys | rhs.keys, lambda s: lhs(s) and rhs(s))
|
|
106
|
+
|
|
107
|
+
def p_expr_or(self, p):
|
|
108
|
+
"expr : expr OR expr"
|
|
109
|
+
lhs, rhs = p[1], p[3]
|
|
110
|
+
p[0] = self.Expr(lhs.keys | rhs.keys, lambda s: lhs(s) or rhs(s))
|
|
111
|
+
|
|
112
|
+
def p_expr_not(self, p):
|
|
113
|
+
"expr : NOT expr"
|
|
114
|
+
cond = p[2]
|
|
115
|
+
p[0] = self.Expr(cond.keys, lambda s: not cond(s))
|
|
116
|
+
|
|
117
|
+
def p_expr_parenthesis(self, p):
|
|
118
|
+
"expr : LPAREN expr RPAREN"
|
|
119
|
+
p[0] = p[2]
|
|
120
|
+
|
|
121
|
+
def p_term_topic_less_number(self, p):
|
|
122
|
+
"term : TOPIC LESS NUMBER"
|
|
123
|
+
lhs, rhs = p[1], p[3]
|
|
124
|
+
p[0] = self.Expr(set([lhs]), lambda s: float(s[lhs]) < rhs)
|
|
125
|
+
|
|
126
|
+
def p_term_topic_less_equal_number(self, p):
|
|
127
|
+
"term : TOPIC LESS_EQUAL NUMBER"
|
|
128
|
+
lhs, rhs = p[1], p[3]
|
|
129
|
+
p[0] = self.Expr(set([lhs]), lambda s: float(s[lhs]) <= rhs)
|
|
130
|
+
|
|
131
|
+
def p_term_topic_greater_equal_number(self, p):
|
|
132
|
+
"term : TOPIC GREATER_EQUAL NUMBER"
|
|
133
|
+
lhs, rhs = p[1], p[3]
|
|
134
|
+
p[0] = self.Expr(set([lhs]), lambda s: float(s[lhs]) >= rhs)
|
|
135
|
+
|
|
136
|
+
def p_term_topic_greater_number(self, p):
|
|
137
|
+
"term : TOPIC GREATER NUMBER"
|
|
138
|
+
lhs, rhs = p[1], p[3]
|
|
139
|
+
p[0] = self.Expr(set([lhs]), lambda s: float(s[lhs]) > rhs)
|
|
140
|
+
|
|
141
|
+
def p_term_topic_equal_literal(self, p):
|
|
142
|
+
"term : TOPIC EQUAL literal"
|
|
143
|
+
lhs, rhs = p[1], p[3]
|
|
144
|
+
cast = type(rhs)
|
|
145
|
+
p[0] = self.Expr(set([lhs]), lambda s: cast(s[lhs]) == rhs)
|
|
146
|
+
|
|
147
|
+
def p_term_topic_not_equal_literal(self, p):
|
|
148
|
+
"term : TOPIC NOT_EQUAL literal"
|
|
149
|
+
lhs, rhs = p[1], p[3]
|
|
150
|
+
cast = type(rhs)
|
|
151
|
+
p[0] = self.Expr(set([lhs]), lambda s: cast(s[lhs]) != rhs)
|
|
152
|
+
|
|
153
|
+
def p_term_topic_regex_match_string(self, p):
|
|
154
|
+
"term : TOPIC REGEX_MATCH STRING"
|
|
155
|
+
lhs, rhs = p[1], p[3]
|
|
156
|
+
pattern = re.compile(rhs)
|
|
157
|
+
p[0] = self.Expr(set([lhs]), lambda s: pattern.search(str(s[lhs])) is not None)
|
|
158
|
+
|
|
159
|
+
def p_literal(self, p):
|
|
160
|
+
"""literal : NUMBER
|
|
161
|
+
| STRING"""
|
|
162
|
+
p[0] = p[1]
|
|
163
|
+
|
|
164
|
+
def p_error(self, p):
|
|
165
|
+
assert False, "Syntax error at: %s" % (p or "EOF")
|
|
166
|
+
|
|
167
|
+
def __init__(self, **kwargs):
|
|
168
|
+
self.lexer = lex.lex(module=self)
|
|
169
|
+
self.parser = yacc.yacc(module=self, **kwargs)
|
|
170
|
+
|
|
171
|
+
def parse(self, text: str, **kwargs):
|
|
172
|
+
return self.parser.parse(text, lexer=self.lexer, **kwargs)
|
snips_skill/i18n.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import gettext
|
|
2
|
+
import locale
|
|
3
|
+
import os
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"_",
|
|
8
|
+
"ALL_ROOMS",
|
|
9
|
+
"CONFIRMATIONS",
|
|
10
|
+
"DEFAULT_ROOM_NAMES",
|
|
11
|
+
"ROOMS",
|
|
12
|
+
"get_translations",
|
|
13
|
+
"ngettext",
|
|
14
|
+
"room_with_article",
|
|
15
|
+
"room_with_preposition",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_translations(path: str, domain: str = "messages"):
|
|
20
|
+
"Install translations"
|
|
21
|
+
language, encoding = locale.getlocale()
|
|
22
|
+
assert type(language) is str, "Unsupported locale: %s" % locale.getlocale()
|
|
23
|
+
locale.setlocale(locale.LC_ALL, (language, encoding))
|
|
24
|
+
translation = gettext.translation(
|
|
25
|
+
domain,
|
|
26
|
+
localedir=os.path.join(os.path.dirname(path), "locale"),
|
|
27
|
+
languages=[language],
|
|
28
|
+
fallback=True,
|
|
29
|
+
)
|
|
30
|
+
return translation.gettext, translation.ngettext
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_, ngettext = get_translations(__file__, "snips_skill")
|
|
34
|
+
|
|
35
|
+
RoomName = namedtuple("RoomName", "with_article, with_preposition")
|
|
36
|
+
|
|
37
|
+
CONFIRMATIONS = (
|
|
38
|
+
_("done"),
|
|
39
|
+
_("here you are"),
|
|
40
|
+
_("OK"),
|
|
41
|
+
_("yes"),
|
|
42
|
+
_("finished"),
|
|
43
|
+
_("alright"),
|
|
44
|
+
_("at your service"),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
DEFAULT_ROOM_NAMES = (
|
|
48
|
+
_("here"),
|
|
49
|
+
_("this room"),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
ALL_ROOMS = (
|
|
53
|
+
_("all"),
|
|
54
|
+
_("everywhere"),
|
|
55
|
+
_("everything"),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
ROOMS = {
|
|
59
|
+
# Map translated room names to room names with articles and prepositions
|
|
60
|
+
# for languages that use genders for room names, e.g. German
|
|
61
|
+
_("bathroom").lower(): RoomName(_("the bathroom"), _("in the bathroom")),
|
|
62
|
+
_("bedroom").lower(): RoomName(_("the bedroom"), _("in the bedroom")),
|
|
63
|
+
_("dining room").lower(): RoomName(_("the dining room"), _("in the dining room")),
|
|
64
|
+
_("livingroom").lower(): RoomName(_("the livingroom"), _("in the livingroom")),
|
|
65
|
+
_("kid's room").lower(): RoomName(_("the kid's room"), _("in the kid's room")),
|
|
66
|
+
_("kitchen").lower(): RoomName(_("the kitchen"), _("in the kitchen")),
|
|
67
|
+
_("office").lower(): RoomName(_("the office"), _("in the office")),
|
|
68
|
+
_("hall").lower(): RoomName(_("the hall"), _("in the hall")),
|
|
69
|
+
_("garden").lower(): RoomName(_("the garden"), _("in the garden")),
|
|
70
|
+
_("unknown room").lower(): RoomName(_("an unknown room"), _("in an unknown room")),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def room_with_article(room_name: str) -> str:
|
|
75
|
+
"Get the spoken room name with the definite article"
|
|
76
|
+
room = ROOMS.get(room_name.lower())
|
|
77
|
+
return room.with_article if room else _("the {room}").format(room=room_name)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def room_with_preposition(room_name: str) -> str:
|
|
81
|
+
'Get the spoken room name with "in" preposition'
|
|
82
|
+
room = ROOMS.get(room_name.lower())
|
|
83
|
+
return room.with_preposition if room else _("in the {room}").format(room=room_name)
|
snips_skill/intent.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from pydantic.alias_generators import to_camel
|
|
7
|
+
|
|
8
|
+
__all__ = ("IntentPayload",)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UnknownValue(BaseModel):
|
|
12
|
+
kind: Literal["Unknown"]
|
|
13
|
+
value: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NumericValue(BaseModel):
|
|
17
|
+
kind: Literal["Number"]
|
|
18
|
+
value: int | float
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# See: https://docs.snips.ai/articles/platform/dialog/slot-types#time-related-entities
|
|
22
|
+
class InstantTimeValue(BaseModel):
|
|
23
|
+
kind: Literal["InstantTime"]
|
|
24
|
+
grain: str
|
|
25
|
+
precision: str
|
|
26
|
+
value: datetime
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TimeIntervalValue(BaseModel):
|
|
30
|
+
kind: Literal["TimeInterval"]
|
|
31
|
+
t1: datetime | None = Field(alias="from", default=None)
|
|
32
|
+
t2: datetime | None = Field(alias="to", default=None)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# See: https://docs.snips.ai/articles/platform/dialog/slot-types#duration-entities
|
|
36
|
+
class DurationValue(BaseModel):
|
|
37
|
+
kind: Literal["Duration"]
|
|
38
|
+
precision: str
|
|
39
|
+
years: int = 0
|
|
40
|
+
quarters: int = 0
|
|
41
|
+
months: int = 0
|
|
42
|
+
weeks: int = 0
|
|
43
|
+
days: int = 0
|
|
44
|
+
hours: int = 0
|
|
45
|
+
minutes: int = 0
|
|
46
|
+
seconds: int = 0
|
|
47
|
+
|
|
48
|
+
def as_timedelta(self) -> timedelta:
|
|
49
|
+
# TODO: years, quarters and months are not parsed
|
|
50
|
+
return timedelta(
|
|
51
|
+
weeks=self.weeks,
|
|
52
|
+
days=self.days,
|
|
53
|
+
hours=self.hours,
|
|
54
|
+
minutes=self.minutes,
|
|
55
|
+
seconds=self.seconds,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TemperatureValue(BaseModel):
|
|
60
|
+
kind: Literal["Temperature"]
|
|
61
|
+
unit: str
|
|
62
|
+
value: float
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class MonetaryValue(BaseModel):
|
|
66
|
+
kind: Literal["AmountOfMoney"]
|
|
67
|
+
unit: str
|
|
68
|
+
precision: str
|
|
69
|
+
value: float
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Range(BaseModel, alias_generator=to_camel, serialize_by_alias=True):
|
|
73
|
+
start: int
|
|
74
|
+
end: int
|
|
75
|
+
raw_start: int
|
|
76
|
+
raw_end: int
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
SlotValue = Annotated[
|
|
80
|
+
UnknownValue
|
|
81
|
+
| NumericValue
|
|
82
|
+
| InstantTimeValue
|
|
83
|
+
| TimeIntervalValue
|
|
84
|
+
| DurationValue
|
|
85
|
+
| TemperatureValue
|
|
86
|
+
| MonetaryValue,
|
|
87
|
+
Field(discriminator="kind"),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# See: https://docs.snips.ai/reference/dialogue#slot
|
|
92
|
+
class Slot(BaseModel, alias_generator=to_camel, serialize_by_alias=True):
|
|
93
|
+
entity: str
|
|
94
|
+
slot_name: str
|
|
95
|
+
raw_value: str
|
|
96
|
+
confidence: float
|
|
97
|
+
range: Range | None
|
|
98
|
+
value: SlotValue
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# See: https://docs.snips.ai/reference/dialogue#intent
|
|
102
|
+
class Intent(BaseModel, alias_generator=to_camel, serialize_by_alias=True):
|
|
103
|
+
intent_name: str
|
|
104
|
+
confidence_score: float
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AsrToken(BaseModel, alias_generator=to_camel, serialize_by_alias=True):
|
|
108
|
+
value: str
|
|
109
|
+
confidence: float
|
|
110
|
+
range_start: int
|
|
111
|
+
range_end: int
|
|
112
|
+
time: None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# See: https://docs.snips.ai/reference/dialogue#intent
|
|
116
|
+
class IntentPayload(BaseModel, alias_generator=to_camel, serialize_by_alias=True):
|
|
117
|
+
session_id: str
|
|
118
|
+
input: str
|
|
119
|
+
intent: Intent
|
|
120
|
+
site_id: str
|
|
121
|
+
slots: list[Slot] | None = None
|
|
122
|
+
id: str | None = None
|
|
123
|
+
session_id: str
|
|
124
|
+
custom_data: str
|
|
125
|
+
asr_tokens: list[list[AsrToken]]
|
|
126
|
+
asr_confidence: float
|
|
127
|
+
raw_input: str
|
|
128
|
+
# wakeword_id: str
|
|
129
|
+
# lang: None
|
|
130
|
+
|
|
131
|
+
@cached_property
|
|
132
|
+
def slot_values(self) -> dict[str, SlotValue]:
|
|
133
|
+
return {s.slot_name: s.value for s in self.slots or []}
|
|
134
|
+
|
|
135
|
+
def __getitem__(self, name):
|
|
136
|
+
return self.slot_values[name]
|
|
Binary file
|