札记

信息压缩引擎:架构、本质与设计哲学

一、问题的本质

表面上看,我们在做"AI 聊天"、"深度研究"、"npm 包推荐"。但这些都是同一个问题的不同表现:

信息压缩问题。

人类需要获取信息,但信息量远超人类大脑的处理能力。一个 PDF 几百万 bit,人类意识带宽约 120 bits/秒。压缩比需要达到 10000:1。

现有工具做到的最多 100:1(摘要还是太长,人还是得读)。真正的压缩引擎需要做到:把世界的信息压缩进人类大脑,保留价值,丢弃噪声。

这不是"PDF 对话"、不是"搜索引擎"、不是"知识库"——这是一个通用的信息压缩引擎,数据源是输入参数,压缩结果是输出。


二、压缩目标是相对的

通用压缩(Wikipedia 摘要)对所有人一样。但信息的价值是相对的——

  • 对你已经知道的内容,压缩率可以 100000:1 直接丢弃

  • 对你不知道但需要的内容,要保留更多粒度

  • 对你的目标无关的内容,无论多重要都可以丢

所以真正的压缩引擎需要两个输入:信息本身 + 读者的知识状态

这就是记忆库存在的深层意义——不只是"以后查",而是决定当下压多狠。已知的知识,下次直接跳过;未知的,保留细节。


三、这是人类认知的基本结构

这套架构不是工程设计,是认知本身的结构被重新发现:

环境刺激  →  注意力过滤  →  工作记忆处理  →  长期记忆巩固
数据源    →    粗排       →     精排        →    记忆库

粗排 = 注意力机制

人眼每秒接收约 10MB 数据,意识每秒只处理约 50 bit,过滤比 200000:1。这个过滤是自动的、无意识的、基于规则的——威胁识别、动态物体、名字呼唤。不经过理解,直接触发。

注意力不思考,它只筛选。这就是为什么粗排必须是程序,不能是 LLM。

精排 = 工作记忆

工作记忆容量 7±2 个组块,生理硬限制。精排就是工作记忆——串行、慢、资源受限,但能做语义理解。

这意味着粗排必须先把候选压到合理数量,不是优化问题,是架构约束。

记忆库 = 长期记忆巩固

人类的记忆不是录像,是压缩后的模式。海马体在睡眠中重放工作记忆,提取规律,转移到皮层。存的不是事件,是从事件里蒸馏出的结构。

最关键的动态:专家的精排会变成粗排。

棋手初学时需要慢慢分析每一步(精排),练到一定程度后直接"看到"好棋(粗排)。这就是组块化。系统越用,越多精排结论下沉为粗排直觉,精排就能处理更深层的问题。


四、核心架构

数据源 → 贪婪扩展 → 粗排 → 精排 → 记忆库

数据源

原始世界,高熵,不可信。作为接口参数传入,不硬编码:

interface DataSource {
    search: (query: string, count?: number) => Promise<SearchResult[]>
    read: (url: string) => Promise<string>
    mode?: 'web' | 'lib'
    thinkPrompt?: string
    decideRole?: string
    summarizeRole?: string
}

不同数据源有不同的领域 prompt,但走同一条压缩管道。

贪婪扩展

发散 100%,最大化召回,保证完备性。

一个用户 query → N 个搜索 query(关键词扩展)→ 并行搜索 → 极大候选集。

目的不是精确,是不漏。这是到达理论上限的必要条件——

局部最优:best(subset)   ← 大多数系统的天花板
全局最优:best(全集)     ← 贪婪才能到达

如果在入口就剪枝,后面精排再聪明也是在局部最优里打转。那个月下载量 500 但完美解决问题的新库,只有穷举才能找到。

贪婪之所以可行,是因为贪婪阶段的单位成本接近零

  • 关键词扩展:1 次 LLM 调用,输入极小

  • 并行搜索:网络 I/O,只返回 snippet,可并发

  • 整个扩展阶段不读全文,无 token 爆炸

粗排

程序过滤,不用 LLM,不需要理解内容。

操作对象是结构化元数据(下载量、日期、snippet 关键词),不是语义:

  • 硬规则:downloads >= 10000,域名黑名单,时效过滤

  • 关键词匹配:query terms vs title+description

  • 目的:候选集 N → k(k << N)

粗排追求召回率,不追求精确率。放进来多余的,精排会过滤;漏掉的,后面没有机会补救。

精排

语义理解,LLM 读、理解、生成。

