示例#1
0
    def _add_weather_db_config(cls, loader):
        """
        添加天气问答数据库配置
        """
        _logger = loader.logger
        # 检查配置是否已经增加
        if (StdQuestion.select().where(
            (StdQuestion.milvus_id == -1) & (StdQuestion.q_type == 'context')
                & (StdQuestion.question == '天气问答')).count()) > 0:
            if _logger is not None:
                _logger.info('Weather config already exists!')
            return

        # 插入标准问题
        _std_q = StdQuestion.create(q_type='context',
                                    milvus_id=-1,
                                    collection=WEATHER_COLLECTION,
                                    partition=WEATHER_PARTITION,
                                    question='天气问答')

        # 插入问题答案
        Answer.create(
            std_question_id=_std_q.id,
            a_type='ask',
            type_param="['ApiToolAsk', 'weather', '%s', '%s', {}, True]" %
            (WEATHER_COLLECTION, WEATHER_PARTITION),
            replace_pre_def='N',
            answer=WEATHER_ERROR)

        # 插入NLP意图识别配置
        NlpPurposConfigDict.create(
            action='天气查询',
            match_collection='',
            match_partition='',
            collection=WEATHER_COLLECTION,
            partition=WEATHER_PARTITION,
            std_question_id=_std_q.id,
            order_num=0,
            exact_match_words='[]',
            exact_ignorecase='N',
            match_words="['天气', '今天天气']",
            ignorecase='N',
            word_scale=0.0,
            info=
            "['InitInfo', 'get_wordclass_list', {'condition': [{'key': 'time', 'class': ['t']}, {'key': 'addr', 'class': ['ns']}]}]",
            check=
            "['InitCheck', 'reject_by_nest', {'next': {'天气': ['真好', '不错', '真差']}, }]"
        )
示例#2
0
    def remove_config(cls, qa_manager: QAManager, logger: Logger):
        """
        清空所有配置

        @param {QAManager} qa_manager - 数据管理对象
        @param {Logger} logger - 日志对象
        """
        # 标准问题和答案
        _std_q = StdQuestion.get_or_none(
            StdQuestion.tag == 'form_direct_action')
        if _std_q is not None:
            # 删除对应问题的意图
            _ret = NlpPurposConfigDict.delete().where(
                NlpPurposConfigDict.std_question_id == _std_q.id).execute()
            if logger is not None:
                logger.debug('remove form plugin nlp config success: %s !' %
                             str(_ret))

            _ret = Answer.delete().where(
                Answer.std_question_id == _std_q.id).execute()
            _ret = StdQuestion.delete().where(
                StdQuestion.id == _std_q.id).execute()
            if logger is not None:
                logger.debug(
                    'remove form plugin std question config success: %s !' %
                    str(_ret))
示例#3
0
    def remove_config(cls, qa_manager: QAManager, logger: Logger):
        """
        清空所有配置

        @param {QAManager} qa_manager - 数据管理对象
        @param {Logger} logger - 日志对象
        """
        # 意图配置
        _ret = NlpPurposConfigDict.delete().where(
            NlpPurposConfigDict.action == 'leave_message').execute()
        if logger is not None:
            logger.debug(
                'remove leave message plugin nlp config success: %s !' %
                str(_ret))

        # 文件上传参数
        _ret = UploadFileConfig.delete().where(
            UploadFileConfig.upload_type ==
            LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['upload_type']).execute()
        if logger is not None:
            logger.debug(
                'remove leave message plugin file upload config success: %s !'
                % str(_ret))

        # 标准问题和答案
        _std_q = StdQuestion.get_or_none(
            StdQuestion.tag == 'leave_message_direct_action')
        if _std_q is not None:
            _ret = Answer.delete().where(
                Answer.std_question_id == _std_q.id).execute()
            _ret = StdQuestion.delete().where(
                StdQuestion.id == _std_q.id).execute()
            if logger is not None:
                logger.debug(
                    'remove leave message plugin std question config success: %s !'
                    % str(_ret))
