Пример #1
0
    def save_info_with_para(cls, question: str, session_id: str,
                            match_list: list, qa: QA, qa_manager: QAManager,
                            **kwargs) -> list:
        """
        将传参的参数值保存到session的info字典中

        @param {str} question - 原始问题
        @param {str} session_id - session_id
        @param {list} match_list - 匹配上的问题、答案对象: [(StdQuestion, Answer)]
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 要保存的参数

        @returns {list} - 按照不同的处理要求返回内容
            'answer', [str, ...]  - 直接返回回复内容,第二个参数为回复内容
                注:如果第二个参数返回None代表使用传入的答案的answer字段作为提示
            'to', int - 跳转到指定问题处理,第二个参数为std_question_id
        """
        _info = dict()
        for _key in kwargs:
            if _key not in ('action', 'is_sure'):
                _info[_key] = kwargs[_key]

        qa.update_session_info(session_id, _info)

        # 直接返回当前答案的提示信息
        return 'answer', None
Пример #2
0
    def _complaint_op_predeal(cls, op_para: dict, question: str,
                              session_id: str, qa: QA, qa_manager: QAManager,
                              **kwargs):
        """
        留言的表单操作预处理函数

        @param {dict} op_para - 上送的表单操作参数
        @param {str} question - 原始问题
        @param {str} session_id - session_id
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 扩展传入参数

        @returns {str, object} - 返回控制处理的参数: action, action_para
            action取值的类型和对应的行为如下:
            'answer', [str, ...]  - 直接返回回复内容,第二个参数为回复内容
            'to', int - 跳转到指定问题处理,第二个参数为std_question_id
            'save', None - 根据op_para参数保存表单
            'upd', None - 根据op_para参数更新表单
            'preview', None - 获取表单预览信息
            'get', None - 获取表单完整信息
            'create', None - 创建表单
        """
        # 检查用户是否已登陆
        if qa.get_info_by_key(session_id, 'user_id', -1) == -1:
            return 'answer', [
                '亲, 投诉需要先登陆哦',
            ]

        # 不改变操作模式
        return op_para['action'], None
Пример #3
0
    def multiple_save_info(cls, question: str, session_id: str,
                           context_id: str, std_question_id: int,
                           collection: str, partition: str, qa: QA,
                           qa_manager: QAManager, **kwargs):
        """
        多轮问答保存信息至session的info中

        @param {str} question - 客户反馈的信息文本(提问回答)
        @param {str} session_id - 客户的session id
        @param {str} context_id - 上下文临时id
        @param {int} std_question_id - 上下文中对应的提问问题id
        @param {str} collection - 提问答案参数指定的问题分类
        @param {str} partition - 提问答案参数指定的场景标签
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 扩展传入参数
            ask {list} - 问题列表,注意最后的问题必须有tips或to参数
                [
                    {'info_key': '保存的key值', 'next_tips': '下一个问题'},
                    ...,
                    {'info_key': '保存的key值', 'tips': '处理完成提示', 'to': 跳转到指定问题id},
                ]
        @param {str, object} - 按照不同的处理要求返回内容
            'answer', [str, ...]  - 直接返回回复内容,第二个参数为回复内容
            'to', int - 跳转到指定问题处理,第二个参数为std_question_id
            'again', [str, ...] - 再获取一次答案,第二个参数为提示内容,如果第2个参数为None代表使用原来的参数再提问一次
            'break', [collection, partition] - 跳出问题(让问题继续走匹配流程),可以返回[collection, partition]变更分类和场景
            默认为'again'
        """
        _step = qa.get_cache_value(session_id, 'multiple_save_info',
                                   {}).get(context_id, 0)
        _ask = kwargs.get('ask')[_step]

        # 保存值
        _info_key = _ask.get('info_key')
        _info = dict()
        _info[_info_key] = question
        qa.update_session_info(session_id, _info)

        # 处理下一个问题
        _to = _ask.get('to', None)
        _tips = _ask.get('tips', None)
        _next_tips = _ask.get('next_tips', None)
        if _to is not None or _tips is not None:
            # 最后一个问题
            qa.del_cache(session_id, 'multiple_save_info')
            if _to is None:
                return 'answer', [
                    _tips,
                ]
            else:
                return 'to', _to
        else:
            # 还有下一个问题
            qa.add_cache(session_id, 'multiple_save_info',
                         {context_id: _step + 1})
            return 'again', [
                _next_tips,
            ]
