Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈 - L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合) - 文档三级总结与 2.5 级回退 - query 解析路由与分类 - /admin 管理页面
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
"""文档三级总结模块
|
||||
|
||||
通过 Ollama 本地小模型对文档进行分级总结:
|
||||
- L1: 总结(一句话高度概括)
|
||||
- L2: 大纲(主要章节和关键主题)
|
||||
- L3: 内容大纲(每个章节的详细内容摘要)
|
||||
|
||||
当文档内容不足以支撑三级总结时,自动降级为 2.5 级(L1 + L2.5 内容大纲)。
|
||||
"""
|
||||
|
||||
import structlog
|
||||
|
||||
from app.core.headings import parse_headings, render_outline
|
||||
from app.models.document import DocumentSummary, SummaryLevel
|
||||
from app.services.ollama import OllamaClient
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
# 文本长度阈值(字符数),低于此值触发 2.5 级回退
|
||||
MIN_TEXT_LENGTH_FOR_L3 = 500
|
||||
|
||||
|
||||
class Summarizer:
|
||||
"""文档三级总结器"""
|
||||
|
||||
def __init__(self, ollama: OllamaClient | None = None) -> None:
|
||||
self.ollama = ollama or OllamaClient()
|
||||
|
||||
async def summarize(self, text: str, *, title: str = "") -> DocumentSummary:
|
||||
"""对文档文本进行三级总结
|
||||
|
||||
Args:
|
||||
text: 文档纯文本内容
|
||||
title: 文档标题(可选,辅助总结)
|
||||
|
||||
Returns:
|
||||
DocumentSummary: 包含各级总结的结果
|
||||
"""
|
||||
text_length = len(text.strip())
|
||||
|
||||
# 生成 L1 总结
|
||||
l1_summary = await self._generate_l1(text, title)
|
||||
logger.info("L1 总结完成", title=title, l1=l1_summary[:100])
|
||||
|
||||
# 判断是否需要 2.5 级回退
|
||||
use_fallback = text_length < MIN_TEXT_LENGTH_FOR_L3
|
||||
|
||||
if use_fallback:
|
||||
logger.info("文档内容不足,使用 2.5 级回退", text_length=text_length)
|
||||
# L2.5: 直接生成内容大纲(跳过大纲层)
|
||||
l2_half = await self._generate_l2_half(text, title, l1_summary)
|
||||
return DocumentSummary(
|
||||
l1_summary=l1_summary,
|
||||
l2_outline=None,
|
||||
l3_content_outline=l2_half,
|
||||
level=SummaryLevel.L2_HALF,
|
||||
)
|
||||
|
||||
# 生成 L2 大纲:优先使用文档原生标题树(结构导航,不调 LLM),
|
||||
# 无结构文本(标题数 < 2)回退 LLM 生成
|
||||
headings = parse_headings(text)
|
||||
if len(headings) >= 2:
|
||||
l2_outline = render_outline(headings)
|
||||
logger.info("L2 大纲完成", title=title, outline_source="headings", heading_count=len(headings))
|
||||
else:
|
||||
l2_outline = await self._generate_l2(text, title, l1_summary)
|
||||
logger.info("L2 大纲完成", title=title, outline_source="llm")
|
||||
|
||||
# 生成 L3 内容大纲
|
||||
l3_content_outline = await self._generate_l3(text, title, l1_summary, l2_outline)
|
||||
logger.info("L3 内容大纲完成", title=title)
|
||||
|
||||
return DocumentSummary(
|
||||
l1_summary=l1_summary,
|
||||
l2_outline=l2_outline,
|
||||
l3_content_outline=l3_content_outline,
|
||||
level=SummaryLevel.L3,
|
||||
)
|
||||
|
||||
async def _generate_l1(self, text: str, title: str) -> str:
|
||||
"""生成 L1 总结:一句话高度概括"""
|
||||
prompt = (
|
||||
"请用一句话对以下文档内容进行高度概括,要求简洁精炼,"
|
||||
"突出文档的核心主题和关键信息。\n\n"
|
||||
)
|
||||
if title:
|
||||
prompt += f"文档标题:{title}\n\n"
|
||||
prompt += f"文档内容:\n{text}"
|
||||
|
||||
return await self.ollama.generate(prompt)
|
||||
|
||||
async def _generate_l2(self, text: str, title: str, l1_summary: str) -> str:
|
||||
"""生成 L2 大纲:主要章节和关键主题"""
|
||||
prompt = (
|
||||
"请提取以下文档的主要章节结构和关键主题,"
|
||||
"以大纲形式呈现。每个主题用一行表示,"
|
||||
"格式为:序号. 主题名称\n\n"
|
||||
f"文档总结:{l1_summary}\n\n"
|
||||
)
|
||||
if title:
|
||||
prompt += f"文档标题:{title}\n\n"
|
||||
prompt += f"文档内容:\n{text}"
|
||||
|
||||
return await self.ollama.generate(prompt)
|
||||
|
||||
async def _generate_l3(
|
||||
self, text: str, title: str, l1_summary: str, l2_outline: str
|
||||
) -> str:
|
||||
"""生成 L3 内容大纲:每个章节的详细内容摘要"""
|
||||
prompt = (
|
||||
"请对以下文档的每个章节/主题进行详细的内容摘要,"
|
||||
"格式为:\n"
|
||||
"## 章节名称\n"
|
||||
"详细摘要内容...\n\n"
|
||||
f"文档总结:{l1_summary}\n\n"
|
||||
f"文档大纲:\n{l2_outline}\n\n"
|
||||
)
|
||||
if title:
|
||||
prompt += f"文档标题:{title}\n\n"
|
||||
prompt += f"文档内容:\n{text}"
|
||||
|
||||
return await self.ollama.generate(prompt)
|
||||
|
||||
async def _generate_l2_half(self, text: str, title: str, l1_summary: str) -> str:
|
||||
"""生成 L2.5 内容大纲(2.5 级回退)
|
||||
|
||||
跳过大纲层,直接对短文本生成详细摘要。
|
||||
"""
|
||||
prompt = (
|
||||
"请对以下文档内容进行详细摘要,"
|
||||
"涵盖所有关键信息点。以要点形式呈现。\n\n"
|
||||
f"文档总结:{l1_summary}\n\n"
|
||||
)
|
||||
if title:
|
||||
prompt += f"文档标题:{title}\n\n"
|
||||
prompt += f"文档内容:\n{text}"
|
||||
|
||||
return await self.ollama.generate(prompt)
|
||||
Reference in New Issue
Block a user