Initial commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
术语表管理器 (Glossary Manager)
|
||||
|
||||
负责从书籍内容中提取采样文本,调用 LLM 生成术语表,并管理术语表的持久化。
|
||||
"""
|
||||
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any
|
||||
from loguru import logger
|
||||
from .manifest_manager import ManifestManager
|
||||
from .llm_client import OpenRouterClient
|
||||
|
||||
class GlossaryManager:
|
||||
def __init__(self, config: Dict, llm_client: OpenRouterClient):
|
||||
self.config = config
|
||||
self.llm_client = llm_client
|
||||
self.glossary_path = Path("cache/glossary.json")
|
||||
self.prompts = self._load_prompts()
|
||||
|
||||
def _load_prompts(self) -> Dict:
|
||||
try:
|
||||
with open("config/prompts.json", "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
logger.warning("未找到 config/prompts.json,使用默认 Prompt")
|
||||
return {}
|
||||
|
||||
def extract_samples(self, manifest: ManifestManager, sample_size: int = 3000) -> str:
|
||||
"""
|
||||
从 Manifest 中提取采样文本。
|
||||
策略:
|
||||
1. 优先提取前言/绪论 (通常在文件的前部)。
|
||||
2. 随机抽取中间段落。
|
||||
"""
|
||||
all_items = manifest.get_items()
|
||||
if not all_items:
|
||||
return ""
|
||||
|
||||
# 1. 提取开头部分 (Preface/Intro) - 假设在前 50 个段落中
|
||||
intro_sample = [item.clean_text for item in all_items[:50] if len(item.clean_text) > 50]
|
||||
|
||||
# 2. 随机提取正文
|
||||
body_items = [item for item in all_items[50:] if len(item.clean_text) > 50]
|
||||
random_sample = []
|
||||
if body_items:
|
||||
# 随机取 10 个片段
|
||||
sample_count = min(10, len(body_items))
|
||||
random_items = random.sample(body_items, sample_count)
|
||||
random_sample = [item.clean_text for item in random_items]
|
||||
|
||||
# 组合并截断
|
||||
full_text = "\n\n".join(intro_sample + random_sample)
|
||||
if len(full_text) > sample_size:
|
||||
full_text = full_text[:sample_size] + "..."
|
||||
|
||||
return full_text
|
||||
|
||||
async def generate_glossary(self, manifest: ManifestManager) -> Dict[str, str]:
|
||||
"""
|
||||
生成术语表。
|
||||
"""
|
||||
# 1. 采样
|
||||
sample_text = self.extract_samples(manifest)
|
||||
if not sample_text:
|
||||
logger.warning("采样文本为空,跳过术语表生成")
|
||||
return {}
|
||||
|
||||
logger.info(f"提取了 {len(sample_text)} 字符的采样文本,正在生成术语表...")
|
||||
|
||||
# 2. 构建 Prompt
|
||||
prompt_cfg = self.prompts.get("glossary_extraction", {})
|
||||
system_prompt = prompt_cfg.get("system", "Analyze the text and extract named entities.")
|
||||
user_template = prompt_cfg.get("user_template", "Text:\n{{content}}")
|
||||
user_prompt = user_template.replace("{{content}}", sample_text)
|
||||
|
||||
# 3. 调用 LLM (使用 smart 模型)
|
||||
# 注意:这里需要 LLMClient 支持直接传入 system/user prompt,而不是封装好的 translate 接口
|
||||
# 我们稍后会扩展 LLMClient
|
||||
try:
|
||||
response = await self.llm_client.raw_chat_completion(
|
||||
system_prompt,
|
||||
user_prompt,
|
||||
model_type="smart"
|
||||
)
|
||||
|
||||
# 4. 解析 JSON
|
||||
# 简单的 JSON 提取逻辑 (处理可能的 markdown code block)
|
||||
json_str = response.strip()
|
||||
if "```json" in json_str:
|
||||
json_str = json_str.split("```json")[1].split("```")[0].strip()
|
||||
elif "```" in json_str:
|
||||
json_str = json_str.split("```")[1].split("```")[0].strip()
|
||||
|
||||
glossary = json.loads(json_str)
|
||||
self.save_glossary(glossary)
|
||||
return glossary
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"术语表生成失败: {e}")
|
||||
return {}
|
||||
|
||||
def save_glossary(self, glossary: Dict[str, str]):
|
||||
self.glossary_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.glossary_path, "w", encoding="utf-8") as f:
|
||||
json.dump(glossary, f, ensure_ascii=False, indent=2)
|
||||
logger.info(f"术语表已保存至: {self.glossary_path}")
|
||||
|
||||
def load_glossary(self) -> Dict[str, str]:
|
||||
if self.glossary_path.exists():
|
||||
try:
|
||||
with open(self.glossary_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
return {}
|
||||
Reference in New Issue
Block a user