config.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Data Modules - 配置文件
  5. """
  6. import os
  7. from pathlib import Path
  8. from dataclasses import dataclass, field
  9. from typing import Optional
  10. @dataclass
  11. class DataModulesConfig:
  12. """数据模块配置"""
  13. # ================= 项目路径 =================
  14. project_root: Path = field(default_factory=lambda: Path.cwd())
  15. @property
  16. def webnovel_dir(self) -> Path:
  17. return self.project_root / ".webnovel"
  18. @property
  19. def state_file(self) -> Path:
  20. return self.webnovel_dir / "state.json"
  21. @property
  22. def index_db(self) -> Path:
  23. return self.webnovel_dir / "index.db"
  24. @property
  25. def alias_index_file(self) -> Path:
  26. return self.webnovel_dir / "alias_index.json"
  27. @property
  28. def chapters_dir(self) -> Path:
  29. return self.project_root / "正文"
  30. @property
  31. def settings_dir(self) -> Path:
  32. return self.project_root / "设定集"
  33. @property
  34. def outline_dir(self) -> Path:
  35. return self.project_root / "大纲"
  36. # ================= Modal API Endpoints =================
  37. # 注意:以下为默认 Modal 端点,可通过环境变量或显式传参覆盖
  38. llm_base_url: str = "https://lingfengqaq--qwen3-30b-vllm-serve.modal.run/v1"
  39. llm_model: str = "Qwen/Qwen3-30B-A3B-Instruct-2507"
  40. # ================= Embedding API 配置 =================
  41. # api_type: "openai" (通用 OpenAI 兼容接口) | "modal" (Modal 自定义接口)
  42. embed_api_type: str = "openai"
  43. embed_base_url: str = "https://lingfengqaq--qwen-embedding-server-qwenembedding-embeddings.modal.run"
  44. embed_model: str = "qwen-embedding"
  45. embed_api_key: str = "" # OpenAI 兼容接口需要 API Key
  46. # 保留旧字段兼容
  47. @property
  48. def embed_url(self) -> str:
  49. """兼容旧代码:返回 embed_base_url"""
  50. return self.embed_base_url
  51. # ================= Rerank API 配置 =================
  52. # api_type: "openai" (如 Jina/Cohere 兼容接口) | "modal" (Modal 自定义接口)
  53. rerank_api_type: str = "modal"
  54. rerank_base_url: str = "https://lingfengqaq--qwen-reranker-server-qwenreranker-rerank.modal.run"
  55. rerank_model: str = "qwen-reranker"
  56. rerank_api_key: str = "" # Jina/Cohere 等需要 API Key
  57. # 保留旧字段兼容
  58. @property
  59. def rerank_url(self) -> str:
  60. """兼容旧代码:返回 rerank_base_url"""
  61. return self.rerank_base_url
  62. # ================= 并发配置 =================
  63. llm_concurrency: int = 32
  64. embed_concurrency: int = 64
  65. rerank_concurrency: int = 32
  66. embed_batch_size: int = 64
  67. # ================= 超时配置 =================
  68. cold_start_timeout: int = 300 # 5 分钟
  69. normal_timeout: int = 180 # 3 分钟
  70. # ================= LLM 生成配置 =================
  71. llm_temperature: float = 0.1
  72. llm_max_tokens: int = 4096
  73. # ================= 检索配置 =================
  74. vector_top_k: int = 30
  75. bm25_top_k: int = 20
  76. rerank_top_n: int = 10
  77. rrf_k: int = 60
  78. # 向量检索性能开关
  79. # - 向量数量较少时(<= full_scan_max_vectors)可全表扫描,召回更稳
  80. # - 规模变大后默认走预筛选(BM25 + 最近片段),避免 O(n) 扫描拖慢 Context Agent
  81. vector_full_scan_max_vectors: int = 500
  82. vector_prefilter_bm25_candidates: int = 200
  83. vector_prefilter_recent_candidates: int = 200
  84. # ================= 实体提取配置 =================
  85. extraction_confidence_high: float = 0.8
  86. extraction_confidence_medium: float = 0.5
  87. # ================= 列表截断限制 =================
  88. # state.json 列表最大保留条数
  89. max_disambiguation_warnings: int = 500
  90. max_disambiguation_pending: int = 1000
  91. max_state_changes: int = 2000
  92. # Context Pack 输出切片
  93. context_recent_summaries_window: int = 5
  94. context_alerts_slice: int = 10
  95. context_max_appearing_characters: int = 10
  96. context_max_urgent_foreshadowing: int = 5
  97. # 导出上下文时的列表截断
  98. export_recent_changes_slice: int = 20
  99. export_disambiguation_slice: int = 20
  100. # ================= 查询默认限制 =================
  101. query_recent_chapters_limit: int = 10
  102. query_scenes_by_location_limit: int = 20
  103. query_entity_appearances_limit: int = 50
  104. query_recent_appearances_limit: int = 20
  105. # ================= 伏笔紧急度 =================
  106. # 紧急度阈值(基于 章节差 / 目标差 × 权重)
  107. foreshadowing_urgency_pending_high: int = 100 # 超过 100 章未回收
  108. foreshadowing_urgency_pending_medium: int = 50 # 超过 50 章
  109. foreshadowing_urgency_target_proximity: int = 5 # 距目标章节 5 章内
  110. foreshadowing_urgency_score_high: int = 100
  111. foreshadowing_urgency_score_medium: int = 60
  112. foreshadowing_urgency_score_target: int = 80
  113. foreshadowing_urgency_score_low: int = 20
  114. foreshadowing_urgency_threshold_show: int = 60 # >= 此值才显示
  115. # 层级权重
  116. foreshadowing_tier_weight_core: float = 3.0
  117. foreshadowing_tier_weight_sub: float = 2.0
  118. foreshadowing_tier_weight_decor: float = 1.0
  119. # ================= 角色活跃度 =================
  120. character_absence_warning: int = 30 # 轻度掉线阈值
  121. character_absence_critical: int = 100 # 严重掉线阈值
  122. character_candidates_limit: int = 800 # 扫描时候选角色上限
  123. # ================= Strand Weave 节奏 =================
  124. strand_quest_max_consecutive: int = 5 # Quest 线最大连续章数
  125. strand_fire_max_gap: int = 10 # Fire 线最大缺失章数
  126. strand_constellation_max_gap: int = 15 # Constellation 线最大缺失章数
  127. # 目标占比范围 (%)
  128. strand_quest_ratio_min: int = 55
  129. strand_quest_ratio_max: int = 65
  130. strand_fire_ratio_min: int = 20
  131. strand_fire_ratio_max: int = 30
  132. strand_constellation_ratio_min: int = 10
  133. strand_constellation_ratio_max: int = 20
  134. # ================= 爽点节奏 =================
  135. pacing_segment_size: int = 100 # 每段分析的章节数
  136. pacing_words_per_point_excellent: int = 1000
  137. pacing_words_per_point_good: int = 1500
  138. pacing_words_per_point_acceptable: int = 2000
  139. # ================= RAG 存储 =================
  140. @property
  141. def rag_db(self) -> Path:
  142. return self.webnovel_dir / "rag.db"
  143. @property
  144. def vector_db(self) -> Path:
  145. return self.webnovel_dir / "vectors.db"
  146. def ensure_dirs(self):
  147. """确保必要目录存在"""
  148. self.webnovel_dir.mkdir(parents=True, exist_ok=True)
  149. @classmethod
  150. def from_project_root(cls, project_root: str | Path) -> "DataModulesConfig":
  151. """从项目根目录创建配置"""
  152. return cls(project_root=Path(project_root))
  153. # 全局默认配置
  154. _default_config: Optional[DataModulesConfig] = None
  155. def get_config(project_root: Optional[Path] = None) -> DataModulesConfig:
  156. """获取配置实例"""
  157. global _default_config
  158. if project_root is not None:
  159. return DataModulesConfig.from_project_root(project_root)
  160. if _default_config is None:
  161. _default_config = DataModulesConfig()
  162. return _default_config
  163. def set_project_root(project_root: str | Path):
  164. """设置项目根目录"""
  165. global _default_config
  166. _default_config = DataModulesConfig.from_project_root(project_root)