示例#4
0
    def delete_collection(self, collection: str, with_question: bool = False):
        """
        删除问题分类

        @param {str} collection - 要删除的问题分类
        @param {bool} with_question=False - 是否同时删除对应的问题
        """
        # 删除collection_order
        _ret = CollectionOrder.delete().where(CollectionOrder.collection == collection).execute()
        self._log_debug(
            'Delete collection_order with collection [%s] success: %s' % (collection, str(_ret))
        )

        # 删除collection
        self.delete_milvus_collection([collection, ], truncate=False)

        # 删除问题,包括扩展问题,问题答案
        if with_question:
            _std_q_list = StdQuestion.select(StdQuestion.id).where(
                StdQuestion.collection == collection
            )
            # 删除扩展问题
            _ret = ExtQuestion.delete().where(ExtQuestion.std_question_id.in_(_std_q_list)).execute()
            self._log_debug(
                'Delete ext_question with collection [%s] success: %s' % (collection, str(_ret))
            )
            # 删除答案
            _ret = Answer.delete().where(Answer.std_question_id.in_(_std_q_list)).execute()
            self._log_debug(
                'Delete answer with collection [%s] success: %s' % (collection, str(_ret))
            )
            # 删除标准问题
            _ret = StdQuestion.delete().where(StdQuestion.collection == collection).execute()
            self._log_debug(
                'Delete std_question with collection [%s] success: %s' % (collection, str(_ret))
            )
示例#5
0
    def import_config(cls, qa_manager: QAManager, logger: Logger):
        """
        添加标准配置(不考虑删除问题)

        @param {QAManager} qa_manager - 数据管理对象
        @param {Logger} logger - 日志对象
        """
        # 插入标准问题
        _std_q = StdQuestion.create(tag='leave_message_direct_action',
                                    q_type='context',
                                    milvus_id=-1,
                                    collection=LEAVE_MESSAGE_PLUGIN_COLLECTION,
                                    partition=LEAVE_MESSAGE_PLUGIN_PARTITION,
                                    question='留言插件通用处理')

        # 插入问题答案
        Answer.create(
            std_question_id=_std_q.id,
            a_type='ask',
            type_param="['LeaveMessagePlugin', 'save_msg', '', '', {}, True]",
            replace_pre_def='N',
            answer='留言插件通用处理')

        if logger is not None:
            logger.info(
                'create leave message plugin std question config success!')

        # 创建留言意图参数
        NlpPurposConfigDict.create(
            action='leave_message',
            match_collection='',
            match_partition='',
            collection=LEAVE_MESSAGE_PLUGIN_COLLECTION,
            partition=LEAVE_MESSAGE_PLUGIN_PARTITION,
            std_question_id=_std_q.id,
            order_num=LEAVE_MESSAGE_PLUGIN_NLP_CONFIG['order_num'],
            exact_match_words=str(
                LEAVE_MESSAGE_PLUGIN_NLP_CONFIG['exact_match_words']),
            exact_ignorecase=LEAVE_MESSAGE_PLUGIN_NLP_CONFIG[
                'exact_ignorecase'],
            match_words=str(LEAVE_MESSAGE_PLUGIN_NLP_CONFIG['match_words']),
            ignorecase=LEAVE_MESSAGE_PLUGIN_NLP_CONFIG['ignorecase'],
            word_scale=LEAVE_MESSAGE_PLUGIN_NLP_CONFIG['word_scale'],
            info=str(LEAVE_MESSAGE_PLUGIN_NLP_CONFIG['info']),
            check=str(LEAVE_MESSAGE_PLUGIN_NLP_CONFIG['check']))
        if logger is not None:
            logger.info('create leave message plugin nlp config success!')

        # 创建文件上传参数
        UploadFileConfig.create(
            upload_type=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['upload_type'],
            exts=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['exts'],
            size=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['size'],
            save_path=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['save_path'],
            url=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['url'],
            rename=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['rename'],
            after=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['after'],
            remark=LEAVE_MESSAGE_PLUGIN_UPLOAD_FILE_CONFIG['remark'])

        if logger is not None:
            logger.info(
                'create leave message plugin upload file config success!')