Пример #4
0
    def _complaint_get_default(cls, question: str, session_id: str, qa: QA,
                               qa_manager: QAManager, **kwargs) -> dict:
        """
        投诉表单默认值字典的函数

        @param {str} question - 原始问题
        @param {str} session_id - session_id
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 扩展传入参数

        @returns {dict} - 返回的默认值字典
        """
        _dict = {
            'user_id': qa.get_info_by_key(session_id, 'user_id', -1),
            'user_name': qa.get_info_by_key(session_id, 'user_name', ''),
        }
        if question not in ['投诉', '我要投诉']:
            _dict['content'] = question

        return _dict
Пример #5
0
    def save_info(cls, question: str, session_id: str, context_id: str,
                  std_question_id: int, collection: str, partition: str,
                  qa: QA, qa_manager: QAManager, **kwargs):
        """
        直接保存信息至session的info中

        @param {str} question - 客户反馈的信息文本(提问回答)
        @param {str} session_id - 客户的session id
        @param {str} context_id - 上下文临时id
        @param {int} std_question_id - 上下文中对应的提问问题id
        @param {str} collection - 提问答案参数指定的问题分类
        @param {str} partition - 提问答案参数指定的场景标签
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 扩展传入参数
            info_key {str} - 从info字典中设置的key值
            to {int} - 处理完成后跳转到要处理的问题id,如果有该值则tips参数无效
            tips {str} - 处理完成的提示信息

        @returns {str, object} - 按照不同的处理要求返回内容
            'answer', [str, ...]  - 直接返回回复内容,第二个参数为回复内容
            'to', int - 跳转到指定问题处理,第二个参数为std_question_id
            'again', [str, ...] - 再获取一次答案,第二个参数为提示内容,如果第2个参数为None代表使用原来的参数再提问一次
            'break', [collection, partition] - 跳出问题(让问题继续走匹配流程),可以返回[collection, partition]变更分类和场景
            默认为'again'
        """
        _info_key = kwargs.get('info_key')
        _tips = kwargs.get('tips', 'save success!')
        _to = kwargs.get('to', None)
        _info = dict()
        _info[_info_key] = question
        qa.update_session_info(session_id, _info)
        if _to is None:
            return 'answer', [
                _tips,
            ]
        else:
            return 'to', _to
