Files
rag/core/adaptive_topk.py
lacerate551 100d1a06eb init: RAG 知识库服务初始提交
- 后端 API(Flask + Gunicorn)
- RAG 引擎(混合检索 + 云端 Reranker + 引用溯源)
- 文档解析(MinerU + 多格式支持)
- Docker 生产部署配置
- 排除前端项目、敏感配置、模型文件
2026-06-04 17:35:27 +08:00

263 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
自适应 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}")