示例#6
0
    def import_config(cls, qa_manager: QAManager, logger: Logger):
        """
        添加标准配置(不考虑删除问题)

        @param {QAManager} qa_manager - 数据管理对象
        @param {Logger} logger - 日志对象
        """
        FORM_PLUGIN_CONFIG = RunTool.get_global_var('FORM_PLUGIN_CONFIG')
        if FORM_PLUGIN_CONFIG is None:
            FORM_PLUGIN_CONFIG = dict()
            RunTool.set_global_var('FORM_PLUGIN_CONFIG', FORM_PLUGIN_CONFIG)

        FORM_PLUGIN_SELF_TABLE = RunTool.get_global_var(
            'FORM_PLUGIN_SELF_TABLE')
        if FORM_PLUGIN_SELF_TABLE is None:
            FORM_PLUGIN_SELF_TABLE = dict()
            RunTool.set_global_var('FORM_PLUGIN_SELF_TABLE',
                                   FORM_PLUGIN_SELF_TABLE)

        # 插入标准问题
        _std_q = StdQuestion.create(tag='form_direct_action',
                                    q_type='context',
                                    milvus_id=-1,
                                    collection=FORM_PLUGIN_COLLECTION,
                                    partition=FORM_PLUGIN_PARTITION,
                                    question='表单插件通用处理')

        # 插入问题答案
        Answer.create(std_question_id=_std_q.id,
                      a_type='job',
                      type_param="['FormPlugin', 'operate', {}]",
                      replace_pre_def='N',
                      answer='表单插件通用处理')

        if logger is not None:
            logger.info('create form plugin std question config success!')

        # 处理扩展插件
        if FORM_PLUGIN_SEARCH_PATH is not None:
            _path = os.path.join(os.path.dirname(__file__),
                                 FORM_PLUGIN_SEARCH_PATH)
            _file_list = FileTool.get_filelist(path=_path,
                                               regex_str=r'.*\.py$',
                                               is_fullname=False)
            for _file in _file_list:
                if _file == '__init__.py':
                    continue

                # 执行加载
                _module = ImportTool.import_module(_file[0:-3],
                                                   extend_path=_path,
                                                   is_force=True)
                _clsmembers = inspect.getmembers(_module, inspect.isclass)
                for (_class_name, _class) in _clsmembers:
                    if _module.__name__ != _class.__module__:
                        # 不是当前模块定义的函数
                        continue

                    # 判断类型
                    _get_form_type = getattr(_class, 'get_form_type', None)
                    if _get_form_type is None or not callable(_get_form_type):
                        # 不是标准的插件类
                        continue

                    _form_type = _get_form_type()
                    _get_form_config = getattr(_class, 'get_form_config', None)

                    # 加入配置
                    FORM_PLUGIN_CONFIG[_form_type] = _get_form_config()

        # 循环插件实例进行处理
        for _form_type in FORM_PLUGIN_CONFIG.keys():
            _config = FORM_PLUGIN_CONFIG[_form_type]

            # 创建表单类型意图参数
            NlpPurposConfigDict.create(
                action=_form_type,
                match_collection='',
                match_partition='',
                collection=FORM_PLUGIN_COLLECTION,
                partition=FORM_PLUGIN_PARTITION,
                std_question_id=_std_q.id,
                order_num=_config['order_num'],
                exact_match_words=str(_config['exact_match_words']),
                exact_ignorecase=_config['exact_ignorecase'],
                match_words=str(_config['match_words']),
                ignorecase=_config['ignorecase'],
                word_scale=_config['word_scale'],
                info=str(_config['info']),
                check=str(_config['check']))

            if logger is not None:
                logger.info('create form plugin [%s] success!' % _form_type)
