chore: 死代码清理与标注

- 删除 chat_routes.py 中未被使用的 reciprocal_rank_fusion 函数(engine 有同功能方法平替)
- 删除 search_hybrid 中无效的 candidates 参数(未传递给 engine,实际由 RERANK_CANDIDATES 控制)
- 标注 RAG_SEARCH_CANDIDATES 为死代码
- 标注 VECTOR_WEIGHT/BM25_WEIGHT 在动态 RRF 启用时被覆盖
- 标注 llm_budget.py 模块当前未集成到主流程
- 标注 MAX_LLM_CALLS_PER_QUERY/MAX_QUERY_REWRITES 暂不生效
This commit is contained in:
lacerate551
2026-06-17 20:04:49 +08:00
parent 4afa30a946
commit 4fd53f48f9
3 changed files with 385 additions and 51 deletions

288
config.py Normal file
View File

@@ -0,0 +1,288 @@
# RAG 知识库服务配置
# ================================
# 敏感信息通过环境变量注入,默认值为空字符串
import os
# 加载 .env 文件(敏感信息不写入代码,通过 .env 注入)
try:
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))
except ImportError:
pass # python-dotenv 未安装时静默跳过
# ==============================================================================
# 一、API 密钥与模型
# ==============================================================================
# 通义千问 LLM 服务
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "")
DASHSCOPE_BASE_URL = os.getenv("DASHSCOPE_BASE_URL", "https://token-plan-cn.xiaomimimo.com/v1")
DASHSCOPE_MODEL = os.getenv("DASHSCOPE_MODEL", "mimo-v2.5") # 文本生成模型
RAG_CHAT_MODEL = os.getenv("RAG_CHAT_MODEL", "mimo-v2.5") # RAG 对话模型
INTENT_MODEL = os.getenv("INTENT_MODEL", "mimo-v2.5") # 意图分析模型
VLM_MODEL = os.getenv("VLM_MODEL", "mimo-v2.5") # 视觉语言模型(图片描述)
# 兼容旧变量名(逐步迁移到 DASHSCOPE_* 命名)
API_KEY = DASHSCOPE_API_KEY
BASE_URL = DASHSCOPE_BASE_URL
MODEL = DASHSCOPE_MODEL
# ==============================================================================
# 二、环境与功能开关
# ==============================================================================
APP_ENV = os.getenv("APP_ENV", "dev") # dev / prod
IS_DEV = APP_ENV == "dev"
IS_PROD = APP_ENV == "prod"
# 开发模式开关(控制 mock token 登录、模拟用户等开发功能)
# 默认开启,生产环境需在 .env 中设置 DEV_MODE=false
DEV_MODE = os.getenv("DEV_MODE", "true").lower() != "false"
# 开发/生产环境自动切换
ENABLE_SESSION = IS_DEV # 会话存储(仅开发环境)
ENABLE_FEEDBACK = True # 反馈系统
# 扩展功能(手动开启)
ENABLE_WEB_SEARCH = False # 网络搜索(需 SERPER_API_KEY
# ==============================================================================
# 三、路径配置
# ==============================================================================
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
MODELS_DIR = os.path.join(PROJECT_ROOT, "models")
EMBEDDING_MODEL_PATH = os.path.join(MODELS_DIR, "bge-base-zh-v1.5")
RERANK_MODEL_PATH = os.path.join(MODELS_DIR, "bge-reranker-base")
_vector_store_path = os.path.join(PROJECT_ROOT, "knowledge", "vector_store")
CHROMA_DB_PATH = os.path.join(_vector_store_path, "chroma")
DOCUMENTS_PATH = os.path.join(PROJECT_ROOT, "documents")
BM25_INDEXES_PATH = os.path.join(_vector_store_path, "bm25")
# 文件存储类型: local / smb / s3 / http
STORAGE_TYPE = os.getenv("STORAGE_TYPE", "local")
# SMB/CIFS 配置
STORAGE_SMB_HOST = os.getenv("STORAGE_SMB_HOST", "")
STORAGE_SMB_SHARE = os.getenv("STORAGE_SMB_SHARE", "")
STORAGE_SMB_USERNAME = os.getenv("STORAGE_SMB_USERNAME", "")
STORAGE_SMB_PASSWORD = os.getenv("STORAGE_SMB_PASSWORD", "")
STORAGE_SMB_DOMAIN = os.getenv("STORAGE_SMB_DOMAIN", "")
STORAGE_SMB_BASE_PATH = os.getenv("STORAGE_SMB_BASE_PATH", "")
# S3 配置
STORAGE_S3_ENDPOINT = os.getenv("STORAGE_S3_ENDPOINT", "")
STORAGE_S3_BUCKET = os.getenv("STORAGE_S3_BUCKET", "")
STORAGE_S3_ACCESS_KEY = os.getenv("STORAGE_S3_ACCESS_KEY", "")
STORAGE_S3_SECRET_KEY = os.getenv("STORAGE_S3_SECRET_KEY", "")
STORAGE_S3_REGION = os.getenv("STORAGE_S3_REGION", "us-east-1")
# HTTP 文件服务配置
STORAGE_HTTP_BASE_URL = os.getenv("STORAGE_HTTP_BASE_URL", "")
STORAGE_HTTP_TOKEN = os.getenv("STORAGE_HTTP_TOKEN", "")
STORAGE_HTTP_TIMEOUT = int(os.getenv("STORAGE_HTTP_TIMEOUT", "60"))
# ==============================================================================
# 四、设备配置GPU / CPU
# ==============================================================================
EMBEDDING_DEVICE = os.getenv("EMBEDDING_DEVICE", os.getenv("DEVICE", "auto"))
RERANK_DEVICE = os.getenv("RERANK_DEVICE", os.getenv("DEVICE", "auto"))
# ==============================================================================
# 五、LLM 参数
# ==============================================================================
# ----- 通用问答 -----
LLM_TEMPERATURE = 0.7 # 生成温度0=确定性1=随机性)
LLM_MAX_TOKENS = 3000 # 最大输出 token 数
# ----- 意图分析(轻量、确定性高)-----
# INTENT_MODEL 在顶部「一、API 密钥与模型」中统一配置
INTENT_TEMPERATURE = 0.1
INTENT_MAX_TOKENS = 2048 # 推理模型需要额外 token 用于思维链
INTENT_HISTORY_WINDOW = 6 # 分析时取最近几条历史消息
# ==============================================================================
# 六、检索参数
# ==============================================================================
# ----- 混合检索 -----
USE_MULTI_KB = True # 多向量库模式
USE_HYBRID_SEARCH = True # 向量 + BM25 混合检索
VECTOR_WEIGHT = 0.5 # 向量检索权重(仅在 USE_MULTI_KB=False 时生效;动态 RRF 启用时被覆盖)
BM25_WEIGHT = 0.5 # BM25 检索权重(同上)
RAG_SEARCH_TOP_K = 30 # 最终返回结果数(不小于 MMR_TOP_K避免截断 MMR 输出)
RAG_SEARCH_CANDIDATES = 100 # [死代码] 候选池大小 — 实际由 RERANK_CANDIDATES 控制,此值未传递给 engine
RECALL_MULTIPLIER = 3 # 候选池最小倍数 = top_k * 此值
# ----- 重排序 -----
USE_RERANK = True # 是否启用重排序
RERANK_CANDIDATES = 20 # 送入重排序的候选数
RERANK_TOP_K = 15 # 重排序后保留数
RERANK_USE_ONNX = os.getenv("RERANK_USE_ONNX", "true").lower() == "true"
RERANK_CONTEXT_MIN_SCORE = 0.05 # Phase 1Rerank 分数低于此值的切片不送入 LLM
# ----- 云端 RerankerDashScope API-----
# RERANK_BACKEND: "local"=本地模型(CPU/GPU)"cloud"=云端API"fallback"=优先云端、失败回退本地
RERANK_BACKEND = os.getenv("RERANK_BACKEND", "local")
RERANK_CLOUD_MODEL = os.getenv("RERANK_CLOUD_MODEL", "xop3qwen8breranker")
RERANK_CLOUD_API_KEY = os.getenv("RERANK_CLOUD_API_KEY", DASHSCOPE_API_KEY)
RERANK_CLOUD_BASE_URL = os.getenv("RERANK_CLOUD_BASE_URL", "https://maas-api.cn-huabei-1.xf-yun.com/v1/rerank")
RERANK_CLOUD_TIMEOUT = int(os.getenv("RERANK_CLOUD_TIMEOUT", "15")) # 云端请求超时(秒)
# ----- RRF 融合 -----
RRF_K = 60 # RRF 常数(越大越平滑)
DYNAMIC_RRF_ENABLED = True # 根据查询类型动态调整向量/BM25 权重
# ----- MMR 多样性去重 -----
MMR_ENABLED = True
MMR_USE_EMBEDDING = os.getenv("MMR_USE_EMBEDDING", "false").lower() == "true" # True=语义向量False=文本相似度(快,使用 jieba 词级 Jaccard
MMR_TOP_K = 30 # MMR 处理后保留数
MMR_LAMBDA = 0.5 # 相关性 vs 多样性权衡0=纯多样1=纯相关)
# ----- 查询扩展 -----
QUERY_EXPANSION_ENABLED = True
QUERY_EXPANSION_THRESHOLD = 0.8 # 扩展词相似度阈值
# ----- 章节过滤 -----
SECTION_FILTER_ENABLED = True # 查询提到章节时优先匹配对应切片
# ==============================================================================
# 七、上下文构建
# ==============================================================================
MAX_CONTEXT_CHUNKS = 20 # 送给 LLM 的最大文本切片数
CONTEXT_MAX_CHARS = 8000 # Phase 2上下文最大字符数约 4000 token
CONTEXT_SOFT_LIMIT = 6000 # Phase 2软限制超过后只接受高分切片组
MAX_SOURCES_RETURNED = 10 # 返回给前端的最大来源数
MAX_HISTORY_ROUNDS = 10 # 对话历史最大轮数
IMAGE_CONTEXT_HISTORY = 4 # 图片上下文取最近几轮历史
DIRECT_CONTEXT_MAX_CHARS = 2000 # 直接回答模式上下文截断字符数
# ==============================================================================
# 八、FAQ 与黑名单
# ==============================================================================
# FAQ 召回与权重
FAQ_RECALL_TOP_K = 3 # FAQ 集合单独召回数
FAQ_BOOST_AMOUNT = 0.1 # FAQ 命中时距离减少量(提升排名)
# FAQ 时间衰减(防止过期 FAQ 长期霸榜)
FAQ_DECAY_MONTHS = 6 # 超过此月数开始衰减
FAQ_DECAY_RATE = 0.01 # 每超一个月的距离惩罚
FAQ_DECAY_MAX = 0.1 # 最大衰减惩罚
# 黑名单(负反馈过滤)
BLACKLIST_MIN_DISLIKES = 3 # 差评达到此数量进入黑名单
BLACKLIST_CACHE_TTL = 300 # 黑名单缓存刷新间隔(秒)
# ==============================================================================
# 九、缓存配置
# ==============================================================================
# 查询结果缓存
QUERY_CACHE_ENABLED = True
QUERY_CACHE_SIZE = 500
QUERY_CACHE_TTL = 3600 # 秒
# Embedding 缓存
EMBEDDING_CACHE_ENABLED = True
EMBEDDING_CACHE_SIZE = 2000
EMBEDDING_CACHE_TTL = 86400
# Rerank 缓存
RERANK_CACHE_ENABLED = True
RERANK_CACHE_SIZE = 1000
RERANK_CACHE_TTL = 3600
# 语义缓存(相似查询复用结果)
SEMANTIC_CACHE_ENABLED = True
SEMANTIC_CACHE_THRESHOLD = 0.92 # 相似度阈值
# 缓存写入最低置信度
# 注意ChromaDB cosine distance 范围 [0,2]score = 1 - dist
# 当前 embedding 模型的 cosine similarity 普遍在 0.03-0.06 之间
# 搜索管线已通过 rerank 过滤低质量结果,此处不再额外限制
CACHE_MIN_SCORE = 0.0
# LLM 调用预算(当前 llm_budget 模块未集成到主流程,以下配置暂不生效)
MAX_LLM_CALLS_PER_QUERY = 2
MAX_QUERY_REWRITES = 1
# ==============================================================================
# 十、文档解析
# ==============================================================================
# MinerU 解析器
MINERU_DEVICE_MODE = os.getenv("MINERU_DEVICE_MODE", "cpu") # cpu / cuda
# MinerU 在线 API优先使用解析效果更好
MINERU_API_TOKEN = os.getenv("MINERU_API_TOKEN", "") # 在 https://mineru.net/apiManage/token 申请
MINERU_API_URL = os.getenv("MINERU_API_URL", "https://mineru.net/api/v4/extract/task")
MINERU_PREFER_ONLINE = os.getenv("MINERU_PREFER_ONLINE", "true").lower() == "false" # 优先使用在线 API
# MinerU 解析模式(本地 + 云端统一配置)
MINERU_MODEL_VERSION = os.getenv("MINERU_MODEL_VERSION", "pipeline") # 云端解析模型: pipeline(快速推荐) / vlm(高精度慢) / MinerU-HTML
MINERU_LOCAL_BACKEND = os.getenv("MINERU_LOCAL_BACKEND", "vlm-auto-engine") # 本地解析后端: pipeline(快速推荐) / vlm-auto-engine / hybrid-auto-engine
MINERU_ONLINE_TIMEOUT = int(os.getenv("MINERU_ONLINE_TIMEOUT", "300")) # 云端解析轮询超时(秒),大文档/VLM 模式建议 600+
# 分块参数
CHUNK_SIZE = 1000
CHUNK_OVERLAP = 100
MIN_CHUNK_SIZE = 200
MAX_CHUNK_SIZE = 1200
# 自适应 TopK根据置信度动态调整返回数
ADAPTIVE_TOPK_ENABLED = True
ADAPTIVE_LOW_CONFIDENCE = 0.5
ADAPTIVE_HIGH_CONFIDENCE = 0.8
ADAPTIVE_EXPAND_RATIO = 2.0
ADAPTIVE_SHRINK_RATIO = 0.5
ADAPTIVE_MIN_TOPK = 15
ADAPTIVE_MAX_TOPK = 20
# 连续切片完整性保护(枚举/条款/清单类问题)
CONTEXT_EXPANSION_ENABLED = True
CONTEXT_EXPANSION_BEFORE = 1
CONTEXT_EXPANSION_AFTER = 8
CONTEXT_EXPANSION_MAX_CHUNKS = 50
EXPANSION_SCORE_THRESHOLD = 0.3 # Phase 3Rerank 分数低于此值的切片不扩展邻居
MAX_EXPANDED_NEIGHBORS = 8 # Phase 3每个种子切片最多扩展的邻居数
CONFIDENCE_WARN_THRESHOLD = 0.15 # Phase 4top-3 均分低于此值时,提示 LLM 谨慎回答
CONFIDENCE_CAUTION_THRESHOLD = 0.30 # Phase 4top-3 均分低于此值时,提示 LLM 优先引用原文
ENUM_QUERY_DISABLE_TOPK_SHRINK = True
ENUM_QUERY_MMR_LAMBDA = 0.85
# ----- 章节聚类救援Section-Cluster Rescue-----
# 当同一 section 下多个切片text+table同时出现在候选集中
# 即使单个切片 CrossEncoder 分数很低,也视为强信号进行提升/救援。
SECTION_CLUSTER_BOOST_ENABLED = True # 引擎层聚类提升rerank 后、扩展前)
SECTION_CLUSTER_RESCUE_ENABLED = True # 路由层聚类救援min_score 过滤前)
BM25_DIVERGENCE_RESCUE_ENABLED = True # 路由层BM25-CrossEncoder 分歧检测救援
BM25_DIVERGENCE_MAX_RANK = 3 # 仅救援 BM25 rank <= 此值的切片top-3 是强信号)
CLUSTER_MIN_MEMBERS = 3 # 触发聚类的最小切片数
CLUSTER_MIN_TYPES = 2 # 触发聚类的最小类型多样性text+table=2
CLUSTER_SEED_FLOOR = 0.35 # 引擎层聚类提升后的最低分数(略高于 EXPANSION_SCORE_THRESHOLD=0.3
CLUSTER_RESCUE_FLOOR = 0.06 # 路由层救援保底分数(略高于 RERANK_CONTEXT_MIN_SCORE=0.05
CLUSTER_MAX_BOOST_PER_SECTION = 8 # 引擎层:每个 section 最大提升切片数
CLUSTER_MAX_SECTIONS = 3 # 全局最大提升/救援 section 数
CLUSTER_MAX_RESCUE_PER_SECTION = 6 # 路由层:每个 section 最大救援切片数
CLUSTER_SECTION_PREFIX_LEVELS = 1 # section_path 归一化保留的层级数(按章节顶层分组)
# ==============================================================================
# 十一、可选功能配置
# ==============================================================================
# 网络搜索(需 Serper API
SERPER_API_KEY = os.getenv("SERPER_API_KEY", "")
# ==============================================================================
# 工具函数
# ==============================================================================
def get_llm_client():
"""获取 LLM 客户端实例"""
from openai import OpenAI
return OpenAI(api_key=DASHSCOPE_API_KEY, base_url=DASHSCOPE_BASE_URL)