huoyan-enterprise/backend/services/summary_service.py

320 lines
11 KiB
Python
Raw Permalink 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.

"""
文件摘要生成服务
基于 LangChain 和大模型生成文件内容的精准摘要。
参考 server/aaa/jenius_attachment_knowledge_base/jenius_rag.py 的实现。
"""
import asyncio
from typing import List, Optional
try:
from langchain_core.documents import Document
except ImportError:
from langchain.schema import Document
from langchain_core.prompts import PromptTemplate
from core.llm_catalog import build_chat_model
from logger.logging import get_logger
logger = get_logger(__name__)
# 摘要生成 Prompt - 优化版:强调全面覆盖
GENERATE_SUMMARY_PROMPT = """
你是一个精准的文件内容总结专家。你的任务是提取并总结用户提供的文件内容或片段的**所有核心内容**。
## 核心要求
- **总结结果长度为150-300个字**,根据内容复杂度灵活调整
- **必须覆盖文档中的所有主要主题和关键信息点**,不能遗漏任何重要内容
- 完全基于提供的文件内容生成总结,不添加任何未在文件内容中出现的信息
- 如果文档包含多个不同主题(如:多张图片内容、多个段落主题),**必须逐一概括每个主题**
- 对于包含数据、事实、人物、事件的内容,**必须保留具体细节**(如:人名、数字、时间等)
- 直接输出总结结果,不包含任何引言、前缀或解释
## 特别说明
- 如果文档包含**图片内容标记**(如 [图片 1 内容]、[图片 2 内容]**必须总结每张图片的核心内容**
- 如果文档包含**多个独立段落或章节****必须概括每个段落的要点**
- 对于**人名、数字、时间、地点**等关键信息,**必须在摘要中体现**
## 格式与风格
- 使用客观、中立的第三人称陈述语气
- 使用清晰简洁的中文表达
- 保持逻辑连贯性,确保句与句之间有合理过渡
- 多个主题之间使用分号或换行分隔
- 以"此文件"开头,直接输出总结结果
## 注意事项
- 绝对不输出"无法生成""无法总结""内容不足"等拒绝回应的词语
- 不要只总结开头或某一部分,**必须通读全文后再生成摘要**
- 对于任何文本都尽最大努力提取**所有**重点并总结,无论长度或复杂度
## 以下是用户给出的文件相关信息:
{doc_content}
"""
class SummaryService:
"""文件摘要生成服务类"""
_llm_cache = None
_lock = asyncio.Lock()
@classmethod
async def _get_llm(cls):
"""获取或创建 LLM 实例(单例模式)"""
if cls._llm_cache is not None:
return cls._llm_cache
async with cls._lock:
if cls._llm_cache is None:
cls._llm_cache = build_chat_model(
provider="tongyi",
api_model="qwen-plus-latest",
streaming=False,
temperature=0.3, # 适度提高灵活性,更好地总结全文
)
return cls._llm_cache
@classmethod
async def generate_file_summary(
cls,
docs: List[Document],
max_docs: int = 2
) -> str:
"""
生成文件摘要
Args:
docs: 文档列表
max_docs: 最多使用的文档数量默认2个
Returns:
str: 生成的摘要文本
"""
if not docs:
return ""
try:
llm = await cls._get_llm()
# 限制文档数量,避免超长
docs = docs[:max_docs]
# 合并文档内容,去除重叠部分
doc_content = cls._merge_doc_contents(docs)
if not doc_content:
return ""
# 生成摘要
prompt = PromptTemplate(
template=GENERATE_SUMMARY_PROMPT,
input_variables=["doc_content"]
)
chain = prompt | llm
response = await chain.ainvoke({"doc_content": doc_content})
summary = response.content.strip()
logger.info(f"成功生成文件摘要,长度: {len(summary)}")
return summary
except Exception as e:
logger.error(f"生成文件摘要失败: {e}")
return ""
@classmethod
def _merge_doc_contents(cls, docs: List[Document], overlap_size: int = 50) -> str:
"""
合并文档内容,去除重叠部分
Args:
docs: 文档列表
overlap_size: 重叠检测大小
Returns:
str: 合并后的内容
"""
if not docs:
return ""
# 简单去重策略
contents = []
for doc in docs:
content = doc.page_content.strip()
if content:
contents.append(content)
return "\n".join(contents)
class ExcelSummaryService:
"""Excel 文件摘要生成服务"""
EXCEL_DESCRIPTION_PROMPT = """
指令:请根据以下 Excel 文件的内容,为每个工作表生成简洁的描述,然后再生成整个文件的简要描述。
Excel 结构如下每个sheet提供前5行数据
{sheet_description_array}
sheet_description_array 对每个sheet表的内容进行描述不超过20字工作表的数量为: {sheet_number}个;
sheet_summary 对所有sheet表的描述进行总结不超过20字
输出格式:JSON
输出格式示例如下:
{{
"sheet_description_array": ["表1的描述","表2的描述"],
"sheet_summary": "所有sheet表的简要描述",
}}
请直接输出JSON格式的结果不要输出其他内容。
"""
_llm_cache = None
_lock = asyncio.Lock()
@classmethod
async def _get_llm(cls):
"""获取或创建 LLM 实例"""
if cls._llm_cache is not None:
return cls._llm_cache
async with cls._lock:
if cls._llm_cache is None:
cls._llm_cache = build_chat_model(
provider="tongyi",
api_model="qwen-plus-latest",
streaming=False,
temperature=0.7,
model_kwargs={"response_format": {"type": "json_object"}},
)
return cls._llm_cache
@classmethod
async def generate_excel_description(cls, sheet_description_array: List[dict]) -> dict:
"""
生成 Excel 文件的描述
Args:
sheet_description_array: Sheet 描述数组,格式为 [{"sheet_name": "xxx", "sheet_data": "xxx"}]
Returns:
dict: 包含 sheet_summary 和 sheet_description_array 的字典
"""
try:
llm = await cls._get_llm()
prompt = PromptTemplate(
template=cls.EXCEL_DESCRIPTION_PROMPT,
input_variables=["sheet_description_array", "sheet_number"]
)
chain = prompt | llm
sheet_number = len(sheet_description_array)
response = await chain.ainvoke({
"sheet_description_array": sheet_description_array,
"sheet_number": sheet_number
})
result_dict = {
"sheet_summary": "",
"sheet_description_array": []
}
# 解析 JSON 响应
import json
try:
result = json.loads(response.content)
if "sheet_summary" in result:
result_dict["sheet_summary"] = result["sheet_summary"]
if "sheet_description_array" in result and len(result["sheet_description_array"]) == sheet_number:
result_dict["sheet_description_array"] = result["sheet_description_array"]
except json.JSONDecodeError as e:
logger.error(f"解析 Excel 描述 JSON 失败: {e}")
logger.info(f"成功生成 Excel 文件描述")
return result_dict
except Exception as e:
logger.error(f"生成 Excel 文件描述失败: {e}")
return {"sheet_summary": "", "sheet_description_array": []}
class CSVSummaryService:
"""CSV 文件摘要生成服务"""
CSV_DESCRIPTION_PROMPT = """
指令:请根据以下 csv 文件的内容,生成整个文件的简要描述。
csv文件的文件名和前5行数据(包括表头和样例数据)
{csv_description_dict}
csv_description: 对csv表格的内容进行描述不超过20字
输出格式:JSON
输出格式示例如下:
{{
"csv_description": "csv表格的描述"
}}
请直接输出JSON格式的结果不要输出其他内容。
"""
_llm_cache = None
_lock = asyncio.Lock()
@classmethod
async def _get_llm(cls):
"""获取或创建 LLM 实例"""
if cls._llm_cache is not None:
return cls._llm_cache
async with cls._lock:
if cls._llm_cache is None:
cls._llm_cache = build_chat_model(
provider="tongyi",
api_model="qwen-plus-latest",
streaming=False,
temperature=0.7,
model_kwargs={"response_format": {"type": "json_object"}},
)
return cls._llm_cache
@classmethod
async def generate_csv_description(cls, csv_description_dict: dict) -> dict:
"""
生成 CSV 文件的描述
Args:
csv_description_dict: CSV 描述字典,格式为 {"file_name": "xxx", "csv_data": "xxx"}
Returns:
dict: 包含 file_description 的字典
"""
try:
llm = await cls._get_llm()
prompt = PromptTemplate(
template=cls.CSV_DESCRIPTION_PROMPT,
input_variables=["csv_description_dict"]
)
chain = prompt | llm
response = await chain.ainvoke({
"csv_description_dict": csv_description_dict
})
result_dict = {"file_description": ""}
# 解析 JSON 响应
import json
try:
result = json.loads(response.content)
if "csv_description" in result:
result_dict["file_description"] = result["csv_description"]
except json.JSONDecodeError as e:
logger.error(f"解析 CSV 描述 JSON 失败: {e}")
logger.info(f"成功生成 CSV 文件描述")
return result_dict
except Exception as e:
logger.error(f"生成 CSV 文件描述失败: {e}")
return {"file_description": ""}