LLM应用实战:当KBQA集成LLM(二)


1. 背景

又两周过去了,本qiang~依然奋斗在上周提到的项目KBQA集成LLM,感兴趣的可通过传送门查阅先前的文章《LLM应用实战:当KBQA集成LLM》。

本次又有什么更新呢?主要是针对上次提到的缺点进行优化改进。主要包含如下方面:

1. 数据落库

上次文章提到,KBQA服务会将图谱的概念、属性、实体、属性值全部加载到内存,所有的查询均在内存中进行,随之而来的问题就是如果图谱的体量很大呢,那内存不爆了么…

2. 支持基于属性值查实体

上篇文章不支持属性值查找实体,比如”最会照顾宝宝的是什么龙”,”什么龙是大龙和大龙生活,小龙和小龙生活”。本次已经此问题优化。

此篇文章是对这两周工作的一个整体总结,其中包含部分工程层面的优化。

2. 整体框架

整体框架和上篇大致相同,不同之处在于:

1. 对齐模块:先前是基于SIM筛选候选实体,本次基于ES进行候选实体召回

2. 解析模块:先前是基于hugegraph和内存中的实体信息进行解析,本次优化为基于hugegraph和elasticsearch

3. 核心功能

3.1 数据库选型

由于需要支撑语义相似度检索,因此数据库选型为Milvus与Elasticsearch。

二者之间的比对如下:

但由于Milvus针对国产化环境如华为Atlas适配不佳,而Es支持国产化环境,因此考虑到环境通用性,选择Es,且其文本搜索能力较强。

3.2 表结构设计

由于知识图谱的概念、属性一般量级较少,而实体数随着原始数据的丰富程度客场可短。因此将实体及其属性值在Es中进行存储。

针对KBQA集成LLM的场景,有两块内容会涉及语义搜索召回。

1. 对齐prompt中的候选实体

2. 解析模块中存在需要基于属性值查询实体的情况。

3. 涉及到数值类型的查询,如大于xx,最大,最小之类。

综合考虑,将Es的index结构设计如下:

3.3 安装部署

项目使用的Es版本是8.12.2,原因是elastiknn插件和Ik插件针对该版本均支持,且8.12.2版本是当前阶段的次新版本。

3.3.1 基于docker的ES部署



# 拉取镜像(最好先设置国内镜像加入)

docker pull elasticsearch:8.12.2


# es容器启动,存在SSL鉴权

docker run -d --name es01 --net  host  -p 9200:9200 -it -e  "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" elasticsearch:8.13.2


# 容器中拉取需要鉴权的信息到本地

docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt .

chmode 777 http_ca.crt


# 密码第一次启动的日志中有,需要保存下来

export ELASTIC_PASSWORD=xxxxxx


# 验证es是否启动成功

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD

  https://localhost:9200


3.3.2 elastiknn插件集成

elastiknn插件是为了优化ES自身的向量检索性能,安装此插件后,ES的向量检索性能会提升数倍,如果再增加SSD固态硬盘,性能会进一步提升数倍。


#下载插件包

wgethttps://github.com/alexklibisz/elastiknn/releases/download/8.12.2.1/elastiknn-8.12.2.1.zip


# 导入容器中指定目录

docker cp  elastiknn-8.12.2.1.zip  es01:/usr/share/elasticsearch/


# 进入容器,默认目录即为/usr/share/elasticsearch/

docker exec -it es01 bash


# 安装插件

elasticsearch-plugin installfile:elastiknn-8.12.2.1.zip


# 退出,重启容器

docker restart es01


# 验证

# 创建mapping

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD -XPOST  https://localhost:9200/test/_mapping -H 'Content-Type:application/json' -d '

{

         "properties":  {

                  "embeddings":  {

                          "type":  "elastiknn_dense_float_vector",

                          "elastiknn":  {

                                   "model":  "lsh",

                                   "similarity":  "cosine",

                                   "dims":  768,

                                   "L":  99,

                                   "k":  3

                          }

                  }

         }

}'


# 验证mapping是否生效

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD -XGET  https://localhost:9200/test/_mapping?pretty



采坑总结:

1. elastiknn插件导入始终无法安装,且报错。

解决:

(1) 一定要注意,安装es插件需要指定路径,且增加”file:” 的前缀,不加此前缀,那就等着报错吧

(2) 拷贝到容器内部,一定要注意,不要将elastiknn-8.12.2.1.zip拷贝至/usr/share/elasticsearch/plugins目录,否则安装也报错。

3.3.3 ik分词器插件集成


#下载插件包

wget https://github.com/infinilabs/analysis-ik/releases/download/v8.12.2/elasticsearch-analysis-ik-8.12.2.zip


# 导入容器中指定目录

docker cp elasticsearch-analysis-ik-8.12.2.zip  es01:/usr/share/elasticsearch/


# 进入容器,默认目录即为/usr/share/elasticsearch/

docker exec -it es01 bash


# 安装插件

elasticsearch-plugin install file:elasticsearch-analysis-ik-8.12.2.zip


# 退出,重启容器

docker restart es01