Пример #6
0
    def save_msg(cls, question: str, session_id: str, context_id: str,
                 std_question_id: int, collection: str, partition: str, qa: QA,
                 qa_manager: QAManager, **kwargs):
        """
        保存留言信息

        @param {str} question - 客户反馈的信息文本(提问回答)
        @param {str} session_id - 客户的session id
        @param {str} context_id - 上下文临时id
        @param {int} std_question_id - 上下文中对应的提问问题id
        @param {str} collection - 提问答案参数指定的问题分类
        @param {str} partition - 提问答案参数指定的场景标签
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 扩展传入参数

        @returns {str, object} - 按照不同的处理要求返回内容
            'answer', [str, ...]  - 直接返回回复内容,第二个参数为回复内容
            'to', int - 跳转到指定问题处理,第二个参数为std_question_id
            'again', [str, ...] - 再获取一次答案,第二个参数为提示内容,如果第2个参数为None代表使用原来的参数再提问一次
            'break', [collection, partition] - 跳出问题(让问题继续走匹配流程),可以返回[collection, partition]变更分类和场景
            默认为'again'
        """
        _context_dict = qa.get_context_dict(session_id)
        if 'leave_message' not in _context_dict['ask'].keys():
            # 第一次进入留言模块
            _leave_message = {
                'context_id': context_id,  # 临时会话id
                'ref_id': -1,
                'ref_msg': '',
                'pic_urls': [],
            }
            if 'action' in kwargs.keys():
                # 操作意图发起,属于客户聊天发起的留言
                if kwargs['match_type'] == 'nlp_match' and len(question) > len(
                        kwargs['match_word']) + 4:
                    # 分词模式并且留言后面有内容,可以直接保存
                    _leave_message['msg'] = question
            else:
                # 直接发起的留言,可以传入引用信息
                _ref_info = eval(question)
                _ref_id = _ref_info.get('ref_id', -1)
                if _ref_id != -1:
                    _ref_data = LeaveMessagePluginData.get_or_none(
                        LeaveMessagePluginData.id == _ref_id)
                    if _ref_data is not None and _ref_data.ref_id != -1:
                        _ref_id = _ref_data.ref_id

                _leave_message['ref_id'] = _ref_id
                _leave_message['ref_msg'] = _ref_info.get('ref_msg', '')
        else:
            # 第二次进入留言模块,检查是否上传图片的记录
            _leave_message = _context_dict['ask']['leave_message']
            try:
                _op_para = eval(question)
                _context_id = _op_para.get('context_id', '')
                if _context_id != context_id:
                    # 非本次会话的留言
                    return 'answer', [
                        LEAVE_MESSAGE_PLUGIN_TIPS['context_error']
                    ]

                if 'action' in _op_para.keys():
                    if _op_para['action'] == 'upload_file':
                        _leave_message['pic_urls'].append(_op_para['url'])
                    elif _op_para['action'] == 'cancle':
                        # 取消留言
                        return 'answer', [{
                            'data_type':
                            'leave_message',
                            'tips':
                            LEAVE_MESSAGE_PLUGIN_TIPS['cancle'],
                            'action':
                            'cancle',
                            'context_id':
                            context_id
                        }]
                    else:
                        _leave_message['msg'] = question
                else:
                    _leave_message['msg'] = question
            except:
                _leave_message['msg'] = question

        if 'msg' not in _leave_message.keys():
            # 第一次处理,保存问题缓存并提示客户回复留言
            _context_dict['ask']['leave_message'] = _leave_message
            qa.add_ask_context(session_id, _context_dict['ask'])
            if len(_leave_message['pic_urls']) > 0:
                return 'again', [
                    LEAVE_MESSAGE_PLUGIN_TIPS['upload_success'],
                ]
            else:
                return 'again', [{
                    'data_type':
                    'leave_message',
                    'tips':
                    LEAVE_MESSAGE_PLUGIN_TIPS['start_tips'],
                    'action':
                    'add',
                    'context_id':
                    context_id
                }]
        else:
            # 保存留言
            _data = LeaveMessagePluginData.create(
                ref_id=_leave_message['ref_id'],
                user_id=qa.get_info_by_key(session_id, 'user_id', default=-1),
                user_name=qa.get_info_by_key(session_id, 'user_name', ''),
                msg=_leave_message['msg'],
                ref_msg=_leave_message['ref_msg'],
                pic_urls=','.join(_leave_message['pic_urls']),
            )

            # 返回客户提示
            return 'answer', [{
                'data_type': 'leave_message',
                'tips': LEAVE_MESSAGE_PLUGIN_TIPS['success'],
                'action': 'success',
                'context_id': context_id,
                'msg_id': _data.id,
            }]
