From a8204a50b5103010a5bc8b68554156def85f8bda Mon Sep 17 00:00:00 2001 From: Elaina Date: Wed, 22 Apr 2026 09:49:17 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20README.md=EF=BC=8C?= =?UTF-8?q?=E5=8C=85=E5=90=AB=E7=AE=97=E6=B3=95=E7=BB=86=E8=8A=82=E3=80=81?= =?UTF-8?q?=E5=B1=80=E9=99=90=E6=80=A7=E3=80=81=E9=80=82=E7=94=A8=E5=9C=BA?= =?UTF-8?q?=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 125 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 971e508..30105d0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,32 @@ # 上下文门控器 (Context Gatekeeper) +**论文:** https://gitea.ephron.ren/elaina/context-gatekeeper/src/branch/main/paper.md + 轻量级上下文选择器,在同一会话中自动从历史对话里选出最小且相关的片段,减少话题污染和控制上下文长度。 ## 特性 -- 🚀 **纯 Python**,无需额外模型依赖 -- 💻 **轻量运行**,支持 2 核 2G 环境 -- 🔍 **话题门控**,智能判断继续/切换 -- 📦 **稀疏召回**,BM25/IDF-overlap 评分 -- 🎯 **最小覆盖**,贪心算法选择最优子集 -- ⚙️ **稳定约束区**,持久化用户偏好 +- 🚀 **纯 Python**,无需向量化模型依赖(无 embedding、reranker、分类器) +- 💻 **轻量运行**,2 核 2G 环境可流畅运行 +- 🔍 **话题门控**,通过锚点 overlap + new_ratio 判断继续/切换,含指代词强制继承 +- 📦 **稀疏召回**,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 gate = ContextGatekeeper(token_budget=4000) -# 添加对话历史 -gate.add_turn("Redis 锁续租为什么会脑裂", "因为 TTL 设置不合理...") -gate.add_turn("如何避免脑裂", "可以增加时钟偏移检测...") +# 添加多轮对话 +gate.add_turn("如何设计一个 Redis 分布式锁?", + "分布式锁需要满足互斥性、死锁避免、性能要求。常用 Redisson 实现。") +gate.add_turn("锁的 TTL 设置多少合适?", + "TTL 取决于业务操作耗时,建议 3-5 倍 buffer,同时要续期机制。") # 为当前查询选择上下文 -selected = gate.select("锁的 TTL 怎么设置") +selected = gate.select("锁的 TTL 设置多少合适?") for item in selected: print(f"轮次 {item['turn_id']}: {item['user']}") print(f"助手: {item['assistant']}\n") -# 设置稳定约束 -gate.set_constraint("language", "中文") -gate.set_constraint("style", "简洁") - -# 构建完整 prompt -prompt = gate.build_prompt("Redis 集群如何搭建") -``` - -## 核心流程 - -``` -用户输入 → 锚点提取 → 话题门控 → 稀疏召回 → 最小覆盖选择 → 组装 Prompt +# 构建完整 prompt(可直接发给 LLM) +prompt = gate.build_prompt("锁的 TTL 设置多少合适?") +print(prompt) ``` ## 项目结构 @@ -55,20 +65,19 @@ prompt = gate.build_prompt("Redis 集群如何搭建") ``` context-gatekeeper/ ├── src/ -│ ├── __init__.py -│ ├── anchor.py # 锚点提取 +│ ├── anchor.py # 锚点提取(2/3-gram + IDF) │ ├── block.py # Block 数据结构 -│ ├── topic_gate.py # 话题门控 -│ ├── sparse.py # 稀疏召回 -│ ├── selector.py # 最小覆盖选择 -│ └── gatekeeper.py # 主模块 +│ ├── topic_gate.py # 话题门控(overlap + new_ratio + 指代词) +│ ├── sparse.py # 稀疏召回(BM25/IDF + exact + recency) +│ ├── selector.py # 最小覆盖选择(IDF加权贪心) +│ └── gatekeeper.py # 主模块(组合各子模块) ├── tests/ -│ ├── test_gatekeeper.py # 单元测试 -│ └── test_e2e.py # 端到端测试 -├── SPEC.md # 规格文档 -├── README.md -├── .env.example -└── .gitignore +│ ├── test_gatekeeper.py # 单元测试(9/9) +│ └── test_full_evaluation.py # 完整评测 +├── evaluation_results.json # 评测结果 +├── paper.md # 技术论文 +├── SPEC.md # 规格文档 +└── README.md ``` ## 运行测试 @@ -77,26 +86,52 @@ context-gatekeeper/ # 单元测试 pytest tests/test_gatekeeper.py -v -# 端到端测试(需要配置 .env) -cp .env.example .env -# 编辑 .env 填入你的 MiniMax API Key -pytest tests/test_e2e.py -v +# 完整评测(20轮对话) +pytest tests/test_full_evaluation.py -v ``` ## 算法细节 -### 话题门控 +### 话题门控判断 -- **overlap > 0.45**:继续当前话题 -- **overlap < 0.20** 且 **new_ratio > 0.70**:切换新话题 -- 有指代词(这个/那个/它/上面)→ 强制继续 +```python +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 预算耗尽。 \ No newline at end of file +``` +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