init: RAG 知识库服务初始提交
- 后端 API(Flask + Gunicorn) - RAG 引擎(混合检索 + 云端 Reranker + 引用溯源) - 文档解析(MinerU + 多格式支持) - Docker 生产部署配置 - 排除前端项目、敏感配置、模型文件
This commit is contained in:
262
core/adaptive_topk.py
Normal file
262
core/adaptive_topk.py
Normal file
@@ -0,0 +1,262 @@
|
||||
"""
|
||||
自适应 TopK 策略
|
||||
|
||||
根据检索置信度动态调整 top_k:
|
||||
- 低置信度:扩大检索范围
|
||||
- 高置信度:缩小检索范围
|
||||
- 中等置信度:保持原样
|
||||
|
||||
使用方式:
|
||||
from core.adaptive_topk import AdaptiveTopK
|
||||
|
||||
strategy = AdaptiveTopK()
|
||||
adjusted_k, should_retrieve = strategy.adjust(top_score, initial_k)
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Tuple, Optional
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdaptiveConfig:
|
||||
"""自适应配置"""
|
||||
# 置信度阈值
|
||||
low_confidence_threshold: float = 0.5 # 低于此值认为是低置信度
|
||||
high_confidence_threshold: float = 0.8 # 高于此值认为是高置信度
|
||||
|
||||
# 扩展/收缩比例
|
||||
expand_ratio: float = 2.0 # 低置信度时扩大倍数
|
||||
shrink_ratio: float = 0.5 # 高置信度时缩小比例
|
||||
|
||||
# 限制
|
||||
min_top_k: int = 3 # 最小 top_k
|
||||
max_top_k: int = 20 # 最大 top_k
|
||||
|
||||
# 是否启用
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class AdaptiveTopK:
|
||||
"""
|
||||
自适应 TopK 策略
|
||||
|
||||
核心逻辑:
|
||||
1. 第一次检索返回结果后,检查最高得分
|
||||
2. 根据得分判断置信度
|
||||
3. 决定是否需要调整 top_k 重新检索
|
||||
"""
|
||||
|
||||
def __init__(self, config: AdaptiveConfig = None):
|
||||
"""
|
||||
初始化
|
||||
|
||||
Args:
|
||||
config: 配置对象,如果为None则使用默认配置
|
||||
"""
|
||||
self.config = config or AdaptiveConfig()
|
||||
|
||||
def adjust(
|
||||
self,
|
||||
top_score: float,
|
||||
initial_k: int,
|
||||
current_results_count: int = 0
|
||||
) -> Tuple[int, bool, str]:
|
||||
"""
|
||||
根据置信度调整 top_k
|
||||
|
||||
Args:
|
||||
top_score: 当前检索结果的最高得分 (0-1)
|
||||
initial_k: 初始 top_k
|
||||
current_results_count: 当前结果数量
|
||||
|
||||
Returns:
|
||||
(adjusted_k, should_retrieve, reason)
|
||||
- adjusted_k: 调整后的 top_k
|
||||
- should_retrieve: 是否需要重新检索
|
||||
- reason: 调整原因
|
||||
"""
|
||||
if not self.config.enabled:
|
||||
return initial_k, False, "disabled"
|
||||
|
||||
# 低置信度:扩大检索范围
|
||||
if top_score < self.config.low_confidence_threshold:
|
||||
adjusted_k = min(
|
||||
int(initial_k * self.config.expand_ratio),
|
||||
self.config.max_top_k
|
||||
)
|
||||
if adjusted_k > initial_k:
|
||||
return adjusted_k, True, f"low_confidence({top_score:.2f}<{self.config.low_confidence_threshold})"
|
||||
|
||||
# 高置信度:可以缩小范围(但不重新检索)
|
||||
elif top_score > self.config.high_confidence_threshold:
|
||||
adjusted_k = max(
|
||||
int(initial_k * self.config.shrink_ratio),
|
||||
self.config.min_top_k
|
||||
)
|
||||
# 高置信度时不需要重新检索,只是返回时可以截断
|
||||
return adjusted_k, False, f"high_confidence({top_score:.2f}>{self.config.high_confidence_threshold})"
|
||||
|
||||
# 中等置信度:保持原样
|
||||
return initial_k, False, f"medium_confidence({top_score:.2f})"
|
||||
|
||||
def get_final_results(
|
||||
self,
|
||||
results: list,
|
||||
adjusted_k: int,
|
||||
reason: str
|
||||
) -> list:
|
||||
"""
|
||||
获取最终结果
|
||||
|
||||
Args:
|
||||
results: 检索结果列表
|
||||
adjusted_k: 调整后的 top_k
|
||||
reason: 调整原因
|
||||
|
||||
Returns:
|
||||
截断后的结果列表
|
||||
"""
|
||||
if "high_confidence" in reason:
|
||||
# 高置信度时截断结果
|
||||
return results[:adjusted_k]
|
||||
return results
|
||||
|
||||
def get_config_dict(self) -> dict:
|
||||
"""获取配置字典"""
|
||||
return {
|
||||
"enabled": self.config.enabled,
|
||||
"low_confidence_threshold": self.config.low_confidence_threshold,
|
||||
"high_confidence_threshold": self.config.high_confidence_threshold,
|
||||
"expand_ratio": self.config.expand_ratio,
|
||||
"shrink_ratio": self.config.shrink_ratio,
|
||||
"min_top_k": self.config.min_top_k,
|
||||
"max_top_k": self.config.max_top_k
|
||||
}
|
||||
|
||||
|
||||
class AdaptiveTopKWithStats(AdaptiveTopK):
|
||||
"""
|
||||
带统计的自适应 TopK 策略
|
||||
|
||||
记录每次调整的统计信息,用于分析和优化
|
||||
"""
|
||||
|
||||
def __init__(self, config: AdaptiveConfig = None):
|
||||
super().__init__(config)
|
||||
self.stats = {
|
||||
"total_queries": 0,
|
||||
"low_confidence_count": 0,
|
||||
"high_confidence_count": 0,
|
||||
"medium_confidence_count": 0,
|
||||
"re_retrieve_count": 0
|
||||
}
|
||||
|
||||
def adjust(
|
||||
self,
|
||||
top_score: float,
|
||||
initial_k: int,
|
||||
current_results_count: int = 0
|
||||
) -> Tuple[int, bool, str]:
|
||||
"""带统计的调整"""
|
||||
self.stats["total_queries"] += 1
|
||||
|
||||
adjusted_k, should_retrieve, reason = super().adjust(
|
||||
top_score, initial_k, current_results_count
|
||||
)
|
||||
|
||||
# 更新统计
|
||||
if "low_confidence" in reason:
|
||||
self.stats["low_confidence_count"] += 1
|
||||
elif "high_confidence" in reason:
|
||||
self.stats["high_confidence_count"] += 1
|
||||
else:
|
||||
self.stats["medium_confidence_count"] += 1
|
||||
|
||||
if should_retrieve:
|
||||
self.stats["re_retrieve_count"] += 1
|
||||
|
||||
return adjusted_k, should_retrieve, reason
|
||||
|
||||
def get_stats(self) -> dict:
|
||||
"""获取统计信息"""
|
||||
return self.stats.copy()
|
||||
|
||||
def reset_stats(self):
|
||||
"""重置统计"""
|
||||
self.stats = {
|
||||
"total_queries": 0,
|
||||
"low_confidence_count": 0,
|
||||
"high_confidence_count": 0,
|
||||
"medium_confidence_count": 0,
|
||||
"re_retrieve_count": 0
|
||||
}
|
||||
|
||||
|
||||
# ==================== 便捷函数 ====================
|
||||
|
||||
def create_adaptive_topk(
|
||||
enabled: bool = True,
|
||||
low_threshold: float = 0.5,
|
||||
high_threshold: float = 0.8,
|
||||
expand_ratio: float = 2.0,
|
||||
shrink_ratio: float = 0.5
|
||||
) -> AdaptiveTopK:
|
||||
"""
|
||||
创建自适应 TopK 策略
|
||||
|
||||
Args:
|
||||
enabled: 是否启用
|
||||
low_threshold: 低置信度阈值
|
||||
high_threshold: 高置信度阈值
|
||||
expand_ratio: 扩展比例
|
||||
shrink_ratio: 收缩比例
|
||||
|
||||
Returns:
|
||||
AdaptiveTopK 实例
|
||||
"""
|
||||
config = AdaptiveConfig(
|
||||
enabled=enabled,
|
||||
low_confidence_threshold=low_threshold,
|
||||
high_confidence_threshold=high_threshold,
|
||||
expand_ratio=expand_ratio,
|
||||
shrink_ratio=shrink_ratio
|
||||
)
|
||||
return AdaptiveTopK(config)
|
||||
|
||||
|
||||
# ==================== 测试 ====================
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
print("=" * 60)
|
||||
print("自适应 TopK 策略测试")
|
||||
print("=" * 60)
|
||||
|
||||
strategy = AdaptiveTopKWithStats()
|
||||
|
||||
test_cases = [
|
||||
(0.3, 5, "低置信度 - 应该扩展"),
|
||||
(0.6, 5, "中等置信度 - 保持"),
|
||||
(0.9, 5, "高置信度 - 应该收缩"),
|
||||
(0.45, 10, "边界低置信度"),
|
||||
(0.8, 10, "边界高置信度"),
|
||||
]
|
||||
|
||||
for top_score, initial_k, description in test_cases:
|
||||
adjusted_k, should_retrieve, reason = strategy.adjust(top_score, initial_k)
|
||||
print(f"\n{description}")
|
||||
print(f" 输入: top_score={top_score}, initial_k={initial_k}")
|
||||
print(f" 输出: adjusted_k={adjusted_k}, should_retrieve={should_retrieve}")
|
||||
print(f" 原因: {reason}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("统计信息:")
|
||||
stats = strategy.get_stats()
|
||||
for key, value in stats.items():
|
||||
print(f" {key}: {value}")
|
||||
Reference in New Issue
Block a user