diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..72675d4 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,143 @@ +# 上下文门控器 - SPEC.md + +## 1. 项目概述 + +**项目名称:** context-gatekeeper +**功能:** 轻量级上下文选择器,在同一会话中自动从历史对话里选出最小且相关的片段,减少话题污染和控制上下文长度。 +**约束:** 纯 Python,无额外模型依赖,2 核 2G 可运行。 + +--- + +## 2. 核心架构 + +### 四步流程 + +``` +用户输入 → 锚点提取 → 话题门控 → 稀疏召回 → 最小覆盖选择 → 组装 Prompt +``` + +### 数据结构 + +```python +Block: + - user_text: str # 用户消息 + - assistant_text: str # 助手回复 + - tokens_user: int # 用户消息 token 数(估算) + - tokens_assistant: int # 助手消息 token 数(估算) + - anchors: list[str] # 锚点列表(2-gram/3-gram) + - turn_id: int # 对话轮次 +``` + +--- + +## 3. 功能模块 + +### 3.1 锚点提取 (Anchor Extraction) + +- 中文: 2-gram / 3-gram 连续字符串 +- 英文: 单词 +- 代码标识符、版本号、引号短语、反引号内代码 +- 计算 IDF 权重,高频通用词自动降权 + +### 3.2 话题门控 (Topic Gate) + +输入: 当前 query 锚点集合 + 活跃话题锚点集合 +输出: `continue` | `switch` + +规则: +- `overlap > 0.45`: 继续当前话题 +- `overlap < 0.20` 且 `new_ratio > 0.70`: 切换新话题 +- 有指代词(这个/那个/它/上面/刚才/继续/展开)→ 强暗示继续 + +### 3.3 稀疏召回 (Sparse Retrieval) + +评分函数: +``` +score(block, query) = 1.5 * lex(user_text, query) + + 0.7 * lex(assistant_text, query) + + 1.0 * exact_match(query, block) + + 0.2 * recency +``` + +- `lex`: BM25/IDF overlap,简单实现 +- `exact_match`: 英文术语、代码标识符、数字版本号、引号短语完整命中加分 +- `recency`: 弱先验,仅做微调 + +候选集: top 20 blocks + +### 3.4 最小覆盖选择 (Minimum Coverage Selection) + +贪心算法: +``` +每步选"单位长度收益最大"的 block: +gain = covered_anchor_idf / cost^alpha + +停止条件: +- 覆盖率达到 85% (η=0.85) +- 或达到 token 预算上限 +``` + +### 3.5 句级裁剪 (Intra-Block Trimming) + +选中 block 后,进一步裁剪: +- 保留覆盖了 query anchors 的句子 +- 保留 exact match 的句子 +- 连带保留对应用户问题句 + +### 3.6 常量区 (Stable Constraints) + +维护一个小的稳定约束区: +- 用户显式声明的长期偏好 +- 输出语言、风格、禁用项 + +不是记忆系统,只是一个固定 KV。 + +--- + +## 4. 对外接口 + +```python +class ContextGatekeeper: + def __init__(self, token_budget: int = 4000) + + def add_turn(self, user_text: str, assistant_text: str) -> None + """添加一轮对话到历史""" + + def select(self, query: str) -> list[dict]: + """为当前 query 选择上下文 blocks""" + # 返回: [{"user": ..., "assistant": ..., "turn_id": ...}, ...] + + def set_constraint(self, key: str, value: str) -> None + """设置稳定约束""" + + def get_constraints(self) -> dict: + """获取当前所有约束""" +``` + +--- + +## 5. 测试验证 + +使用 MiniMax API 做一个端到端对话测试: +- 模拟多轮对话(至少 3 轮,其中包含话题切换) +- 验证: + 1. 同一话题内,后续问题能召回前面对话 + 2. 切换话题后,旧话题内容不会被召回 + 3. 有指代词时,强制继承最近 1-2 个 block + 4. token 预算控制正常 + +.env 文件格式: +``` +MINIMAX_API_KEY=你的key +``` + +--- + +## 6. 验收标准 + +- [ ] 纯 Python,无第三方模型依赖 +- [ ] 可在 2 核 2G 环境运行 +- [ ] 单元测试覆盖核心模块 +- [ ] 端到端对话测试通过 +- [ ] .env 正确 gitignore +- [ ] 代码上传 Gitea 仓库 \ No newline at end of file