diff --git a/plugins/bdunit/README.md b/plugins/bdunit/README.md new file mode 100644 index 0000000..56ac5c7 --- /dev/null +++ b/plugins/bdunit/README.md @@ -0,0 +1,14 @@ +利用百度UNIT实现智能对话 + 1.解决问题:chatgpt无法处理的指令,交给百度UNIT处理如:天气,日期时间,数学运算等 + 2.如问时间:现在几点钟,今天几号 + 3.如问天气:明天广州天气怎么样,这个周末深圳会不会下雨 + 4.如问数学运算:23+45=多少,100-23=多少,35转化为二进制是多少? +大家可以在百度UNIT官网上自己创建应用,申请百度机器人,可以把预先训练好的模型导入到自己的应用中, + see https://ai.baidu.com/unit/home#/home?track=61fe1b0d3407ce3face1d92cb5c291087095fc10c8377aaf + https://console.bce.baidu.com/ai平台申请 + 然后在百度UNIT官网上获取应用的API Key和Secret Key,然后在config.json中配置 + { + "service_id": "s...", #"机器人ID" + "api_key": "", + "secret_key": "" + } \ No newline at end of file diff --git a/plugins/bdunit/__init__.py b/plugins/bdunit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/bdunit/bdunit.py b/plugins/bdunit/bdunit.py new file mode 100644 index 0000000..c991727 --- /dev/null +++ b/plugins/bdunit/bdunit.py @@ -0,0 +1,297 @@ +# encoding:utf-8 +import json +import os +import uuid +import requests +from bridge.context import ContextType +from bridge.reply import Reply, ReplyType +from common.log import logger +import plugins +from plugins import * +from uuid import getnode as get_mac + + +"""利用百度UNIT实现智能对话 + 如果命中意图,返回意图对应的回复,否则返回继续交付给下个插件处理 +""" + + +@plugins.register(name="BDunit", desc="Baidu unit bot system", version="0.1", author="jackson", desire_priority=0) +class BDunit(Plugin): + def __init__(self): + super().__init__() + try: + curdir = os.path.dirname(__file__) + config_path = os.path.join(curdir, "config.json") + conf = None + if not os.path.exists(config_path): + conf = {"service_id": "", "api_key": "", + "secret_key": ""} + with open(config_path, "w") as f: + json.dump(conf, f, indent=4) + else: + with open(config_path, "r") as f: + conf = json.load(f) + self.service_id = conf["service_id"] + self.api_key = conf["api_key"] + self.secret_key = conf["secret_key"] + self.access_token = self.get_token() + self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context + logger.info("[BDunit] inited") + except Exception as e: + logger.warn( + "BDunit init failed: %s, ignore " % e) + + def on_handle_context(self, e_context: EventContext): + + if e_context['context'].type != ContextType.TEXT: + return + + content = e_context['context'].content + logger.debug("[BDunit] on_handle_context. content: %s" % content) + parsed = self.getUnit2(content) + intent = self.getIntent(parsed) + if intent: # 找到意图 + logger.debug("[BDunit] Baidu_AI Intent= %s", intent) + reply = Reply() + reply.type = ReplyType.TEXT + reply.content = self.getSay(parsed) + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑 + else: + e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑 + + def get_help_text(self, **kwargs): + help_text = "本插件会处理询问实时日期时间,天气,数学运算等问题,这些技能由您的百度智能对话UNIT决定\n" + return help_text + + def get_token(self): + """获取访问百度UUNIT 的access_token + #param api_key: UNIT apk_key + #param secret_key: UNIT secret_key + Returns: + string: access_token + """ + url = "https://aip.baidubce.com/oauth/2.0/token?client_id={}&client_secret={}&grant_type=client_credentials".format( + self.api_key, self.secret_key) + payload = "" + headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + + # print(response.text) + return response.json()['access_token'] + + def getUnit(self, query): + """ + NLU 解析version 3.0 + :param query: 用户的指令字符串 + :returns: UNIT 解析结果。如果解析失败,返回 None + """ + + url = ( + 'https://aip.baidubce.com/rpc/2.0/unit/service/v3/chat?access_token=' + + self.access_token + ) + request = {"query": query, "user_id": str( + get_mac())[:32], "terminal_id": "88888"} + body = { + "log_id": str(uuid.uuid1()), + "version": "3.0", + "service_id": self.service_id, + "session_id": str(uuid.uuid1()), + "request": request, + } + try: + headers = {"Content-Type": "application/json"} + response = requests.post(url, json=body, headers=headers) + return json.loads(response.text) + except Exception: + return None + + def getUnit2(self, query): + """ + NLU 解析 version 2.0 + + :param query: 用户的指令字符串 + :returns: UNIT 解析结果。如果解析失败,返回 None + """ + url = ( + "https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=" + + self.access_token + ) + request = {"query": query, "user_id": str(get_mac())[:32]} + body = { + "log_id": str(uuid.uuid1()), + "version": "2.0", + "service_id": self.service_id, + "session_id": str(uuid.uuid1()), + "request": request, + } + try: + headers = {"Content-Type": "application/json"} + response = requests.post(url, json=body, headers=headers) + return json.loads(response.text) + except Exception: + return None + + def getIntent(self, parsed): + """ + 提取意图 + + :param parsed: UNIT 解析结果 + :returns: 意图数组 + """ + if ( + parsed + and "result" in parsed + and "response_list" in parsed["result"] + ): + try: + return parsed["result"]["response_list"][0]["schema"]["intent"] + except Exception as e: + logger.warning(e) + return "" + else: + return "" + + def hasIntent(self, parsed, intent): + """ + 判断是否包含某个意图 + + :param parsed: UNIT 解析结果 + :param intent: 意图的名称 + :returns: True: 包含; False: 不包含 + """ + if ( + parsed + and "result" in parsed + and "response_list" in parsed["result"] + ): + response_list = parsed["result"]["response_list"] + for response in response_list: + if ( + "schema" in response + and "intent" in response["schema"] + and response["schema"]["intent"] == intent + ): + return True + return False + else: + return False + + def getSlots(self, parsed, intent=""): + """ + 提取某个意图的所有词槽 + + :param parsed: UNIT 解析结果 + :param intent: 意图的名称 + :returns: 词槽列表。你可以通过 name 属性筛选词槽, + 再通过 normalized_word 属性取出相应的值 + """ + if ( + parsed + and "result" in parsed + and "response_list" in parsed["result"] + ): + response_list = parsed["result"]["response_list"] + if intent == "": + try: + return parsed["result"]["response_list"][0]["schema"]["slots"] + except Exception as e: + logger.warning(e) + return [] + for response in response_list: + if ( + "schema" in response + and "intent" in response["schema"] + and "slots" in response["schema"] + and response["schema"]["intent"] == intent + ): + return response["schema"]["slots"] + return [] + else: + return [] + + def getSlotWords(self, parsed, intent, name): + """ + 找出命中某个词槽的内容 + + :param parsed: UNIT 解析结果 + :param intent: 意图的名称 + :param name: 词槽名 + :returns: 命中该词槽的值的列表。 + """ + slots = self.getSlots(parsed, intent) + words = [] + for slot in slots: + if slot["name"] == name: + words.append(slot["normalized_word"]) + return words + + def getSayByConfidence(self, parsed): + """ + 提取 UNIT 置信度最高的回复文本 + + :param parsed: UNIT 解析结果 + :returns: UNIT 的回复文本 + """ + if ( + parsed + and "result" in parsed + and "response_list" in parsed["result"] + ): + response_list = parsed["result"]["response_list"] + answer = {} + for response in response_list: + if ( + "schema" in response + and "intent_confidence" in response["schema"] + and ( + not answer + or response["schema"]["intent_confidence"] + > answer["schema"]["intent_confidence"] + ) + ): + answer = response + return answer["action_list"][0]["say"] + else: + return "" + + def getSay(self, parsed, intent=""): + """ + 提取 UNIT 的回复文本 + + :param parsed: UNIT 解析结果 + :param intent: 意图的名称 + :returns: UNIT 的回复文本 + """ + if ( + parsed + and "result" in parsed + and "response_list" in parsed["result"] + ): + response_list = parsed["result"]["response_list"] + if intent == "": + try: + return response_list[0]["action_list"][0]["say"] + except Exception as e: + logger.warning(e) + return "" + for response in response_list: + if ( + "schema" in response + and "intent" in response["schema"] + and response["schema"]["intent"] == intent + ): + try: + return response["action_list"][0]["say"] + except Exception as e: + logger.warning(e) + return "" + return "" + else: + return "" diff --git a/plugins/bdunit/config.json.template b/plugins/bdunit/config.json.template new file mode 100644 index 0000000..aac83b1 --- /dev/null +++ b/plugins/bdunit/config.json.template @@ -0,0 +1,5 @@ +{ + "service_id": "s...", + "api_key": "", + "secret_key": "" +} \ No newline at end of file