# 验证是否生效

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD -XPOST

  https://localhost:9200/_analyze?pretty -H 'Content-Type:application/json' -d

  '{"text":"三角龙或者霸王龙","analyzer":  "ik_smart"}'

# 返回结果中不包含”或者”,因为”或者”在默认的停用词表中。


采坑总结:

1. ik分词器插件导入始终无法安装,且报错。

解决:一定要注意,安装es插件需要指定路径,且增加”file:” 的前缀,不加此前缀,那就等着报错吧

2. ik分词器添加自定义专有名词以及停用词不生效(浪费了1天的时间来排查)

解决:

(1) 一定要注意,8.12.2版本的ik分词器如果想要配置自定义专有名词或停用词,配置的完整目录是/usr/share/elasticsearch/config/analysis-ik,而不是/usr/share/elasticsearch/plugins/analysis-ik,这点需要注意下。

在config/analysis-ik中配置IKAnalyzer.cfg.xml,修改内容如下:


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>

<comment>IK Analyzer 扩展配置</comment>

<!--用户可以在这里配置自己的扩展字典 -->

<entry key="ext_dict">extra_main.dic</entry>

<!--用户可以在这里配置自己的扩展停止词字典-->

<entry key="ext_stopwords">extra_stopword.dic</entry>

<!--用户可以在这里配置远程扩展字典 -->

<!-- <entry key="remote_ext_dict">words_location</entry> -->

<!--用户可以在这里配置远程扩展停止词字典-->

<!-- <entry key="remote_ext_stopwords">words_location</entry> -->

</properties>


(2) 一定要注意,extra_main.dic和extra_stopword.dic的编码格式是UTF-8,如果编码格式不对的话,分词也不生效。


4. Es操作相关源码

4.1 es_client连接


self.es_client =  Elasticsearch(config['url'],

                                                                                     basic_auth=(config['user'],  config['password']),

                                                                                     ca_certs=config['crt_path'],

                                                                                     http_compress=True,


  request_timeout=int(config['request_timeout']) if 'request_timeout' in  config else 60,

                                                                                     max_retries=int(config['max_retries']) if  'max_retries' in config else 5,

                                                                                     retry_on_timeout=True)


4.2 构建表结构


def index(self, kg_id, force=False):

            """

            构建表

            """

            if  force:

                       try:

                                   self.es_client.indices.delete(index=kg_id,  ignore_unavailable=True)

                       except  EngineError as e:

                                   logger.exception(f"code:{ES_DELETE_INDEX_ERROR},  message:{str(e)}")

                                   raise  e


            if  not self.es_client.indices.exists(index=kg_id):

                       body  = {

                                   'settings':  {'index': {'number_of_shards': 2}},

                                   'mappings':  {

                                               'dynamic':  False,

                                               'properties':  {

                                                          'name':  {'type': 'keyword'},

                                                          'concepts':  {'type': 'keyword'},

                                                          'property':  {'type': 'keyword'},

                                                          'value':  {'type': 'text', 'analyzer': 'ik_max_word', 'search_analyzer': 'ik_smart'},

                                                          'numbers':  {'type': 'double_range'},

                                                          'embeddings':  {'type': 'elastiknn_dense_float_vector', 'elastiknn': {'dims': 768, 'model':  'lsh', 'similarity': 'cosine', 'L': 99, 'k': 3}}

                                               }

                                   }

                       }

                       try:

                                   self.es_client.indices.create(index=kg_id,  body=body)

                       except  EngineError as e:

                                   logger.exception(f"code:1008,  message:{str(e)}")

                                   raise  e

            try:  

                       self.es_client.indices.refresh(index=kg_id,  ignore_unavailable=True)

            except  EngineError as e:

                       logger.exception(f"code:1008,  message:{str(e)}")

                       raise  e


说明:

1. value字段需要经过IK分词,分词方式ik_max_word,查询方式是ik_smart

2. embeddings的类型为elastiknn_dense_float_vector,其中向量维度为768,相似度计算使用cosine

4.3 候选实体查询


def get_candidate_entities(self, kg_id, query, limit=15):

         """

         基于查询串查找候选实体名称

         """

         body = {

                  '_source':  {'excludes': ["embeddings"]},

                  'query': {

                          'function_score':  {

                                   'query':  {

                                            'bool':  {

                                                     'must':  [

                                                             {'match':  {'value': query}},

                                                             {'bool':  {

                                                                      'filter':  {

                                                                               'bool':  {

                                                                                        'should':  [

                                                                                                {'term':  {"property": "名称"}},

                                                                                                {'term':  {"property": "别名"}},

                                                                                        ]

                                                                               }

                                                                      }

                                                             }}

                                                     ]

                                            }

                                   },

                                   'functions':  [

                                            {

                                               'elastiknn_nearest_neighbors': {

                                                        'field': 'embeddings',

                                                        'vec': self.get_callback_ans({'query':  [query]})['result'][0]['embeddings'],

                                                        'model': 'lsh',

                                                        'similarity': 'cosine',

                                                        'candidates': 100

                                               }

                                            }

                                   ]

                          }

                  },

                  'size':  limit

         }

         return  self.es_client.search(index=kg_id, body=body)['hits']['hits']


