From 0d002dfc39913bfaa6ebec68fc04672f2454be2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xu=C2=A0?= Date: Tue, 17 Nov 2020 20:40:07 +0800 Subject: [PATCH] Initial commit --- .idea/.gitignore | 2 + .idea/Chatbot_Action.iml | 20 + .idea/deployment.xml | 22 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + README.md | 6 + action/__init__.py | 14 + action/action_express.py | 14 + action/action_weather.py | 77 +++ action/actions.py | 310 +++++++++ config/__init__.py | 14 + config/service_config.py | 25 + knowledge_base/__init__.py | 14 + knowledge_base/data_source.py | 20 + knowledge_base/util.py | 86 +++ log/test | 0 model/Events.py | 207 ++++++ model/Executor.py | 426 ++++++++++++ model/Forms.py | 636 ++++++++++++++++++ model/Interfaces.py | 302 +++++++++ model/Model_config.py | 20 + model/__init__.py | 19 + model/utils.py | 161 +++++ requirements.txt | 1 + server/__init__.py | 14 + server/endpoint.py | 149 ++++ server/params.py | 55 ++ server/parse.py | 44 ++ server/run_server.py | 144 ++++ service/__init__.py | 14 + service/config/service_config.py | 14 + service/express/__init__.py | 14 + service/express/ali_express.py | 54 ++ service/express/expressList.py | 55 ++ service/faq/__init__.py | 14 + service/weather/__init__.py | 14 + service/weather/base.py | 24 + service/weather/condition.py | 6 + service/weather/seniverse.py | 70 ++ start_server.sh | 3 + utils/build_nlu_data.py | 14 + utils/combine_train_data.py | 15 + utils/mongo_utils.py | 14 + utils/mysql_utils.py | 98 +++ utils/time_utils.py | 53 ++ utils/transfer_diag_to_story.py | 14 + 48 files changed, 3319 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/Chatbot_Action.iml create mode 100644 .idea/deployment.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 action/__init__.py create mode 100644 action/action_express.py create mode 100644 action/action_weather.py create mode 100755 action/actions.py create mode 100644 config/__init__.py create mode 100644 config/service_config.py create mode 100644 knowledge_base/__init__.py create mode 100644 knowledge_base/data_source.py create mode 100644 knowledge_base/util.py create mode 100644 log/test create mode 100644 model/Events.py create mode 100644 model/Executor.py create mode 100644 model/Forms.py create mode 100644 model/Interfaces.py create mode 100644 model/Model_config.py create mode 100644 model/__init__.py create mode 100644 model/utils.py create mode 100644 requirements.txt create mode 100644 server/__init__.py create mode 100644 server/endpoint.py create mode 100644 server/params.py create mode 100644 server/parse.py create mode 100644 server/run_server.py create mode 100644 service/__init__.py create mode 100644 service/config/service_config.py create mode 100644 service/express/__init__.py create mode 100644 service/express/ali_express.py create mode 100644 service/express/expressList.py create mode 100644 service/faq/__init__.py create mode 100644 service/weather/__init__.py create mode 100644 service/weather/base.py create mode 100644 service/weather/condition.py create mode 100644 service/weather/seniverse.py create mode 100755 start_server.sh create mode 100644 utils/build_nlu_data.py create mode 100644 utils/combine_train_data.py create mode 100644 utils/mongo_utils.py create mode 100644 utils/mysql_utils.py create mode 100644 utils/time_utils.py create mode 100644 utils/transfer_diag_to_story.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/Chatbot_Action.iml b/.idea/Chatbot_Action.iml new file mode 100644 index 0000000..71968c0 --- /dev/null +++ b/.idea/Chatbot_Action.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 0000000..7509e20 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..876ba18 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..fc3c6b4 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c065651 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Chatbot_Action + + +### 启动服务 + +- run_server.py \ No newline at end of file diff --git a/action/__init__.py b/action/__init__.py new file mode 100644 index 0000000..1a696fd --- /dev/null +++ b/action/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : __init__.py + +@Time : 2020/8/23 8:30 下午 + +@Desc : + +""" \ No newline at end of file diff --git a/action/action_express.py b/action/action_express.py new file mode 100644 index 0000000..6ca81be --- /dev/null +++ b/action/action_express.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : action_express.py + +@Time : 2020/8/25 11:03 上午 + +@Desc : + +""" \ No newline at end of file diff --git a/action/action_weather.py b/action/action_weather.py new file mode 100644 index 0000000..b1370e3 --- /dev/null +++ b/action/action_weather.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : action_weather.py + +@Time : 2020/8/23 8:27 下午 + +@Desc : + +""" +import pathlib +import os +import logging + +from typing import Any, Text, Dict, List, Union + +from model import Action +from model.Events import SlotSet, AllSlotsReset +from model.Executor import CollectingDispatcher +from model.Interfaces import Tracker + +from service.weather.seniverse import SeniverseWeatherAPI +from utils.time_utils import get_time_unit + +api_secret = "Sq6NfAburbGs9MGQb" +sw = SeniverseWeatherAPI(api_secret) + + +class ActionReportWeather(Action): + """ + 天气查询 + """ + + def name(self) -> Text: + return "action_report_weather" + + @staticmethod + def required_slots(tracker): + # type: () -> List[Text] + """A list of required slots that the form has to fill""" + return ["address", "date-time"] + + def run(self, dispatcher: CollectingDispatcher, + tracker: Tracker, + domain: Dict[Text, Any]) -> List[Dict[Text, Any]]: + address = tracker.get_slot('address') + date_time = tracker.get_slot('date-time') + + if date_time is None: + date_time = '今天' + date_time_number = get_time_unit(date_time) # 传入时间关键词,返回归一化的时间 + + if isinstance(date_time_number, str): # parse date_time failed + return [SlotSet("matches", "暂不支持查询 {} 的天气".format([address, date_time_number]))] + elif date_time_number is None: + return [SlotSet("matches", "暂不支持查询 {} 的天气".format([address, date_time]))] + else: + condition = sw.get_weather_by_city_and_day(address, date_time_number) # 调用天气API + weather_data = forecast_to_text(address, condition) + + return [SlotSet("matches", "{}".format(weather_data))] + + +def forecast_to_text(address, condition): + msg_tpl = "{city} {date} 的天气情况为:{condition};气温:{temp_low}-{temp_high} 度" + msg = msg_tpl.format( + city= address, + date=condition.date, + condition=condition.condition, + temp_low=condition.low_temperature, + temp_high=condition.high_temperature + ) + return msg diff --git a/action/actions.py b/action/actions.py new file mode 100755 index 0000000..8917da0 --- /dev/null +++ b/action/actions.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- + +''' +@Author : Xu + +@Software: PyCharm + +@File : actions.py + +@Time : 2019-09-10 16:31 + +@Desc : 1、连接neo4j查询, 当rasa无法回复的时候到图数据库寻找答案 + 2、重写name和run + + 3、时间解析 + 4、 + +''' + +import pathlib +import os +import logging + +from typing import Any, Text, Dict, List, Union + +from model import Action +from model.Events import SlotSet, AllSlotsReset +from model.Executor import CollectingDispatcher +from model.Interfaces import Tracker + +from service.weather.seniverse import SeniverseWeatherAPI +from utils.time_utils import get_time_unit + +# from service.FAQ import get_qa +# +# from rasa_sdk.knowledge_base.utils import ( +# SLOT_OBJECT_TYPE, +# SLOT_LAST_OBJECT_TYPE, +# SLOT_ATTRIBUTE, +# reset_attribute_slots, +# SLOT_MENTION, +# SLOT_LAST_OBJECT, +# SLOT_LISTED_OBJECTS, +# get_object_name, +# get_attribute_slots, +# ) + +logger = logging.getLogger(__name__) + +basedir = str(pathlib.Path(os.path.abspath(__file__)).parent.parent) + +api_secret = "Sq6NfAburbGs9MGQb" +sw = SeniverseWeatherAPI(api_secret) + + +# class ActionAskProblem(Action): +# ''' +# 询问问题 +# ''' +# +# def name(self) -> Text: +# return "action_ask_problem" +# +# def run(self, +# dispatcher: CollectingDispatcher, # Send messages back to user +# tracker: Tracker, +# domain: Dict[Text, Any]) -> List[Dict[Text, Any]]: +# # KB-QA +# txt = tracker.latest_message['text'] # 最新一轮对话的text,作为query传入知识库查询 +# # car = tracker.get_slot('car') +# # result = list(selector.match()) # 这里查询neo4j +# +# print(txt) +# result = '' +# +# dispatcher.utter_message('这里查询知识库') +# return [SlotSet('org', result if result is not None else [])] + + +class ActionDefaultFallback2(Action): + ''' + 默认回复 + ''' + + def name(self): # type: () -> Text + return "action_default_fallback" + + def run( + self, + dispatcher, # type: CollectingDispatcher + tracker, # type: Tracker + domain, # type: Dict[Text, Any] + ): # type: (...) -> List[Dict[Text, Any]] + + result = '' + dispatcher.utter_message("我不知道您在说什么哟,请换一种方式吧") + return [SlotSet('org', result if result is not None else [])] + + +class ActionDefaultFallback(Action): + """Executes the fallback action and goes back to the previous state + of the dialogue""" + + def name(self) -> Text: + return "my_fallback_action" + + def run(self, dispatcher, tracker, domain): + # dispatcher.utter_template("utter_my_default", tracker) + state = tracker.current_state() + logger.info("action_my_fallback_action current state is {}\n".format(state)) + message_text = tracker.latest_message.get('text') + response = get_tuling_response(message_text).get('text') + logger.info("action_my_fallback_action latest_message is {},response is {}".format(message_text, response)) + dispatcher.utter_message(response) + # return [UserUtteranceReverted()] + return [] + + +# class HeightWeightForm(FormAction): +# """Example of a custom form action""" +# +# logger.info('Doing HeightWeightForm') +# def name(self) -> Text: +# """Unique identifier of the form""" +# +# return "height_weight_form" +# +# @staticmethod +# def required_slots(tracker: Tracker) -> List[Text]: +# """A list of required slots that the form has to fill""" +# return ["height", "weight"] +# +# def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]: +# """A dictionary to map required slots to +# - an extracted entity +# - intent: value pairs +# - a whole message +# or a list of them, where a first match will be picked""" +# +# return { +# "height": [ +# self.from_entity( +# entity="height", intent=["inform_height_weight"] +# ), +# ], +# "weight": [ +# self.from_entity( +# entity="weight", intent=["inform_height_weight"] +# ), +# ], +# } +# +# def submit( +# self, +# dispatcher: CollectingDispatcher, +# tracker: Tracker, +# domain: Dict[Text, Any], +# ) -> List[Dict]: +# """Define what the form has to do +# after all required slots are filled""" +# # print("submit------tracker is {}",tracker) +# slot_to_fill = tracker.get_slot("requested_slot") +# logger.info("-------submit start slot_to_fill is '{}'" +# "".format(slot_to_fill)) +# # utter submit template +# dispatcher.utter_template("utter_cloth_recommend", tracker) +# return [] + + +# class ActionMyKB(ActionQueryKnowledgeBase): +# +# def __init__(self): +# knowledge_base = InMemoryKnowledgeBase(basedir + '/data/knowledge_base_data.json') +# knowledge_base.set_representation_function_of_object( +# "hotel", lambda obj: obj["name"] + " (" + obj["city"] + ")" +# ) +# +# super().__init__(knowledge_base) + + +# class ActionReportWeather(Action): +# ''' +# 天气查询 +# ''' +# +# def name(self) -> Text: +# return "action_report_weather" +# +# @staticmethod +# def required_slots(tracker): +# # type: () -> List[Text] +# """A list of required slots that the form has to fill""" +# return ["address", "date-time"] +# +# def run(self, dispatcher: CollectingDispatcher, +# tracker: Tracker, +# domain: Dict[Text, Any]) -> List[Dict[Text, Any]]: +# address = tracker.get_slot('address') +# date_time = tracker.get_slot('date-time') +# +# if date_time is None: +# date_time = '今天' +# date_time_number = get_time_unit(date_time) # 传入时间关键词,返回归一化的时间 +# +# if isinstance(date_time_number, str): # parse date_time failed +# return [SlotSet("matches", "暂不支持查询 {} 的天气".format([address, date_time_number]))] +# elif date_time_number is None: +# return [SlotSet("matches", "暂不支持查询 {} 的天气".format([address, date_time]))] +# else: +# condition = sw.get_weather_by_city_and_day(address, date_time_number) # 调用天气API +# weather_data = forecast_to_text(address, condition) +# +# return [SlotSet("matches", "{}".format(weather_data))] + + +def forecast_to_text(address, condition): + msg_tpl = "{city} {date} 的天气情况为:{condition};气温:{temp_low}-{temp_high} 度" + msg = msg_tpl.format( + city= address, + date=condition.date, + condition=condition.condition, + temp_low=condition.low_temperature, + temp_high=condition.high_temperature + ) + return msg + + +class ActionUnknowIntent(Action): + """ + 处理未知意图,调用FAQ + """ + def name(self): + return 'action_unknow_intent' + + def run(selfs, dispatcher: CollectingDispatcher, + tracker: Tracker, + domain: Dict[Text, Any]) -> List[Dict[Text, Any]]: + text = tracker.latest_message.get('text') # 用户说的话 + qa_message = get_qa(text) + + if qa_message != "未找到答案": + dispatcher.utter_message("{}".format(qa_message)) + else: + message = get_qa(text) + if message['code'] == 100000 or message['code'] == 200000: + dispatcher.utter_message("{}".format(message['text'])) + else: + dispatcher.utter_template('utter_default', tracker, silent_fail=True) + return [] + + +# class CaseForm(FormAction): +# """A custom form action""" +# +# def name(self): +# # type: () -> Text +# """Unique identifier of the form""" +# return "case_form" +# +# @staticmethod +# def required_slots(tracker): +# # type: () -> List[Text] +# """A list of required slots that the form has to fill""" +# return ["case", "place", "day"] +# +# def slot_mappings(self): +# return {"case": self.from_entity(entity="case", not_intent="unknown_intent"), +# "place": [self.from_entity(entity="place"), +# self.from_text()], +# "day": [self.from_entity(entity="day"), +# self.from_text()] +# } +# +# # # 无数据验证可省略 +# # def validate(self, +# # dispatcher: CollectingDispatcher, +# # tracker: Tracker, +# # domain: Dict[Text, Any]) -> List[Dict]: +# # """Validate extracted requested slot +# # else reject the execution of the form action +# # """ +# # # extract other slots that were not requested +# # # but set by corresponding entity +# # slot_values = self.extract_other_slots(dispatcher, tracker, domain) +# # +# # # extract requested slot +# # slot_to_fill = tracker.get_slot(REQUESTED_SLOT) +# # if slot_to_fill: +# # slot_values.update(self.extract_requested_slot(dispatcher, +# # tracker, domain)) +# # if not slot_values: +# # # reject form action execution +# # # if some slot was requested but nothing was extracted +# # # it will allow other policies to predict another action +# # raise ActionExecutionRejection(self.name(), +# # "Failed to validate slot {0} " +# # "with action {1}" +# # "".format(slot_to_fill, +# # self.name())) +# # return [SlotSet(slot, value) for slot, value in slot_values.items()] +# +# def submit(self, dispatcher, tracker, domain): +# # type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict] +# """Define what the form has to do +# after all required slots are filled""" +# # utter submit template +# dispatcher.utter_template('utter_search_template', tracker) +# dispatcher.utter_message("{},在{}发生一起性质恶劣的{},引起全市人民的高度关注,以下是详细信息:" +# .format(tracker.get_slot("day"), tracker.get_slot("place"), tracker.get_slot("case"))) +# return [AllSlotsReset()] \ No newline at end of file diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..7a4501a --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : __init__.py + +@Time : 2020/8/21 6:15 下午 + +@Desc : + +""" \ No newline at end of file diff --git a/config/service_config.py b/config/service_config.py new file mode 100644 index 0000000..94d1984 --- /dev/null +++ b/config/service_config.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : service_config.py + +@Time : 2020/7/27 7:13 下午 + +@Desc : 外部服务调用配置 + +""" + + +class ServiceConfig: + + def __init__(self): + + # 快递查询 + self.exhost = 'https://kdwlcxf.market.alicloudapi.com' + self.appcode = 'de7111ca3c0c4112bc726409e375c014' # 阿里云快递查询接口 + + self.api_secret = "Sq6NfAburbGs9MGQb" # 心知天气API \ No newline at end of file diff --git a/knowledge_base/__init__.py b/knowledge_base/__init__.py new file mode 100644 index 0000000..f4ba500 --- /dev/null +++ b/knowledge_base/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : __init__.py.py + +@Time : 2020/8/22 7:19 下午 + +@Desc : + +""" \ No newline at end of file diff --git a/knowledge_base/data_source.py b/knowledge_base/data_source.py new file mode 100644 index 0000000..99a2284 --- /dev/null +++ b/knowledge_base/data_source.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : data_source.py + +@Time : 2020/8/24 9:13 上午 + +@Desc : kbqa数据源 + +""" + + +class KnowledgeBaseDataSource: + + def __init__(self): + pass \ No newline at end of file diff --git a/knowledge_base/util.py b/knowledge_base/util.py new file mode 100644 index 0000000..15b8ab0 --- /dev/null +++ b/knowledge_base/util.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : util.py + +@Time : 2020/8/23 10:13 下午 + +@Desc : kbqa工具类 + +""" + +from typing import Text, Callable, Dict, List, Any, Optional +import typing + +from model.Events import SlotSet +from model.Executor import Tracker + +SLOT_MENTION = "mention" +SLOT_OBJECT_TYPE = "object_type" +SLOT_ATTRIBUTE = "attribute" +SLOT_LISTED_OBJECTS = "knowledge_base_listed_objects" +SLOT_LAST_OBJECT = "knowledge_base_last_object" +SLOT_LAST_OBJECT_TYPE = "knowledge_base_last_object_type" + + +def get_object_name( + tracker: "Tracker", + ordinal_mention_mapping: Dict[Text, Callable], + use_last_object_mention: bool = True +) -> Optional[Text]: + """ + + :param tracker: + :param ordinal_mention_mapping: + :param use_last_object_mention: + :return: + """ + + return None + + +def resolve_mention( + tracker: "Tracker", + ordinal_mention_mapping: Dict[Text, Callable] +) -> Optional[Text]: + """ + + :param tracker: + :param ordinal_mention_mapping: + :return: + """ + return None + + +def get_attribute_slots( + tracker: "Tracker", + object_attributes: List[Text] +) -> List[Dict[Text, Text]]: + """ + 获取slot的属性 + :param tracker: + :param object_attributes: + :return: a list of attributes + """ + attributes = [] + + return attributes + + +def reset_attributes_slots( + tacker: "Tracker", + object_attributes: List[Text] +) -> List[Dict]: + """ + 重置Slots + :param tacker: + :param object_attributes: + :return: a list of Slots + """ + slots = [] + + return slots diff --git a/log/test b/log/test new file mode 100644 index 0000000..e69de29 diff --git a/model/Events.py b/model/Events.py new file mode 100644 index 0000000..e0257b1 --- /dev/null +++ b/model/Events.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : Events.py + +@Time : 2020/7/24 3:16 下午 + +@Desc : + +""" + +import logging +import warnings +import datetime + +from typing import Dict, Text, Any, List, Optional, Union + +logger = logging.getLogger(__name__) + +EventType = Dict[Text, Any] + + +def UserUttered( + text: Optional[Text], + parse_data: Optional[Dict[Text, Any]] = None, + timestamp: Optional[float] = None, + input_channel: Optional[Text] = None, +) -> EventType: + return { + "event": "user", + "timestamp": timestamp, + "text": text, + "parse_data": parse_data, + "input_channel": input_channel, + } + + +def BotUttered( + text: Optional[Text] = None, + data: Optional[Dict[Text, Any]] = None, + metadata: Optional[Dict[Text, Any]] = None, + timestamp: Optional[float] = None, +) -> EventType: + return { + "event": "bot", + "timestamp": timestamp, + "text": text, + "data": data, + "metadata": metadata, + } + + +def SlotSet( + key: Text, value: Any = None, timestamp: Optional[float] = None +) -> EventType: + return {"event": "slot", "timestamp": timestamp, "name": key, "value": value} + + +# noinspection PyPep8Naming +def Restarted(timestamp: Optional[float] = None) -> EventType: + return {"event": "restart", "timestamp": timestamp} + + +# noinspection PyPep8Naming +def SessionStarted(timestamp: Optional[float] = None) -> EventType: + return {"event": "session_started", "timestamp": timestamp} + + +# noinspection PyPep8Naming +def UserUtteranceReverted(timestamp: Optional[float] = None) -> EventType: + return {"event": "rewind", "timestamp": timestamp} + + +# noinspection PyPep8Naming +def AllSlotsReset(timestamp: Optional[float] = None) -> EventType: + return {"event": "reset_slots", "timestamp": timestamp} + + +def _is_probably_action_name(name: Optional[Text]) -> bool: + return name is not None and ( + name.startswith("utter_") or name.startswith("action_") + ) + + +# noinspection PyPep8Naming +def ReminderScheduled( + intent_name: Text, + trigger_date_time: datetime.datetime, + entities: Optional[Union[List[Dict[Text, Any]], Dict[Text, Text]]] = None, + name: Optional[Text] = None, + kill_on_user_message: bool = True, + timestamp: Optional[float] = None, +) -> EventType: + if _is_probably_action_name(intent_name): + warnings.warn( + f"ReminderScheduled intent starts with 'utter_' or 'action_'. " + f"If '{intent_name}' is indeed an intent, then you can ignore this warning.", + FutureWarning, + ) + return { + "event": "reminder", + "timestamp": timestamp, + "intent": intent_name, + "entities": entities, + "date_time": trigger_date_time.isoformat(), + "name": name, + "kill_on_user_msg": kill_on_user_message, + } + + +# noinspection PyPep8Naming +def ReminderCancelled( + name: Optional[Text] = None, + intent_name: Optional[Text] = None, + entities: Optional[Union[List[Dict[Text, Any]], Dict[Text, Text]]] = None, + timestamp: Optional[float] = None, +) -> EventType: + if _is_probably_action_name(intent_name): + warnings.warn( + f"ReminderCancelled intent starts with 'utter_' or 'action_'. " + f"If '{intent_name}' is indeed an intent, then you can ignore this warning.", + FutureWarning, + ) + return { + "event": "cancel_reminder", + "timestamp": timestamp, + "intent": intent_name, + "entities": entities, + "name": name, + } + + +# noinspection PyPep8Naming +def ActionReverted(timestamp: Optional[float] = None) -> EventType: + return {"event": "undo", "timestamp": timestamp} + + +# noinspection PyPep8Naming +def StoryExported(timestamp: Optional[float] = None) -> EventType: + return {"event": "export", "timestamp": timestamp} + + +# noinspection PyPep8Naming +def FollowupAction(name: Text, timestamp: Optional[float] = None) -> EventType: + return {"event": "followup", "timestamp": timestamp, "name": name} + + +# noinspection PyPep8Naming +def ConversationPaused(timestamp: Optional[float] = None) -> EventType: + return {"event": "pause", "timestamp": timestamp} + + +# noinspection PyPep8Naming +def ConversationResumed(timestamp: Optional[float] = None) -> EventType: + return {"event": "resume", "timestamp": timestamp} + + +# noinspection PyPep8Naming +def ActionExecuted( + action_name, + policy=None, + confidence: Optional[float] = None, + timestamp: Optional[float] = None, +) -> EventType: + return { + "event": "action", + "name": action_name, + "policy": policy, + "confidence": confidence, + "timestamp": timestamp, + } + + +# noinspection PyPep8Naming +def AgentUttered( + text: Optional[Text] = None, data=None, timestamp: Optional[float] = None +) -> EventType: + return {"event": "agent", "text": text, "data": data, "timestamp": timestamp} + + +# noinspection PyPep8Naming +def Form(name: Optional[Text], timestamp: Optional[float] = None) -> EventType: + return {"event": "form", "name": name, "timestamp": timestamp} + + +# noinspection PyPep8Naming +def FormValidation(validate, timestamp: Optional[float] = None) -> EventType: + return {"event": "form_validation", "validate": validate, "timestamp": timestamp} + + +def ActionExecutionRejected( + action_name: Text, + policy: Optional[Text] = None, + confidence: Optional[float] = None, + timestamp: Optional[float] = None, +) -> EventType: + return { + "event": "action_execution_rejected", + "name": action_name, + "policy": policy, + "confidence": confidence, + "timestamp": timestamp, + } diff --git a/model/Executor.py b/model/Executor.py new file mode 100644 index 0000000..0bb9e76 --- /dev/null +++ b/model/Executor.py @@ -0,0 +1,426 @@ +# -*- coding: utf-8 -*- + +""" +@Author : Xu + +@Software: PyCharm + +@File : Executor.py + +@Time : 2020/7/24 4:33 下午 + +@Desc : + +""" + +import importlib +import inspect +import logging +import warnings +import pkgutil +import types +import sys +import os + +from typing import Text, List, Dict, Any, Type, Union, Callable, Optional, Set +from collections import namedtuple + +from model import utils + +from model.Interfaces import Tracker, ActionNotFoundException, Action + +logger = logging.getLogger(__name__) + +TimestampModule = namedtuple("TimestampModule", ["timestamp", "module"]) + + +class CollectingDispatcher: + """ + 消息发送器 + """ + def __init__(self) -> None: + + self.messages = [] + + def utter_message( + self, + text: Optional[Text] = None, + image: Optional[Text] = None, + json_message: Optional[Dict[Text, Any]] = None, + template: Optional[Text] = None, + attachment: Optional[Text] = None, + buttons: Optional[List[Dict[Text, Any]]] = None, + elements: Optional[List[Dict[Text, Any]]] = None, + **kwargs: Any, + ) -> None: + """ + 将消息发送至output + :param text: + :param image: + :param json_message: + :param template: + :param attachment: + :param buttons: + :param elements: + :param kwargs: + :return: + """ + message = { + "text": text, + "buttons": buttons or [], + "elements": elements or [], + "custom": json_message or {}, + "template": template, + "image": image, + "attachment": attachment, + } + message.update(kwargs) + + self.message.append(message) + + def utter_custom_message(self, *elements: Dict[Text, Any], **kwargs: Any) -> None: + warnings.warn( + "Use of `utter_custom_message` is deprecated. " + "Use `utter_message(elements=)` instead.", + FutureWarning, + ) + self.utter_message(elements=list(elements), **kwargs) + + def utter_elements(self, *elements: Dict[Text, Any], **kwargs: Any) -> None: + """Sends a message with custom elements to the output channel.""" + warnings.warn( + "Use of `utter_elements` is deprecated. " + "Use `utter_message(elements=)` instead.", + FutureWarning, + ) + self.utter_message(elements=list(elements), **kwargs) + + def utter_button_message( + self, text: Text, buttons: List[Dict[Text, Any]], **kwargs: Any + ) -> None: + """Sends a message with buttons to the output channel.""" + warnings.warn( + "Use of `utter_button_message` is deprecated. " + "Use `utter_message(text= , buttons=)` instead.", + FutureWarning, + ) + + self.utter_message(text=text, buttons=buttons, **kwargs) + + def utter_attachment(self, attachment: Text, **kwargs: Any) -> None: + """Send a message to the client with attachments.""" + warnings.warn( + "Use of `utter_attachment` is deprecated. " + "Use `utter_message(attachment=)` instead.", + FutureWarning, + ) + + self.utter_message(attachment=attachment, **kwargs) + + def utter_button_template( + self, + template: Text, + buttons: List[Dict[Text, Any]], + tracker: Tracker, + silent_fail: bool = False, + **kwargs: Any, + ) -> None: + """Sends a message template with buttons to the output channel.""" + warnings.warn( + "Use of `utter_button_template` is deprecated. " + "Use `utter_message(template=