Пример #7
0
    def __init__(self, server_config: dict, app: Flask = None, **kwargs):
        """
        初始化QA问答服务

        @param {dict} server_config - 服务配置字典
        @param {Flask} app=None - 服务
        """
        self.debug = server_config.get('debug', True)
        self.execute_path = server_config['execute_path']

        # 日志处理
        self.logger: Logger = None
        if 'logger' in server_config.keys():
            _logger_config = server_config['logger']
            if len(_logger_config['conf_file_name']
                   ) > 0 and _logger_config['conf_file_name'][0] == '.':
                # 相对路径
                _logger_config['conf_file_name'] = os.path.join(
                    self.execute_path, _logger_config['conf_file_name'])
            if len(_logger_config['logfile_path']
                   ) > 0 and _logger_config['logfile_path'][0] == '.':
                # 相对路径
                _logger_config['logfile_path'] = os.path.join(
                    self.execute_path, _logger_config['logfile_path'])
            self.logger = Logger.create_logger_by_dict(_logger_config)

        self.server_config = server_config
        self.app = app
        if self.app is None:
            self.app = Flask(__name__)
            CORS(self.app)

        self.app.debug = self.debug
        self.app.send_file_max_age_default = datetime.timedelta(
            seconds=1)  # 设置文件缓存1秒
        self.app.config['JSON_AS_ASCII'] = False  # 显示中文
        # 上传文件大小限制
        self.app.config['MAX_CONTENT_LENGTH'] = math.floor(
            self.server_config['max_upload_size'] * 1024 * 1024)

        # 插件字典,先定义清单,启动前完成加载
        self.extend_plugin_path = self.server_config.get(
            'extend_plugin_path', '')
        self.plugins = dict()

        # 装载数据管理模块
        self.qa_manager = QAManager(
            self.server_config['answerdb'],
            self.server_config['milvus'],
            self.server_config['bert_client'],
            logger=self.logger,
            excel_batch_num=self.server_config['excel_batch_num'],
            excel_engine=self.server_config['excel_engine'])

        # 装载NLP
        _nlp_config = self.server_config['nlp_config']
        _user_dict = None
        if _nlp_config['user_dict'] != '':
            _user_dict = _nlp_config['user_dict']
            if _user_dict.startswith('.'):
                # 相对路径
                _user_dict = os.path.join(self.execute_path, _user_dict)
        _set_dictionary = None
        if _nlp_config['set_dictionary'] != '':
            _set_dictionary = _nlp_config['set_dictionary']
            if _set_dictionary.startswith('.'):
                # 相对路径
                _set_dictionary = os.path.join(self.execute_path,
                                               _set_dictionary)
        self.nlp = NLP(plugins=self.plugins,
                       data_manager_para=self.qa_manager.DATA_MANAGER_PARA,
                       set_dictionary=None if _nlp_config['set_dictionary']
                       == '' else _nlp_config['set_dictionary'],
                       user_dict=_user_dict,
                       enable_paddle=_nlp_config['enable_paddle'],
                       parallel_num=_nlp_config.get('parallel_num', None),
                       logger=self.logger)

        # 初始化QA模块
        self.qa = QA(self.qa_manager,
                     self.nlp,
                     self.server_config['execute_path'],
                     plugins=self.plugins,
                     qa_config=self.server_config['qa_config'],
                     redis_config=self.server_config['redis'],
                     logger=self.logger)

        # 动态加载路由
        self.api_class = [Qa, QaDataManager]

        # 完成插件的加载
        # plugins函数字典,格式为{'type':{'class_name': {'fun_name': fun, }, },}
        self.load_plugins(os.path.join(self.execute_path, 'plugins'))
        if self.extend_plugin_path != '':
            if self.extend_plugin_path[0:1] == '.':
                # 相对路径
                self.extend_plugin_path = os.path.join(self.execute_path,
                                                       self.extend_plugin_path)

            self.load_plugins(self.extend_plugin_path)

        # 安全关联
        _security = self.server_config['security']
        self.token_serializer = Serializer(
            _security['secret_key'],
            _security['token_expire'],
            salt=bytes(_security['salt'], encoding='utf-8'),
            algorithm_name=_security['algorithm_name'])
        # 验证ip白名单处理
        _security['token_server_auth_ip_list'] = _security[
            'token_server_auth_ip_list'].split(',')

        # 增加令牌服务的路由
        if _security['enable_token_server']:
            self.api_class.append(TokenServer)

        # 增加静态路径
        _static_path = self.server_config['static_path']
        if _static_path[0:1] == '.':
            # 相对路径
            _static_path = os.path.realpath(
                os.path.join(self.execute_path, _static_path))

        self.app.static_folder = os.path.join(_static_path, 'static')
        self.app.static_url_path = '/static/'

        # 增加客户端路由
        if self.server_config['enable_client']:
            # 客户端路由api服务
            self.api_class.append(Client)

            # 创建测试用户
            if self.server_config['add_test_login_user']:
                _user = RestfulApiUser.get_or_none(
                    RestfulApiUser.user_name == 'test')
                if _user is None:
                    self.register_user('test', '123456')

            # 加入客户端主页
            self.app.url_map.add(Rule('/', endpoint='client', methods=['GET']))
            self.app.view_functions['client'] = self._client_view_function

        FlaskTool.add_route_by_class(self.app, self.api_class)
        self._log_debug(str(self.app.url_map))