说明:

1. '_source':

{'excludes': ["embeddings"]}表示输出结果中过滤embeddings字段

2. 查询以function_score方式,其中的query表示别名或名称与问题的匹配程度,functions表示打分方式,目前的打分是基于向量相似度进行打分,其中, self.get_callback_ans表示语义相似度模型将文本转换为向量。注意:最终的得分由两部分组成,一部分是文本匹配,一部分是语义相似度匹配,不过可以增加参数boost_mode进行设置。

4.4 基于属性及属性值进行查询


def search_by_property_value(self, kg_id, property, value,  limit=100):

         body = {

                  '_source':  {'excludes': ["embeddings"]},

                  'query': {

                          'function_score':  {

                                   'query':  {

                                            'bool':  {

                                                     'must':  [

                                                             {'match':  {'value': value}},

                                                             {'term':  {"property": property}}

                                                     ]

                                            }

                                   },

                                   'functions':  [

                                            {

                                               'elastiknn_nearest_neighbors': {

                                                        'field': 'embeddings',

                                                        'vec': self.get_callback_ans({'query':  [value]})['result'][0]['embeddings'],

                                                        'model': 'lsh',

                                                        'similarity': 'cosine',

                                                        'candidates': 100

                                               }

                                            }

                                   ],

                                   'boost_mode':  'replace'

                          }

                  },

                  'size':  limit

         }

         try:

                  return  self.es_client.search(index=kg_id, body=body)['hits']['hits']

         except EngineError  as e:

                  logger.exception(f"code:{ES_SEARCH_ERROR},  message:{str(e)}")

                  raise e


4.5 数值属性范围查询

主要解决的场景有:体重大于9吨的恐龙有哪些?身长小于10米的角龙类有哪些?

其中,如果提供了实体名称,则查询范围是基于这些实体进行查询比较。


def search_by_number_property(self, kg_id, property, operate,  entities, limit=100):

         musts = [{'term':  {'property': property}}, {'range': {'numbers': operate}}]

         if entities:

                  musts.append({'terms':  {'name': entities}})


         body = {

                  '_source':  {'excludes': ['embeddings']},

                  'query': {

                          'bool':  {

                                   'must':  musts

                          }

                  },

                  'size':  limit

         }

         try:

                  return  self.es_client.search(index=kg_id, body=body)['hits']['hits']

         except EngineError  as e:

                  logger.exception(f"code:{ES_SEARCH_ERROR},  message:{str(e)}")

                  raise e


4.6 数值属性最大最小查询

实现最大最小的逻辑,采用了sort机制,按照numbers进行排序,最大则顺排,最小则倒排。


def search_by_number_property_maxmin(self, kg_id, property,  entities, sort_flag):

         musts = [{'term':  {'property': property}}]

         if entities:

                  musts.append({'terms':  {'name': entities}})


         body = {

                  '_source':  {'excludes': ["embeddings"]},

                  'query': {

                          'bool':  {

                                   'must':  musts

                          }

                  },

                  'sort':  {'numbers': sort_flag},

                  'size': 1

         }

         try:

                  return  self.es_client.search(index=kg_id, body=body)['hits']['hits']

         except EngineError  as e:

                  logger.exception(f"code:{ES_SEARCH_ERROR},  message:{str(e)}")

                  raise e


5. 效果

上一版未解决的问题,在本版本优化的结果。

1. 问:头像鸭头的龙有哪些?

答:头像鸭头的有慈母龙、原角龙、鹦鹉嘴龙、姜氏巴克龙、奇异辽宁龙、多背棘沱江龙、陆家屯鹦鹉嘴龙、盖斯顿龙、小盾龙、肿头龙、弯龙

2. 问:老师说的有一个特别会照顾宝宝的恐龙是什么龙?

答:慈母龙会照顾宝宝。

3. 问:有哪些恐龙会游泳啊?

答:滑齿龙、慢龙和色雷斯龙是会游泳的恐龙。

4. 问:科学家在意大利阿尔卑斯山脉Preone山谷的乌迪内附近发现了一个会飞的史前动物化石,它是谁的化石?

答:科学家在意大利阿尔卑斯山脉Preone山谷的乌迪内附近发现的会飞的史前动物化石是沛温翼龙的化石。

6. 总结

一句话足矣~

本文主要是针对KBQA方案基于LLM实现存在的问题进行优化,主要涉及到图谱存储至Es,且支持Es的向量检索,还有解决了一部分基于属性值倒查实体的场景,且效果相对提升。

     其次,提供了部分Es的操作源码,以飧读者。


附件:

1. es vs milvus:https://zilliz.com/comparison/milvus-vs-elastic

2. docker安装es:https://www.elastic.co/guide/en/elasticsearch/reference/8.12/docker.html

3. elastiknn性能分析:https://blog.csdn.net/star1210644725/article/details/134021552

4. es的function_score:https://www.elastic.co/guide/en/elasticsearch/reference/8.12/query-dsl-function-score-query.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容