import os import json import torch from neo4j import GraphDatabase as GD # from src.plugins import pdf2txt, OneKE from transformers import AutoTokenizer, AutoModel from FlagEmbedding import FlagModel, FlagReranker import warnings from src.plugins import pdf2txt from src.plugins.oneke import OneKE warnings.filterwarnings("ignore", category=UserWarning) UIE_MODEL = None class GraphDatabase: def __init__(self, config, embed_model=None): self.config = config self.driver = None self.files = [] self.status = "closed" assert embed_model, "embed_model=None" self.embed_model = embed_model def start(self): uri = os.environ.get("NEO4J_URI") username = os.environ.get("NEO4J_USERNAME") password = os.environ.get("NEO4J_PASSWORD") self.driver = GD.driver(uri, auth=(username, password)) self.status = "open" def close(self): """关闭数据库连接""" self.driver.close() def create_graph_database(self, kgdb_name): """创建新的数据库,如果已存在则返回已有数据库的名称""" with self.driver.session() as session: existing_databases = session.run("SHOW DATABASES") existing_db_names = [db['name'] for db in existing_databases] if existing_db_names: print(f"已存在数据库: {existing_db_names[0]}") return existing_db_names[0] # 返回所有已有数据库名称 session.run(f"CREATE DATABASE {kgdb_name}") print(f"数据库 '{kgdb_name}' 创建成功.") return kgdb_name # 返回创建的数据库名称 def get_database_info(self, db_name="neo4j"): """获取指定数据库的信息""" self.use_database(db_name) def query(tx): entity_count = tx.run("MATCH (n:Entity) RETURN count(n) AS count").single()["count"] relationship_count = tx.run("MATCH ()-[r]->() RETURN count(r) AS count").single()["count"] triples_count = tx.run("MATCH (n)-[r]->(m) RETURN count(n) AS count").single()["count"] return { "database_name": db_name, "entity_count": entity_count, "relationship_count": relationship_count, "triples_count": triples_count, "status": self.status } with self.driver.session() as session: return session.execute_read(query) def use_database(self, kgdb_name): """切换到指定数据库""" self.driver = GD.driver(f"{os.environ.get('NEO4J_URI')}/{kgdb_name}", auth=(os.environ.get('NEO4J_USERNAME'), os.environ.get('NEO4J_PASSWORD'))) def txt_add_entity(self, triples, kgdb_name='neo4j'): """添加实体三元组""" self.use_database(kgdb_name) def create(tx, triples): for triple in triples: h = triple['h'] t = triple['t'] r = triple['r'] query = ( "MERGE (a:Entity {name: $h}) " "MERGE (b:Entity {name: $t}) " "MERGE (a)-[:" + r.replace(" ", "_") + "]->(b)" ) tx.run(query, h=h, t=t) with self.driver.session() as session: session.execute_write(create, triples) def pdf_file_add_entity(self, file_path, output_path, kgdb_name='neo4j'): self.use_database(kgdb_name) # 切换到指定数据库 text_path = pdf2txt(file_path) global UIE_MODEL if UIE_MODEL is None: UIE_MODEL = OneKE() triples_path = UIE_MODEL.processing_text_to_kg(text_path, output_path) self.jsonl_file_add_entity(triples_path) return kgdb_name def txt_add_vector_entity(self, triples, kgdb_name='neo4j'): """添加实体三元组""" self.use_database(kgdb_name) def _index_exists(tx, index_name): result = tx.run("SHOW INDEXES") for record in result: if record["name"] == index_name: return True return False def _create_graph(tx, data): for entry in data: tx.run(""" MERGE (h:Entity {name: $h}) MERGE (t:Entity {name: $t}) MERGE (h)-[r:RELATION {type: $r}]->(t) """, h=entry['h'], t=entry['t'], r=entry['r']) def _create_vector_index(tx): index_name = "entity-embeddings" if not _index_exists(tx, index_name): tx.run(f""" CREATE VECTOR INDEX {index_name} FOR (n: Entity) ON (n.embedding) OPTIONS {{indexConfig: {{ `vector.dimensions`: 1024, `vector.similarity_function`: 'cosine' }} }}; """) with self.driver.session() as session: session.execute_write(_create_graph, triples) session.execute_write(_create_vector_index) for entry in triples: embedding_h = self.get_embedding(entry['h']) session.execute_write(self.set_embedding, entry['h'], embedding_h) embedding_t = self.get_embedding(entry['t']) session.execute_write(self.set_embedding, entry['t'], embedding_t) def jsonl_file_add_entity(self, file_path, kgdb_name='neo4j'): self.status = "processing" self.use_database(kgdb_name) # 切换到指定数据库 def read_triples(file_path): with open(file_path, 'r', encoding='utf-8') as file: for line in file: yield json.loads(line.strip()) triples = list(read_triples(file_path)) def batch_create(tx, triples): query = """ UNWIND $triples AS triple MERGE (a:Entity {name: triple.h}) MERGE (b:Entity {name: triple.t}) MERGE (a)-[r:RELATION {type: triple.r}]->(b) """ tx.run(query, triples=triples) def batch_add_embeddings(tx, embeddings): query = """ UNWIND $embeddings AS embedding MATCH (e:Entity {name: embedding.name}) SET e.embedding = embedding.vector """ tx.run(query, embeddings=embeddings) with self.driver.session() as session: session.execute_write(batch_create, triples) # 获取embedding并批量添加 embeddings = [] for triple in triples: h = triple['h'] t = triple['t'] embedding_h = self.get_embedding(h) embedding_t = self.get_embedding(t) embeddings.append({"name": h, "vector": embedding_h}) embeddings.append({"name": t, "vector": embedding_t}) session.execute_write(batch_add_embeddings, embeddings) self.status = "open" return kgdb_name def delete_entity(self, entity_name=None, kgdb_name="neo4j"): """删除数据库中的指定实体三元组, 参数entity_name为空则删除全部实体""" self.use_database(kgdb_name) with self.driver.session() as session: if entity_name: session.execute_write(self._delete_specific_entity, entity_name) else: session.execute_write(self._delete_all_entities) def _delete_specific_entity(self, tx, entity_name): query = """ MATCH (n {name: $entity_name}) DETACH DELETE n """ tx.run(query, entity_name=entity_name) def _delete_all_entities(self, tx): query = """ MATCH (n) DETACH DELETE n """ tx.run(query) def query_all_nodes_and_relationships(self, kgdb_name='neo4j', hops = 2): """查询图数据库中所有三元组信息""" self.use_database(kgdb_name) def query(tx, hops): result = tx.run(f""" MATCH (n)-[r*1..{hops}]->(m) RETURN n, r, m """) return result.values() with self.driver.session() as session: return session.execute_read(query, hops) def query_specific_entity(self, entity_name, kgdb_name='neo4j', hops=2): """查询指定实体三元组信息""" self.use_database(kgdb_name) def query(tx, entity_name, hops): result = tx.run(f""" MATCH (n {{name: $entity_name}})-[r*1..{hops}]->(m) RETURN n.name AS node_name, r, m.name AS neighbor_name """, entity_name=entity_name) return result.values() with self.driver.session() as session: return session.execute_read(query, entity_name, hops) def query_by_relationship_type(self, relationship_type, kgdb_name='neo4j', hops = 2): """查询指定关系三元组信息""" self.use_database(kgdb_name) def query(tx, relationship_type, hops): result = tx.run(f""" MATCH (n)-[r:`{relationship_type}`*1..{hops}]->(m) RETURN n, r, m """) return result.values() with self.driver.session() as session: return session.execute_read(query, relationship_type, hops) def query_entity_like(self, keyword, kgdb_name='neo4j', hops = 2): """模糊查询""" self.use_database(kgdb_name) def query(tx, keyword, hops): result = tx.run(f""" MATCH (n:Entity) WHERE n.name CONTAINS $keyword MATCH (n)-[r*1..{hops}]->(m) RETURN n, r, m """, keyword=keyword) return result.values() with self.driver.session() as session: return session.execute_read(query, keyword, hops) def query_by_vector_tep(self, keyword, kgdb_name='neo4j'): """向量查询""" self.use_database(kgdb_name) def query(tx, text): embedding = self.get_embedding(text) result = tx.run(""" CALL db.index.vector.queryNodes('entity-embeddings', 10, $embedding) YIELD node AS similarEntity, score RETURN similarEntity.name AS name, score """, embedding=embedding) # result = result.values() # query = result[0][0] return result.values() with self.driver.session() as session: return session.execute_read(query, keyword) def query_by_vector(self, entity_name, threshold=0.9,kgdb_name='neo4j', hops=2, num_of_res=2): self.use_database(kgdb_name) result = self.query_by_vector_tep(entity_name) querys = [] threshold = 0.9 if threshold is None else threshold num_of_res = 2 if num_of_res is None else num_of_res for i in range(num_of_res): if result[i][1] > threshold: querys.append(result[i][0]) else: break ans = [] for query in querys: tep = self.query_specific_entity(query, hops) # 这里是只获取第一个 TODO: 优化 ans.extend(tep) return ans def query_node_info(self, node_name, kgdb_name='neo4j', hops = 2): """查询指定节点的详细信息返回信息""" self.use_database(kgdb_name) # 切换到指定数据库 def query(tx, node_name, hops): result = tx.run(f""" MATCH (n {{name: $node_name}}) OPTIONAL MATCH (n)-[r*1..{hops}]->(m) RETURN n, r, m """, node_name=node_name) return result.values() with self.driver.session() as session: return session.execute_read(query, node_name, hops) def get_embedding(self, text): inputs = [text] with torch.no_grad(): outputs = self.embed_model.encode(inputs) embeddings = outputs[0] # 假设取平均作为文本的嵌入向量 return embeddings def set_embedding(self, tx, entity_name, embedding): tx.run(""" MATCH (e:Entity {name: $name}) CALL db.create.setNodeVectorProperty(e, 'embedding', $embedding) """, name=entity_name, embedding=embedding) # def format_query_results(self, results): # formatted_results = [] # for row in results: # n, rs, m = row # entity_a = n['name'] # entity_b = m['name'] # for rel in rs: # relationship = rel.type # formatted_results.append(f"实体 {entity_a} 和 实体 {entity_b} 的关系是 {relationship}") # return formatted_results if __name__ == "__main__": config = None kgdb_name = "neo4j" class EmbeddingModel(FlagModel): def __init__(self, config, **kwargs): model_name_or_path = "/data2024/yyyl/model/BAAI/bge-large-zh-v1.5/" super().__init__(model_name_or_path, use_fp16=False, **kwargs) model = EmbeddingModel(config) # 初始化知识图谱数据库 kgdb = GraphDatabase(config, model) # 创建新的数据库 # kgdb.create_graph_database("db2") # 返回指定数据库信息 # info = kgdb.get_database_info(kgdb_name) # print(info) # triples = [ # { # "h": "CCC", # "t": "EE", # "r": "同学" # }, # { # "h": "EE", # "t": "RR", # "r": "同事" # } # ] # kgdb.query_by_vector("z") # def format_query_results(results): # formatted_results = {"nodes": [], "edges": []} # # 用于存储所有唯一的节点信息 # node_dict = {} # for item in results: # # 确保item[1]是一个非空的列表 # if isinstance(item[1], list) and len(item[1]) > 0: # relationship = item[1][0] # rel_id = relationship.element_id # nodes = relationship.nodes # if len(nodes) == 2: # node1, node2 = nodes # # 提取源节点和目标节点信息 # node1_id = node1.element_id # node2_id = node2.element_id # node1_name = item[0] # 假设节点名称和列表中的第一个元素相同 # node2_name = item[2] if len(item) > 2 else 'unknown' # # 记录节点信息 # if node1_id not in node_dict: # node_dict[node1_id] = {"id": node1_id, "name": node1_name} # if node2_id not in node_dict: # node_dict[node2_id] = {"id": node2_id, "name": node2_name} # # 确定关系类型 # relationship_type = relationship._properties.get('type', 'unknown') # if relationship_type == 'unknown': # relationship_type = relationship.type # # 记录边的信息 # formatted_results["edges"].append({ # "id": rel_id, # "type": relationship_type, # "source_id": node1_id, # "target_id": node2_id, # "source_name": node1_name, # "target_name": node2_name # }) # # 将唯一的节点信息添加到结果中 # formatted_results["nodes"] = list(node_dict.values()) # return formatted_results # entities = ['jqy', '维c'] # results = [] # for entitie in entities: # result = kgdb.query_by_vector(entitie) # if result != []: # results.extend(result) # print(format_query_results(results)) # kgdb.txt_add_vector_entity(triples, model) # print("Extend the Graph data base") # kgdb.jsonl_file_add_entity("/data2024/yyyl/ProjectAthena/tep.jsonl", kgdb_name) # print("Extend the Graph data base") # triples_path = "output.jsonl" # def read_triples(file_path): # with open(file_path, 'r', encoding='utf-8') as file: # for line in file: # item = json.loads(line.strip()) # yield [item] # for trio in read_triples(triples_path): # kgdb.txt_add_entity(trio) # 通过文件添加三元组数据 # kgdb.file_add_entity("/data2024/yyyl/ProjectAthena/test.pdf", "/data2024/yyyl/ProjectAthena/output.jsonl", kgdb_name) # print("Extend the Graph data base") # 删除数据库信息 # kgdb.delete_entity() # print("Clear the Graph data base") # 查询所有节点和关系 # results = kgdb.query_all_nodes_and_relationships(kgdb_name) # print(results) # 查询特定实体及其关系 # results = kgdb.query_specific_entity("kllll", kgdb_name) # print(results) # 查询特定实体及其关系 # results = kgdb.query_by_vector_tep("z", model, tokenizer) # print(results) # 查询特定关系类型的所有节点 # results = kgdb.query_by_relationship_type("作用或食用效果", kgdb_name) # print(results) # 模糊查询 # results = kgdb.query_entity_like("三七", kgdb_name) # print(results) # 查询节点信息 # results = kgdb.query_entity_like("三七提取物", kgdb_name) # print(results) # 关闭数据库连接 kgdb.close()