Files
Mofox-Core/src/chat/knowledge/ie_process.py
雅诺狐 2d4745cd58 初始化
2025-08-11 19:34:18 +08:00

176 lines
6.8 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 asyncio
import json
import time
from typing import List, Union
from .global_logger import logger
from . import prompt_template
from .knowledge_lib import INVALID_ENTITY
from src.llm_models.utils_model import LLMRequest
from json_repair import repair_json
def _extract_json_from_text(text: str):
# sourcery skip: assign-if-exp, extract-method
"""从文本中提取JSON数据的高容错方法"""
if text is None:
logger.error("输入文本为None")
return []
try:
fixed_json = repair_json(text)
if isinstance(fixed_json, str):
parsed_json = json.loads(fixed_json)
else:
parsed_json = fixed_json
# 如果是列表,直接返回
if isinstance(parsed_json, list):
return parsed_json
# 如果是字典且只有一个项目,可能包装了列表
if isinstance(parsed_json, dict):
# 如果字典只有一个键,并且值是列表,返回那个列表
if len(parsed_json) == 1:
value = list(parsed_json.values())[0]
if isinstance(value, list):
return value
return parsed_json
# 其他情况,尝试转换为列表
logger.warning(f"解析的JSON不是预期格式: {type(parsed_json)}, 内容: {parsed_json}")
return []
except Exception as e:
logger.error(f"JSON提取失败: {e}, 原始文本: {text[:100] if text else 'None'}...")
return []
def _entity_extract(llm_req: LLMRequest, paragraph: str) -> List[str]:
# sourcery skip: reintroduce-else, swap-if-else-branches, use-named-expression
"""对段落进行实体提取返回提取出的实体列表JSON格式"""
entity_extract_context = prompt_template.build_entity_extract_context(paragraph)
# 使用 asyncio.run 来运行异步方法
try:
# 如果当前已有事件循环在运行,使用它
loop = asyncio.get_running_loop()
future = asyncio.run_coroutine_threadsafe(llm_req.generate_response_async(entity_extract_context), loop)
response, _ = future.result()
except RuntimeError:
# 如果没有运行中的事件循环,直接使用 asyncio.run
response, _ = asyncio.run(llm_req.generate_response_async(entity_extract_context))
# 添加调试日志
logger.debug(f"LLM返回的原始响应: {response}")
entity_extract_result = _extract_json_from_text(response)
# 检查返回的是否为有效的实体列表
if not isinstance(entity_extract_result, list):
if not isinstance(entity_extract_result, dict):
raise ValueError(f"实体提取结果格式错误,期望列表但得到: {type(entity_extract_result)}")
# 尝试常见的键名
for key in ["entities", "result", "data", "items"]:
if key in entity_extract_result and isinstance(entity_extract_result[key], list):
entity_extract_result = entity_extract_result[key]
break
else:
# 如果找不到合适的列表,抛出异常
raise ValueError(f"实体提取结果格式错误,期望列表但得到: {type(entity_extract_result)}")
# 过滤无效实体
entity_extract_result = [
entity
for entity in entity_extract_result
if (entity is not None) and (entity != "") and (entity not in INVALID_ENTITY)
]
if not entity_extract_result:
raise ValueError("实体提取结果为空")
return entity_extract_result
def _rdf_triple_extract(llm_req: LLMRequest, paragraph: str, entities: list) -> List[List[str]]:
"""对段落进行实体提取返回提取出的实体列表JSON格式"""
rdf_extract_context = prompt_template.build_rdf_triple_extract_context(
paragraph, entities=json.dumps(entities, ensure_ascii=False)
)
# 使用 asyncio.run 来运行异步方法
try:
# 如果当前已有事件循环在运行,使用它
loop = asyncio.get_running_loop()
future = asyncio.run_coroutine_threadsafe(llm_req.generate_response_async(rdf_extract_context), loop)
response, _ = future.result()
except RuntimeError:
# 如果没有运行中的事件循环,直接使用 asyncio.run
response, _ = asyncio.run(llm_req.generate_response_async(rdf_extract_context))
# 添加调试日志
logger.debug(f"RDF LLM返回的原始响应: {response}")
rdf_triple_result = _extract_json_from_text(response)
# 检查返回的是否为有效的三元组列表
if not isinstance(rdf_triple_result, list):
if not isinstance(rdf_triple_result, dict):
raise ValueError(f"RDF三元组提取结果格式错误期望列表但得到: {type(rdf_triple_result)}")
# 尝试常见的键名
for key in ["triples", "result", "data", "items"]:
if key in rdf_triple_result and isinstance(rdf_triple_result[key], list):
rdf_triple_result = rdf_triple_result[key]
break
else:
# 如果找不到合适的列表,抛出异常
raise ValueError(f"RDF三元组提取结果格式错误期望列表但得到: {type(rdf_triple_result)}")
# 验证三元组格式
for triple in rdf_triple_result:
if (
not isinstance(triple, list)
or len(triple) != 3
or (triple[0] is None or triple[1] is None or triple[2] is None)
or "" in triple
):
raise ValueError("RDF提取结果格式错误")
return rdf_triple_result
def info_extract_from_str(
llm_client_for_ner: LLMRequest, llm_client_for_rdf: LLMRequest, paragraph: str
) -> Union[tuple[None, None], tuple[list[str], list[list[str]]]]:
try_count = 0
while True:
try:
entity_extract_result = _entity_extract(llm_client_for_ner, paragraph)
break
except Exception as e:
logger.warning(f"实体提取失败,错误信息:{e}")
try_count += 1
if try_count < 3:
logger.warning("将于5秒后重试")
time.sleep(5)
else:
logger.error("实体提取失败,已达最大重试次数")
return None, None
try_count = 0
while True:
try:
rdf_triple_extract_result = _rdf_triple_extract(llm_client_for_rdf, paragraph, entity_extract_result)
break
except Exception as e:
logger.warning(f"实体提取失败,错误信息:{e}")
try_count += 1
if try_count < 3:
logger.warning("将于5秒后重试")
time.sleep(5)
else:
logger.error("实体提取失败,已达最大重试次数")
return None, None
return entity_extract_result, rdf_triple_extract_result