通过引入 config.yaml.example 和环境变量覆盖提升可配置性,同时统一 Word 默认中文字体并忽略本地 config.yaml,避免敏感信息误提交。 Made-with: Cursor
141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
"""
|
||
摘要提取模块
|
||
使用大模型生成文档摘要
|
||
"""
|
||
|
||
import os
|
||
import re
|
||
from openai import OpenAI
|
||
|
||
|
||
def _load_llm_config() -> dict:
|
||
"""从 config.yaml 加载 LLM 配置,环境变量可覆盖。"""
|
||
config = {
|
||
"base_url": "https://yiming.zeroerr.team/v1",
|
||
"api_key": "",
|
||
"model": "minimax-2.5",
|
||
"max_tokens": 40960,
|
||
}
|
||
|
||
# 尝试从项目根目录 config.yaml 读取(与 0209 一致)
|
||
config_path = os.path.join(os.path.dirname(__file__), "..", "config.yaml")
|
||
if os.path.exists(config_path):
|
||
try:
|
||
import yaml
|
||
|
||
with open(config_path, "r", encoding="utf-8") as f:
|
||
data = yaml.safe_load(f) or {}
|
||
llm = data.get("llm", {})
|
||
config.update({k: v for k, v in llm.items() if v})
|
||
except Exception:
|
||
# 保持静默降级,继续使用默认值/环境变量
|
||
pass
|
||
|
||
# 环境变量优先级更高
|
||
config["base_url"] = os.environ.get("ZEROERR_LLM_BASE_URL", config["base_url"])
|
||
config["api_key"] = os.environ.get("ZEROERR_LLM_API_KEY", config["api_key"])
|
||
config["model"] = os.environ.get("ZEROERR_LLM_MODEL", config["model"])
|
||
config["max_tokens"] = int(
|
||
os.environ.get("ZEROERR_LLM_MAX_TOKENS", config["max_tokens"])
|
||
)
|
||
return config
|
||
|
||
|
||
_LLM_CONFIG = _load_llm_config()
|
||
API_BASE_URL = _LLM_CONFIG["base_url"]
|
||
API_KEY = _LLM_CONFIG["api_key"]
|
||
MODEL = _LLM_CONFIG["model"]
|
||
MAX_TOKENS = _LLM_CONFIG["max_tokens"]
|
||
|
||
|
||
def generate_abstract(all_pages: list[dict], category_name: str, index_url: str = None) -> str:
|
||
"""
|
||
使用大模型生成文档摘要
|
||
|
||
Args:
|
||
all_pages: 所有页面数据列表,每个元素包含 'title', 'url', 'markdown' 等字段
|
||
category_name: 文档类别名称(如"应用案例")
|
||
index_url: 索引页完整URL(可选),如果提供则会在摘要前添加原文链接
|
||
|
||
Returns:
|
||
摘要文本(Markdown格式),包含摘要内容和链接列表
|
||
"""
|
||
if not all_pages:
|
||
return ""
|
||
|
||
if not API_KEY:
|
||
print(" 警告: 未设置 ZEROERR_LLM_API_KEY,跳过摘要生成")
|
||
return ""
|
||
|
||
try:
|
||
# 构建文档内容(用于生成摘要)
|
||
# 只使用标题和部分内容,避免内容过长
|
||
content_parts = []
|
||
for page in all_pages:
|
||
title = page.get('title', '')
|
||
markdown = page.get('markdown', '')
|
||
# 只取前500字符的内容,避免输入过长
|
||
content_preview = markdown[:500] if len(markdown) > 500 else markdown
|
||
content_parts.append(f"标题:{title}\n内容预览:{content_preview}")
|
||
|
||
document_content = "\n\n".join(content_parts)
|
||
|
||
# 构建提示词
|
||
prompt = f"""面向客户售前咨询,请为以下"{category_name}"类别的文档集合生成一个简洁的摘要。
|
||
|
||
文档内容:
|
||
{document_content}
|
||
|
||
要求:
|
||
1. 摘要应概括该页面的主题和主要内容
|
||
2. 摘要长度控制在100-200字之间
|
||
3. 使用简洁、专业的语言
|
||
4. 突出该页面主题的价值和特点
|
||
|
||
请直接输出摘要内容,不要包含其他说明文字。"""
|
||
|
||
# 调用大模型API
|
||
client = OpenAI(
|
||
base_url=API_BASE_URL,
|
||
api_key=API_KEY
|
||
)
|
||
|
||
response = client.chat.completions.create(
|
||
model=MODEL,
|
||
temperature=0.3, # 使用较低的温度值,保证摘要的准确性
|
||
max_tokens=MAX_TOKENS,
|
||
messages=[{"role": "user", "content": prompt}],
|
||
)
|
||
|
||
abstract_text = response.choices[0].message.content.strip()
|
||
# 过滤掉 <think>...</think> 推理过程
|
||
abstract_text = re.sub(r"<think>.*?</think>\s*", "", abstract_text, flags=re.DOTALL).strip()
|
||
|
||
# 构建链接列表
|
||
links_section = "\n\n**相关链接:**\n\n"
|
||
for i, page in enumerate(all_pages, 1):
|
||
title = page.get('title', '未命名')
|
||
url = page.get('url', '')
|
||
links_section += f"{i}. [{title}]({url})\n"
|
||
|
||
# 组合摘要和链接,如果提供了索引页URL,则在摘要前添加原文链接
|
||
if index_url:
|
||
result = f"原文链接: {index_url}\n\n{abstract_text}{links_section}"
|
||
else:
|
||
result = f"{abstract_text}{links_section}"
|
||
|
||
return result
|
||
|
||
except Exception as e:
|
||
print(f" 警告: 生成摘要失败: {e}")
|
||
# 如果生成摘要失败,至少返回链接列表
|
||
links_section = "\n\n**相关链接:**\n\n"
|
||
for i, page in enumerate(all_pages, 1):
|
||
title = page.get('title', '未命名')
|
||
url = page.get('url', '')
|
||
links_section += f"{i}. [{title}]({url})\n"
|
||
|
||
# 如果提供了索引页URL,在链接列表前添加原文链接
|
||
if index_url:
|
||
return f"原文链接: {index_url}{links_section}"
|
||
return links_section |