8.5 KiB
上下文门控器 DESIGN.md
本文件描述 context-gatekeeper 的视觉语言、算法哲学与实现规范,供 AI 编码 Agent 在开发/重构时参考。 遵循 Google Stitch DESIGN.md 规范。
1. Visual Theme and Atmosphere
工具型命令行项目,不是面向终端用户的可视化产品。
核心氛围是克制与精确——每个设计决策都有算法层面的理由,不做视觉上的"美化"。
代码即文档。输出即证据。接口即契约。
2. Design Language
审美取向
- 风格:工程师工具(engineer-tool aesthetic)。类比
htop、tcpdump、Shellcheck - 信息密度优先:宁可多给一行调试输出,也不要静默失败
- 零装饰原则:没有 emoji logo、没有渐变色、没有动画。结构即美学
排版规范
- 代码:等宽字体(
'JetBrains Mono','Fira Code',monospace) - 表格:ASCII 风格 Pipe Table,算法伪代码用 Markdown code block
- 日志输出:单行简短,带时间戳或轮次编号
命名哲学
- 变量名即类型注释:不用注释解释的东西,变量名就要说清楚
- ✅
anchor_overlap_ratio,new_token_ratio - ❌
score,val,tmp
- ✅
- 函数名即行为描述:
extract_anchors()→ 提取锚点,gate_topic()→ 判断话题切换 - 模块名即职责边界:
anchor.py专司锚点提取,topic_gate.py专司门控判断
3. Color Palette(CLI 输出配色)
CLI 输出不使用颜色时最安全。本节定义配色规范,供未来添加
--color输出时参考。
| Role | Token | Value | Usage |
|---|---|---|---|
| 正常输出 | --text-normal |
#FFFFFF |
日志、返回值 |
| 高亮/标题 | --text-highlight |
#5EEAD4 |
锚点命中、话题切换提示 |
| 警告 | --text-warn |
#FBBF24 |
Token 超限、召回率低 |
| 错误 | --text-error |
#F87171 |
过滤异常、API 错误 |
| Debug | --text-debug |
#6B7280 |
内部状态、IDF 值 |
| 强调 | --text-accent |
#A78BFA |
核心指标(覆盖增益) |
4. Algorithm Specification
本项目的核心是算法,不是 UI。实现必须严格遵循算法规范,不得随意改参数。
4.1 锚点提取(Anchor Extraction)
职责:从文本中提取有检索区分度的词单元。
提取规则(优先级从高到低):
- 英文术语:完整单词,小写化(
redis、asyncio、postgresql) - 代码标识符:变量名/函数名,提取连续字母数字串(
v1.2.3→v1) - 中文 n-gram:2-gram + 3-gram(
分布式锁、跨进程通信) - 引号短语:双引号/单引号内的完整短语
IDF 计算:
- 语料库:全局所有已处理的对话块
- 平滑公式:
log((N + 1) / (df + 1)) + 1 - 低于
1.5的 n-gram 不参与锚点计算(过高的 IDF 通常是噪声)
输出格式:
# anchor.py
anchors: dict[str, float] # {词单元: IDF值}
4.2 话题门控(Topic Gate)
职责:判断当前 query 是否属于新话题。
决策树:
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)
overlap > 0.45 → continue(继续当前话题)
overlap < 0.20 and new_ratio > 0.70 → switch(话题切换)
has_deictic(q) → continue(指代词强制继续)
else → continue(中间地带默认继续)
指代词检测(必须硬编码,不得依赖规则引擎):
DEICTIC_PATTERNS = [
r'^这个', r'^那个', r'^它', r'^这', r'^那',
r'^继续', r'^然后呢', r'^还有呢',
]
4.3 话题切换时的内容词过滤
触发条件:topic_gate 判定为 switch。
内容词识别:
CONTENT_WORD_MIN_LEN = 4 # 中文词 >= 4字符
CONTENT_WORD_PATTERNS = [
r'\b[a-z]{4,}\b', # 英文术语 >= 4字母
r'\bv?\d+\.\d+\.\d+\b', # 版本号
]
过滤逻辑:
- 候选块必须包含至少一个内容词,否则
score = 0.0(硬过滤) - 不走 IDF 阈值过滤(IDF > 2.0 筛选出的是稀有字符 n-gram,不是话题标识符)
4.4 稀疏召回(Sparse Recall)
评分公式:
score(b, q) = 1.5·lex(u_b,q) + 0.7·lex(a_b,q) + 1.0·exact(b,q) + 0.2·recency(b)
| 符号 | 定义 | 权重 |
|---|---|---|
lex(u_b,q) |
用户轮次 BM25/IDF 与 Query 重叠度 | 1.5 |
lex(a_b,q) |
助手轮次 BM25/IDF 与 Query 重叠度 | 0.7 |
exact(b,q) |
完全匹配奖励(块含 Query 所有锚点) | 1.0 |
recency(b) |
新鲜度奖励:1 - (current_turn - b.turn_id) / window |
0.2 |
召回数量上限:top-20,超出部分不参与后续选择。
4.5 最小覆盖选择(Minimum Coverage Selection)
贪心选择:每次选 gain = Σ IDF(t) for t ∈ cov(b)\covered(S) / cost(b)^α,直到:
- 覆盖率达到 85%,或
- Token 预算耗尽
参数:α = 0.8(不得改动)
Token 估算:len(text) * 1.5(与实际有 2-3 倍误差,保守估算)
4.6 句级裁剪(Sentence-Level Pruning)
输入:选中的 block(可能含多句话)
规则:
- 按
.或\n分割为句子 - 保留含 Query 锚点的句子,最多 3 句
- 助手侧即使不含锚点,也保留第一句(保证上下文衔接)
- 裁剪后的块 token 估算独立重新计算
5. API 接口规范
ContextGatekeeper(token_budget: int)
初始化。token_budget 为单次 prompt 的最大 token 数(不含系统 prompt)。
gate.add_turn(user_text: str, assistant_text: str) -> int
添加一轮对话。返回 turn_id(从 0 开始)。
gate.select(query: str) -> list[dict]
返回选中的上下文块列表。每项格式:
{
'turn_id': int,
'user': str, # 裁剪后的用户轮次文本
'assistant': str, # 裁剪后的助手轮次文本
'score': float, # 该块的最终得分
'covered_ids': list[int], # 该块新增覆盖的锚点 turn_id
}
gate.build_prompt(query: str) -> str
返回可直接发给 LLM 的完整 prompt 字符串(不含系统 prompt)。
6. 模块边界(Do's and Don'ts)
✅ 允许
- 在
src/外层添加新模块(如benchmark.py) - 扩展
anchor.py支持新的 n-gram 规则 - 添加新的话题切换检测模式(需同时更新
DEICTIC_PATTERNS) - 调整
token_budget数值
❌ 禁止
- 在
anchor.py里做话题门控判断(违反单一职责) - 在
selector.py里修改α参数(该参数经过实验验证) - 将
BM25/IDF替换为 embedding/reranker(资源受限前提的核心约束) - 在 CLI 输出中硬编码颜色(Unix 管道场景下颜色会变成乱码)
7. Error Handling
| 场景 | 行为 |
|---|---|
add_turn() 时 token 估算 > budget |
记录 warning,不 abort |
select() 时无候选块 |
返回空 list |
| 锚点提取为空 | fallback:取 query 前 20 字符作为锚点 |
| 句级裁剪后块为空 | 跳过该块,继续选下一个 |
8. 测试规范
单元测试
- 每个模块独立的
test_<module>.py - 锚点提取:输入固定文本,输出 IDF 值在已知范围内
- 话题门控:用预制的(query, history, expected_decision)三元组覆盖所有分支
集成测试
test_100rounds_v2.py:4 话题 × 25 轮交替,验证零跨话题污染- 必须覆盖:话题切换、内容词过滤、Token 预算耗尽、指代词强制继续
回归基准
- 每次修改后必须通过
test_100rounds_v2.py - 对话轮次超过 100 时需重新评估 IDF 偏倚问题
9. Agent Prompt Guide(给 AI 助手的参考)
快速参考
| 概念 | 值 |
|---|---|
| 锚点权重 | 用户侧 1.5 / 助手侧 0.7 |
| 覆盖停止阈值 | 85% |
| 贪心参数 α | 0.8 |
| 召回上限 | top-20 |
| Token 估算系数 | ×1.5 |
常见任务提示词
"我想加一个 BM25 以外的召回策略" →
→ 检查 sparse.py 的 lex() 函数,不要改动 score 公式的权重
"某个话题老被误过滤" →
→ 先查话题切换时的 content_word 过滤逻辑,确认过滤条件是否过严
"我想试试不同的 overlap 阈值" →
→ 修改 topic_gate.py 的 OVERLAP_CONTINUE_THRESHOLD,然后跑 test_100rounds_v2.py 验证零污染
"Token 预算想从 4000 改成 8000" →
→ 改 ContextGatekeeper(token_budget=8000),检查 test_100rounds_v2.py 中对应的 BUDGET 常量