# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import time from pipelines.document_stores import ( BaiduElasticsearchDocumentStore, ElasticsearchDocumentStore, MilvusDocumentStore, ) from pipelines.nodes import DensePassageRetriever from pipelines.utils import convert_files_to_dicts, fetch_archive_from_http, launch_es from pipelines.utils.preprocessing import convert_files_to_dicts_splitter data_dict = { "data/dureader_dev": "https://paddlenlp.bj.bcebos.com/applications/dureader_dev.zip", "data/baike": "https://paddlenlp.bj.bcebos.com/applications/baike.zip", "data/insurance": "https://paddlenlp.bj.bcebos.com/applications/insurance.zip", "data/file_example": "https://paddlenlp.bj.bcebos.com/pipelines/file_examples.zip", } # yapf: disable parser = argparse.ArgumentParser() parser.add_argument("--index_name", default="baike_cities", type=str, help="The index name of the ANN search engine") parser.add_argument("--doc_dir", default="data/baike/", type=str, help="The doc path of the corpus") parser.add_argument('--username', type=str, default="", help='Username of ANN search engine') parser.add_argument('--password', type=str, default="", help='Password of ANN search engine') parser.add_argument("--search_engine", choices=["elastic", "milvus", 'bes'], default="elastic", help="The type of ANN search engine.") parser.add_argument("--host", type=str, default="127.0.0.1", help="host ip of ANN search engine") parser.add_argument("--port", type=str, default="9200", help="port of ANN search engine") parser.add_argument("--embedding_dim", default=768, type=int, help="The embedding_dim of index") parser.add_argument("--split_answers", action="store_true", help="whether to split lines into question and answers") parser.add_argument("--query_embedding_model", default="rocketqa-zh-base-query-encoder", type=str, help="The query_embedding_model path",) parser.add_argument("--passage_embedding_model", default="rocketqa-zh-base-para-encoder", type=str, help="The passage_embedding_model path", ) parser.add_argument("--params_path", default="checkpoints/model_40/model_state.pdparams", type=str, help="The checkpoint path") parser.add_argument("--delete_index", action="store_true", help="Whether to delete existing index while updating index") parser.add_argument("--share_parameters", action="store_true", help="Use to control the query and title models sharing the same parameters",) parser.add_argument('--model_type', choices=['ernie_search', 'ernie', 'bert', 'neural_search'], default="ernie", help="the ernie model types") parser.add_argument('--embed_title', default=False, type=bool, help="The title to be embedded into embedding") parser.add_argument('--device', choices=['cpu', 'gpu'], default="gpu", help="Select devices, defaults to gpu.") parser.add_argument('--search_fields', default=['content', 'name'], help="multi recall BM25Retriever set search_fields") parser.add_argument('--use_splitter', default=False, type=bool, help="How to split documents") parser.add_argument('--chunk_size', type=int, default=300, help="The length of data for indexing by retriever") parser.add_argument('--chunk_overlap', type=int, default=0, help="a larger chunk than the chunk overlap") parser.add_argument('--separator', type=str, default='\n', help="Use symbols to segment text, PDF, and image files, or connect some short chunks") parser.add_argument('--filters', type=list, default=['\n'], help="Filter special symbols") parser.add_argument('--language', type=str, default='chinese', help="the language of files") parser.add_argument('--pooling_mode', choices=['max_tokens', 'mean_tokens', 'mean_sqrt_len_tokens', 'cls_token'], default='cls_token', help='the type of sentence embedding') parser.add_argument("--es_chunk_size", default=500, type=int, help="Number of docs in one chunk sent to es") parser.add_argument("--es_thread_count", default=32, type=int, help="Size of the threadpool to use for the bulk requests") parser.add_argument("--es_queue_size", default=32, type=int, help="Size of the task queue between the main thread (producing chunks to send) and the processing threads.") args = parser.parse_args() # yapf: enable def offline_ann(index_name, doc_dir): use_gpu = True if args.device == "gpu" else False if args.search_engine == "milvus": document_store = MilvusDocumentStore( embedding_dim=args.embedding_dim, host=args.host, index=args.index_name, port=args.port, index_param={"M": 16, "efConstruction": 50}, index_type="HNSW", ) elif args.search_engine == "bes": document_store = BaiduElasticsearchDocumentStore( host=args.host, port=args.port, username=args.username, password=args.password, embedding_dim=args.embedding_dim, similarity="dot_prod", vector_type="bpack_vector", search_fields=["content", "meta"], index=args.index_name, chunk_size=args.es_chunk_size, thread_count=args.es_thread_count, queue_size=args.es_queue_size, ) else: launch_es() document_store = ElasticsearchDocumentStore( host=args.host, port=args.port, username=args.username, password=args.password, embedding_dim=args.embedding_dim, index=index_name, search_fields=args.search_fields, # 当使用了多路召回并且搜索字段设置了除content的其他字段,构建索引时其他字段也需要设置,例如:['content', 'name']。 ) # 将每篇文档按照段落进行切分 if args.use_splitter: dicts = convert_files_to_dicts_splitter( dir_path=doc_dir, split_paragraphs=True, split_answers=args.split_answers, encoding="utf-8", separator=args.separator, filters=args.filters, chunk_size=args.chunk_size, language=args.language, chunk_overlap=args.chunk_overlap, ) else: dicts = convert_files_to_dicts( dir_path=doc_dir, split_paragraphs=True, split_answers=args.split_answers, encoding="utf-8" ) print(dicts[:3]) # 文档数据写入数据库 document_store.write_documents(dicts) # 语义索引模型 retriever = DensePassageRetriever( document_store=document_store, query_embedding_model=args.query_embedding_model, passage_embedding_model=args.passage_embedding_model, params_path=args.params_path, output_emb_size=args.embedding_dim if args.model_type in ["ernie_search", "neural_search"] else None, share_parameters=args.share_parameters, max_seq_len_query=64, max_seq_len_passage=256, batch_size=16, use_gpu=use_gpu, embed_title=args.embed_title, ) # Writing docs may take a while. so waiting until writing docs to be completed. document_count = document_store.get_document_count() while document_count == 0: time.sleep(1) print("Waiting for writing docs to be completed.") document_count = document_store.get_document_count() # 建立索引库 document_store.update_embeddings(retriever) def delete_data(index_name): if args.search_engine == "milvus": document_store = MilvusDocumentStore( embedding_dim=args.embedding_dim, host=args.host, index=args.index_name, port=args.port, index_param={"M": 16, "efConstruction": 50}, index_type="HNSW", ) elif args.search_engine == "bes": document_store = BaiduElasticsearchDocumentStore( host=args.host, port=args.port, username=args.username, password=args.password, embedding_dim=args.embedding_dim, similarity="dot_prod", vector_type="bpack_vector", search_fields=["content", "meta"], index=args.index_name, chunk_size=args.es_chunk_size, thread_count=args.es_thread_count, queue_size=args.es_queue_size, ) else: document_store = ElasticsearchDocumentStore( host=args.host, port=args.port, username="", password="", embedding_dim=args.embedding_dim, index=index_name, ) document_store.delete_index(index_name) print("Delete an existing elasticsearch index {} Done.".format(index_name)) if __name__ == "__main__": if args.doc_dir in data_dict: fetch_archive_from_http(url=data_dict[args.doc_dir], output_dir=args.doc_dir) if args.delete_index: delete_data(args.index_name) offline_ann(args.index_name, args.doc_dir)