输入:full content(README、页面正文)。 输出:新生成的意义,不是摘抄,不是抽取式摘要。

概括这个动作本身就是理解的证明。 能用更少的词说清楚同样的意思,说明真的懂了。这是只有"理解了"才能做的事——LLM 不是在挑句子,而是内化后重新表达。

精排的输出不是分数,不是排名,是新生成的意义,这才是真正的压缩——不是删减,是蒸馏。

记忆库

精排结果持久化,形成自强化闭环:

  1. 已知内容 → 下次粗排直接过滤

  2. 精排确认的模式 → 逐步下沉为粗排规则

  3. 记忆库越丰富,过滤越精准,精排越专注于真正未知的问题


五、贪婪原理:两种错误代价不对称

这是整套架构的数学基础:

漏掉重要项

纳入无关项

粗排

代价极高(不可逆,后面没机会补)

代价低(精排会过滤)

精排

代价中等(结论有缺口)

代价高(结论被污染)

漏掉是不可逆的,多了是可逆的。

所以粗排宁可多,精排宁可少。大多数工程师的直觉是反过来的——想在粗排就做精确,结果既慢又漏。

正确的工作顺序:

贪婪阶段:召回率 → 1.0,精确率无所谓
收敛阶段:精确率 → 1.0,基于完整候选集

先撒大网,再收网。任何提前剪枝都是在赌局部最优就是全局最优。


六、npm source:完整实现参考

npm source 是目前最完整的实现,已经做到了三层漏斗:

expandKeywords(query)          → K=6-10 个搜索关键词

parallel npmSearch(kw, 20) × K → 最多 200 个候选

粗排 T1:downloads >= 10000    → 过滤低热度包(程序)

去重

粗排 T2scoreRelevance(name+desc) → exact/related/weak/irrelevant(LLM,极小输入)

filter(exact | related)        → 候选集(SearchResult)

research 循环:decide → fetchNpmDetail → summarize

scoreRelevance 的关键设计:只用 name + description,不读 README。这是粗排/精排的边界——名字+描述足够判断相关性(语义),README 才是内容理解(精排)。30 个一批,成本极低。

对比 web source:搜索返回 8 条,直接读全部,没有任何粗排。这是要补的缺口。


七、当前代码的问题

research.ts 的 web 搜索流程:

// 直接读全部 results,没有粗排
const pages = await Promise.all(
    results.map(async (r) => { ... })  // 8 次 fetch
)
// 过滤在读完之后才发生
const valid = pages.filter(p => p.content.length > 100)

这是事后清理,不是粗排。正确做法:

// 粗排:在 snippet 上打分,不需要 fetch
function coarseFilter(results: SearchResult[], query: string, k = 3) {
    const terms = query.toLowerCase().split(/\s+/)
    return results
        .map(r => ({
            ...r,
            score: terms.filter(t =>
                (r.title + r.description).toLowerCase().includes(t)
            ).length
        }))
        .sort((a, b) => b.score - a.score)
        .slice(0, k)
}

// 只 fetch top k
const topResults = coarseFilter(results, searchQuery, 3)
const pages = await Promise.all(topResults.map(...))

代价:O(N·|q|),零网络请求,零 token。 收益:fetch 次数从 M×N=48 降到 M×k=18,减少 62%。更重要的是,精排看到的内容更干净。


八、完整数学模型

conductResearch(Q, K):

  // Phase 0:规划
  A = analyze(Q, K)                    → O(|Q|+|K|) tokens

  // Phase 1..M:循环
  for i in 1..M:
    q_i = decide(Q, K, A, {S_j}_{j<i})  → 语义决策,能看到完整 chain
    if q_i == DONE: break

    R_i  = source.search(q_i, N)         // 数据源:N snippets
    R_i* = coarse(R_i, q_i, k)          // 粗排:程序,N → k
    P_i  = source.read(R_i*)            // 只读 top k
    S_i  = summarize(P_i, chain_i, Q)   // 精排:语义压缩

    chain += {record_i, S_i}            // 压缩后写入,不含原始页面

  // Phase 3:汇总
  digest = concat(S_1, ..., S_m)

九、这个系统的产品定位

不是 ChatPDF,不是搜索引擎,不是知识库。

个人信息压缩引擎——用户每天产生的所有研究、对话、决策,都通过这个管道压缩后存入记忆库。系统随着使用越来越准,因为它在持续地把精排的结论下沉为粗排的直觉,越来越像用户本人的思维方式。

表面看是一个简单的聊天界面,底层是一个自强化的认知放大系统。