import re from openai import OpenAI from typing import List, Dict,Tuple import json import time import os from kg_build_v2.Prompt import Prompt_guzhang,Prompt_compose,Prompt_compose_tujie,Prompt_compose_mulu_tujie from kg_build_v2.extract_html import extract_repairproject_tables_html,extract_operation_tables_html,extract_tables,extract_guzhang_tables # from modelsAPI.model_api import OpenaiAPI def read_markdown_file(file_path): """ 读取 Markdown 文件并返回其全部内容作为字符串。 参数: file_path (str): Markdown 文件的路径(支持相对或绝对路径) 返回: str 或 None: 成功时返回文件内容;出错时返回 None """ try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() return content except FileNotFoundError: print(f"错误:文件未找到 - {file_path}") except PermissionError: print(f"错误:没有权限读取文件 - {file_path}") except UnicodeDecodeError: print(f"错误:文件编码不是 UTF-8,无法读取 - {file_path}") except Exception as e: print(f"读取文件时发生未知错误: {e}") return None def openai_chat(query:str, model="Qwen3.5-35B-A3B"): """调用OpenAI API""" try: client = OpenAI( api_key= "none", base_url= "http://192.168.0.46:59800/v1" ) response = client.chat.completions.create( model=model, messages=[ {"role": "user", "content": query}, ], temperature=0.1, stream=False, response_format = {"type": "json_object"}, extra_body={"chat_template_kwargs": {"enable_thinking": False}} ) return response.choices[0].message.content except Exception as e: print(f"调用OpenAI API时出错: {e}") return None def safe_json_loads(s: str): try: return json.loads(s) except json.JSONDecodeError as e: # 尝试修复常见问题 s = s.replace("\\n", "\\\n").replace("\r", "\\r").replace("\t", "\\t") # 或者直接清理非法字符 s = re.sub(r'[\x00-\x1f\x7f]', '', s) # 移除控制字符 try: return json.loads(s) except: raise e #def openai_chat(query: str, model="Qwen3-32B"): # """调用OpenAI API""" # try: # client = OpenAI( # api_key="none", # base_url="http://192.168.0.46:59800/v1" # ) # response = client.chat.completions.create( # model=model, # messages=[ # {"role": "user", "content": query}, # ], # temperature=0.1, # stream=False, # response_format={"type": "json_object"}, # extra_body={"chat_template_kwargs": {"enable_thinking": False}} # ) # return response.choices[0].message.content # except Exception as e: # print(f"调用OpenAI API时出错: {e}") # return None # def extract_guzhang_table_entity(chunks): # """ # 提取故障类实体 # """ # all_results = [] # all_entity_results = [] # all_relation_results = [] # # 确定起始索引 # print("提取故障类实体和关系") # for idx, block in enumerate(chunks, 1): # # 跳过第一个表格 # print(f"=== 故障类 Block {idx} ===") # print(block[:100]) # 只打印前500字符 # print("\n" + "="*60 + "\n") # final_prompt = Prompt_guzhang.format( # text=block # ) # raw_response = openai_chat(final_prompt) # if raw_response is None: # print(f" ⚠️ 第 {idx} 切片 API 调用失败") # continue # # 尝试解析 JSON # try: # cleaned = raw_response.strip() # if cleaned.startswith("```json"): # cleaned = cleaned[7:].lstrip() # if cleaned.endswith("```"): # cleaned = cleaned[:-3].rstrip() # json_result = safe_json_loads(cleaned) # entities = json_result.get("entities", []) # relationships = json_result.get("relationships", []) # for entity in entities: # # 确保 entity 有 "properties" 键,且其值为字典 # if "properties" in entity and isinstance(entity["properties"], dict): # entity["properties"]["切片"] = block # # 打印美化版 # all_entity_results += entities # all_relation_results += relationships # # all_results.append({ # # "entities": entities, # 已添加切片属性的实体列表 # # "relationships": relationships, # 关系列表 # # }) # # 打印美化版 # except Exception as e: # print(f" ⚠️ 第 {idx} 切片 JSON 解析失败: {e}") # print(f" 原始响应: {repr(raw_response[:500])}") # json_result = None # continue # all_results.append({ # "entities": all_entity_results, # 已添加切片属性的实体列表 # "relationships": all_relation_results, # 关系列表 # }) # return all_results def extract_guzhang_table_entity(chunks): """ 提取故障类实体和关系,将所有切片的结果合并为两个大列表 返回: { "entities": [...], "relationships": [...] } """ all_entities = [] all_relationships = [] print("提取故障类实体和关系") for idx, block in enumerate(chunks, 1): print(f"=== 故障类 Block {idx} ===") print(block[:100]) print("\n" + "="*60 + "\n") final_prompt = Prompt_guzhang.format(text=block) raw_response =openai_chat(final_prompt) if raw_response is None: print(f" ⚠️ 第 {idx} 切片 API 调用失败") continue try: cleaned = raw_response.strip() if cleaned.startswith("```json"): cleaned = cleaned[7:].lstrip() if cleaned.endswith("```"): cleaned = cleaned[:-3].rstrip() json_result = safe_json_loads(cleaned) if json_result is None: print(f" ⚠️ 第 {idx} 切片 JSON 解析失败: 解析返回 None") continue entities = json_result.get("entities", []) relationships = json_result.get("relationships", []) # 为每个实体添加来源切片信息 for entity in entities: if "properties" in entity and isinstance(entity["properties"], dict): entity["properties"]["切片"] = block # 拼接到总列表 all_entities.extend(entities) all_relationships.extend(relationships) print(f" ✅ 第 {idx} 切片解析成功: {len(entities)} 个实体, {len(relationships)} 个关系") except Exception as e: print(f" ⚠️ 第 {idx} 切片 JSON 解析失败: {e}") print(f" 原始响应: {repr(raw_response[:500])}") continue # 最终结果:返回一个字典,包含拼接后的两个列表 result = { "entities": all_entities, "relationships": all_relationships } return result # def extract_compose_table_entity(chunks): # """ # 提取组成表的设备、零部件实体以及关系 # """ # print("提取故障类实体和关系") # all_results = [] # all_entity_results = [] # all_relation_results = [] # # 确定起始索引 # for idx, block in enumerate(chunks, 1): # # 跳过第一个表格 # print(f"=== 组成表 Block {idx} ===") # print(block[:100]) # 只打印前500字符 # print("\n" + "="*60 + "\n") # final_prompt = Prompt_compose.format( # text=block # ) # raw_response = openai_chat(final_prompt) # if raw_response is None: # print(f" ⚠️ 第 {idx} 切片 API 调用失败") # continue # # 尝试解析 JSON # try: # cleaned = raw_response.strip() # if cleaned.startswith("```json"): # cleaned = cleaned[7:].lstrip() # if cleaned.endswith("```"): # cleaned = cleaned[:-3].rstrip() # json_result = safe_json_loads(cleaned) # entities = json_result.get("entities", []) # relationships = json_result.get("relationships", []) # for entity in entities: # # 确保 entity 有 "properties" 键,且其值为字典 # if "properties" in entity and isinstance(entity["properties"], dict): # entity["properties"]["切片"] = block # all_entity_results += entities # all_relation_results += relationships # # 打印美化版 # print(json.dumps(json_result, ensure_ascii=False, indent=2)) # # all_results.append({ # # "entities": entities, # 已添加切片属性的实体列表 # # "relationships": relationships, # 关系列表 # # }) # except Exception as e: # print(f" ⚠️ 第 {idx} 切片 JSON 解析失败: {e}") # print(f" 原始响应: {repr(raw_response[:500])}") # json_result = None # continue # all_results.append({ # "entities": all_entity_results, # 已添加切片属性的实体列表 # "relationships": all_relation_results, # 关系列表 # }) # return all_results def extract_compose_table_entity(chunks): """ 提取组成表的设备、零部件实体以及关系 """ print("提取组成表实体和关系") # 建议:将 all_results 改为复数形式以符合习惯 all_entities = [] all_relationships = [] # 确定起始索引 for idx, block in enumerate(chunks, 1): # 跳过第一个表格(如果需要的话,逻辑保持不变) print(f"=== 组成表 Block {idx} ===") print(block[:100]) # 只打印前100字符预览 print("\n" + "="*60 + "\n") final_prompt = Prompt_compose.format(text=block) raw_response =openai_chat(final_prompt) if raw_response is None: print(f" ⚠️ 第 {idx} 切片 API 调用失败") continue # 尝试解析 JSON try: cleaned = raw_response.strip() if cleaned.startswith("```json"): cleaned = cleaned[7:].lstrip() if cleaned.endswith("```"): cleaned = cleaned[:-3].rstrip() json_result = safe_json_loads(cleaned) # === 核心修改点:处理实体并添加切片信息 === entities = json_result.get("entities", []) relationships = json_result.get("relationships", []) # 为当前切片的实体添加属性 for entity in entities: if "properties" in entity and isinstance(entity["properties"], dict): entity["properties"]["切片"] = block # 构建当前块的结果对象 # current_result = { # "entities": entities, # "relationships": relationships, # } all_entities.extend(entities) all_relationships.extend(relationships) # 添加到总结果列表 # 打印美化版 # print(json.dumps(json_result, ensure_ascii=False, indent=2)) except Exception as e: print(f" ⚠️ 第 {idx} 切片 JSON 解析失败: {e}") print(f" 原始响应: {repr(raw_response[:500])}") continue result = { "entities": all_entities, "relationships": all_relationships } # 返回按切片分组的结果列表 return result # def compose_table_tujie_entity(chunks): # """ # 提取图解目录中设备、零部件类实体以及相关关系 # """ # all_results = [] # all_entity_results = [] # all_relation_results = [] # # 确定起始索引 # for idx, block in enumerate(chunks, 1): # # 跳过第一个表格 # print(f"=== 图解目录 Block {idx} ===") # print(block[:100]) # 只打印前500字符 # print("\n" + "="*60 + "\n") # final_prompt = Prompt_compose_tujie.format( # text=block # ) # raw_response = openai_chat(final_prompt) # if raw_response is None: # print(f" ⚠️ 第 {idx} 切片 API 调用失败") # continue # # 尝试解析 JSON # try: # cleaned = raw_response.strip() # if cleaned.startswith("```json"): # cleaned = cleaned[7:].lstrip() # if cleaned.endswith("```"): # cleaned = cleaned[:-3].rstrip() # json_result = safe_json_loads(cleaned) # entities = json_result.get("entities", []) # relationships = json_result.get("relationships", []) # for entity in entities: # # 确保 entity 有 "properties" 键,且其值为字典 # if "properties" in entity and isinstance(entity["properties"], dict): # entity["properties"]["切片"] = block # # 打印美化版 # all_entity_results += entities # all_relation_results += relationships # print(json.dumps(json_result, ensure_ascii=False, indent=2)) # # all_results.append({ # # "entities": entities, # 已添加切片属性的实体列表 # # "relationships": relationships, # 关系列表 # # }) # # 打印美化版 # print(json.dumps(json_result, ensure_ascii=False, indent=2)) # success = True # except Exception as e: # print(f" ⚠️ 第 {idx} 切片 JSON 解析失败: {e}") # print(f" 原始响应: {repr(raw_response[:500])}") # success = False # json_result = None # continue # all_results.append({ # "entities": all_entity_results, # 已添加切片属性的实体列表 # "relationships": all_relation_results, # 关系列表 # }) # return all_results def compose_table_tujie_entity(chunks): """ 提取图解目录中设备、零部件类实体以及相关关系 """ all_entities = [] all_relationships = [] for idx, block in enumerate(chunks, 1): print(f"=== 图解目录 Block {idx} ===") print(block[:100]) # 只打印前100字符预览 print("\n" + "="*60 + "\n") final_prompt = Prompt_compose_tujie.format(text=block) raw_response = openai_chat(final_prompt) if raw_response is None: print(f" ⚠️ 第 {idx} 切片 API 调用失败") continue # 尝试解析 JSON try: # 清理响应中的 Markdown 代码块标记 cleaned = raw_response.strip() if cleaned.startswith("```json"): cleaned = cleaned[7:].lstrip() if cleaned.endswith("```"): cleaned = cleaned[:-3].rstrip() json_result = safe_json_loads(cleaned) # 提取实体和关系 entities = json_result.get("entities", []) relationships = json_result.get("relationships", []) # 为当前切片的实体添加“切片”属性 for entity in entities: if "properties" in entity and isinstance(entity["properties"], dict): entity["properties"]["切片"] = block # 构建当前切片的结果对象 all_entities.extend(entities) all_relationships.extend(relationships) # 将当前结果追加到总列表 # 打印美化版(打印 current_result 而不是原始的 json_result,确保包含修改) # print(json.dumps(json_result, ensure_ascii=False, indent=2)) except Exception as e: print(f" ⚠️ 第 {idx} 切片 JSON 解析失败: {e}") print(f" 原始响应: {repr(raw_response[:500])}") continue result = { "entities": all_entities, "relationships": all_relationships } return result def extract_compose_table_mulu_tujie(chunks): """ 提取图解目录中设备属性信息 """ all_results = [] # 确定起始索引 for idx, block in enumerate(chunks, 1): # 跳过第一个表格 print(f"=== Repair Block {idx} ===") print(block[:100]) # 只打印前500字符 print("\n" + "="*60 + "\n") final_prompt = Prompt_compose_mulu_tujie.format( text=block ) raw_response = openai_chat(final_prompt) if raw_response is None: print(f" ⚠️ 第 {idx} 切片 API 调用失败") continue all_results.append({ "chunk_index": idx, "structured_result": raw_response, "success": "success" }) return all_results # def get_nromal_node_rela(mdcontent): # print("开始提取常规表格实体和关系") # # 提取各类表格 # chunks_repair, cleaned_content = extract_repairproject_tables_html(mdcontent) # chunks_operation, cleaned_content_operation = extract_operation_tables_html(cleaned_content) # slices, contexts, cleaned_content_normal = extract_tables(cleaned_content_operation) # # 分类表格 # compose_table = [] # compose_table_tujie = [] # compose_table_mulu_tujie = [] # guzhang_table = [] # for ins in contexts: # if '组成部分' in ins and '功能描述' in ins and '数量' in ins: # compose_table.append(ins) # elif '组成编码' in ins and '名称' in ins and '是否为关重件' in ins and '是否为寿命件' in ins: # compose_table_tujie.append(ins) # elif '组成编码' in ins and '名称' in ins and '数量' in ins and '重量' in ins: # compose_table_mulu_tujie.append(ins) # elif '故障现象' in ins and '故障原因' in ins and '维修项目' in ins: # guzhang_table.append(ins) # # 初始化结果 # all_entities = [] # all_relationships = [] # # 提取并合并故障表 # if guzhang_table: # result = extract_guzhang_table_entity(guzhang_table) # all_entities.extend(result.get('entities', [])) # all_relationships.extend(result.get('relationships', [])) # print("输出故障类实体和关系") # print(len(result['entities'])) # print(len(result['relationships'])) # # 提取并合并普通组成表 # if compose_table: # result = extract_compose_table_entity(compose_table) # all_entities.extend(result.get('entities', [])) # all_relationships.extend(result.get('relationships', [])) # print("输出组成表类实体和关系") # print(len(result['entities'])) # print(len(result['relationships'])) # # 提取并合并图解组成表 # if compose_table_tujie: # result = compose_table_tujie_entity(compose_table_tujie) # all_entities.extend(result.get('entities', [])) # all_relationships.extend(result.get('relationships', [])) # print("输出图解组成表类实体和关系") # print(len(result['entities'])) # print(len(result['relationships'])) # print(11111111111111111111) # print(result) # print(f"常规提取完成:共提取 {len(all_entities)} 个实体,{len(all_relationships)} 个关系") # # 构造返回结果 # result = [{ # "entities": all_entities, # "relationships": all_relationships, # }] # return result, cleaned_content_normal def get_nromal_node_rela(mdcontent): print("开始提取常规表格实体和关系") # 初始化最终返回的清洗后内容 cleaned_content_normal = mdcontent try: # 提取各类表格 chunks_repair, cleaned_content = extract_repairproject_tables_html(mdcontent) chunks_operation, cleaned_content_operation = extract_operation_tables_html(cleaned_content) slices, contexts, cleaned_content_normal = extract_tables(cleaned_content_operation) guzhangtable = extract_guzhang_tables(mdcontent) # 分类表格 compose_table = [] compose_table_tujie = [] compose_table_mulu_tujie = [] guzhang_table = [] for ins in contexts: if '组成部分' in ins and '功能描述' in ins and '数量' in ins: compose_table.append(ins) elif '组成编码' in ins and '名称' in ins and '是否为关重件' in ins and '是否为寿命件' in ins: compose_table_tujie.append(ins) elif '组成编码' in ins and '名称' in ins and '数量' in ins and '重量' in ins: compose_table_mulu_tujie.append(ins) elif '故障现象' in ins and '故障原因' in ins and '维修项目' in ins: guzhang_table.append(ins) except Exception as e: print(f"预处理表格分类时发生错误: {e}") # 如果预处理出错,初始化的 contexts 为空,后续不会提取数据 contexts = [] # 初始化结果 all_entities = [] all_relationships = [] if guzhangtable: try: result = extract_guzhang_table_entity(guzhangtable) # 确保 result 是字典且包含键 if isinstance(result, dict): entities = result.get('entities', []) relationships = result.get('relationships', []) if entities or relationships: # 确保有数据 all_entities.extend(entities) all_relationships.extend(relationships) print(all_entities) print(all_relationships) print("输出故障类实体和关系") print(len(entities), len(relationships)) else: print("警告: extract_guzhang_table_entity 返回值不是字典") except Exception as e: print(f"提取故障表时发生错误: {e}") # --- 提取并合并故障表 (带容错) --- if guzhang_table: try: result = extract_guzhang_table_entity(guzhang_table) # 确保 result 是字典且包含键 if isinstance(result, dict): entities = result.get('entities', []) relationships = result.get('relationships', []) if entities or relationships: # 确保有数据 all_entities.extend(entities) all_relationships.extend(relationships) print("输出故障类实体和关系") print(len(entities), len(relationships)) else: print("警告: extract_guzhang_table_entity 返回值不是字典") except Exception as e: print(f"提取故障表时发生错误: {e}") # --- 提取并合并普通组成表 (带容错) --- if compose_table: try: result = extract_compose_table_entity(compose_table) if isinstance(result, dict): entities = result.get('entities', []) relationships = result.get('relationships', []) if entities or relationships: all_entities.extend(entities) all_relationships.extend(relationships) print("输出组成表类实体和关系") print(len(entities), len(relationships)) else: print("警告: extract_compose_table_entity 返回值不是字典") except Exception as e: print(f"提取普通组成表时发生错误: {e}") # --- 提取并合并图解组成表 (带容错) --- if compose_table_tujie: try: result = compose_table_tujie_entity(compose_table_tujie) if isinstance(result, dict): entities = result.get('entities', []) relationships = result.get('relationships', []) if entities or relationships: all_entities.extend(entities) all_relationships.extend(relationships) print("输出图解组成表类实体和关系") print(len(entities), len(relationships)) else: print("警告: compose_table_tujie_entity 返回值不是字典") except Exception as e: print(f"提取图解组成表时发生错误: {e}") print(f"常规提取完成:共提取 {len(all_entities)} 个实体,{len(all_relationships)} 个关系") # 构造返回结果 (保持原有格式 [{...}]) result = [{ "entities": all_entities, "relationships": all_relationships, }] return result, cleaned_content_normal # if __name__ == "__main__": # input_directory = "F:\zklnlp\HJ\hj_kg_code\kgrag\extract_html_json\测试LXJwx手册_shenghna_output11_full_content.md" # # 调用目录处理函数 # mdcontent = read_markdown_file(input_directory) # if mdcontent is None: # print("无法读取文件,程序退出") # exit(1) # tables, cleaned_content = get_nromal_node_rela(mdcontent=mdcontent) # for ins in tables: # print(ins) # print(11111111111111111111)