kgrag/kg_build_v2/extrat_normal_node_rela.py
2026-06-30 13:35:52 +08:00

737 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)