Пример #8
0
    def test_pay_fun(cls, question: str, session_id: str, context_id: str,
                     std_question_id: int, collection: str, partition: str,
                     qa: QA, qa_manager: QAManager, **kwargs):
        """
        多轮转账的示例

        @param {str} question - 客户反馈的信息文本(提问回答)
        @param {str} session_id - 客户的session id
        @param {str} context_id - 上下文临时id
        @param {int} std_question_id - 上下文中对应的提问问题id
        @param {str} collection - 提问答案参数指定的问题分类
        @param {str} partition - 提问答案参数指定的场景标签
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 扩展传入参数

        @returns {str, object} - 按照不同的处理要求返回内容
            'answer', [str, ...]  - 直接返回回复内容,第二个参数为回复内容
            'to', int - 跳转到指定问题处理,第二个参数为std_question_id
            'again', [str, ...] - 再获取一次答案,第二个参数为提示内容,如果第2个参数为None代表使用原来的参数再提问一次
            默认为'again'
        """
        _cache = qa.get_cache_dict(session_id,
                                   default={},
                                   context_id=context_id)

        # 存入转账信息
        if 'step' not in _cache.keys():
            # 通过nlp意图发起的处理
            qa.update_cache_dict(session_id, kwargs, context_id=context_id)
            _cache = qa.get_cache_dict(session_id,
                                       default={},
                                       context_id=context_id)
        elif _cache['step'] == 'in_name':
            _cache['in_name'] = question
            qa.add_cache(session_id,
                         'in_name',
                         question,
                         context_id=context_id)
        elif _cache['step'] == 'amount':
            _cache['amount'] = question
            qa.add_cache(session_id, 'amount', question, context_id=context_id)
        elif _cache['step'] == 'confirm':
            if question == '是的':
                return 'answer', ['执行向 {$cache=in_name$} 转账 {$cache=amount$}']
            else:
                return 'answer', ['取消转账操作']

        # 判断要问的问题
        if 'in_name' not in _cache.keys():
            qa.add_cache(session_id, 'step', 'in_name', context_id=context_id)
            return 'again', ['请输入收款人名称']
        elif 'amount' not in _cache.keys():
            qa.add_cache(session_id, 'step', 'amount', context_id=context_id)
            return 'again', ['请输入转账金额']
        else:
            # 最后一次确认
            qa.add_cache(session_id, 'step', 'confirm', context_id=context_id)
            return 'again', [
                '您确定要向 {$cache=in_name$} 转账 {$cache=amount$} 吗?输入 是的 执行转账操作,输入其他将取消操作'
            ]