示例#7
0
    def _import_answers_by_xls(self, excel_io, milvus: mv.Milvus, bert: BertClient,
                               std_question_id_mapping: dict):
        """
        导入Answers

        @param {object} excel_io - pd.io.excel.ExcelFile的IO文件
        @param {Milvus} milvus - Milvus连接对象
        @param {BertClient} bert - bert服务连接对象
        @param {dict} std_question_id_mapping - 标准问题id映射字典
        """
        try:
            # 读取标题行
            _df_header = pd.read_excel(
                excel_io, sheet_name='Answers', nrows=0, engine=self.excel_engine
            )
        except:
            _df_header = None  # 没有获取到指定的页

        if _df_header is not None:
            # 定义替换变量函数
            def replace_var_fun(m):
                _match_str = m.group(0)
                if _match_str.startswith('{$id='):
                    # 替换为映射id
                    _id: str = _match_str[5: -2]
                    if _id.isdigit():
                        # 是数字
                        _new_id = std_question_id_mapping.get(
                            int(_id), _id
                        )
                    else:
                        # 是字符串
                        _new_id = std_question_id_mapping.get(
                            _id, _id
                        )
                    return str(_new_id)

                # 没有匹配到
                return _match_str

            _skiprows = 1  # 跳过的记录数
            _columns = {i: col for i, col in enumerate(_df_header.columns.tolist())}
            while True:
                # 循环处理
                _df = pd.read_excel(
                    excel_io, sheet_name='Answers', nrows=self.excel_batch_num,
                    header=None, skiprows=_skiprows, engine=self.excel_engine
                )
                _skiprows += self.excel_batch_num

                if not _df.shape[0]:
                    # 获取不到数据
                    break

                # 变更标题
                _df.rename(columns=_columns, inplace=True)

                for _index, _row in _df.iterrows():
                    # 逐行添加标准问题答案, _index为行,_row为数据集
                    try:
                        _std_question_id = std_question_id_mapping.get(
                            _row['std_question_id'], _row['std_question_id']
                        )
                        _type_param = re.sub(
                            r'\{\$.+?\$\}', replace_var_fun, str(_row['type_param']), re.M
                        )
                        Answer.create(
                            std_question_id=_std_question_id, a_type=_row['a_type'],
                            type_param=_type_param, replace_pre_def=_row['replace_pre_def'],
                            answer=_row['answer']
                        )
                    except:
                        self._log_error('imported answer [id: %s] [%s] error: %s' % (
                            str(_row['std_question_id']), _row['answer'], traceback.format_exc()
                        ))

                self._log_debug('imported answers[%d]: %s' % (_skiprows, str(_df)))
示例#8
0
    def add_std_question(self, question: str, collection: str = 'chat', q_type: str = 'ask',
                         partition: str = None, answer: str = None, a_type: str = 'text',
                         replace_pre_def: str = 'N', a_type_param: str = '') -> int:
        """
        添加标准问题

        @param {str} question - 标准问题
        @param {str} collection='chat' - 问题分类,默认为'chat', 可以自定义分类
        @param {str} q_type='ask' - 问题类型
            ask-问答类(问题对应答案)
            context-场景类(问题对应上下文场景)
        @param {str} partition=None - 问题所属场景, q_type为context时使用
        @param {str} answer=None - 标准问题对应的答案
        @param {string} a_type='text' - 答案类型
            text-文字答案
        @param {str} replace_pre_def='N' - 是否替换答案的预定义字符
        @param {string} a_type_param='' - 答案类型扩展参数
        @returns {int} - 返回问题记录对应的id
        """
        # 简单校验
        if q_type == 'ask' and answer is None:
            raise AttributeError('parameter answer should be not None!')

        # 获取问题的向量值
        with self.get_bert_client() as _bert, self.get_milvus() as _milvus:
            _vectors = _bert.encode([question, ])
            _question_vectors = self.normaliz_vec(_vectors.tolist())
            self._log_debug('get question vectors: %s' % str(len(_question_vectors)))

            # 存入Milvus服务, 先创建分类
            self._add_collection(collection, _milvus)

            # 创建场景
            if partition is not None and partition != '':
                self._add_partition(collection, partition, _milvus)
            else:
                partition = None

            _milvus_id = self._add_milvus_question(
                _question_vectors[0], collection, partition, _milvus
            )

        # 存入AnswerDb,通过事务处理
        with self.database.atomic() as _txn:
            # 插入标准问题
            _std_q = StdQuestion.create(
                q_type=q_type, milvus_id=_milvus_id, collection=collection,
                partition=('' if partition is None else partition),
                question=question
            )

            # 插入对应的答案
            if answer is not None:
                Answer.create(
                    std_question_id=_std_q.id, a_type=a_type, replace_pre_def=replace_pre_def,
                    type_param=a_type_param, answer=answer
                )

            # 提交事务
            _txn.commit()

        # 返回结果
        self._log_debug('insert question: %s' % str(_std_q))
        return _std_q.id