--- title: "从0-1:如何构建一个基于知识图谱的智能问答助手?" date: 2021-12-30T14:04:31+08:00 draft: false lightgallery: true tags: ["Nebula Graph", "图数据库", "智能问答", "语音助手", "KBQA"] categories: ["Nebula Graph", "Mini Project"] description: "如何利用图数据库从0-1构建一个特定领域问答助手?本文手把手带你构建一个简易版的篮球领域智能问答机器人。" featuredImage: "featured-image.webp" featuredImagePreview: "featured-image.webp" --- > 如何利用图数据库从0-1构建一个特定领域问答助手?本文手把手带你构建一个简易版的篮球领域智能问答机器人。 <!--more--> ## 前言 「问答机器人」在我们日常生活中并不少见到 :像是一些电商客服、智能问诊、技术支持等人工输入与沟通界面的场景下,机器人“智能”问答系统一定程度上可以在无需人力、不需要耗费终端用户心智去做知识库、商品搜索、科室选择等等的情况下实时给出问题答案。 问答机器人系统背后的技术有多重可能: - 基于检索,全文搜索接近的问题 - 基于[机器学习阅读理解](https://arxiv.org/abs/1710.10723) - 基于知识图谱(Knowledge-Based Question Answering system: KBQA) - 其他 基于知识图谱构建问答系统在以下三个情况下很有优势: - 对于领域类型是结构化数据场景:电商、医药、系统运维(微服务、服务器、事件)、产品支持系统等,其中作为问答系统的参考对象已经是结构化数据; - 问题的解答过程涉及多跳查询,比如“姚明的妻子今年是本命年吗?”,“你们家的产品 A 和 A+ 的区别是什么?”; - 为了解决其他需求(风控、推荐、管理),已经构建了图结构数据、知识图谱的情况。 为了方便读者最快速了解如何构建 KBQA 系统,我写了非常简陋的小 KBQA 项目,在本文中,我会带领大家从头到尾把它搭起来。 > 💡:这个小项目叫做 Siwi,它的代码就在 GitHub 上:[github.com/wey-gu/nebula-siwi](https://github.com/wey-gu/nebula-siwi/) > > Siwi 的发音是:`/ˈsɪwi/` 或者叫:`思二为` ,它是一个能解答 NBA 相关问题的机器人。 我们开始吧。 ## 鸟瞰 TL;DR KBQA 用一句话说就是把问题解析、转换成在知识图谱中的查询,查询得到结果之后进行筛选、翻译成结果(句子、卡片或者任何方便人理解的答案格式)。 > 💡:知识图谱的构建实际上是非常重要的过程,在本文中,我们专注在串起来 KBQA 系统的骨架,我们假设需求是基于一个已经有的图谱之上,为其增加一个 QA 系统。 「问题到图谱查询的转换」有不同的方法可以实现。 - 可以是对语义进行分析:理解问题的意图,针对不同意图匹配最可能的问题类型,从而构建这个类型问题的图谱查询,查得结果; - 也可以是基于信息的抽取:从问题中抽取主要的实体,在图谱中获取实体的所有知识、关系条目(子图),再对结果根据问题中的约束条件匹配、排序选择结果。 > 💡:美团技术团队在[这篇文章](https://tech.meituan.com/2021/11/03/knowledge-based-question-answering-in-meituan.html)里分享了他们的真实世界实践,下图是美团结合了机器学习和 NLP 的方案。 ![美团KBQA解决方案](https://tva1.sinaimg.cn/large/008i3skNly1gw13r5dsw2j31g30u0430.jpg) 而在 Siwi 里,我们一切从简,单独选择了语义分析这条路,它的特点是需要人为去标注或者编码一些问题类型的查询方式,但实际上在大多数场景下,尤其单一领域图谱的场景下反而是轻量却效果不差的方案,也是一个便于新手理解 KBQA 的合适的入门方式。 除了核心的问答部分,我还为 Siwi 增加了语音识别和语音回答(感谢浏览器接口标准的发展)的功能,于是,这个项目的结构和问答调用流程就是这样的了:一个语音问题自上而下分别经过三个部分: - 基于网页的 Siwi Frontend 语音、文字问答界面 - Python Flask 实现的 Siwi Backend/API 系统 - [Nebula Graph](https://nebula-graph.com.cn) 开源分布式高性能图数据库之上的知识图谱 ```asciiarmor ┌────────────────┬──────────────────────────────────────┐ │ │ │ │ │ Speech │ │ ┌──────────▼──────────┐ │ │ │ Frontend │ Siwi, /ˈsɪwi/ │ │ │ Web_Speech_API │ A PoC of │ │ │ │ Dialog System │ │ │ Vue.JS │ With Graph Database │ │ │ │ Backed Knowledge Graph │ │ └──────────┬──────────┘ │ │ │ Sentence │ │ ┌────────────┼──────────────────────────────┐ │ │ │ │ Backend │ │ │ │ ┌──────────▼──────────┐ │ │ │ │ │ Web API, Flask │ ./app/ │ │ │ │ └──────────┬──────────┘ │ │ │ │ │ Sentence ./bot/ │ │ │ │ ┌──────────▼──────────┐ │ │ │ │ │ Intent matching, │ ./bot/classifier│ │ │ │ │ Symentic Processing │ │ │ │ │ └──────────┬──────────┘ │ │ │ │ │ Intent, Entities │ │ │ │ ┌──────────▼──────────┐ │ │ │ │ │ Intent Actor │ ./bot/actions │ │ │ └─┴──────────┬──────────┴───────────────────┘ │ │ │ Graph Query │ │ ┌──────────▼──────────┐ │ │ │ Graph Database │ Nebula Graph │ │ └─────────────────────┘ │ └───────────────────────────────────────────────────────┘ ``` > 💡:图数据库相比于其他知识图谱存储系统来说,因为其设计专注于数据内的数据关系,非常擅长实时获取海量数据下实体之间的复杂关联关系。 > > Nebula Graph 的原生分布式设计和 share-nothing 架构使得它擅长于巨大数据量和高并发读写的场景,加上它的开源社区特别活跃,已经被国内很多团队用于支撑生产上的各种业务,[这里](https://nebula-graph.com.cn/cases/)有一些他们分享的选型、落地实践。 ## 知识图谱 Siwi 构建于一个篮球相关的知识图谱之上,它其实是 Siwi 采用的开源分布式图数据库 [Nebula Graph](http://nebula-graph.com.cn/) 社区的官方文档里的示例[数据集](https://docs.nebula-graph.com.cn/master/3.ngql-guide/1.nGQL-overview/1.overview/#basketballplayer)。 在这个非常简单的图谱之中,只有两种点: - player,球员 - team,球队 两种关系: - serve 服役于(比如:姚明 `-服役于->` 休斯顿火箭) - follow 关注 (比如:姚明 `-关注->` 奥尼尔) > 💡:这个数据集在 Nebula 社区上有一个 [在线体验](https://nebula-graph.com.cn/demo/) 环境,任何人都无需登录,通过[Nebula Graph Studio](https://docs.nebula-graph.com.cn/2.6.1/nebula-studio/about-studio/st-ug-what-is-graph-studio/) 可视化探索篮球图谱。 下图就是这个图谱的可视化探索截图,可以看到左边的中心节点勇士队(Warriors)有杜兰特(Durant)还有其他几个队员在其中服役(serve);除了服役之外,还可以看到队员和队员之中也有关注(follow)的关系存在。 ![篮球图谱](https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-website/images/demo/demo1.png) 有了这个知识图谱,咱们接下来就在它之上搭一个简单的基于语法解析的 QA 系统吧😁。 ## Siwi-backend ```asciiarmor ┌────────────┼──────────────────────────────┐ │ │ Backend │ │ ┌──────────▼──────────┐ │ │ │ Web API, Flask │ ./app/ │ │ └──────────┬──────────┘ │ │ │ Sentence ./bot/ │ │ ┌──────────▼──────────┐ │ │ │ Intent matching, │ ./bot/classifier│ │ │ Symentic Processing │ │ │ └──────────┬──────────┘ │ │ │ Intent, Entities │ │ ┌──────────▼──────────┐ │ │ │ Intent Actor │ ./bot/actions │ └─┴──────────┬──────────┴───────────────────┘ │ Graph Query ┌──────────▼──────────┐ │ Graph Database │ Nebula Graph └─────────────────────┘ ``` 如上图的设计流程,Siwi 的后端部分需要接收问句,处理之后访问知识图谱(图数据库),然后将处理结果返回给用户。 ### 接收 HTTP 请求(app) 对于请求,就简单地用 Flask 作为 web server 来接收 HTTP 的 POST 请求: > 💡:还不熟悉 Flask 的同学,可以在 [freeCodeCamp 上搜索一下](https://www.freecodecamp.org/news/tag/flask/),有一些不错的课程哈。 下边的代码就是告诉 Flask : 1. 如果用户发过来 `http://<server>/query` 的 POST 请求,提的问题就在请求的 body 里的 `question` 的 Key 之下。 2. 取得问题之后,调用把请求传给 `siwi_bot` 的 `query()`,得到 `answer` 。 代码段:`src/siwi/app/__init__.py` ```python #... from siwi.bot import bot #... @app.route("/query", methods=["POST"]) def query(): request_data = request.get_json() question = request_data.get("question", "") # <----- 1. if question: answer = siwi_bot.query( request_data.get("question", "")) # <----- 2. else: answer = "Sorry, what did you say?" return jsonify({"answer": answer}) ``` 接下来我们来实现 `siwi_bot`,真正处理提问的逻辑。 ### 处理请求(bot) ```asciiarmor │ │ Sentence ./bot/ │ │ ┌──────────▼──────────┐ │ │ │ Intent matching, │ ./bot/classifier│ │ │ Symentic Processing │ │ │ └──────────┬──────────┘ │ │ │ Intent, Entities │ │ ┌──────────▼──────────┐ │ │ │ Intent Actor │ ./bot/actions │ └─┴──────────┬──────────┴───────────────────┘ ``` 前边提到过,KBQA 基本上是 a. 把问题解析、转换成在知识图谱中的查询 b. 查询得到结果之后进行筛选、翻译成结果 这里,我们把 a. 的逻辑放在 `classifier` 里,b. 的逻辑放在 `actions`(actor) 里。 a. HTTP 请求的问题句子 `sentence` 传过来,用 `classifier` 解析它的意图和句子实体 b. 用意图和句子实体构造 `action`,并链接图数据库执行,获取结果。 代码段:`src/siwi/bot/bot/__init__.py` ```python from siwi.bot.actions import SiwiActions from siwi.bot.classifier import SiwiClassifier class SiwiBot(): def __init__(self, connection_pool) -> None: self.classifier = SiwiClassifier() self.actions = SiwiActions() self.connection_pool = connection_pool def query(self, sentence): intent = self.classifier.get(sentence) # <--- a. action = self.actions.get(intent) # <--- b. return action.execute(self.connection_pool) ``` 首先咱们来进一步实现一下 `SiwiClassifier` 吧。 #### 语义解析(classifier) `classifier ` 需要在 `get(sentence)` 方法里将句子中的实体和句子的意图解析、分类出来。通常来说,这里是需要借助机器学习、NLP去分词、分类实现的,这里只是为了展示这个过程实际上只是各种 `if/ else`。 我们这里实现了三类意图的问题: - 关系(A,B):获得 A 和 B 在图谱中的关系路径,比如姚明和湖人队的关系是? - 服役情况:比如乔纳森在哪里服役? - 关注情况:比如邓肯关注了谁? > ❓ 开放问题: > > 如果看教程的你觉得这几个问题太没意思了,这里留一个开放问题,你可以在 Siwi li 帮我们实现:「共同好友(A,B)获得 A 和 B 的一度共同好友」这个意图(或者更酷的其他句子)么?欢迎来 Github:github.com/wey-gu/nebula-siwi/ 提 PR 哦,看看谁先实现。 代码片段:`src/siwi/bot/classfier/__init__.py` ```python class SiwiClassifier(): def get(self, sentence: str) -> dict: """ Classify Sentences and Fill Slots. This should be done by NLP, here we fake one to demostrate the intent Actor --> Graph DB work flow. sentense: relation: - What is the relationship between Yao Ming and Lakers? - How does Tracy McGrady and Lakers connected? serving: - Which team had Jonathon Simmons served? friendship: - Whom does Tim Duncan follow? - Who are Tracy McGrady's friends? returns: { "entities": entities, "intents": intents } """ entities = self.get_matched_entities(sentence) intents = self.get_matched_intents(sentence) return { "entities": entities, "intents": intents } ``` 这里,我把匹配的规则(等价于 if else...)写在了 `src/siwi/bot/test/data` 之下的 YAML 文件里,这样增加 `classifier` 之中新的规则只需要更新这个文件就可以了: ##### 意图识别(intent) ```python def load_entity_data(self) -> None: # load data from yaml files module_path = f"{ siwi.__path__[0] }/bot/test/data" #... with open(f"{ module_path }/intents.yaml", "r") as file: self.intents = yaml.safe_load(file)["intents"] ``` 对于每一个意图来说: - `intents.<名字>` 代表名字 - 名字之后的 `action` 代表后边在要实现的相应的 `xxxAction` 的类 - 比如 `RelationshipAction` 将是用来处理查询关系(A,B)这样的问题的 Action 类 - `keywords` 代表在句子之中匹配的关键词 - 比如问句里出现 serve,served,serving 的字眼的时候,将会匹配服役的问题 > 💡:写 if else 条件来对应意图是不容易的,因为不同意图不可能没有关键词相交的情况,我们的实现只是一个非常简陋、不严谨的方式。在实际场景下,训练模型去做匹配效果会更好,有意思的是,那些做的比较好的模型的输入和我们的 YAML 的格式是很类似的。 ```yaml --- intents: fallback: action: FallbackAction keywords: [] relationship: action: RelationshipAction keywords: - between - relation - relationship - related - connect - correlate serve: action: ServeAction keywords: - serve - served - serving friend: action: FollowAction keywords: - follows - followed - follow - friend - friends ``` ##### 实体识别(entity) 类似的,实体识别的部分本质上也是 if else,只不过这里利用到了**[Aho–Corasick算法](https://zh.wikipedia.org/wiki/AC%E8%87%AA%E5%8A%A8%E6%9C%BA%E7%AE%97%E6%B3%95)**来帮助搜索实体,在生产(非玩具)的情况下,应该用 NLP 里的分词的方法来做。 > 💡:大家可以去了解一下这个 [AC自动机算法](https://zh.wikipedia.org/wiki/AC%E8%87%AA%E5%8A%A8%E6%9C%BA%E7%AE%97%E6%B3%95) ```python def setup_entity_tree(self) -> None: self.entity_type_map.update({ key: "player" for key in self.players.keys() }) self.entity_type_map.update({ key: "team" for key in self.teams.keys() }) self.entity_tree = ahocorasick.Automaton() for index, entity in enumerate(self.entity_type_map.keys()): self.entity_tree.add_word(entity, (index, entity)) self.entity_tree.make_automaton() #... def get_matched_entities(self, sentence: str) -> dict: """ Consume a sentence to be matched with ahocorasick Returns a dict: {entity: entity_type} """ _matched = [] for item in self.entity_tree.iter(sentence): entities_matched.append(item[1][1]) return { entity: self.entity_type_map[entity] for entity in _matched } ``` 至此,我们的 `SiwiClassifier.get(sentence)` 已经能返回解析、分类出来的意图和实体了,这时候,它们会被传给 Actions 来让 siwi bot 知道如何去执行只是图谱的查询啦! #### 构造图谱查询(action) 还记得前边的 bot 代码里,最后一步,图谱查询的动作是这么被构造的: `action = self.actions.get(intent)` 现在咱们就把它实现一下: 1. 在前边提到过的 `intents.yaml` 里获取这个意图里配置的意图的类名称 2. 导入相应的 Action 类 代码段:`src/bot/actions/__init__.py` ```python class SiwiActions(): def __init__(self) -> None: self.intent_map = {} self.load_data() def load_data(self) -> None: # load data from yaml files module_path = f"{ siwi.__path__[0] }/bot/test/data" with open(f"{ module_path }/intents.yaml", "r") as file: self.intent_map = yaml.safe_load(file)["intents"] def get(self, intent: dict): """ returns SiwiActionBase """ if len(intent["intents"]) > 0: intent_name = intent["intents"][0] else: intent_name = "fallback" cls_name = self.intent_map.get( intent_name).get("action") #-------> 1. action_cls = getattr( #-------> 2. importlib.import_module("siwi.bot.actions"), cls_name) action = action_cls(intent) return action ``` 最后,我们来实现这个类吧,比如 `RelationshipAction` 对应的代码如下: 1. 根据提供的 A 和 B,构造并执行图数据库之中的 `FIND PATH` 2. 将 `FIND PATH` 的结果进行解析,通过 `as_path()` 方法的封装,获得 path 类型的数据,并处理一个句子返回给用户 > 💡:FIND PATH 就是字面意思的查找路径,[这里](https://docs.nebula-graph.com.cn/2.6.1/3.ngql-guide/16.subgraph-and-path/2.find-path/)有详细的解释哦。 ```python class RelationshipAction(SiwiActionBase): """ USE basketballplayer; FIND NOLOOP PATH FROM "player100" TO "team204" OVER * BIDIRECT UPTO 4 STEPS; """ def __init__(self, intent): print(f"[DEBUG] RelationshipAction intent: { intent }") super().__init__(intent) try: self.entity_left, self.entity_right = intent["entities"] self.left_vid = self._vid(self.entity_left) self.right_vid = self._vid(self.entity_right) except Exception: print( f"[WARN] RelationshipAction entities recognition Failure " f"will fallback to FallbackAction, " f"intent: { intent }" ) self.error = True def execute(self, connection_pool) -> str: self._error_check() query = ( f'USE basketballplayer;' f'FIND NOLOOP PATH ' f'FROM "{self.left_vid}" TO "{self.right_vid}" ' f'OVER * BIDIRECT UPTO 4 STEPS;' ) print( f"[DEBUG] query for RelationshipAction :\n\t{ query }" ) with connection_pool.session_context("root", "nebula") as session: result = session.execute(query) #--------------------> 1. if not result.is_succeeded(): return ( f"Something is wrong on Graph Database connection when query " f"{ query }" ) if result.is_empty(): return ( f"There is no relationship between " f"{ self.entity_left } and { self.entity_right }" ) path = result.row_values(0)[0].as_path() #-------------------> 2. relationships = path.relationships() relations_str = self._name( relationships[0].start_vertex_id().as_string()) for rel_index in range(path.length()): rel = relationships[rel_index] relations_str += ( f" { rel.edge_name() }s " f"{ self._name(rel.end_vertex_id().as_string()) }") return ( f"There are at least { result.row_size() } relations between " f"{ self.entity_left } and { self.entity_right }, " f"one relation path is: { relations_str }." ) ``` 至此,咱们就已经实现了后端的所有功能,我们可以把它启动起来试试了! ### 测试一下 #### 启动图数据库 我们在 Nebula Graph 里建立(导入数据)一个篮球的知识图谱。 > 💡:在导入数据之前,请先部署一个 Nebula Graph 集群。最简便的部署方式是使用 Nebula-UP 这个小工具,只需要一行命令就能在 Linux 机器上同时启动一个 Nebula Graph 核心和可视化图探索工具 [Nebula Graph Studio](https://docs.nebula-graph.com.cn/2.6.1/nebula-studio/about-studio/st-ug-what-is-graph-studio/)。如果你更愿意用 Docker 部署,请参考[这个文档](https://docs.nebula-graph.com.cn/2.6.1/4.deployment-and-installation/2.compile-and-install-nebula-graph/3.deploy-nebula-graph-with-docker-compose/)。 本文假设我们使用 [Nebula-UP](https://siwei.io/nebula-up/) 来部署一个 Nebula Graph: ```bash curl -fsSL nebula-up.siwei.io/install.sh | bash ``` 之后,我们会看到这样的提示: ![https://github.com/wey-gu/nebula-up/raw/main/images/nebula-up-demo-shell.png](https://github.com/wey-gu/nebula-up/raw/main/images/nebula-up-demo-shell.png) 按照提示,我们可以通过这个命令进入到有 Nebula Console 的容器里: > 💡:[Nebula Console](https://github.com/vesoft-inc/nebula-console) 是命令行访问 Nebula Graph 图数据库的客户端,支持 Linux,Windows 和 macOS,下载地址:[这里](https://github.com/vesoft-inc/nebula-console/releases) ```bash ~/.nebula-up/console.sh ``` 然后,在 `#` 的提示符下就表示我们进来了,我们在里边可以执行: ``` nebula-console -addr graphd -port 9669 -user root -p nebula ``` 这样就表示我们连接上了 Nebula Graph 图数据库: ```mysql / # nebula-console -addr graphd -port 9669 -user root -p nebula Welcome to Nebula Graph! (root@nebula) [(none)]> ``` 在这里,我们就可以通过 nGQL 去操作 Nebula Graph,不过我们先退出来,执行 `exit`: ```mysql (root@nebula) [(none)]> exit Bye root! Fri, 31 Dec 2021 04:11:28 UTC ``` 我们在这个容器内把基于 nGQL 语句的数据下载下来: ```bash / # wget https://docs.nebula-graph.io/2.0/basketballplayer-2.X.ngql ``` 然后通过 Nebula Console 的 `-f <file_path>` 把数据导入进去: ```bash nebula-console -addr graphd -port 9669 -user root -p nebula -f basketballplayer-2.X.ngql ``` 至此,我们就启动了一个 Nebula Graph 图数据库,还在里边加载了篮球的知识图谱! > 💡:还记得前边我们提到的 [在线体验](https://nebula-graph.com.cn/demo/) 环境么?现在,我们可以在这个利用 Nebula-UP 部署了 Nebula 的环境里启动自己的 Nebula Studio 啦,按照上边 Nebula-UP 的提示:http://<本机IP>:7001 就是它的地址,然后大家可以参考[文档](https://docs.nebula-graph.com.cn/2.6.1/nebula-studio/deploy-connect/st-ug-connect/)和[在线体验介绍](https://www.bilibili.com/video/BV1hq4y1177e)去了解更多。 > > ![studio](https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-website/images/demo/demo1.png) #### 启动 Siwi-backend 大家可以直接 clone 我的代码:`git clone https://github.com/wey-gu/nebula-siwi/` 然后安装、启动 Siwi Backend: ```bash cd nebula-siwi # Install dependencies python3 -m pip install -r src/requirements.txt # Install siwi backend python3 -m build # Configure Nebula Graph Endpoint export NG_ENDPOINTS=127.0.0.1:9669 # Run Backend API server gunicorn --bind :5000 wsgi --workers 1 --threads 1 --timeout 60 ``` 启动之后,我们可以另外开窗口,通过 cURL 去发起问题给 backend,更多细节大家可以参考 GitHub 上的 README: [![backend-demo](https://github.com/wey-gu/nebula-siwi/raw/main/images/backend-demo.webp)](https://github.com/wey-gu/nebula-siwi/blob/main/images/backend-demo.webp) 至此,我们已经写好了 QA 系统的重要的代码啦,大家是不是对一个 KBQA 的构成有了更清晰的概念了呢? 接下来,我们为它增加一个界面! ## Siwi-frontend ### 聊天界面 我们利用 [Vue Bot UI](https://github.com/juzser/vue-bot-ui) 这个可爱的机器人界面的 Vue 实现可以很容易构造一个 代码段:`src/siwi/frontend/src/App.vue` ```vue <template> <div id="app"> <VueBotUI :messages="msg" :options="botOptions" :bot-typing="locking" :input-disable="locking" @msg-send="msgSender" /> </div> </template> <script> import { VueBotUI } from 'vue-bot-ui' ``` [![demo](https://github.com/wey-gu/nebula-siwi/raw/main/src/siwi_frontend/images/demo.webp)](https://github.com/wey-gu/nebula-siwi/blob/main/src/siwi_frontend/images/demo.webp) 注意到那个小飞机按钮了吧,它是发出问题请求的按键,我们要在按下它的时候对后端做出请求。 ### 访问后端 这部分用到了[Axios](https://github.com/axios/axios),它是浏览器里访问其他地址的 HTTP 客户端。 1. 在按下的时候,`@msg-send="msgSender"` 会触发 `msgSender()` 2. ` msgSender()`去构造`axios.post(this.apiEndpoint, { "question": data.text })` 的请求给 Siwi 的后端 3. 后端的结果被 `push()` 到界面的聊天消息里,渲染出来 `this.msg.push()` 代码段:`src/siwi/frontend/src/App.vue` ```vue <template> <div id="app"> <button id="mic_btn" @click="record = !record"> {{record?'👂':'🎙️'}} --------------------------> 1. </button> <vue-web-speech v-model="record" @results="onResults" --------------------------> 1. @unrecognized="unrecognized" > </vue-web-speech> ... <vue-web-speech-synth v-model="agentSpeak" :voice="synthVoice" :text="synthText" @list-voices="listVoices" --------------------------> 4. /> </div> </template> <script> import { VueBotUI } from 'vue-bot-ui' import axios from "axios"; export default { name: 'App', components: { VueBotUI, }, onResults (data) { -------------------------> 2. this.results = data; this.locking = true; this.msg.push({ agent: "user", type: "text", text: data[0], }); this.locking = true; console.log(data[0]); axios.post(this.apiEndpoint, { "question": data[0] }).then((response) => { console.log(response.data); this.msg.push({ agent: "bot", type: "text", text: response.data.answer, }); this.synthText = response.data.answer; ----------> 3. this.agentSpeak = true; }); this.locking = false; }, } } </script> ``` 现在,我们已经有了一个图形界面的机器人啦,不过,更进一步,我们可以利用现代浏览器的接口,实现语音识别和机器人说话! ### 语音识别 我们借助于 [Vue Web Speech](https://github.com/Drackokacka/vue-web-speech), 这个语音 API 的 VueJS 的绑定,可以很容易在按下 🎙️ 的时候接收人的语音,并把语音转换成文字发出去,在回答被返回之后,它(还是他/她😁?)也会把回答的句子读出来给用户。 1. `record` 在 `🎙️` 被按下之后,变成 `👂` 2. 触发 `onResults()` 监听 3. 把返回结果发给 `this.synthText` 合成器,准备读出 4. `<vue-web-speech-synth>` 把语音读出 代码段:`src/siwi/frontend/src/App.vue` ```vue <template> <div id="app"> <button id="mic_btn" @click="record = !record"> {{record?'👂':'🎙️'}} -----------------------------> 1. </button> <vue-web-speech v-model="record" @results="onResults" -----------------------------> 1. @unrecognized="unrecognized" > </vue-web-speech> ... <vue-web-speech-synth v-model="agentSpeak" :voice="synthVoice" :text="synthText" @list-voices="listVoices" ---------------------------> 4. /> </div> </template> <script> import { VueBotUI } from 'vue-bot-ui' import axios from "axios"; export default { name: 'App', components: { VueBotUI, }, onResults (data) { -------------------> 2. this.results = data; this.locking = true; this.msg.push({ agent: "user", type: "text", text: data[0], }); this.locking = true; console.log(data[0]); axios.post(this.apiEndpoint, { "question": data[0] }).then((response) => { console.log(response.data); this.msg.push({ agent: "bot", type: "text", text: response.data.answer, }); this.synthText = response.data.answer; ----------------------> 3. this.agentSpeak = true; }); this.locking = false; }, } } </script> ``` ## 总结 至此,我们已经学会了搭建自己的第一个 KBQA:知识图谱驱动的问答系统。 回顾下它的代码结构: - `src/siwi` 对应后端 - App 是 Flask API 处理的部分 - Bot 是处理请求、访问 Nebula Graph 的部分 - `src/siwi_frontend` 是前端 希望大家在这个简陋的基础之上,多多探索,做出来更加成熟的聊天机器人,欢迎你来给我邮件、留言告诉我呀,这里:https://siwei.io/about 有我的联系方式。 ```bash . ├── README.md ├── src │ ├── siwi # Siwi-API Backend │ │ ├── app # Web Server, take HTTP requests and calls Bot API │ │ └── bot # Bot API │ │ ├── actions # Take Intent, Slots, Query Knowledge Graph here │ │ ├── bot # Entrypoint of the Bot API │ │ ├── classifier # Symentic Parsing, Intent Matching, Slot Filling │ │ └── test # Example Data Source as equivalent/mocked module │ └── siwi_frontend # Browser End │ ├── README.md │ ├── package.json │ └── src │ ├── App.vue # Listening to user and pass Questions to Siwi-API │ └── main.js └── wsgi.py ``` 如果你很喜欢这样的小项目,欢迎来看看我之前的分享: 「[从0-1:如何构建一个企业股权图谱系统?](https://siwei.io/corp-rel-graph/)」哦。 > 💡:你知道吗,我其实借助于 Katacoda 已经为大家搭建了一个交互式体验 Siwi + Nebula 的部署的环境,如果您的网络条件够快(Katacoda服务器在国外),可以在[这里](https://siwei.io/learn/nebula-101-siwi-kgqa/)点点鼠标就交互式体验它。 > > 视频介绍 > > <iframe src="//player.bilibili.com/player.html?aid=421964672&bvid=BV1Rm4y1Q7B5&cid=448090671&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe> ## 感谢用到的开源项目 ❤️ 这个小项目里我们用到了好多开源的项目,非常感谢这些贡献者们的慷慨与无私,开源是不是很酷呢? ### Backend - [KGQA on MedicalKG](https://github.com/liuhuanyong/QASystemOnMedicalKG) by [Huanyong Liu](https://liuhuanyong.github.io/) - [Flask](https://github.com/pallets/flask) - [pyahocorasick](https://github.com/WojciechMula/pyahocorasick) created by [Wojciech Muła](http://0x80.pl/) - [PyYaml](https://pyyaml.org/) ### Frontend - [VueJS](https://vuejs.org/) for frontend framework - [Vue Bot UI](https://github.com/juzser/vue-bot-ui), as a lovely bot UI in vue - [Vue Web Speech](https://github.com/Drackokacka/vue-web-speech), for speech API vue wrapper - [Axios](https://github.com/axios/axios) for browser http client - [Solarized](https://en.wikipedia.org/wiki/Solarized_(color_scheme)) for color scheme - [Vitesome](https://github.com/alvarosaburido/vitesome) for landing page design ### Graph Database - [Nebula Graph](https://github.com/vesoft-inc/nebula/) 高性能、云原生的开源分布式图数据库