Пример #9
0
    def weather(cls, question: str, session_id: str, context_id: str,
                std_question_id: int, collection: str, partition: str, qa: QA,
                qa_manager: QAManager, **kwargs):
        """
        查询天气

        @param {str} question - 客户反馈的信息文本(提问回答)
        @param {str} session_id - 客户的session id
        @param {str} context_id - 上下文临时id
        @param {int} std_question_id - 上下文中对应的提问问题id
        @param {str} collection - 提问答案参数指定的问题分类
        @param {str} partition - 提问答案参数指定的场景标签
        @param {QA} qa - 服务器的问答处理模块实例对象
        @param {QAManager} qa_manager - 服务器的问答数据管理实例对象
        @param {kwargs} - 扩展传入参数
            time {list} - nlp分词的时间词语列表
            addr {list} - nlp分词的地址词语列表

        @returns {str, object} - 按照不同的处理要求返回内容
            'answer', [str, ...]  - 直接返回回复内容,第二个参数为回复内容
            'to', int - 跳转到指定问题处理,第二个参数为std_question_id
            'again', [str, ...] - 再获取一次答案,第二个参数为提示内容,如果第2个参数为None代表使用原来的参数再提问一次
            'break', [collection, partition] - 跳出问题(让问题继续走匹配流程),可以返回[collection, partition]变更分类和场景
            默认为'again'
        """
        _match_city_code = ''
        _match_day = 0  # 0代表当前,1代表明天,2代表后天
        # 检查是不是第二次的选项
        _cache_info = qa.get_cache_value(session_id,
                                         'weather',
                                         default=None,
                                         context_id=context_id)
        if _cache_info is not None:
            if question.isdigit():
                if question not in _cache_info['addr_dict'].keys():
                    # 回答不在选项范围
                    return 'again', [
                        '亲, 您输入的序号不对, 请输入正确的序号选择城市',
                    ]
                else:
                    _match_day = _cache_info['day']
                    _match_city_code = _cache_info['addr_dict'][question]
            else:
                # 不是选项,是文字
                return 'answer', [
                    WEATHER_ERROR,
                ]
        else:
            # 尝试获取日期
            for _word in kwargs.get('time', []):
                if _word in ['明天']:
                    _match_day = 1
                elif _word in ['后天']:
                    _match_day = 2

            # 尝试获取地址
            with redis.Redis(connection_pool=qa.redis_pool) as _redis:
                _match_addr = cls._search_city(kwargs.get('addr', []), _redis)
                if len(_match_addr) == 0:
                    # 没有匹配到地址,尝试获取客户info中的地址
                    _addr = qa.get_info_by_key(session_id, 'addr', default='')

                    if _addr == '' and WEATHER_TRY_USE_IP_ADDR:
                        # 尝试通过IP地址获取地址
                        _addr = cls._get_addr_by_ip(
                            qa.get_info_by_key(session_id, 'ip', default=''),
                            qa)

                    if _addr != '':
                        _addrs_with_class = qa.nlp.cut_sentence(_addr)
                        _addrs = [item[0] for item in _addrs_with_class]
                        _match_addr = cls._search_city(_addrs, _redis)

                _len = len(_match_addr)
                if _len == 1:
                    _match_city_code = _match_addr[0][1][0]
                elif _len > 1:
                    # 匹配到多个,进行提问让客户选择
                    _tips = ['找到了多个地址,您想查询哪个地址的天气?请输入序号进行选择: ']
                    _index = 1
                    _cache_info = dict()
                    _cache_info['day'] = _match_day
                    _cache_info['addr_dict'] = dict()
                    for item in _match_addr:
                        # 加入到上下文
                        _cache_info['addr_dict'][str(_index)] = item[1][0]

                        # 获取详细地址
                        _tips.append(
                            '%d.%s' %
                            (_index, cls._get_city_full_name(item[0], _redis)))
                        _index += 1

                    # 添加到cache中
                    qa.add_cache(session_id,
                                 'weather',
                                 _cache_info,
                                 context_id=context_id)

                    # 重新提问
                    return 'again', _tips

        if _match_city_code == '':
            # 没有找到地址,直接返回退出
            return 'answer', [
                WEATHER_ERROR,
            ]

        # 查询天气, 先查本地缓存
        _today = datetime.datetime.now().strftime('%Y-%m-%d')
        with redis.Redis(connection_pool=qa.redis_pool) as _redis:
            _json = _redis.get('api_tool_ask:weather:cache:%s:%s' %
                               (_match_city_code, _today))
            if _json is None:
                # 要进行查询
                _api_back = NetTool.restful_api_call(
                    'http://t.weather.sojson.com/api/weather/city/%s' %
                    _match_city_code,
                    back_type='text',
                    logger=qa.logger)
                if _api_back['is_success']:
                    _json = _api_back['back_object']
                    _wdata = json.loads(_json)
                    if _wdata['status'] == 200:
                        # 存入缓存,一天到期
                        _redis.set('api_tool_ask:weather:cache:%s:%s' %
                                   (_match_city_code, _today),
                                   _json,
                                   ex=86400)
                    else:
                        return 'answer', [
                            WEATHER_ERROR,
                        ]
                else:
                    # 获取失败
                    return 'answer', [
                        WEATHER_ERROR,
                    ]
            else:
                _wdata = json.loads(_json)

        # 返回结果
        _end_str = ''  # 结束语
        if _match_day == 0:
            _end_str = '湿度%s, PM2.5: %s, 空气质量%s, %s, ' % (
                _wdata['data']['shidu'], _wdata['data']['pm25'],
                _wdata['data']['quality'], _wdata['data']['ganmao'])

        _answer = '亲, %s%s的天气%s, 吹%s%s, %s, %s, %s%s' % (
            _wdata['cityInfo']['city'],
            _wdata['data']['forecast'][_match_day]['ymd'],
            _wdata['data']['forecast'][_match_day]['type'],
            _wdata['data']['forecast'][_match_day]['fl'],
            _wdata['data']['forecast'][_match_day]['fx'],
            _wdata['data']['forecast'][_match_day]['high'],
            _wdata['data']['forecast'][_match_day]['low'], _end_str,
            _wdata['data']['forecast'][_match_day]['notice'])

        return 'answer', [
            _answer,
        ]