docs: 更新 README.md,包含算法细节、局限性、适用场景
This commit is contained in:
123
README.md
123
README.md
@@ -1,15 +1,32 @@
|
|||||||
# 上下文门控器 (Context Gatekeeper)
|
# 上下文门控器 (Context Gatekeeper)
|
||||||
|
|
||||||
|
**论文:** https://gitea.ephron.ren/elaina/context-gatekeeper/src/branch/main/paper.md
|
||||||
|
|
||||||
轻量级上下文选择器,在同一会话中自动从历史对话里选出最小且相关的片段,减少话题污染和控制上下文长度。
|
轻量级上下文选择器,在同一会话中自动从历史对话里选出最小且相关的片段,减少话题污染和控制上下文长度。
|
||||||
|
|
||||||
## 特性
|
## 特性
|
||||||
|
|
||||||
- 🚀 **纯 Python**,无需额外模型依赖
|
- 🚀 **纯 Python**,无需向量化模型依赖(无 embedding、reranker、分类器)
|
||||||
- 💻 **轻量运行**,支持 2 核 2G 环境
|
- 💻 **轻量运行**,2 核 2G 环境可流畅运行
|
||||||
- 🔍 **话题门控**,智能判断继续/切换
|
- 🔍 **话题门控**,通过锚点 overlap + new_ratio 判断继续/切换,含指代词强制继承
|
||||||
- 📦 **稀疏召回**,BM25/IDF-overlap 评分
|
- 📦 **稀疏召回**,BM25/IDF-overlap 评分,用户侧权重高于助手侧
|
||||||
- 🎯 **最小覆盖**,贪心算法选择最优子集
|
- 🎯 **最小覆盖**,基于 IDF 加权集合覆盖的贪心选择
|
||||||
- ⚙️ **稳定约束区**,持久化用户偏好
|
- ⚙️ **稳定约束区**,持久化用户偏好(语言/风格/禁用项)
|
||||||
|
|
||||||
|
## 核心流程
|
||||||
|
|
||||||
|
```
|
||||||
|
用户查询 q
|
||||||
|
↓
|
||||||
|
① 锚点提取(中文 2/3-gram、英文单词、代码标识符、版本号、引号短语)
|
||||||
|
↓
|
||||||
|
② 话题门控(overlap > 0.45 → 继续;overlap < 0.20 且 new_ratio > 0.70 → 切换;
|
||||||
|
有指代词 → 强制继续;中间地带默认继续)
|
||||||
|
↓
|
||||||
|
③ 稀疏召回(top-20,BM25/IDF-overlap + exact match + 新鲜度奖励)
|
||||||
|
↓
|
||||||
|
④ 最小覆盖选择(gain = ΣIDF(t) / cost^α,贪心选择达到 85% 覆盖停止)
|
||||||
|
```
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
@@ -25,29 +42,22 @@ from src.gatekeeper import ContextGatekeeper
|
|||||||
# 初始化,token 预算 4000
|
# 初始化,token 预算 4000
|
||||||
gate = ContextGatekeeper(token_budget=4000)
|
gate = ContextGatekeeper(token_budget=4000)
|
||||||
|
|
||||||
# 添加对话历史
|
# 添加多轮对话
|
||||||
gate.add_turn("Redis 锁续租为什么会脑裂", "因为 TTL 设置不合理...")
|
gate.add_turn("如何设计一个 Redis 分布式锁?",
|
||||||
gate.add_turn("如何避免脑裂", "可以增加时钟偏移检测...")
|
"分布式锁需要满足互斥性、死锁避免、性能要求。常用 Redisson 实现。")
|
||||||
|
gate.add_turn("锁的 TTL 设置多少合适?",
|
||||||
|
"TTL 取决于业务操作耗时,建议 3-5 倍 buffer,同时要续期机制。")
|
||||||
|
|
||||||
# 为当前查询选择上下文
|
# 为当前查询选择上下文
|
||||||
selected = gate.select("锁的 TTL 怎么设置")
|
selected = gate.select("锁的 TTL 设置多少合适?")
|
||||||
|
|
||||||
for item in selected:
|
for item in selected:
|
||||||
print(f"轮次 {item['turn_id']}: {item['user']}")
|
print(f"轮次 {item['turn_id']}: {item['user']}")
|
||||||
print(f"助手: {item['assistant']}\n")
|
print(f"助手: {item['assistant']}\n")
|
||||||
|
|
||||||
# 设置稳定约束
|
# 构建完整 prompt(可直接发给 LLM)
|
||||||
gate.set_constraint("language", "中文")
|
prompt = gate.build_prompt("锁的 TTL 设置多少合适?")
|
||||||
gate.set_constraint("style", "简洁")
|
print(prompt)
|
||||||
|
|
||||||
# 构建完整 prompt
|
|
||||||
prompt = gate.build_prompt("Redis 集群如何搭建")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心流程
|
|
||||||
|
|
||||||
```
|
|
||||||
用户输入 → 锚点提取 → 话题门控 → 稀疏召回 → 最小覆盖选择 → 组装 Prompt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
@@ -55,20 +65,19 @@ prompt = gate.build_prompt("Redis 集群如何搭建")
|
|||||||
```
|
```
|
||||||
context-gatekeeper/
|
context-gatekeeper/
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── __init__.py
|
│ ├── anchor.py # 锚点提取(2/3-gram + IDF)
|
||||||
│ ├── anchor.py # 锚点提取
|
|
||||||
│ ├── block.py # Block 数据结构
|
│ ├── block.py # Block 数据结构
|
||||||
│ ├── topic_gate.py # 话题门控
|
│ ├── topic_gate.py # 话题门控(overlap + new_ratio + 指代词)
|
||||||
│ ├── sparse.py # 稀疏召回
|
│ ├── sparse.py # 稀疏召回(BM25/IDF + exact + recency)
|
||||||
│ ├── selector.py # 最小覆盖选择
|
│ ├── selector.py # 最小覆盖选择(IDF加权贪心)
|
||||||
│ └── gatekeeper.py # 主模块
|
│ └── gatekeeper.py # 主模块(组合各子模块)
|
||||||
├── tests/
|
├── tests/
|
||||||
│ ├── test_gatekeeper.py # 单元测试
|
│ ├── test_gatekeeper.py # 单元测试(9/9)
|
||||||
│ └── test_e2e.py # 端到端测试
|
│ └── test_full_evaluation.py # 完整评测
|
||||||
|
├── evaluation_results.json # 评测结果
|
||||||
|
├── paper.md # 技术论文
|
||||||
├── SPEC.md # 规格文档
|
├── SPEC.md # 规格文档
|
||||||
├── README.md
|
└── README.md
|
||||||
├── .env.example
|
|
||||||
└── .gitignore
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 运行测试
|
## 运行测试
|
||||||
@@ -77,26 +86,52 @@ context-gatekeeper/
|
|||||||
# 单元测试
|
# 单元测试
|
||||||
pytest tests/test_gatekeeper.py -v
|
pytest tests/test_gatekeeper.py -v
|
||||||
|
|
||||||
# 端到端测试(需要配置 .env)
|
# 完整评测(20轮对话)
|
||||||
cp .env.example .env
|
pytest tests/test_full_evaluation.py -v
|
||||||
# 编辑 .env 填入你的 MiniMax API Key
|
|
||||||
pytest tests/test_e2e.py -v
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 算法细节
|
## 算法细节
|
||||||
|
|
||||||
### 话题门控
|
### 话题门控判断
|
||||||
|
|
||||||
- **overlap > 0.45**:继续当前话题
|
```python
|
||||||
- **overlap < 0.20** 且 **new_ratio > 0.70**:切换新话题
|
overlap = Σ IDF(t) for t ∈ A(q)∩A(T) / Σ IDF(t) for t ∈ A(q)
|
||||||
- 有指代词(这个/那个/它/上面)→ 强制继续
|
new_ratio = Σ IDF(t) for t ∈ A(q)\A(T) / Σ IDF(t) for t ∈ A(q)
|
||||||
|
|
||||||
|
if overlap > 0.45: continue
|
||||||
|
elif overlap < 0.20 and new_ratio > 0.70: switch
|
||||||
|
elif has_deictic: continue # 指代词强制继承
|
||||||
|
else: continue # 中间地带默认继续
|
||||||
|
```
|
||||||
|
|
||||||
### 稀疏召回评分
|
### 稀疏召回评分
|
||||||
|
|
||||||
```
|
```
|
||||||
score = 1.5 * lex(user) + 0.7 * lex(assistant) + 1.0 * exact + 0.2 * recency
|
score = 1.5·lex(u_b,q) + 0.7·lex(a_b,q) + 1.0·exact(b,q) + 0.2·recency(b)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 最小覆盖选择
|
### 最小覆盖 gain
|
||||||
|
|
||||||
贪心选择"单位长度收益最大"的 block,直到覆盖率达到 85% 或 token 预算耗尽。
|
```
|
||||||
|
gain(b|S) = Σ IDF(t) for t ∈ cov(b)\covered(S) / cost(b)^α, α=0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
## 局限性与适用场景
|
||||||
|
|
||||||
|
**局限性:**
|
||||||
|
- 稀疏检索在语义相似但词形不同时召回率有限
|
||||||
|
- 中文锚点无停用词过滤,高频无意义词可能干扰 IDF
|
||||||
|
- Token 估算为粗略估算(字符数×1.5),与实际有 2-3 倍误差
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 资源受限的生产环境(边缘设备、私有部署)
|
||||||
|
- 对延迟敏感的实时对话
|
||||||
|
- 中等复杂度对话(10-50轮)
|
||||||
|
|
||||||
|
**不适用:**
|
||||||
|
- 需要精确语义匹配的场景(建议用向量检索)
|
||||||
|
- 极长对话(>100轮,IDF 全量更新有偏)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|||||||
Reference in New Issue
Block a user