Files
QMDSearch/app/core/result_summarizer.py
T
kplam 2ab8b56a01 feat: 完成全量功能开发,包括前端管理后台与后端服务优化
此提交实现了完整的知识库管理系统:
1. 新增Vue3 + Antd Vue前端管理后台,包含登录、文档管理、检索、类目设置等完整页面
2. 重构后端LLM调用抽象层,支持Ollama与OpenAI兼容服务动态切换
3. 调整默认嵌入模型配置为本地bge-m3模式
4. 优化入库任务去重逻辑与缓存清理机制
5. 完善Docker镜像构建与docker-compose部署配置
6. 修复多项测试用例与兼容性问题
7. 新增运行时配置API,支持动态调整系统参数
2026-07-31 12:05:25 +08:00

70 lines
2.6 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.
"""检索结果 AI 总结
对检索返回的 chunk 命中结果,调用 LLMOllama 或 OpenAI 兼容服务,由
runtime_settings.models.query 决定)生成一段针对用户 query 的总结回答。
仅基于检索结果内容,不编造未提及的信息。
"""
import structlog
from app.config import settings
from app.models.search import SearchHit
from app.services.llm import LLMClient, create_llm_client
logger = structlog.get_logger()
class ResultSummarizer:
"""检索结果总结器"""
def __init__(self, ollama: LLMClient | None = None) -> None:
# 默认按 runtime_settings.models.query 选择 LLM 实现;
# 测试可通过 ollama 参数注入替身。
self.ollama = ollama or create_llm_client("query")
async def summarize(self, query: str, hits: list[SearchHit]) -> str:
"""对检索结果生成针对 query 的总结
取前 settings.result_summary_max_hits 条命中拼接为上下文,
无命中时返回空字符串(不调 LLM)。
"""
if not hits:
return ""
max_hits = settings.result_summary_max_hits
selected = hits[:max_hits]
context = self._build_context(selected)
prompt = self._build_prompt(query, context)
try:
summary = await self.ollama.generate(prompt)
except Exception:
logger.warning("检索结果总结生成失败,返回空字符串", exc_info=True)
return ""
logger.info("检索结果总结完成", query=query, hits_count=len(selected), summary_len=len(summary))
return summary.strip()
@staticmethod
def _build_context(hits: list[SearchHit]) -> str:
"""拼接命中结果为带编号的上下文"""
blocks: list[str] = []
for i, hit in enumerate(hits, start=1):
header_parts = [f"[{i}]"]
if hit.title:
header_parts.append(hit.title)
if hit.section_path:
header_parts.append(hit.section_path)
header = " / ".join(header_parts)
blocks.append(f"{header}\n{hit.text}")
return "\n\n---\n\n".join(blocks)
@staticmethod
def _build_prompt(query: str, context: str) -> str:
return (
"请根据以下检索结果,针对用户问题生成一段简洁的总结回答。\n"
"要求:\n"
"- 综合多条结果信息,不要简单逐条罗列;\n"
"- 只基于检索结果内容,不编造未提及的信息;\n"
"- 用中文回答,简洁明了。\n\n"
f"用户问题:{query}\n\n"
f"检索结果:\n{context}"
)