• 主要逻辑图:7.3 主要逻辑服务 - 图1

    • 逻辑图分析:
      • 主要逻辑服务接收werobot发送的请求后,根据用户id查询redis查找用户上一次说过的话,根据结果判断是否为他的第一句.
      • 如果是第一句话,直接查询数据库,判断句子中是否包含症状实体,并返回该症状连接的疾病,并填充在规则对话模版中,如果查询不到则调用Unit API返回结果.
      • 如果不是该用户的第一句话,则连同上一句话的内容一起请求句子相关模型服务,判断两句话是否讨论同一主题,如果是,则继续查询图数据库,如果不是,使用unit api返回结果.

    • 构建主要逻辑服务的步骤:
      • 第一步: 导入必备工具和配置
      • 第二步: 完成查询neo4j数据库的函数
      • 第三步: 编写主要逻辑处理类
      • 第四步: 编写服务中的主函数
      • 第五步: 使用gunicorn启动服务
      • 第六步: 编写测试脚本并进行测试:

    • 第一步: 导入必备工具和配置
      1. # 服务框架使用Flask
      2. # 导入必备的工具
      3. from flask import Flask
      4. from flask import request
      5. app = Flask(__name__)
      6. # 导入发送http请求的requests工具
      7. import requests
      8. # 导入操作redis数据库的工具
      9. import redis
      10. # 导入加载json文件的工具
      11. import json
      12. # 导入已写好的Unit API调用文件
      13. from unit import unit_chat
      14. # 导入操作neo4j数据库的工具
      15. from neo4j import GraphDatabase
      16. # 从配置文件中导入需要的配置
      17. # NEO4J的连接配置
      18. from config import NEO4J_CONFIG
      19. # REDIS的连接配置
      20. from config import REDIS_CONFIG
      21. # 句子相关模型服务的请求地址
      22. from config import model_serve_url
      23. # 句子相关模型服务的超时时间
      24. from config import TIMEOUT
      25. # 规则对话模版的加载路径
      26. from config import reply_path
      27. # 用户对话信息保存的过期时间
      28. from config import ex_time
      29. # 建立REDIS连接池
      30. pool = redis.ConnectionPool(**REDIS_CONFIG)
      31. # 初始化NEO4J驱动对象
      32. _driver = GraphDatabase.driver(**NEO4J_CONFIG)

    • 代码位置: /data/doctor_online/main_serve/app.py

    • 配置文件内容如下:
    1. REDIS_CONFIG = {
    2. "host": "0.0.0.0",
    3. "port": 6379
    4. }
    5. NEO4J_CONFIG = {
    6. "uri": "bolt://0.0.0.0:7687",
    7. "auth": ("neo4j", "********"),
    8. "encrypted": False
    9. }
    10. model_serve_url = "http://0.0.0.0:5001/v1/recognition/"
    11. TIMEOUT = 2
    12. reply_path = "./reply.json"
    13. ex_time = 36000

    • 代码位置: /data/doctor_online/main_serve/config.py

    • 规则对话模版文件reply.json内容如下:
    1. {
    2. "1": "亲爱的用户, 在线医生一个医患问答机器人,请您说一些当前的症状吧!",
    3. "2": "根据您当前的症状描述, 您可能患有以下疾病, %s, 再想想还有更多的症状吗?",
    4. "3": "对不起, 您所说的内容超出了在线医生的知识范围. 请尝试换一些描述方式!",
    5. "4": "您的这次描述并没有给我带来更多信息,请您继续描述您的症状."
    6. }

    • 代码位置: /data/doctor_online/main_serve/reply.json

    • 第二步: 完成查询neo4j数据库的函数
      1. def query_neo4j(text):
      2. """
      3. description: 根据用户对话文本中的可能存在的症状查询图数据库.
      4. :param text: 用户的输入文本.
      5. :return: 用户描述的症状对应的疾病列表.
      6. """
      7. # 开启一个session操作图数据库
      8. with _driver.session() as session:
      9. # cypher语句, 匹配句子中存在的所有症状节点,
      10. # 保存这些节点并逐一通过关系dis_to_sym进行对应病症的查找, 返回找到的疾病名字列表.
      11. cypher = "MATCH(a:Symptom) WHERE(%r contains a.name) WITH \
      12. a MATCH(a)-[r:dis_to_sym]-(b:Disease) RETURN b.name LIMIT 5" %text
      13. # 运行这条cypher语句
      14. record = session.run(cypher)
      15. # 从record对象中获得结果列表
      16. result = list(map(lambda x: x[0], record))
      17. return result

    • 代码位置: /data/doctor_online/main_serve/app.py

    • 调用:
    1. if __name__ == "__main__":
    2. text = "我最近腹痛!"
    3. result = query_neo4j(text)
    4. print("疾病列表:", result)

    • 输出效果:
    1. 疾病列表: ['胃肠道癌转移卵巢', '胃肠道功能紊乱', '胃肠积液', '胃肠型食物中毒', '胃结核']

    • 第三步: 编写主要逻辑处理类
      1. class Handler(object):
      2. """主要逻辑服务的处理类"""
      3. def __init__(self, uid, text, r, reply):
      4. """
      5. :param uid: 用户唯一标示uid
      6. :param text: 该用户本次输入的文本
      7. :param r: redis数据库的连接对象
      8. :param reply: 规则对话模版加载到内存的对象(字典)
      9. """
      10. self.uid = uid
      11. self.text = text
      12. self.r = r
      13. self.reply = reply
      14. def non_first_sentence(self, previous):
      15. """
      16. description: 非首句处理函数
      17. :param previous: 该用户当前句(输入文本)的上一句文本
      18. :return: 根据逻辑图返回非首句情况下的输出语句
      19. """
      20. # 尝试请求模型服务, 若失败则打印错误结果
      21. try:
      22. data = {"text1": previous, "text2": self.text}
      23. result = requests.post(model_serve_url, data=data, timeout=TIMEOUT)
      24. if not result.text: return unit_chat(self.text)
      25. except Exception as e:
      26. print("模型服务异常:", e)
      27. return unit_chat(self.text)
      28. # 继续查询图数据库, 并获得结果
      29. s = query_neo4j(self.text)
      30. # 判断结果为空列表, 则直接使用UnitAPI返回
      31. if not s: return unit_chat(self.text)
      32. # 若结果不为空, 获取上一次已回复的疾病old_disease
      33. old_disease = self.r.hget(str(self.uid), "previous_d")
      34. if old_disease:
      35. # new_disease是本次需要存储的疾病, 是已经存储的疾病与本次查询到疾病的并集
      36. new_disease = list(set(s) | set(eval(old_disease)))
      37. # res是需要返回的疾病, 是本次查询到的疾病与已经存储的疾病的差集
      38. res = list(set(s) - set(eval(old_disease)))
      39. else:
      40. # 如果old_disease为空, 则它们相同都是本次查询结果s
      41. res = new_disease = list(set(s))
      42. # 存储new_disease覆盖之前的old_disease
      43. self.r.hset(str(self.uid), "previous_d", str(new_disease))
      44. # 设置过期时间
      45. self.r.expire(str(self.uid), ex_time)
      46. # 将列表转化为字符串, 添加到规则对话模版中返回
      47. if not res:
      48. return self.reply["4"]
      49. else:
      50. res = ",".join(res)
      51. return self.reply["2"] %res
      52. def first_sentence(self):
      53. """首句处理函数"""
      54. # 直接查询图数据库, 并获得结果
      55. s = query_neo4j(self.text)
      56. # 判断结果为空列表, 则直接使用UnitAPI返回
      57. if not s: return unit_chat(self.text)
      58. # 将s存储为"上一次返回的疾病"
      59. self.r.hset(str(self.uid), "previous_d", str(s))
      60. # 设置过期时间
      61. self.r.expire(str(self.uid), ex_time)
      62. # 将列表转化为字符串, 添加到规则对话模版中返回
      63. res = ",".join(s)
      64. return self.reply["2"] %res

    • 代码位置: /data/doctor_online/main_serve/app.py

    • 第四步: 编写服务中的主函数
      1. # 设定主要逻辑服务的路由和请求方法
      2. @app.route('/v1/main_serve/', methods=["POST"])
      3. def main_serve():
      4. # 接收来自werobot服务的字段
      5. uid = request.form['uid']
      6. text = request.form['text']
      7. # 从redis连接池中获得一个活跃连接
      8. r = redis.StrictRedis(connection_pool=pool)
      9. # 根据该uid获取他的上一句话(可能不存在)
      10. previous = r.hget(str(uid), "previous")
      11. # 将当前输入的文本设置成上一句
      12. r.hset(str(uid), "previous", text)
      13. # 读取规则对话模版内容到内存
      14. reply = json.load(open(reply_path, "r"))
      15. # 实例化主要逻辑处理对象
      16. handler = Handler(uid, text, r, reply)
      17. # 如果previous存在, 说明不是第一句话
      18. if previous:
      19. # 调用non_first_sentence方法
      20. return handler.non_first_sentence(previous)
      21. else:
      22. # 否则调用first_sentence()方法
      23. return handler.first_sentence()

    • 第五步: 使用gunicorn启动服务
      1. gunicorn -w 1 -b 0.0.0.0:5000 app:app
      2. # -w 代表开启的进程数, 我们只开启一个进程
      3. # -b 服务的IP地址和端口
      4. # app:app 是指执行的主要对象位置, 在app.py中的app对象

    • 代码位置: 在/data/doctor_online/main_serve/路径下执行.

    • 第六步: 编写测试脚本并进行测试
      • 编写测试脚本:
    1. import requests
    2. # 定义请求url和传入的data
    3. url = "http://0.0.0.0:5000/v1/main_serve/"
    4. data = {"uid":"13424", "text": "头晕"}
    5. # 向服务发送post请求
    6. res = requests.post(url, data=data)
    7. # 打印返回的结果
    8. print(res.text)

    • 运行脚本:
    1. python test.py

    • 输出效果:
    1. 根据您当前的症状描述, 您可能患有以下疾病, 中毒,虫媒传染病,小儿肥厚型心肌病,血红蛋白E病,铍中毒, 再想想还有更多的症状吗?

    • 小节总结:
      • 学习了服务的主要逻辑:
        • 主要逻辑服务接收werobot发送的请求后,根据用户id查询redis查找用户上一次说过的话,根据结果判断是否为他的第一句.
        • 如果是第一句话,直接查询数据库,判断句子中是否包含症状实体,并返回该症状连接的疾病,并填充在规则对话模版中,如果查询不到则调用Unit API返回结果.
        • 如果不是该用户的第一句话,则连同上一句话的内容一起请求句子相关模型服务,判断两句话是否讨论同一主题,如果是,则继续查询图数据库,如果不是,使用unit api返回结果.

    • 构建主要逻辑服务的步骤:
      • 第一步: 导入必备工具和配置
      • 第二步: 完成查询neo4j数据库的函数
      • 第三步: 编写主要逻辑处理类
      • 第四步: 编写服务中的主函数
      • 第五步: 使用gunicorn启动服务
      • 第六步: 编写测试脚本并进行测试