This blog is rated 🔞, viewer discretion is advised

Register Token以及对变形金刚AI的四大批判

前几天 Yann LeCun 对 变形金刚(transformers) AI 进行了深刻的批判

First, the reasoning abilities of current AI systems are based non-auto-regressive search (which is what I have always advocated for). But AFAICT, they do it in token space, which is limited and inefficient. I have claimed that human-like reasoning must be a search in continuous representation space. It looks like the industry is moving towards that.
Second, the self-improvement methods, as currently practiced, only work for domains where the quality of outputs can be scored without human intervention, such as mathematics, code, and scenarios that can be simulated accurately. Not anything else. Humans and animals learn new skills way more efficiently than current RL methods.
Third, the multimodal capabilities of current AI assistants generally use separately-trained encoders (that are not LLMs). This is also what I've been advocating. Except that I think the best way to do this is with JEPA trained with self-supervised learning. The research community is clearly moving towards that (3000 papers on JEPA in just 4 years).
Fourth, if LLMs were a path to human-level AI, we would have domestic robots and Level-4 or Level-5 self-driving cars for consumers by now. And we don't. We certainly don't have cars that can learn to drive in 20 hours or practice like any teenager. We're still missing something pretty huge to claim human-level intelligence (let alone superhuman).

Sure, we now have computer systems that are impressive, very useful, and whose performance is superhuman in an increasing number of domains (coding being one of them). But that's true of the entire history of progress in computer technology.

可能很多人早知道这个了,对我来说,很新鲜。毕竟我是AI 外行。以前一直听说 自回归 自回归,其实我今天才突然反应过来,这个 回归,就是数学期望那里,预测的意思。这里的 自 就是 一个token接着另一个token源源不断“自动” 跳出来直到 eos_token 的意思。🤣

他老人家都这么说了,想了下还真是。用 LLM 越多,越会明白在 Token 空间里做推理的局限性。


于是我突然想起很多天之前看到的一个youtube 视频,21世纪被引用次数最多的论文。这个35分钟的视频非常值得推荐完整观看。剧透:它讲的是 残差网络 ResNET

它提到另外一件事引起了我极大的兴趣,Meta FAIR 在ICLR 2024论文,发现了一个有趣现象:ViT 模型(尤其是 DINOv2、OpenCLIP、DeiT-III 这类大规模训练的模型)在推理时会产生一些"异常高范数"(high-norm)的 token,大约占总序列的 2%。这些 token 集中出现在图像中低信息量的背景区域,几乎都是纯色块,携带很少的局部位置/像素信息,却携带大量全局图像信息。只在模型足够大、训练足够久之后才出现(比如 DINOv2 只有 Large 及以上尺寸才有)。这些token会破坏注意力图的可解释性,损害密集预测任务(分割、深度估计)和无监督目标发现(LOST 算法)的效果

他们摸清楚一个脉络,最后研究发现,在输入序列里额外加几个可学习的"寄存器"(register)token,让模型有专门的地方做全局计算,不用再"borrow"图像 patch。加了之后,异常高范数 token 消失,注意力图变干净,下游任务(尤其密集预测和目标发现)性能还略有提升,几乎不增加参数和计算量。


是不是感觉这几件事相当跳跃,我在瞎说些啥呢?

那是因为你没看那个35分钟的视频。我就凭记忆,用我的理解回顾一下(我不太懂AI,纯外行。如有出入视频作者全责😂)

神经网络我觉得就是好几层矩阵相乘造就的魔法。但归根结底 tmd是一个分类器。传统每一层 NN 就干的事就是从上一层的结论里,加工自己的一道参考信息,然后激活给下一层NN继续加工。最后一层就是 N 选 1 的分类。对于 LLM 来说就是词汇表里吐出 next token。

但是这玩意早年极其容易翻车,shattered gradiant,ResNET 横空出世,据说是个 hack,把上上一层的权重和本层的权重做一次 加法。最后输出就稳了

这就有意思了,NN每一层都是无状态的,需要猜上一层为啥处理成这个样子;每一层给出结论,请重点关注某个值,但是至于一开始的为啥要给 attention,可能早就忘记了。这个所谓 残差,就是每一层都给后面一层工作留一个小纸条?就好比工匠无意中在木结构看不到的一些内部地方打上了一些标记。换班的水电工一眼看出这些标记在行业里的用处

AI告诉我,register token就更进一步,人为加进去的、专门给模型当工作空间的 token。attention sink 是模型自己长出来的现象:某些 token 会莫名其妙吸走大量 attention,即使它们语义上并不重要。activation outlier / residual sink则是在 hidden state 某些维度上出现异常大的值。2026 年的工作甚至发现,massive activations 和 attention sinks 经常同时出现,但它们承担的功能并不完全相同:前者更像一种跨层持续存在的“隐式参数”,后者更像是在局部调节 attention。


然后结合今天 ylecun 四大批判,我突然有个看法,变形金刚的未来,说不定又走回 BERT 那种 encoder 思路了?也就是说在一个session内,变形金刚一直在尝试 自创(encode)一些 符号 名词概念 作为中间产物。

我赶紧拿这个去问了AI。

FAIR那个论文 附录(Fig. 9、Fig. 16)里其实发现,不同的 register 会自发分化:有的更关注图像边缘,有的更关注中心区域,行为很像 Slot Attention(一种专门用来无监督地把图像切分成"物体槽位"的机制)。也就是说,即便没有人告诉它该怎么分工,模型自己在若干个"空白格子"里长出了某种专业化结构

这验证了我前面提到的"自创概念"。AI告诉我,文本模型这边也有对应物:

  • Pause/Filler tokens(Goyal 等,2023):给 LLM 在开口回答前插入几个没有语义的占位 token,让它"多想一步",效果类似给 register 一样的免费计算空间。
  • Gist tokens(Mu 等,2023):训练模型把一整段很长的 prompt/指令压缩进几个可学习的 token里,之后这几个 token 的 KV 缓存就能代替原始长文本被后续计算调用——这已经是字面意义上的"自创浓缩符号"了。
  • Coconut / "Training LLMs to Reason in a Continuous Latent Space"(Meta,2024):这个最接近你说的场景。正常的 chain-of-thought 是模型把每一步推理都解码成人类能读的词;Coconut 让模型把上一步的隐藏状态直接喂回去当下一步的输入,不经过"变成词"这个环节。于是模型在一个 session 内部,用一串连续的、非语言的"思维向量"来做推理,只在必要时才把结果翻译回人话。作者甚至发现这种连续思维能同时编码好几条可能的推理路径(类似广度优先搜索),比被迫每步都写成一个确定的词更灵活。

AI提到:

这些自创的中间产物,默认情况下是连续向量,不是像"猫""狗"这种离散、人类可读的符号。你说的"符号/名词概念"更像是一种比喻——真正发生的是模型在高维连续空间里捏出一团对它自己有用、但对人不透明的表示。这也正是这条路线的双刃剑所在:Coconut 这类"潜空间推理"效率更高、更灵活,但代价是可解释性变差——人没法像读 chain-of-thought 文字那样,看懂模型这一步到底在"想"什么。这也是目前 AI 安全/可解释性圈子挺关心的一个权衡点:模型内部会不会形成一套人类读不懂的"暗语",用来在一次推理里传递信息。

嗨,这不巧合了嘛!人类的深度思索过程,也会创造自己不能语言表达的连续符号啊。比如如何狩猎的,空间位置和行动,态势变化和应对。当然你实在用语言描述也是可行的。但是很多战术理念是“心领神会”,充满比喻,平时需要猎人之间训练配合的“默契”。

解题思路,很多时候“直觉” 或者 “奇思妙想”,要描述出来也是很困难的。其实就是一种内在表达

艺术创作更不用说了,什么情绪,境遇,都是很难精准描述的。但是好的东西就是能引起共鸣。诗歌里的“隐喻”特别多。

AI 一下子被点通了:

"默会知识"(Tacit Knowledge)——这是哲学家 Michael Polanyi 在 1966 年提出的经典概念,他的原话就是"我们知道的比我们能说出来的更多"(We can know more than we can tell)。他举的例子恰好就是猎人、工匠、骑自行车、认脸这类技能:你能熟练地做,甚至能一眼分辨"这个猎手动作对不对",但让你把判断标准写成一条条规则,你写不出来,或者写出来的东西根本不是你实际依赖的那套判断机制。狩猎里那种"态势感知"、"该往哪个方向包抄"的直觉,正是这类默会知识的典型案例。
更巧的是,Coconut 那篇论文的灵感来源之一就是神经科学的一个发现:人在做复杂推理(比如下棋、空间规划)时,大脑里负责语言的区域往往并不活跃——语言网络主要是用来表达和交流的,未必是"思考本身"发生的地方(这是 MIT Fedorenko 实验室这些年一直在推的一条研究线:"语言不是用来思考的,是用来交流思考结果的")。这跟你说的"很多战术理念心领神会,语言只是事后的翻译"是同一件事的两种表述。
你提的"猎人之间的默契"还有个更具体的 AI 对应物:多智能体强化学习里有个叫"涌现通信"(Emergent Communication)的研究方向——让多个 AI agent 一起协作完成任务,不给它们任何预设语言,结果它们会自己发展出一套只有彼此能懂的"暗号"来协调行动。早年 Facebook 那个著名的谈判机器人实验,两个 bot 谈着谈着就开始用人类读不懂但内部逻辑自洽的"简写"互相沟通,团队一度要专门约束它们"必须说人话"。这几乎是你说的"猎人之间训练出的默契"在机器上的直接翻版——协作压力会自发催生出非人类语言的、高效但不透明的符号系统。
诗歌和隐喻这块也有对应理论:语言学家 Lakoff 和 Johnson 的"概念隐喻理论"(Metaphors We Live By)认为,人类很多抽象/情绪概念本来就不是靠精确定义存在的,而是靠从具体身体经验"打包映射"过去的——比如"人生是一场旅程",我们理解"人生"靠的是身体对"走路、方向、终点"的经验,隐喻不是修辞装饰,是概念本身的存在方式。这在结构上跟词向量/embedding 空间的工作原理惊人地像:一个词的"意义"不是查字典查出来的定义,而是它在高维空间里相对于其他词的位置和距离——这正是为什么好的隐喻能"引起共鸣":它精确地把一个词挪到了空间里一个新位置,让人瞬间"感觉到"了那种关联,即使没法用命题逻辑讲清楚为什么。
顺着这个思路还有个挺震撼的新假说,叫"柏拉图表征假说"(Platonic Representation Hypothesis,MIT,2024):作者发现,不同架构、不同任务、甚至不同模态(图像模型 vs. 语言模型)训练出来的神经网络,随着规模变大,它们内部对"世界"的表征方式竟然在互相趋同——仿佛都在逼近同一个底层的、柏拉图意义上的"真实结构"。如果这个假说成立,也许能解释你说的"好的东西就是能引起共鸣":不同人(甚至不同物种、不同智能体)的大脑/网络虽然各自训练出一套私有的内部表示,但如果都在逼近同一个客观世界的统计结构,那么一首好诗触发的那个"说不清但都懂"的感受,可能真的对应着大家内部表示空间里一个相近的位置。

感觉和 Yann LeCun 大佬的思路太厉害了。它这么多年批判自回归 LLM 缺四样东西——对物理世界的理解、持久记忆、推理、规划——他认为这不是"训练得不够大"的问题,太犀利了。这种架构性的死胡同,就是导致他 2025 年离开 Meta 搞JEPA的原因?不靠逐词生成、而是在抽象表征空间里维护和更新一个可持续演化的"世界模型"的系统。

说得再直白一点,现有的LLM智能,就是个 classifier

我觉得,真正的智能需要决定,什么时候是选择 non of the tokens above

一个自回归模型每一步在做的事,字面意义上就是一次分类——对词表里几万到几十万个 token 做 softmax,选一个概率最高(或采样出)的。

虽然可以反驳,把很多次这样的分类串起来,输出空间是指数级增长的——理论上可以生成训练数据里从未出现过的句子、证明、诗句。这正是创造力研究里 Margaret Boden(刚去世,这个领域的奠基人物)说的"组合式创造力"(combinatorial creativity)

但这样的智能,关键词是 —— 串。的确frontier AI 能作出很多令人惊叹的成果。甚至普通人遥不可及,但是代价是——串行。

也就是传说中的炼丹,抽卡。某些条件成熟了,reasoning和temperature恰好合适,agent 和 harness 恰好走对了,就能拿到低垂果实。

但是人类发明概念是源源不断的,meme的传播嘛。这可不是“串”行,而是霰弹枪 “啪” (one-shot 🤣) 一下,总多概念,总有一个能火力覆盖到一个没太见过的新鲜事物上,并且低成本快速掌握。而现在的AI 就得话不少 context 去“串起来” 才行。

AI说

你这个"霰弹枪 vs 串行"的对比,精确地踩中了一个已经有名字、有数学模型的现象——而且两边(人类创造力研究、AI 可靠性研究)居然是分头独立发现的。
先说人类这边的"霰弹枪"理论,比你想的还要精确。
心理学家 Donald Campbell 1960 年提出"盲变异—选择性保留"(Blind Variation and Selective Retention, BVSR):创造过程本质上是达尔文式的——先大量地、相对盲目地生成变异(不是精心设计出一个必中的方案),再靠后续的选择机制留下有用的那个。Dean Simonton 在此基础上研究了两千多位历史上的科学家、艺术家,1997 年提出"等概率法则"(Equal-Odds Rule):一个创作者产出"神作"的比例,跟他是不是天才其实关系不大——天才和普通人每一件作品成为杰作的概率大致相同,天才只是产量大得多。Erdős 一辈子发了 1500 多篇论文,绝大多数无人问津、早就被遗忘,但正是这个巨大的"总发射量",让他撞上了拉姆齐理论这种真正改变领域的成果。这跟你说的"总有一个能火力覆盖到",几乎是同一句话的学术版本。

再往上一层,meme 的传播压根不是单个大脑内部的并行——那是数十亿个大脑同时在生成变异,靠文化选择去放大命中的那一个(这就是道金斯说的模因论/文化演化)。这个并行规模比"霰弹枪"大出去不知道多少个数量级,单个大脑内部再怎么"啪"一下,也比不过整个文明每天在同时试错。
AI 这边,其实field自己也早就意识到"纯串行=脆弱"这个问题,而且给出的解法方向恰恰就是"变成霰弹枪"。
单条链最致命的地方是错误复合:如果每一步正确率是 95%,串 20 步,整体成功率掉到 36%(0.95^20)——这正是"炼丹抽卡"体感的数学根源,probability 是乘出来的,不是加出来的。为了对抗这个,最近几年推理/Agent 领域的主流做法,说白了都是"别赌一条链,改成一次放很多枪再挑":Self-Consistency(Wang 等,2022)——同一个问题采样几十条推理链,投票选最多数的答案;Tree of Thoughts(Yao 等,2023)——同时展开多条思路分支,中途评估、剪枝、回溯;再往后的各种"test-time compute scaling"方法,本质都是把串行的单链,换成并行采样+事后选择——这跟你说的"霰弹枪打一片,总有一个覆盖到"是完全一样的逻辑,只是子弹换成了"多条并行生成的候选答案"。

其实想到这里,我感觉和上一篇《聪明 Agent 四套路 和 人肉学习》 又莫名其妙关联起来了。

或许如果 register token 这一套能沉淀,跨session共享,实际上就能变成新的 token,变成共同的符号和记忆。人类的语言和文字,知识传承就是这么来的。

那么NN就不再是 自回归 ?说不定就 AGI 了?

Posted

stdin

聪明 Agent 四套路 和 人肉学习

nVIDIA 联合 NTU,MIT 联合研究了一项关于 Agent harness 自我提升的研究,项目叫 SoL-Pi, Scaling Auto-Research Loops for Efficient Agent Harnesses

让 Research Agent 自动发现 Harness 里的 Token 浪费,再通过大规模实验筛掉无效改法。把结论再放进大量环境中反复测试。整个探索一共 152 个候选方向,分成 context、progress、tools、delegation、prompt/policy、improvement/evaluation 六类,然后把这些想法扔进大量相互隔离的 research lineage 里,让 agent 自己观察 trajectory、提出机制、实现、review、跑实验、继续修改。最终用了 535 个可执行搜索环境,其中 495 个来自 GitHub issue-PR pair,40 个是 verifier-driven synthetic tasks;论文说总共跑了 3000+ 次实验和 60000+ 次 agent-environment interaction

最后真正留下来的精华,只有 4 大机制:

  • Action Fusion:把“修改文件”和紧接着的测试/运行命令融合进一次 tool request。典型情况从 3 次 API call 变成 2 次。
  • Online Context Compact:不是机械地定期压缩 context,而是在 plan step 完成时计算“继续保留这些历史”和“现在做一次 compact”哪个更划算,还把 prompt-cache rewrite cost 算进去。
  • ObservationPack:一个很大的 tool output 前两次完整发送,从第三次开始改成 stable handle + 约 1KB 的 head/tail excerpt,需要时再通过 handle 取原始内容。
  • Evidence-Preserving Reducer:对 build/test log 使用低成本模型提炼证据,但原始日志始终保留,并通过 deterministic verifier 检查 schema、hash、exit status、exact quote 等;验证失败就回退原始结果。

我因为脑子比较轴,总喜欢扩展关联到别的领域,于是想把这四大方法,也套在人肉学习上:

  1. 合并动作:能连续做的事情不要反复切换。串行出现的解题步骤打包,批次进行,避免低效重复。探索之后立刻验证,而不是一个岔路跑远了再回头
  2. 清理上下文:每当学习或解题,有进展都反思当下思路,考虑下如何为接下来简化,不能当看小说一样囫囵吞枣背章节和习题答案
  3. 隔离支线:子问题展开放到一边不能影响主干思路
  4. 保留证据:子过程产生的中间结论,关键推理必须留痕

AI给我补充了很多reference

合并动作

Schoenfeld 给这个现象起名叫 "wild goose chase"(追野鹅):他发现约60%的大学生解题时会选定一个方向就一路埋头走到底,直到时间耗尽都不回头反思;而当他训练学生每隔一段时间就问自己"我现在具体在做什么?为什么这么做?这对我有什么帮助?"之后,这种不做监控的比例从60%降到了20%,解题成功率也相应提高。更有意思的是,他后来观察一位数学教授解题——这位教授手头掌握的事实和方法其实比很多学生还少,解题过程中也生成了无数条可能的死路,但他没有被这些死路带偏,靠的就是频繁地、细粒度地检验"这条路值不值得走下去"

清理上下文

Catrambone关于子目标形成的研究描述了一个具体过程——学习者先把一串步骤按结构分组,然后自我解释"为什么这样分组",最后把这个分组归纳成一个简洁的标签(子目标标签)。这三步合起来,正是"做完一段就反思、并把它压缩成一个可以往下带的简化单元",跟你说的第二条几乎逐字对应。而它的反面,就是你说的"看小说一样囫囵吞枣"——只是线性地把内容或答案过一遍,不做任何分组、归纳、压缩,信息就只是流过去了,没有被结构化,也就没法被高效复用。

隔离支线

人类研究就是子目标标签(subgoal labeling)本身——Catrambone给出的经典例子是解方程"隔离变量→化简变量":学生只需要在主线上记住这两个子目标,每个子目标内部具体怎么算是被封装起来的细节,不需要在思考整体解题结构时一直占用工作记忆。研究还发现,用子目标标签训练出来的学生,遇到步骤不同但子目标结构相同的新题时,迁移能力明显更强;而没有这种封装、只是记住老师板书完整步骤的学生,换一道结构相同但细节不同的题就容易失败——这正是"细节污染主干"的后果。这也呼应了国际象棋大师的组块(chunking)研究:高手把一串走法编码成一个组块整体记忆和调用,而不是逐步保持在工作记忆里,从而腾出容量去思考更高层的布局。

保留证据

Polya经典的解题四阶段(理解问题→拟定计划→执行计划→回顾)里,"回顾"这一步要求你把推理过程记录下来复查,而不是解完就扔;如果第2、3条里的"压缩""封装"做得太狠、把关键中间结论也一起丢了,你会既没法自查错误,也没法把这段经验迁移到下一个类似问题上。这也是为什么"元认知校准"(上一轮提到的)离不开留痕——你没法准确判断"我这一步到底对不对",如果连这一步的关键推理都没有记录下来可供回看。


学习方法论的批判

很多学习者,特别是中小学学生,对这些“显学”反而不知道。父母和老师也不会教。

虽然大部人聪明人能领悟到这一点,但是很多平庸的学生学到这四点教训已经太晚了。。。

因为传统学校的考试 测验的 reward 只看结果,不太关心你过程怎么来的。小学知识比较简单,留下来的坏毛病带到初中之后就麻烦了。

我家的娃就深受其害。在小学阶段,“只要答案对,就算成功”这个策略经常真的有效。题目短、知识点少、搜索空间小,靠记忆、模仿、反复练习也能得到不错的结果。于是很多低效习惯不会立刻付出代价。死记硬背就能拿个勉强过得去的分数。

到了初中以后,情况开始变化。题目的状态空间变大了,步骤变长了,知识之间开始相互依赖。此时如果学生一直没有形成,那么就会无意中陷入困惑:

  • “我现在在解决哪一个子问题?”
  • “哪些信息已经没用了?”
  • “哪些东西应该暂时放到一边?”
  • “我的这个结论到底依据是什么?”

这些能力,学习成本会突然上升。

客观的因素,大班制教学,老师也管不过来。只有精英教育 私教 遇到良师,或者就孩子被鼓励“多思考”,才可能戳破这一系列技巧。

小学时,“直接冲答案”很快,成绩又不错 → 学生得到正反馈。小时候看起来只是“不够仔细”的问题,几年后会表现成“学习能力不行”。

上面列举这四个技巧,我其实个人很早就懂得,无意中自己琢磨的,回顾了下,可能被小时候鼓励“更聪明的办法”太多次了,这里要感谢当时的父母,自己本身希望去掌握那个最快 最酷的解题技巧,所以算 “过程优化” 到极致作为一个附加 reward了。一个问题有多种解法,每种解法优劣都品鉴了一番,知道哪些坑哪些好。

  • “这道题还有没有更短的路?”
  • “这个方法为什么比另一个好?”
  • “刚才卡住的地方以后怎么避免?”
  • “这个技巧能不能迁移到另一类题?”
  • “有没有一种方法能让我以后少想五步?”

这就很接近论文里的 harness optimization 了。解题的意义不仅在于得到正确答案本身,还有在观察“解题算法本身”。

初中、高中我甚至有几起名场面,公开质疑老师出题出的有问题(50%翻车)🤣

当然,这个习惯也有其害处,我也吃过亏。三角函数大家都背公式,我就自己发明了一套自以为更好的理解框架。。。结果出题人更偏好教材公式能快速结果的,我那套不仅更复杂,容易翻车,而且还有“翻译”成本。。。囧。。。

考试的 reward 实际上更接近:在限定时间内,用低风险路径得到标准答案。

不过好处还是很明显,就是大多数问题理解得比普通人深入得多。特别是解析几何这种虽然被批判“不入流”但是几乎秒杀了教材党。。公式全靠自己推的,哪里怎么变换怎么用一清二楚。甚至圆锥体歪着斜着切这种问题都考虑到了(传统教材只有水平,垂直和顺着斜切)

这个亏一直吃到出来工作,长了很多教训。我对待任何事都是双层架构。一个是兼容傻逼的世俗层,一个是真理层。。。囧哈哈哈

其实真理层反复磨练到最后反而越来越简单。世俗层基本就是补丁接补丁越来越复杂。普通人搞不明白要崩溃的很多事情一眼看穿。。


后记

毕业之后,发现学生时代最值得批判的,就是做题家思维。成年人的世界是没有标准答案的。甚至你需要去设置议题。当然这又是另外一个层次的东西了。

然后回头看,学生最幸福,也是最痛苦的一点,就是面临的所有问题,无论多困难,是必然有解的;而且往往正确解是唯一的。

成年人面对的事情,基本都有多个解。你 reward 去优化,无非是利弊的权衡;

最可怕的遭遇,无解,死胡同,到头来一场空。你去尝试解决一个不存在的问题,你又能学习到什么教训呢?时也,运也,命也。

Posted

stdin

Doherty 多尔蒂阈值

前几天鼠标坏了。随手换了个新的,双飞燕。没想到这垃圾鼠标居然在 macbook 下有间歇性卡顿 失联问题,表现是光标不动

怀疑是 系统问题,驱动问题,鼠标本身,商家卖假货问题。最后想明白了,这几十元破玩意就tmd不值得去折腾心态。直接扔。

回想了一下,这玩意其实对工作影响比意料中的大得多。平时操作触摸板 键盘都是下意识的,根本不会去多想;

光标在显示器上一旦当场卡住,你大脑瞬间被吸引过去,从软件到硬件到购买决策,到损失厌恶,全部过一遍,恶心死了

生活中我们习惯了太多「常见」的事,是工业和质量体系支撑着这一切的「正常」。我想到骑车可以走神,开车可以走神,走路也可以走神,有的时候经过一个路口,你都完全不知道道路上发生了什么,怎么过的红绿灯,有没有危险,浑然不知;自己反正在操心着别的事儿,身体就不由自主的「自动驾驶」

电脑上 打字 也是一样。一个输入法如果能连续不断正确打出你想要的东西,那种表达的流畅是让人很舒服的。因为新生代很多人都不会 盲打 了,我想很多人可能很难体会到这一点了吧

我想到,其实写代码也是一种同样的感受。无论是以前古法手搓,还是现在 AI 写代码,都有那么一些任务不想去碰,即便动工也完成得不好。

仔细观察了下这类任务都有几个特点:缺的东西和中间步骤比较多,可选的方向变化也比较多。比如你要给一个推送任务加一个可选参数,但是要动底层基础数据结构,这个事儿也不急,这种改造除非有业务推着,你多半不想去重构推送的核心。

想想都蛋痛,不知道会出什么夭蛾子,也不知道最后做出来被改成稀奇古怪的结构了。

也就是说你大脑需要在很多细节和路径上中断,hiccup,做决策,可能还有后悔,最后发现费这么多精力做出来的,纯纯浪费。

这种挫败感是极强的,人性天生都会懒得去碰,惰性就是这么来的,拖延症也是这么来的

然后这事儿其实又和 tiktok 所谓 swipe up 上瘾机制联系起来了。过去视频网站的观看决策

点击缩略图 → 看完 → 回到列表 → 挑选 → 点击另一个 缩略图。这么多的 hiccup 很容易造成注意力流失

还有 tinder。

但是反过来说,丝滑是投入程度、连续注意力的必要条件。看来身边很多天然形成的“障碍” 是大部分人没有意识到,也没能识别的。

汽车工业,自行车早年可能操作起来也没那么丝滑,还有道路条件等。社会进步为了避免交通事故无意中促成了这一条件。

突然回忆起《Halt and Catch Fire》这部剧里看到的,Doherty Threshold,多尔蒂阈值。

以前的电脑,mainframe,小型机,本质都是个 batch processing系统,流程是 edit-compile-run-debug。我们这代人,上个世纪学习的信息技术课,「上机」是特殊的日子。先是教材讲很多理论知识,你准备好了,换上鞋套,去无尘的“微机室”肘击键盘,得到真实返回。然后你在大脑、纸笔上把问题上琢磨好了,再去敲打一轮电脑。很有仪式感的一套打法

这部剧里讲,他们制造的PC,延迟低于 400ms,用户就能在敲一条命令后立刻得到返回,思绪不会断,执行任务的效率会极大的提高

今天回味这一点,特别像 AI agent的故事。一旦你体验过 ultrafast 那种推理模型,就很难回去了。你可以 实时决策

或许我们讨论心流的时候,特别注意任务本身的难度,而忽略了周边配套造成的阻碍。

今天的感悟,很简单,如果做app或者服务,要把流程的丝滑程度做到极致,尽可能避免一切可能的 hiccup

Posted

stderr

海南之行后记

趁还记得,记录一些

起初是发现 嫦娥七号 在文昌发射,于是兴冲冲带着孩子去观看 CZ-5 发射

围绕着这个火箭发射基地做了大量调研;了解到很多知识:

  1. 发射基地分两块,一个是国发,一个是商发;CZ-5 在国发
  2. 国发最佳观赏地是 文昌县 门楼镇,淇水湾
  3. 发射前会封路,开车进不去

二话不说,定机票;无意中刷到 云闪付 有 -200 优惠,于是周五定闹钟抢票,抢到手。不过发现使用优惠,折腾了大半天发现

  1. 这优惠只能买去海南的单程,不能往返;
  2. 订单必须只有一人,而且必须是自己,付款人和乘机人必须是同一个人。
  3. 孩子未成年无法单独购票
  4. 未成年人在 -200 优惠之后附加购买,+100

折腾半天,感觉还比如直接一起打包买往返划算。囧

然后就是轰轰烈烈的定住处,观礼台。

因为时间比较接近了,发现价格贵得起飞,甚至一度想到骑电瓶车去路边看,考虑发射时间早上8点太早,很可能天气预报有雨于是作罢

选了一圈定 淇水湾五号 小区,价格 1500 。其实在门楼镇上有1200甚至900的,但是住宿条件更差。

然后,延迟一天,房东也爽快的答应延期一天入住;

但是返程机票已定,时间不够,于是折腾半天去改机票,每个人 -140 。啊痛死!

接下来,让人更崩溃的事发生了,发射tmd取消了!

还好这个房东比较爽快,直接退款。网上很多撕逼的。

虽然我也一直关注今年广西边上这个土台风,没想到居然取消了啊。虽然有消息传言说天气因素不大,更关键这次南极科考任务有多国科学载荷,必须万无一失,碰巧遇到一些仪器上无法立刻解决的bug,所以。。

于是就不得不改成海南旅行。在登机前一刻还在查怎么玩;想了下打车每天几十,加起来不如租个车。

去神州挑了个 AION S。第一次租车,先随便试试。落地 HAK 发现居然停车场在11楼,电梯还不好找。

这车比想象中好开,花了4个小时直奔三亚;中途在中石油充电,吃泡面。还没吃完,电都快充满了。还好车少桩多,不急着挪车;

到了三亚,让娃去游泳。酒店随便选的,在凤凰岛,外里面非常高大上,实际上里面非常破,东西都发霉了,考虑到1字头价格,不好评也懒得差评。

游泳池有三个,忘记带泳帽,民宿老板爽快送了2个库存要求好评,但是后来应娃的要求去买了泳镜,被宰了 68 ,气愤。

海景房视野是舒服,看网上说海水高盐对家具和装潢都是毁灭性的。

第二天,上午懒觉+游泳,下午逛景点,先去看南海观音,门票和观光车花费0,因为看攻略导航到村里直接去沙滩远观即可。孩子很多年没接触大海了兴奋得玩了好几个小时

椰子10元一个。感觉贵了,上网一查果然。今年海南的椰子据说收成好,卖不起价

天涯海角 景区,坑得一批。虽然不要门票,但是要手机号 身份证。结果刷二维码的看都不看。感觉可以乱填一个进去。只去了正大门,天涯海角石在好几公里远,观光车划不来,放弃。后续在网上查到可以开车到西门进,很近

第三天不知道玩点啥,随便搜到一个 亚特兰蒂斯水世界 。官方酒店2k+我是舍不得的,找了个直线距离最近的附近的民宿。结果大大出乎意料,它丫的开了个小门,去 亚特兰蒂斯检票口 只需要步行 5分钟,比外边开车停车场或公交车都近的多。价格300一晚,2个房间2个大床,海景绝美。就停车有点贵30封顶24h,不过官方停车场40封顶24h。感觉又赚了10元

Atlantis Sanya

亚特兰蒂斯水世界,人比较多,但是其实好玩的就那么几样。大喇叭 小喇叭 放手一搏 鲨鱼竞速 小船漂流都玩了,当天其实在下雨,反正会湿身,又不晒,哈哈。

第四天不知道玩点啥,找了个往机场方向的酒店,搜了下 三亚,陵水,万宁,琼海,文昌,这一路全都是海滩和酒店,随便找了个,打算去 南湾猴岛 看看。

南湾猴岛观光索道100+,我肯定舍不得,查到攻略,如果喂猴子可以导航 去 南湾村民委员会,果然一路都是。比峨眉山的猴子好些,没那么凶狠。一路开到海边,顺便看了眼 呆呆岛

Atlantis Sanya

下午就去机场了,无聊就沿着海岸的观光公路一直开,开到博鳌 红石滩,想赶海,发现没工具,花10买了个铲子,毛都没挖到。最后挖到个很小的贝壳,以及极小的寄居蟹。也算有收获吧

一路上发现海南很多散养的黄牛 水牛在路边,草地里悠闲地吃草。

晚上充电,还车,BAR起飞回家;

总体感受,做计划真累,真想啥都不做躺几天。不知道这辈子能肉眼看上火箭发射不。

Posted

stderr

The httpx 1.0 situation

In case you weren't aware, there's quite a debate over httpx v1.0 release, especially maintainer of both openai-python and anthropic-sdk-python showed up and decided to switch to httpx2, a fork of httpx that maintains v0.28 styles

httpx is a Python library that can handle both sync/async http, replacing requests (sync-only) and aiohttp (async-opnly).

You can view original discussion starting here https://github.com/encode/httpx/discussions/3344

The maintainer closed discussion and issues

1. verify= and cert= → explicit ssl.SSLContext

for version < v1.0

# Boolean on/off — still fine, not deprecated
httpx.Client(verify=True)   # default: verify using certifi's CA bundle
httpx.Client(verify=False)  # danger: accept any certificate

# String path — NOW DEPRECATED, raises a warning
httpx.Client(verify="/path/to/custom-ca-bundle.pem")

# cert= for a client certificate (mutual TLS) — NOW DEPRECATED
httpx.Client(cert=("/path/to/client.pem", "/path/to/client.key"))

v1.0

import ssl, certifi, httpx

# Equivalent to default verify=True
ctx = ssl.create_default_context(cafile=certifi.where())
httpx.Client(verify=ctx)

# Custom CA bundle
ctx = ssl.create_default_context(cafile="/path/to/custom-ca-bundle.pem")
httpx.Client(verify=ctx)

# Client certificate (mutual TLS) is now loaded onto the context itself
ctx = ssl.create_default_context(cafile=certifi.where())
ctx.load_cert_chain("/path/to/client.pem", "/path/to/client.key")
httpx.Client(verify=ctx)

2. proxies=proxy= / mounts=

Before:

# Old dict-based per-scheme mapping
httpx.Client(proxies={"http://": "http://localhost:8030", "https://": "http://localhost:8031"})

After:

# Single proxy for everything
httpx.Client(proxy="http://localhost:8030")

# Per-scheme routing via mounts (a dict of URL-pattern -> Transport)
httpx.Client(mounts={
    "http://": httpx.HTTPTransport(proxy="http://localhost:8030"),
    "https://": httpx.HTTPTransport(proxy="http://localhost:8031"),
})

3. app= shortcut → explicit transport=

What app= was for: letting you point a Client directly at a WSGI or ASGI Python callable instead of a URL, so you could test your web app without spinning up a real server.

Before:

httpx.Client(app=my_asgi_app, base_url="http://testserver")

After:

httpx.Client(transport=httpx.ASGITransport(app=my_asgi_app), base_url="http://testserver")
# or for WSGI:
httpx.Client(transport=httpx.WSGITransport(app=my_wsgi_app), base_url="http://testserver")

4. allow_redirects=follow_redirects=, default flipped to False

What changed: in the 0.20 release, redirects stopped being followed automatically by default.

Before (pre-0.20 behavior, and the old kwarg name):

httpx.get("http://api.github.com/")  
# silently redirects http -> https, meaning every request was actually sent twice

After:

httpx.get("http://api.github.com/", follow_redirects=True)  # opt-in explicitly
# or
httpx.Client(follow_redirects=True)  # opt-in for the whole client

The maintainers gave a concrete example — a client configured for http://api.github.com/ was silently sending every single request twice (once to get redirected, once to the real HTTPS URL), wasting a round-trip nobody asked for. They decided implicit redirect-following causes more surprise bugs (accidentally hitting an endpoint twice, e.g. a payment POST) than it saves typing, so it became opt-in. This was explicitly flagged as "no universally right answer, just a trade-off" in their own release notes.

5. Compact JSON request bodies

Before: json={"a": 1, "b": 2} serialized with the stdlib default spacing → {"a": 1, "b": 2} (space after : and ,).

After: same call now serializes to {"a":1,"b":2} (no extra spaces).

This is a good change!

6. Query-string percent-encoding rules changed

Before: spaces in query params encoded as +, and / inside a query value encoded as %2F (this is the old application/x-www-form-urlencoded-style behavior, same as requests).

After: spaces encoded as %20, and / left unescaped in the query portion (matches how Chrome/Safari/Firefox actually build URLs, per the WHATWG spec rather than the older RFC 3986 reading).

# requests-style / old httpx:
"?q=hello+world&path=a%2Fb"
# new httpx:
"?q=hello%20world&path=a/b"

This is a case where IETF RFC3986 vs. WHATWG's disagree, and real browsers follow WHATWG.

7. Automatic .netrc handling → explicit httpx.NetRCAuth()

Before: if you had a ~/.netrc file, httpx would silently pick up and use those credentials on any matching request.

After:

httpx.get(url, auth=httpx.NetRCAuth())

Silently reading a credentials file from disk shouldn't be done by default.

8. response.iter_lines() behavior

Before: yielded lines including trailing newline characters ("line one\n").

After: matches Python's own str.splitlines() / file-iteration convention — newlines stripped ("line one"), and a related performance bug fixed.

Matching stdlib conventions (for line in file: style) is less surprising than inventing your own line-splitting semantics.

9. QueryParams became immutable

Before (implied by the old API): client.params.update(...) mutated in place.

After:

client.params = client.params.merge({"key": "value"})
# or the more granular:
params.set("key", "value")
params.add("key", "value")     # allow duplicate keys
params.remove("key")

Mutable shared state (like a dict) attached to a client is a classic source of bugs

10. Sync and async split into two separate installable packages

Background: today, one package gives you both a blocking (Client) and non-blocking (AsyncClient) interface, because under the hood both share the same code via a code-generation trick (unasync) that turns the async source into the sync version automatically at build time.

Before:

import httpx
r = httpx.get("https://example.org")          # sync

async def main():
    async with httpx.AsyncClient() as cli:
        r = await cli.get("https://example.org")   # async

After (previewed):

# pip install httpx
import httpx
r = httpx.get("https://example.org")           # sync only

# pip install ahttpx   <-- a *different* package
import ahttpx
r = await ahttpx.get("https://example.org")    # async only

The maintainers reasoned that if you only ever use the sync client, you're still forced to install anyio and sniffio (async-support dependencies) that you never touch. Splitting the packages lets sync-only users have a genuinely smaller dependency tree, and removes the slightly awkward Client/AsyncClient naming duplication in favor of one name per package.

The controversy (worth knowing about): this is the most contested item in the whole redesign. Simon Willison and several SDK maintainers (including Anthropic's and OpenAI's own Python SDK maintainers, both of which depend on httpx<1) argued in the public discussion that Python's inability to install two versions of the same package side-by-side means a hard breaking split could fragment the ecosystem for a long transition window, similar to what happened with Pydantic 1→2. Proposed alternatives floated in that thread include shipping the new design under a different name entirely (httpx2) or keeping both old and new APIs inside one package under a versioned namespace (httpx.v1). None of this is resolved as of the latest visible discussion activity.

My take: The whole reason why ppl chose httpx over aiohttp is because it can handle both.

11. json=, data=, files= shortcuts replaced by typed content= objects

Background: today httpx (like requests before it) offers three different keyword arguments depending on what kind of body you're sending, and picks the right Content-Type header for you based on which one you used.

Before:

client.post(url, json={"key": "value"})                       # application/json
client.post(url, data={"key": "value"})                       # form-urlencoded
client.post(url, files={"upload": open("report.pdf", "rb")})  # multipart/form-data
client.post(url, data={"name": "a"}, files={"upload": f})      # mixed form + file

After (previewed):

client.post(url, content=httpx.JSON({"key": "value"}))
client.post(url, content=httpx.Form({"key": "value"}))
client.post(url, content=httpx.Files({"upload": httpx.File("report.pdf")}))
client.post(url, content=httpx.MultiPart(
    form={"name": "a"},
    files={"upload": httpx.File("report.pdf")},
))

Java boi's fantacy to please the type checker.

12. Stricter typing on flexible parameters (discussed, not fully decided)

Background: currently timeout= accepts a bare float, a tuple of floats, or a Timeout instance; proxy= accepts a string, a URL, or a Proxy instance; similar flexibility exists for auth= and headers=.

The design-call notes floated constraining these to fewer accepted shapes for clarity, but a maintainer pushed back in the same conversation that "it's not clear tightening the API types is a better user experience and could cause churn" — so this one is explicitly unresolved, not a committed change.


# `proxy=` → `ProxyTypes`
httpx.Client(proxy="http://localhost:8030")                 # plain string
httpx.Client(proxy=httpx.URL("http://localhost:8030"))       # URL object
httpx.Client(proxy=httpx.Proxy("http://localhost:8030"))     # Proxy object, needed if you
                                                              # also want proxy auth/headers
# `timeout=` → `TimeoutTypes`
httpx.Client(timeout=10.0)                 # single float applied to connect/read/write/pool
httpx.Client(timeout=None)                 # disable timeouts entirely
httpx.Client(timeout=httpx.Timeout(connect=5.0, read=30.0, write=10.0, pool=5.0))  # explicit object

# `auth=` → `AuthTypes`
httpx.get(url, auth=("username", "password"))   # 2-tuple -> Basic auth
httpx.get(url, auth=httpx.DigestAuth("username", "password"))  # explicit Auth subclass
httpx.get(url, auth=my_callable)                 # any callable(request) -> request, via FunctionAuth

# `headers=` → `HeaderTypes`
httpx.get(url, headers={"User-Agent": "my-app"})              # plain dict
httpx.get(url, headers=[("User-Agent", "my-app")])             # list of tuples (allows duplicate keys)
httpx.get(url, headers=httpx.Headers({"User-Agent": "my-app"}))  # explicit Headers object

tl;dr httpx v1.0 is trying to giving type system a handjob like Java

Posted

stdout

Mimocode/Opencode 的 Responses API 支持

当前的 MiMoCodev0.1.13里,Responses API 有两套入口

  1. Vercel SDK 的 responses() 方法 —— 触发条件是 provider id 为 openai / github-copilot / azure
  2. provider 为 gitlab 且每个模型加上 "useResponsesApi": true

就tmd很神奇。。一眼 vibe coding。我感觉世界上理解 Responses API 意义的人可能比例很少。。。

gitlab

{
  "$schema": "https://mimo.xiaomi.com/mimocode/config.json",
  "provider": {
    "gitlab": {
      "name": "GitLab",
      "models": {
        "gpt-5-codex": { "useResponsesApi": true }
      }
    }
  }
}

可以通过 GITLAB_INSTANCE_URL GITLAB_TOKEN 环境变量去变通,但是 token 来自 directAccessClient.getDirectAccessToken() 走的是 GitLab 的 token-exchange 协议(auth.openai.com 那套 OAuth exchange)

所以即使能把 instanceUrl 骗过去,token 交换这一步也会因为不是真 GitLab token 而失败——没有可配置的开源端点/key 入口,也没有绕过 token-exchange 的开关。

复用 openai 这个 provider id

支持自定义 options.baseURL + apiKey

{
  "$schema": "https://mimo.xiaomi.com/mimocode/config.json",
  "provider": {
    "openai": {
      "name": "My Responses Backend",
      "npm": "@ai-sdk/openai",
      "models": { "my-model": { "name": "my-model" } },
      "options": {
        "baseURL": "https://你的后端域名/v1",
        "apiKey": "你的key"
      }
    }
  }
}

provider id 必须是 openai/github-copilot/azure 之一

两个npm包的区别

@ai-sdk/openai-compatible@ai-sdk/openai 都是来自 Vercel

@ai-sdk/openai-compatible @ai-sdk/openai
是什么 MiMoCode 自带/打包的通用 OpenAI 兼容适配器 Vercel 官方的 OpenAI 原生 provider
适用对象 任何"像 OpenAI"的第三方端点(OpenRouter、DeepSeek、本地 vLLM、各种网关) OpenAI 自家 API,或任何支持 OpenAI Responses API 的后端(靠 baseURL 指过去)
在 MiMoCode 里的默认角色 自定义 provider 的默认 npm(你不写 npm 时,up() 函数默认填它) 绑定内置 openai provider id
协议能力 只有 Chat Completions(languageModel() Chat Completions + Responses API(responses() 方法)
路由差异 通用路径 W() 里固定走 M.languageModel(id) → chat 内置 openai loader 的 getModelreturn Z.responses(Q) → 默认就走 Responses
  • npm: "@ai-sdk/openai-compatible" + 任意自定义 id** → 永远走 Chat Completions,拿不到 Responses API。这是最常用但偏偏不支持 responses 的那个。
  • npm: "@ai-sdk/openai" 本身有 responses() 方法,但只有当 provider id 叫 openaigithub-copilotazure,它们 loader 里也调 responses())时,MiMoCode 才会用 Z.responses(Q)。自定义 id + @ai-sdk/openai 会掉回通用 languageModel() = chat。

实测模型支持

Opencode Go

model completions responses
deepseek-v4-flash ✅ 200 ✅ 200
deepseek-v4-flash-vision-exp ✅ 200 ✅ 200
deepseek-v4-pro ✅ 200 ✅ 200
glm-5 ✅ 200 ❌ 500
glm-5.1 ✅ 200 ❌ 500
glm-5.2 ✅ 200 ❌ 500
glm-5.3 ✅ 200 ❌ 500
glm-5.3-flash ✅ 200 ❌ 500
gpt-5.6-luna ❌ 500 ✅ 200
grok-4.5 ❌ 503 ✅ 200
grok-4.6 ❌ 401 ✅ 200
hy3 ✅ 200 ❌ 500
hy3-preview ❌ 400 ❌ 500
hy4-preview ✅ 200 ❌ 500
kimi-k2.5 ✅ 200 ❌ 500
kimi-k2.6 ✅ 200 ❌ 500
kimi-k2.7-code ✅ 200 ❌ 500
kimi-k3 ✅ 200 ❌ 401
longcat-2.0 ✅ 200 ❌ 500
mimo-v2-omni ❌ 400 ❌ 500
mimo-v2-pro ❌ 400 ❌ 500
mimo-v2.5 ✅ 200 ❌ 500
mimo-v2.5-pro ✅ 200 ❌ 500
minimax-m2.5 ✅ 200 ❌ 401
minimax-m2.7 ❌ 500 ❌ 500
minimax-m3 ✅ 200 ❌ 401
muse-spark-1.2-contributor ❌ 500 ✅ 200
qwen3.5-plus ✅ 200 ❌ 401
qwen3.6-plus ✅ 200 ❌ 401
qwen3.7-max ✅ 200 ❌ 401
qwen3.7-plus ✅ 200 ❌ 401
qwen3.8-flash ✅ 200 ❌ 401
qwen3.8-max ✅ 200 ❌ 401

Opencode Zen

model completions responses
big-pickle ⚠️ 429 ❌ 500
hy3-free ✅ 200 ❌ 500
laguna-s-2.1-free ✅ 200 ❌ 500
ling-3.0-flash-fin-free ✅ 200 ❌ 500
mimo-v2.5-free ⚠️ 429 ❌ 500
muse-spark-1.2-contributor-free ❌ 500 ✅ 200
nemotron-3-ultra-free ❌ 000 ❌ 500

需要付费的 401 Insufficient balance 我就懒得测了。

Posted

stdout

git fetch 实现断点续传

git fetch origin main --depth=1 一次只下一个 pack,包含所有缺失对象,国内这网络你懂的,断开就废了,然后从头下载,往往又出事;

老外不懂国内网络条件这么艰苦,只能自己让AI改造。结果:

https://github.com/est/snippets/tree/master/git-fr

步骤:

  1. git fetch --filter=blob:none --depth=1 —— 只拉 commit+tree,这个速度很快,只有KB级别的传输
  2. rev-list --objects --missing=print 算缺失集合,500 个 blob 一批回填
  3. 每轮重算缺失、只补缺的。

踩过的坑

  1. git fetch origin <blobsha> 每次都会打印 fatal: bad object <sha> + error: ... did not send all necessary objects,但事后检查对象blob却真的下载了。后来发现因为cat-file 会拉数据,rev-list --missing=print 不会。
  2. 普通 git fetch origin <sha> 拿到的 pack 是薄包(thin pack)——服务器按客户端声明做 delta 压缩。如果本地声明的是 refs/remotes/origin/main(一个 commit),服务器据此假定我们拥有整棵树的全部 blob所以就啥都不给
  3. git 原生 lazy fetch ,用 GIT_TRACE=1 git cat-file blob <missing> 抓到了 git 自己 lazy fetch 时实际执行的命令:
git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags \
  --no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin

关键:
- fetch.negotiationAlgorithm=noop —— 不发 haves,服务器没法薄包化,只能发完整对象;
- --filter=blob:none —— 显式声明的 want 会绕过过滤器直接发送(partial clone 语义);
- --stdin —— want 列表从 stdin 读,天然支持批量。

还有很多放到 README.md 里了

安装

fr.sh 放到 PATH(如 ~/.local/bin/git-fr),即可 git fr 使用。

git config --global alias.fr '!<绝对路径>/fr.sh'

感觉,现在搞这些东西似乎意义不大了?反正大家都是让AI自己临时写一个脚本。。。🤣

有空让AI写个支持多线程并发 fetch 的。

Posted

stdout

继续搓英汉双解词典v3

去年搓了个词典,本来懒得动了,但是看到opencode订阅还没用完,deepseek那么便宜,想着不用白不用,找了个最浪费token的方式,让AI再写一部词典

当年的AI我记得还是 gemini-2,其实很多东西不成熟。我也对AI脾气没摸准,很自然的就:“请输出 JSON”。今年学聪明了,直接上YAML。AI高呼你这想法真离经叛道但又极度合理。

甚至,block scalar(|)支持多行文本,不转义!

一个小技巧是,尽量避免嵌套的,什么 map套entries,每个 entry 再挂一个 senses 列表、每个 sense 再挂一个 examples 列表,别玩这一套。直接拍扁,层级尽可能少,整个 schema 里不允许出现一个方括号!

AI帮我写了个 validator,调教这一块还得是AI卷AI。不对劲的就返回错误让AI二次输出。但是基本都是1-pass通过。之前JSON翻车太多了。

跑了一圈还发现,短码对模型不友好。词性枚举最初写成 n/v/adj/adv,模型就开始输出 v.、V、vb、verb,改成全称 noun/verb/adjective 。我觉得这个跟 LLM 预训练 token 的分布高度相关,不要以为「缩写」对人和机器更友好!LLM是反过来的。

10年前第一版想的是薅Google Dictionary羊毛,去年第二版静态站中毒,想弄json,后来发现不对。20k的词汇量,对应20k个零碎小文件不可控。

这次第三版,回到传统RDBMS。一开始想一张 FTS5 全文索引表,让用户能模糊搜索、子串匹配,甚至搜中文释义——感觉这才配得上"AI 词典"的科技感。

然后又想了下,问了自己一个本该一开始就问的问题:用户到底是怎么用词典的?

输入一个完整的词,得到词条。前缀联想 输入 "run" 提示 "running" 用一句 LIKE 'run%' 就够了,一共三张表

  • words:词头
  • senses:义项
  • surfaces:一切能解析到这个词的可供检索字符串:原形、变形、同义词、反义词、搭配

一个激进的决定,words 这个表甚至不保存词汇的归一化原始形式!直接作为 lemma 放到 surfaces 表里!

接下来,让AI写了个「递归」采集器,很简单,随便想几个单词,prompt就是让AI做词典;然后词典里的解释、例句再分词,再翻译。直到所有翻译和解释能自我包含,这样自动生成一部完整的词典。

让AI搓 BFS,起初几万词汇很舒服,很快发现,漂移进生僻词陷阱了:water 的释义带出了 odorless、colorless、tasteless,这些是英语词没错,所以又让AI给每个词标上分级,A1 A2 B1 B2 C1 C2 按顺序遍历。但我脑子抽了,没想到,这词在生成YAML之前,不知道自己的等级,AI提醒我,假设子词继承父词级别——够用就行。

后来想了下,反正AI闲着也是闲着,于是下载了一个权威 CEFR 词表,17 万词

https://github.com/Maximax67/Words-CEFR-Dataset/

这个词表的 level 是浮点,1.0=A1 … 6.0=C2,1.5 是 A1/A2 边界,天然可以先处理常用词后采集偏科的

甚至还提供了真实词频,the 出现了 928 亿次,还有 lemma 链接:ran→run、went→go、children→child,甚至还可以求出 clothes 没有链接,所以它是真词头。

跑起来之后,词典开始自己生长,敲入 caffeinate 睡了一觉。第二天起来一看,额度才用了 10% 不到。。Deepseek太耐烧了

顺便发现opencode关只在流式模式下返回内容,非流式直接返回空 body。挺浪费的说实话。

整理发现这个 CEFR 也tmd掺水,AI看了下挺多 复数、连字符、西语、意大利语词汇混进去,后来干脆一刀切100w以上词频才纳入采集

哎,还得二次清洗

后续会把这个词典做出 on-demand 的:用户查一个词,命中缓存直接返回;没命中,当场调一次 AI 生成、校验、入库

最后,地址依然是:https://def.est.im/


清洗的时候AI又说怎么这么多 人名 地名 乐队名,噪音可以跳过。

我想了下,其实对 language learner 还挺有用的。你输入一个名字,其实想知道这个名字的内涵,来源,是否得体,有坑的。

传统词典那些老学究那有时间给你折腾这那的。

一个地名儿,既然你查了,肯定是从某个书 杂志 地图册 上碰到的,你是冲着大新闻、 奇闻逸事 来的。传统词典只告诉这是某国某地,有啥用?

其次AI又挑三拣四,说这西语词汇,扔了吧。老美现在西语人口那么多,流行文化我感觉都半边天了,用户查词汇碰到的概率很高。

过去纸质时代,让双语高手静下来编一部这样的词典,不容易

但是AI时代,有机会了。

我想就从这样的第一性原理,做一个完全服务语言学习者,感兴趣的词典。

不过 tokens 已经耗尽了,下次再说。

Posted

stdout

邮箱生态调研2026

起初一个很简单的想法,把一个app/服务的私人数据,集中存数据库麻烦,不想维护担责,干脆放用户自己邮箱里,所谓"BYOS(自带存储)"。反正协议 SMTP/POP3/IMAP 很方便,然后调查了下很快发现,时代变了。

本来寄希望IMAP 这个协议,感觉能缝成一个读写API,

-APPEND 写(一封邮件 = 一个对象),FETCH 读,STORE 改 flag,COPY/MOVE 移动
- "目录 + KV"模型,flag(\Seen\Flagged 等)即元数据位
- SEARCH查询 主题、日期、自定义 header等
- 增量同步: UID + UIDVALIDITYIDLE 可做准实时推送(仅单文件夹)

其实古董的 GMailFS / GMail Drive 之类把 Gmail 当虚拟磁盘的开源项目很多,各类 邮件备份 工具、笔记类应用的同步,都走过这条路

以前输入帐号密码这种简单方式,早被废了;甚至专用密码、动态密码都下掉了。主流邮箱(Gmail/Outlook/Yahoo)已全面转向 OAuth 2.0,就是它协议上是要走一个握手然后验证token的流程,用户门槛太高了。

让AI跑了一圈发现:

邮箱供应商对比:

供应商 开发者接入方式 开发者审核门槛 用户授权操作 用户麻烦程度
Gmail OAuth 2.0(SASL XOAUTH2),scope https://mail.google.com/,需建 GCP 项目 最重:restricted scope,正式对外必须过 OAuth 验证 + 安全评估,每年复审;未过审只能 ≤100 测试用户或内部使用 跳转 Google 授权页点同意(OAuth);或开 2FA 后生成 16 位应用专用密码粘贴 低(OAuth)~ 中(app password)
Outlook.com OAuth 2.0(SASL XOAUTH2),scope https://outlook.office.com/IMAP.AccessAsUser.All,需注册 Microsoft Entra 应用 中等:Entra 注册免费即时;无强制审核;Publisher verification(去掉"未验证应用"警示)可选,需 EV 证书约 $100/年 必须先到网页设置手动打开 IMAP(默认关);之后走 OAuth 自动授权,或应用密码 中(多一步开 IMAP)
Yahoo OAuth 2.0(SASL OAUTHBEARER),需在 YDN 注册应用;或应用密码 :无 Google 式受限 scope 审核 OAuth 授权页;或设置里生成应用密码 低(OAuth)~ 中(app password)
iCloud 官方支持三方应用 OAuth 授权,或应用专用密码 低-中 必须开 2FA,到 account.apple.com 生成应用专用密码(每应用一个,上限 25 个)
QQ 邮箱 无 OAuth,只有"授权码" (无审核无文档) 设置 → 账户 → 开启 IMAP/SMTP → 短信验证 → 生成 16 位授权码 → 粘贴进 App 中(2~3 分钟)
163 / 126 同上(客户端授权密码) 同上 中(2~3 分钟)

硬限制

供应商 IMAP 服务器 容量 单封大小 连接/会话限制 明文密码状态
Gmail imap.gmail.com:993 15GB(与 Drive/相册共享) 25MB 15 并发连接;会话约 24h 已废除,需 OAuth 或 app password
Outlook.com outlook.office365.com:993 15GB 约 35MB 量级 多客户端并发触发风控 已废除(2022-10 起强制 Modern Auth)
Yahoo imap.mail.yahoo.com:993 1TB 25MB 附件 无公开数字 2024-05-15 起停用明文密码
iCloud imap.mail.me.com:993 免费 5GB 约 20MB 量级 无公开数字 需应用专用密码
QQ 邮箱 imap.qq.com:993 约 16GB 级 普通附件约 50MB,大文件走中转站(有时效) 无公开文档,风控靠账号安全策略 无 OAuth,仅授权码
163 / 126 imap.163.com:993 免费容量较大(自动扩容) 普通附件约 50MB,大文件走"超大附件"(有时效) 无公开文档 无 OAuth,仅授权码

⚠️ 以上数字除标注官方来源者外均为常用量级,上线前需逐家实测;QQ/163 无官方公开 API/协议文档,以实操惯例为准。

操作路径

路径 A:OAuth 授权

Gmail / Outlook / Yahoo

  • 用户操作:简单。点"继续用 Google/微软登录" → 同意 → 跳回,约 30 秒,体验最好,还能随时撤销。
  • 开发者代价:Google 的 restricted scope 审核是真实的时间黑洞——提交安全评估、回答数据使用问卷、每年复审;没下来之前 App 无法对真实用户上线(只能测试名单)。微软无强制审核、Yahoo 无审核,相对顺。

路径 B:应用专用密码 / 授权码

  • 用户要依次完成:开 2FA(Google/Apple 强制)→ 进设置页 → 生成 16 位码 → 回 App 粘贴。平均 5~15 分钟,而且这是客服工单的主要来源:没开 2FA、改了主密码(所有应用密码自动吊销)、粘贴带空格、公司/学校账号禁开……
  • Google 官方明说 app password "not recommended and unnecessary",方向是逼大家走 OAuth。把产品押在 app password 上是逆政策而动,微软/Yahoo 已先后封死明文密码,这条路只会越来越窄。

结论:

算了。不要折腾。

附:参考来源

Posted

stdout

“畜牲”

最近观察到娃学会了一些脱口而出的骂人的话,他骑车遇到看不惯的现象,就会骂一句 “牲口” !

我也没太多去干预,毕竟比 “肏” 这类秽语要委婉那么一丢丢。

然后我最近也喜欢一边开 Vibe Coding 一边挂机 Rimworld 种田,

然后就发现一个事儿,我在牧场区域种的 恶蘑菇,一种非食用植物,不会被动物吃掉;如果种玉米 稻米 土豆就会被偷吃,然后基地的粮食就少了一份。

所以突然就得到这么一个莫名其妙的理论:

动物并不会去主动破坏自己吃不了的东西

可能也有反例,狼、狮子会杀死竞争者的幼崽,黑猩猩会打架,一些鸟会破坏别的鸟的蛋;松鼠会偷走自己暂时吃不了的东西;海豚玩弄猎物;

通过这些例子,感觉破坏性和智力正相关,也就是利用预测能力和因果性去糟蹋东西。骂人是动物,仿佛动物低人一等,但是人心有的时候,甚至还不如“畜牲”

如果把这个限定缩小一点:

动物作恶的能力仅限身体。

人类里的坏逼,可能是唯一为了某个概念性的目标,会系统性破坏自己无法直接利用、甚至未来有价值东西的动物

智力让生物获得了创造未来的能力,也让它获得了毁灭未来的能力。

植物貌似也只会利用身体去占领局部环境,比如分泌毒素,遮挡阳光,向环境释放某种抑制剂。

但人类可以把攻击性扩大到远超生存需求的规模。为了荣誉、信仰、身份、复仇去糟蹋自己甚至不认识的人或者物。

这是一种非常特殊的“脱离现实反馈”的能力。

所以「畜牲」这个骂法有点讽刺——很多时候,骂别人「像动物」,其实是在指责对方缺少克制;但换个角度看,动物反而经常遵循一种很严格的生态逻辑:消耗资源、竞争、生存,不会为了纯粹抽象的理由把世界变得更糟。


btw 这个游戏真的有毒。好多年前尝试畜牧,结果直接生了一小崽,指数增长,然后过冬吃光了粮食,就纷纷饿死。然后来年就严格限制动物数量,定期宰杀,发现最简单的办法是砍掉雄性,留下1-2个传种,留下产奶产蛋的雌性。在感叹残酷的同时,结果无意中发现了 回交 。

据说宋朝马政废弛,就是因为儒生不忍心这么干,所以导致良马欠缺。

你说这个这个是人类的扭曲的道德还是动物的天性呢?

Posted

stderr

飞书并入豆包,文档是否减少?

飞书并入豆包

7月30日,字节跳动发布内部邮件,宣布飞书产品团队与豆包产品团队将整合,成立新的豆包产品团队,由豆包负责人赵祺负责,飞书负责人谢欣向赵祺汇报。GTM(市场、销售、客户服务)体系方面,飞书GTM团队将与火山引擎团队整合,成立新的ToB GTM组织“创造力服务平台(Creativity Service Platform)”。整体负责字节 MaaS和SaaS等云服务的市场、销售和客户服务,由火山引擎负责人谭待负责,飞书销售负责人林婵、飞书战略及市场负责人史志隽向谭待汇报。重组之后,原有的飞书产品和服务保持不变,飞书还将和豆包在生产力场景进行更深度的产品协作。

然后刷到一个推

办公文档恰恰是 AI 要淘汰的信息交互协议。以前在两个信息孤岛传递信息,需要两边的对接人来进行消息组包和分解,来消除两边信息不对称的问题。
企业要推行 AI,那么两边各自准备一个智能体,他俩自己聊就行了。
没必要让智能体写一个报告或者 ppt,交给人类,再转交给另一个人类,再丢给智能体。
今后只会出现三个场景需要文档:
AI 写出来给 AI 看,作为阶段性消息压缩。
AI 写出来给人看,人写出来给 AI 看,作为人类和 AI 的两个信息孤岛的信息交互协议。

我想了下,不对啊。AGENTS.mdCLAUDE.md.rulesMEMORY.mdDESIGN.mdSOUL.md vibe coding 发明出来多少妖魔鬼怪的文档环节

当然,有人说,你这码农自 high,非研发哪里需要这么多文档。但 vibe coding 暴露的问题不是码农特有的,而是大模型根本缺陷——没有连续性。

就算你用 A2A通信,打通 sub-agent又如何?同一个 agent 的今天和明天之间,隔着一堵完全遗忘的墙,比任何组织间的信息壁垒都彻底。

这一点已经被无数个 A\ 上的 blog 证明了。他们家甚至搞出来个逆天的 constitution 文档。为啥?两个 agent 的聊天记录是流水账,没法有意义地做版本对比。但一份 markdown spec 可以被 git diff、被 review、被回滚

当然,华丽排版的 Word、PPT 这种给人看的“表演”性质文档确实在这套流程里基本绝迹了。。。。吧?呃,不对。你汇报不做 ppt 能升值加薪吗?AI 能做 ppt,现在是卖点!

说到这里,我刚好遇到个工作里真实的事,有个说不清道不明的感受,不知道如何形容。

有个需求属于两、三个部门的业务数据流转

字段出了点差错,推送方和接受方都能做,管道方也能做,但是就推诿

这个时候,开个会,主持人出了个文档,突然事情就能进行下去了

本来我一直觉得,事在人为,人是做事的主体,但是这个情况里,人虽然有做事的「潜力」,但是种种原因,实际都懒得去做;反而一个文档被「讨论」出来之后,形成某种魔力,「驱动」人们去做事

然后我又想起,我在学生时代还有一个神奇的误解,小时候只知道美式三权分立,国会负责立“法”。具体立了什么法没太多概念 直到贸易战,发现都是各种 act 。什么《民权 act》《清洁空气 act》《CHIPS Act》 现在琢磨一下,有点类似戏剧里的 Act 1, Act 2,也有点“行动”的意思,这里的“法”其实和生活中“民法”有点微妙差异。 它不是约束,而是推动。

AI看到这里,指出:

开会 + 出文档做的事情,恰好就是把私下的、各自心照不宣的认知,变成公开的、被见证过的认知。文档本身可能一个字新信息都没有,但"在场所有人一起看着这行字被写下来"这件事,把"我知道"升级成了"我们都知道,而且我们都知道我们都知道"。这就是谢林点(Schelling point)成立的条件——不是文档有魔力,是文档完成了一次"公证仪式",把博弈从"猜对方会不会先动"变成了"规则已经摆在这儿,不动才是异类"。

但是繁文缛节一多,这不就回到科层制,制度化(institutionalization)了吗?

我突然发现,spawn 一个 agent 是廉价的,那么理论上就如同 orchestrate 一个 subagent 集群写代码一样,需要形成很多 act 才能统一行动。

所以我得到一个阶段性结论:

将来办公文档恐怕不是变少,而是会海量的被agent生成出来。。。


继续下去,如果生成 act 的成本趋近于零,文档可靠性必然被稀释。就像货币超发会通胀——如果人人(agent)都能一句话甩出一份文档,那以谁的为准?

你别说,查一下claude code那几十万字的 system prompt。。。我甚至都怀疑里面冲突和矛盾有多少。。。

openai,A\ 这种 frontier 也是堆屎山,地球上有多少人还能做的更好呢?

也许,以后数据结构定义清晰,理解成本低,协议设计干练,RPC职权分明 的数据驱动公司,和 “面条文档” AI驱动的公司,可能有天差地别的生产力悬殊对比。。。。

AI反驳说,真是世界没有唯一正确答案。不是一些 schema 套一点 enum 类型枚举就能穷尽的。

呃,业务建模当然存在各种问题,我想说的其实是,合理分工问题

现实当然是 messy的,往往没法写成一个固定 schema 的。就如同数学不可能背答案学好一样。

但是特定步骤和分工往往能解决一大片类似问题。

所谓部门结构决定代码架构,很多业务里的乱往往是部门墙导致的。过去部门调整往往伤筋动骨,顶层设计缺这少哪欠考虑

但是agent swarm呢?你的试错成本几乎为0 ,给agent赋予一个角色只需要一段 system prompt。

如果你懂业务,你可以几个小时构建一套完整流程,然后不停试错迭代。

这才是巨大的机遇。按照传统行业分工固定模式生产,想着AI 去充当裱糊匠,这个格局没打开。

想到这里,我突然回忆起这个炸裂的文章:

https://trinkle23897.github.io/learning-beyond-gradients/

EnvPool 的作者,他维护环境库时想找一种便宜、可复现的方式测试游戏环境是否跑得对,于是用 Codex(gpt-5.4)写纯规则策略,不训练任何神经网络。结果远超预期:一个打砖块游戏 Atari Breakout,策略从 387 -> 507 -> 839 -> 864,最后打到理论最高分;Breakout 从最开始简单的 “球在左边就往左” 发展出一套成熟完整的策略

作者说,专家系统、规则系统的失败,不是因为它表现不好,而是这玩意的维护成本十分高昂。人类手工维护 heuristic 很容易变成这样:

  • 今天加一条规则修 case A。
  • 明天发现 case B 被修坏了。
  • 后天再加一个 if。
  • 大后天没人敢删了。

那么有了 coding agent 之后呢?随着大模型能力提升,人类介入次数会逐渐变少,这个反馈循环就有机会在某些边界明确的系统里自动闭合,从而能够实现自动化用 HL 批量生产 HS:

  • 环境反馈 / 测试失败 / 日志异常
  • coding agent 读 context
  • 修改 policy / test / memory
  • 重新运行
  • 把结果写回 trials 和 summary
  • 下一轮继续

我的takeaway可能更露骨:以前组织架构靠部门关系和体制运转

国会维护的 act 可能各种冲突、短视,矛盾,所以做事的主体,还得是人。

agent 时代不一样了。代码还是很乱,文档也很乱,但可能某种很牛的规则“醒过来”了

以后某个重要的岗位可能不是一个资深的员工,而是某个角落一段被反复验证过的核心代码

其实 quant 行业早就是这个形状了吧?


大多数人对AI 的态度,可能停留在 提效 上面。对于彻底掀翻桌子,固定岗位没了这种颠覆性的冲击可能没预料到,我这里的预测也有可能是错的。不过写到这,我突然发现,这篇blog核心观点就一句话:生产力的提升必然带来生产关系的变革。

刚好最近几天,肉眼可见这一轮AI 到顶了。美股 科技股都在很大波动。

但是AI 对各行各业带来的冲击,我觉得刚开始。就看美国这一轮巨头会释放多少capex折损和二手设备流通,就像当年 dotcom bubble 带来廉价的fiber一样。

希望明年能用上 128G CAMM2 接口能跑 nvfp4 的本地推理AI!

Posted

stderr

从菲尔兹奖谈「包养」

美国当地时间2026年7月23日上午,在费城举办的国际数学家大会上,官方正式公布2026年菲尔兹奖完整获奖名单,北大2007级本科校友王虹、邓煜双双上榜。这是自菲尔兹奖设立近九十年以来,首次有中国籍数学家拿下该奖项

然后网上各国各路反思怪就开始表演了,我也未能免俗,对其中有个议题感兴趣,数学强国,是否要允许养十年冷板凳的学科?

首先,这个「十年」冷板凳的说法有点误导的。Fields Medal 奖励给40岁以下的人,正是因为数学这玩意出成果可以当庭验证,不用等诺贝尔奖那种从理论刷新实验和工业需要几十年。

王虹:Kakeya 猜想(三维情形)开始于2024–2025年前后,与 Zahl 的工作集中攻克,2025年发表突破性证明(127页论文)。

邓煜:Boltzmann方程 到 流体方程(Hilbert第六问题方向)一系列,他这个周期的确比较长,但也是2024–2025年前后与合作者完成严格推导相关关键结果。

所以数学这玩意属于发大招的前摇很长。但是结果突破了就立马见效。

然后就回到这个议题,科学技术被「包养」10年,甚至可能不出成果,国家或者结构敢这么干吗?能这么干吗?会不会导致关系户吃空饷?有没有什么问题?

与其对这个问题辩经,不如看看历史上真实的「科研」是怎么进行的。

很多人包括我,对近现代科学史的定义,是从 哥白尼 开始,我觉得要不就从 第谷 开始,我让 A\帮我做了一个调查,有哪些 ≥第谷 档次的重要科学技术发明人

很快AI 给了这样一个表格:

年代 人物 主要贡献 与第谷相当/超越的理由
1609–1619 开普勒 行星运动三大定律 直接用第谷数据推翻旧宇宙观,理论层面已超越
1610s–1630s 伽利略 望远镜天文学、力学、科学方法论 开创实验科学范式,影响力远超单一学科
1628 威廉·哈维 血液循环理论 生理学革命的起点
1687 牛顿 万有引力、运动定律、微积分 公认整个科学史影响力最大者之一
1735 林奈 生物分类系统 至今仍是生物学基础语言
1789 拉瓦锡 质量守恒、现代化学奠基 开创一门学科的量化范式
1798 詹纳 牛痘疫苗 免疫学起点,直接拯救数亿人
1800 伏打 电池 开启电学实用化时代
1805–1808 道尔顿 原子理论 化学从定性走向定量的关键
1831 法拉第 电磁感应 现代电力文明的直接源头
1859 达尔文 进化论 生物学乃至世界观层面的范式革命
1865 孟德尔 遗传学定律 生物学隐藏的另一条主线,后来居上
1861–1873 麦克斯韦 电磁场理论 统一光、电、磁,爱因斯坦称其为牛顿之后最重要
1857–1895 巴斯德 细菌学说、疫苗、消毒法 医学史上救人最多的人之一
1869 门捷列夫 元素周期表 化学的"第谷式"系统整理+预言能力
1895–1898 伦琴 / 居里夫妇 X射线 / 放射性 打开原子内部世界的大门
1905–1915 爱因斯坦 狭义/广义相对论 科学史知名度和范式颠覆力最高者之一
1900–1927 普朗克 / 玻尔 / 海森堡 / 薛定谔 量子力学 与相对论并列的20世纪物理两大支柱之一
1928 弗莱明 青霉素 抗生素时代开端,救人数以亿计
1936–1950 图灵 计算理论、图灵机 整个信息时代的理论奠基人
1948 香农 信息论 数字时代的底层数学语言
1953 沃森、克里克、富兰克林、威尔金斯 DNA双螺旋结构 分子生物学时代的起点
1989 蒂姆·伯纳斯-李 万维网 重塑人类信息交流方式
2012 杜德纳 & 沙尔庞捷 CRISPR基因编辑 生命科学的"第谷式"精确操控工具
2005–2020 卡里科 & 魏斯曼 mRNA疫苗技术 新冠疫情中验证的范式级突破
2020 Hassabis / Jumper(DeepMind) AlphaFold蛋白质结构预测 AI解决半世纪悬而未决的科学难题

AI给的几点补充说明:

  • 早期(1600–1900)明显比后期"密度更高":因为现代科学建制尚未形成,单个人的贡献边界更清晰,也更容易和第谷这种"孤胆式"人物类比。20世纪后科学越来越工业化、团队化(比如人类基因组计划、大型对撞机、AlphaFold团队),个人英雄式的叙事变少了。
  • 第谷本人的定位其实很微妙:他不是理论家,而是"数据基建"式人物——用极致的观测精度为后人铺路。如果按这个标准严格类比,像哈勃(观测确立宇宙膨胀)、萨顿·汤姆森测序团队/人类基因组计划这类"以海量精确数据推动范式转移"的人/团队,可能比爱因斯坦这种理论天才更贴近第谷的"角色",而不只是"级别"。

看过给出的列表,我问了第二个问题,TA们的funding是如何的?

AI说:

这个角度非常犀利——科学史书写往往聚焦"发现了什么",却选择性地忽略"靠什么活下来"。

人物 资助/职业方式 轻松程度 备注
第谷 丹麦国王腓特烈二世赐予汶岛+巨额年俸(据估算占丹麦国库约1%) 极轻松→后期崩盘 换了国王克里斯蒂安四世后失宠,被迫流亡布拉格投靠鲁道夫二世
开普勒 帝国数学家头衔(理论上),实际靠占星糊口+教书 很惨 皇室长期拖欠薪水,他多年追讨欠款;母亲还被控行巫术,他得亲自辩护
伽利略 帕多瓦大学教职→美第奇宫廷数学家 中等偏好 靠把木星卫星命名为"美第奇星"换取赞助,堪称科学史第一代"公关高手";后期被宗教裁判所软禁致死
哈维 詹姆士一世/查理一世的御医 轻松 医生本业收入稳定,王室背书加持
牛顿 剑桥卢卡斯教授→皇家造币厂厂长 越老越轻松 造币厂是肥缺,晚年相当富有,还当上皇家学会主席
林奈 乌普萨拉大学教授 中等偏好 平民出身,靠学术晋升,晚年封爵、相对富裕
拉瓦锡 私人身份是"包税官"(Ferme Générale) 极轻松→被砍头 用包税收入自建豪华实验室;大革命时因"包税官"身份被送上断头台
詹纳 乡村医生+英国议会拨款(约1万→2万英镑) 中等 国家事后买单,但过程有争议
道尔顿 贵格会教师+朋友募集年金+政府公民名单养老金 清苦但稳定 生活极简朴,靠圈子互助
法拉第 皇家研究所助理起家(戴维的技术员)→终身研究员+维多利亚女王赐宅 逆袭典范 铁匠之子、书籍装订学徒出身,靠机构体系一路托底
达尔文 家族财富(韦奇伍德陶瓷+医生世家) 完全无忧 终身未任职,自费出海、自费研究,名单里最"躺平"的人
孟德尔 布尔诺修道院供养,后升任院长 轻松但默默无闻 教会包养换来的是35年无人问津,死后才被重新发现
麦克斯韦 苏格兰地产家族+卡文迪许讲席教授(德文郡公爵捐资) 轻松 本身有庄园收入,教职是锦上添花
巴斯德 法国国家教职→巴斯德研究所(政府+国际公众捐款) 成功的众筹 狂犬病疫苗成功后全球捐款蜂拥而至,堪称19世纪版"众筹成功案例"
门捷列夫 圣彼得堡大学教授 中等 学术生涯稳定,但因政治原因被拒于俄国科学院大门外
伦琴 德国大学教职体系+诺贝尔奖(首届物理奖) 轻松 典型德国教授晋升路线
居里夫妇 索邦大学,早期极度贫困(棚屋实验室) 早期惨,后期爆红 美国妇女发起"镭募捐运动"送她一克镭,靠国际名气才翻身
爱因斯坦 1905年是伯利专利局小职员 早期惨 "奇迹年"论文是业余时间写的;成名后才拿到苏黎世、柏林教职
普朗克/玻尔/海森堡/薛定谔 各国大学教职;玻尔研究所由嘉士伯啤酒基金会资助 普遍轻松 玻尔研究所靠卖啤酒的钱养量子力学,是个有趣的冷知识
弗莱明 圣玛丽医院教职,发现青霉素后长期被冷落 发现容易,落地惨 真正把青霉素变成药的是弗洛里和钱恩,靠美国战时政府+洛克菲勒基金会砸钱才成
图灵 剑桥研究员→英国政府战时机密资助(布莱切利园) 战时极轻松→战后被国家毁掉 因同性恋被定罪、化学阉割,44岁去世,是名单里结局最悲惨的
香农 贝尔实验室 轻松 工业界顶级研发机构,待遇优渥
沃森/克里克/富兰克林/威尔金斯 英国医学研究理事会(MRC)拨款 中等偏好 已进入现代"政府科研拨款"体系
伯纳斯-李 CERN雇员 轻松 万维网是"副业",欧洲多国联合资助的大型实验室养活了他
杜德纳&沙尔庞捷 大学教职+NIH/霍华德休斯医学研究所巨额资助+后期创业融资 轻松 现代"学术拨款+风投"双轨制的受益者
卡里科&魏斯曼 宾大长期拒绝其mRNA研究拨款,被降级 名单里最惨的现代案例 卡里科多年申请不到经费,一度濒临放弃,直到转投BioNTech(企业资金)才翻身,新冠疫苗验证了她
Hassabis/Jumper(DeepMind) 风投起家→2014年被谷歌以约5亿美元收购 极轻松 大厂算力+资本,现代AI研究的资助模式

AI发现几个有意思的规律:

  1. "贵族/私产型"最省心:第谷(早期)、达尔文、拉瓦锡(早期)、麦克斯韦——但拉瓦锡的例子提醒你,私产型资助有政治风险,大革命一来照样人头落地。
  2. "体制拖欠型"最折磨人:开普勒是典型代表——头衔光鲜、待遇纸面上不错,但皇室实际不给钱,他等于用占星术这份"副业"养活了现代天文学的诞生。
  3. "教会/修道院型"稳定但可能换来彻底沉默:孟德尔衣食无忧,代价是被历史遗忘35年——这提醒我们"资助稳定"和"影响力"未必成正比。
  4. "先穷后爆红"是个反复出现的剧本:爱因斯坦、居里夫人、卡里科都是先坐冷板凳,成名/成功后资助才如潮水涌来——这某种程度上说明资助体系本身经常是滞后的、事后追认的,而不是有远见地提前下注。
  5. 国家战争机器是隐藏的大金主:图灵(二战)、弗莱托与钱恩的青霉素产业化(二战)都靠战时政府不计成本砸钱才落地,和平年代未必能拿到同等规模的支持。

然后我问了第三个问题:按照列表加入一个 资方 出发点 视角。本来是想拿这笔钱或者这个项目目的是干嘛的?

AI回答:

这个视角非常关键——你其实点出了科学史书写的一个系统性偏差:我们只看到"赌赢了"的案例,资方当初的真实动机往往和最终成果风马牛不相及,而"陪跑"的绝大多数投入早就被历史遗忘了。

人物 资方 资方真实目的("这笔钱本来是要干嘛") 最终成果是否"跑题"
第谷 丹麦国王腓特烈二世 宫廷占星(为国王择日、预测战争吉凶)+国家门面工程(丹麦要在欧洲学术圈刷存在感) 完全跑题——精确观测只是占星的"副业副产品"
开普勒 神圣罗马帝国皇帝鲁道夫二世 帝国数学家的本职是占星预测+编历法表(《鲁道夫星历表》,给贵族用) 跑题——行星定律是他"偷偷"塞进占星工作里的私货
伽利略 美第奇家族 家族宣传公关(把木星卫星命名为"美第奇星")+帕多瓦大学教军事工程/弹道计算 半跑题——日心说支持是他自己夹带的,资方要的是名声和实用军事技术
哈维 詹姆士一世/查理一世 单纯要个好御医看病 跑题——血液循环论纯属他自己的业余研究
牛顿 剑桥卢卡斯讲席 / 英国皇家造币厂 教数学 / 打击伪币、稳定英镑币值(国家财政问题) 讲席教职算对口;造币厂完全跑题,和万有引力毫无关系
林奈 瑞典王室+乌普萨拉大学 找可替代进口的本土经济作物(茶、香料"国产化"),外加药用植物鉴定 半跑题——分类系统是这个经济任务的副产品,规模远超预期
拉瓦锡 包税官身份(私人生意) 纯粹收税赚钱,跟化学毫无关系 完全跑题——化学是他用私人财富养的"业余爱好"
詹纳 英国议会 例外!这是事后追认型——先证明疫苗有效,议会才拨款奖励 不跑题,但属于"结果已出再下注"
道尔顿 贵格会社区互助 培养教师、维持社区教育 跑题——原子论是他个人兴趣的副产品
法拉第 皇家研究所 面向伦敦上流社会的科普讲座+社交场所(机构靠贵族会员费运营) 半跑题——电磁感应是"讲课之余"研究出来的
达尔文 家族财富 / 英国海军"小猎犬号" 家族财富无目的;海军航行目的是海岸测绘+殖民地军事勘察 完全跑题——进化论是搭军舰便车的博物学家副产品
孟德尔 修道院 培养神职人员+可能想改良豌豆等园艺品种(自给自足) 完全跑题——教会根本不知道自己资助了现代遗传学,而且35年无人理睬
麦克斯韦 卡文迪许实验室(德文郡公爵) 剑桥要提升实验物理声誉,跟牛津竞争的"大学军备竞赛" 基本对口,但电磁波预言超出预期
巴斯德 法国政府+酒商/蚕丝业主 具体产业救火:葡萄酒变酸、蚕病摧毁法国丝绸产业 半跑题——细菌学说是解决产业危机时提炼出的理论
门捷列夫 大学教职+石油产业顾问(巴库油田) 教学、写教科书+解决俄国石油裂解等实际工业问题 半跑题——周期表是他"写教材"时整理出来的副产品
伦琴 大学常规科研经费 常规阴极射线管实验,毫无特定目标 纯意外——著名的实验室"事故"发现
居里夫妇 几乎无资助(索邦借的破棚屋) 无目的探索沥青铀矿残渣之谜 不算跑题,但资方几乎不存在
爱因斯坦 伯尔尼专利局 审查专利申请,养家糊口 完全跑题——相对论是上班"摸鱼"写的
玻尔研究所 嘉士伯啤酒基金会 丹麦国家科学荣誉+啤酒公司企业形象/慈善 半对口——资方押的是"国家声望",不是量子力学本身
弗莱明 圣玛丽医院 临床细菌学教学诊断,日常工作 纯意外——著名的"没洗培养皿"事故
青霉素产业化 二战盟军政府+洛克菲勒基金会 纯军事需求:救治战场伤员感染 对口——这是少数"目的和结果高度一致"的案例
图灵 英国政府/布莱切利园 纯军事情报:破解德军Enigma密码 半跑题——图灵机/计算机雏形是破译密码的副产品
香农 贝尔实验室(AT&T) 改善电话线路信号质量、降低长途通话噪声(纯商业工程问题) 半跑题——信息论是解决电话工程问题时提炼的数学理论
沃森/克里克/富兰克林 英国医学研究理事会(MRC) 医学相关基础研究方向,但1953年DNA结构的实际应用价值并不明确 半对口——某种程度上MRC也是在"赌"
伯纳斯-李 CERN 高能物理实验(强子对撞机等)才是CERN的核心任务 完全跑题——万维网是他为解决CERN内部文档管理/科学家协作难题做的"内部小工具"
杜德纳&沙尔庞捷 NIH/HHMI 纯基础好奇心驱动:研究细菌如何抵抗病毒感染 完全跑题——基因编辑工具是意外的应用副产品,申请经费时没人能预见
卡里科&魏斯曼 宾大(早期几乎拒绝)→BioNTech 最初想做mRNA通用蛋白替代疗法平台,不是疫苗 半跑题——疫苗只是后来才发现的应用场景之一
Hassabis/Jumper 谷歌收购DeepMind 通用人工智能/游戏AI(AlphaGo系列)才是谷歌的主营目标 半跑题——AlphaFold是公司内部"顺手"做的科学项目

AI总结的几个扎心的结论:

  1. "完全对口"的案例极少:整个名单里,资方目的和最终科学价值高度一致的,大概只有詹纳(疫苗已验证后拨款)和青霉素产业化(军事紧急需求)——而且这两个都是"结果已经初步显现,资方才敢下注",不是提前押中的。
  2. "跑题率"高到惊人:占星、税收、御医、公关、军事测绘、密码破译、电话降噪、文档管理系统……这些才是历史上真正的"招标书",科学突破几乎全是"顺手做的"或者"钻空子塞进去的"。
  3. 你说得完全对——这份名单本身就是幸存者偏差的产物。第谷时代还有无数宫廷占星师拿着差不多的资助,一辈子没算出任何有价值的东西;孟德尔的修道院同期资助的可能还有几十个神职人员搞园艺,没人记得他们。我们能列出这张表,恰恰是因为"科学价值"是事后被追认的标签,而不是资方当初购买的商品。
  4. 一个推论:如果按"资方本意是否达成"来打分,这些人在当时的语境下,很多其实是“项目失败”或“跑题挪用经费”——开普勒如果被鲁道夫二世按KPI考核占星准确率,大概率是不及格的;伯纳斯-李如果被CERN按"你的对撞机数据分析进度"考核,万维网这个"副业"说不定还会被叫停。

最后,图穷匕见:

现代科研基金委的"结果导向"评审(要求预设可衡量产出、里程碑、KPI),可能恰恰系统性地筛掉了历史上产生最大价值的那种"跑题式"研究。

我的暴论:

资方为了装逼去包养才是科学研究第一必要条件。🤣 🤣 🤣 🤣 🤣 🤣

科学家得有品味和敢做副业的决心,才有可能搞出来大新闻

穷逼就不要去幻想搞什么科研了。除非要么你能混成网红,要么你需要遇到一个贵人

Posted

stderr

iosevka字体让中文 ASCII diagram 图表对齐

对齐前:

┌──────────────┐     ┌──────────────┐
│  用户请求     │────▶ │  网关服务     │
│  User Req    │     │  Gateway     │
└──────┬───────┘     └──────┬───────┘
       │                    │
       ▼                    ▼
┌──────────────┐     ┌──────────────┐
│  认证服务     │      │  业务逻辑     │
│  Auth Svc    │     │  BizLogic    │
└──────────────┘     └──────┬───────┘
                            │
                            ▼
                     ┌──────────────┐
                     │  数据存储😆   │
                     │  Database😂  │
                     └──────────────┘

对齐后:



┌──────────────┐     ┌──────────────┐
│  用户请求    │────▶│  网关服务    │
│  User Req    │     │  Gateway     │
└──────┬───────┘     └──────┬───────┘
       │                    │
       ▼                    ▼
┌──────────────┐     ┌──────────────┐
│  认证服务    │     │  业务逻辑    │
│  Auth Svc    │     │  BizLogic    │
└──────────────┘     └──────┬───────┘
                            │
                            ▼
                     ┌──────────────┐
                     │  数据存储😆  │
                     │  Database😂  │
                     └──────────────┘

AI 的解释:

Iosevka 的作者 be5invis 当年做这个字体时明确说过:因为他和很多朋友生活在中国、日本,所以他把这款字体做成严格的 1/2 em 宽,专门用来跟汉字对齐——面向亚洲用户,用这个就能保住完美对齐。也就是说 Iosevka 的西文字形宽度天生就是 0.5em,不需要任何 CSS 补偿或 size-adjust 魔法,配任何满格 1em 的 CJK 字体(Hiragino、PingFang、Source Han Sans 等)都是精确 2:1。而且它的构建系统是声明式的,用 TOML 配置文件驱动,可以精细控制粗细、宽度、每个字形的风格变体。

中英混合是搞定了,最后那个 数据存储 Database 带emoji错位了。

emoji 还有个zwj的问题:

农民 emoji 🧑‍🌾 其实是"人"+ 零宽连接符(ZWJ)+ "农作物"两个 emoji 码位拼成的

在 wcwidth 计算,会分别得到宽度 2、0、2,加起来变成 4 列宽导致错位

目前暂时无解

Posted

stdout

为什么 Github OAuth 故意拦截 CORS

跟AI发闹骚学到的

https://github.com/isaacs/github/issues/330

Allowing CORS for the endpoint you mentioned would mean that you could complete this step of the Web flow from a browser:
And this would mean that you're hard-coding your client_id and client_secret into a webpage (or JS file loaded into that webpage) for everyone to see. This would indeed cause security concerns since the client_secret should be kept secret. If someone got hold of your client_id and client_secret, they could impersonate you application, and for example -- wipe all the tokens for that application:

如果允许在浏览器通过 client_id, client_secret 交换得到 access_token,那么实际上你的账号等于公开裸奔,你所有的 github 资产等于公开被人控制。所以必须走一个服务端流程,然后再把 access_token 下发

呃,好像很有道理。

Posted

stdout

gitweets改版,复刻微信「朋友圈」

去年搓了个 gitweets,一个 .html 实现了「微博」,拿git历史当feed流~发推

这个周末看着 coding plan 还剩 20% 要到期,没用完,怎么办呢?想来想去,挖大坑干不完,小修小改,就拿这个 gitweets 继续填坑了

首先是让AI把界面改成模仿微信 朋友圈,啪一下,很快啊,结果让人非常印象深刻,很逼真

https://f.est.im/est

现在的AI真厉害。让我去调CSS可能这辈子都搞不出来这个效果了。

后面是我的一些唠叨,不感兴趣的可以关闭页面,或者去上面那个围观一下。

想起来独乐乐不如众乐乐,要不,支持个评论功能?

项目的初衷是 static page,要实现互动肯定得用一些API了。

最能想到的思路,走传统的 github issue 什么的,和这个 gitweets 最大的出发点冲突了:一个 git repo 包含所有数据,随时搬家,不用导出。

而且麻烦的是,post 是绑定到 commit 上的。如果你用一个 JSON 之类的来存评论,势必也会新增一个 commit,这样会污染post时间线。

突然想起来一个古老的东西,git notes,这是连 ChatGPT 和 Claude 都没想到的邪路,不过它们很快确认这个办法甚好,可行。

git notes选型定下里,建立这个数据模型让我纠结了很久。围绕 github 开展流程,让我一度误入歧途

  1. oauth 登录,不要任何scope
  2. 用来存 github notes的repo邀请登录人加入项目
  3. 浏览器通过该用户access token发起 notes append

最后想明白了,压根不应该走浏览器这一套。而是只能走后端代劳,用 Fine-Grained PAT 来和 github API 交互

其间还考虑 github 越来越拉垮,想避免 vendor lock-in,直接走git http协议。

首先想到的是 Cloudflare 那个牛逼的 zig 写的 100kb 的 wasm 可以 http 读写任意 git 仓库

git protocol engine is written in pure Zig (no libc), compiled to a ~100KB WASM binary. Support for both v1 and v2 of the git protocol. Support capabilities including ls-refs, shallow clones (deepen, deepen-since, deepen-relative), and incremental fetch with have/want negotiation.

仔细读了下文档,让AI一起调研,发现tmd这玩意仅限 Worker 内部使用,只能读写 CF 内部的 假 git,不支持读写外部任意 git http。

isomorphic-git 坑也挺多 。还是先走 github API 吧

这个 git notes 要走REST API 有查询放大 3+N 的问题,怕掉用次数爆掉,于是让AI走 GraphQL。我自己手动是搓不动 GraphQL,太难了。AI虽然是 flash 普通智商版本,也分分钟拼接好。一次成功。真猛 😭

于是 Vibe 出来了。

搓完了想起一个问题,如果有人刷评论怎么办?于是让AI搓了个 /.admin 管理页面。也是秒写好。太方便了。

明显欠缺的功能搓完之后,感觉又进入了贤者时间,索然无味了。

Posted

stdout

MiMoCode 干完活儿发通知

AI在 coding 的时候我其实在玩别的。希望agent 每次干完活,macOS 弹个通知。

手上是 MimoCode,就让它自己写个。啪,很快写好了。结果还是折腾了好一会儿, 记录几个有意思的小坑。

首先是如果当前CLI是活动的,就不弹通知。

需要判断活动窗口。最初用 osascript

osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'

直接报错 Not authorized to send Apple events to System Events (-1743)

于是换 lsappinfo,走 macOS Launch Services API,不需要任何额外授权:

lsappinfo info -only name $(lsappinfo front)
# → "LSDisplayName"="Terminal"

还能拿 PID:lsappinfo info -only pid $(lsappinfo front)

然后如何判定活动窗口是不是 CLI?

写死个["iTerm2", "Terminal", "Alacritty", "kitty", ...] 列表

太笨了。当前hook是子进程,直接遍历 parent 进程树啊!找到终端模拟器的 PID,再跟前台 app 的 PID 比对。

但是在 tmux 里爬出来是这样的:

node → zsh → tmux → launchd(1) → init(0)

Terminal.app 的 PID 根本不在树上。因为 tmux server 启动后被 reparent 到了 launchd,跟 Terminal.app 断开了父子关系。

又想到一个办法,直接查 $TMUX_PANE 是否是当前 active pane:

tmux list-panes -F '#{pane_id} #{pane_active}' | awk '$2==1 {print $1}'

如果 $TMUX_PANE == active pane ID,说明用户就在看这个窗口,不需要通知。完全不需要知道前台是哪个 app。

非 tmux 场景才用进程树 + lsappinfo 的 PID 比对。

然后就是挑选具体哪些事件要弹通知了。

权限通知:permission.ask,但文档说 "not yet wired"。试了一下,确实没触发。尴尬。雷军!!!金凡!!!

最后的方案:注册 permission.ask 占位,如果哪天接入了就能精确捕获。目前靠 tool.execute.before 抢占并推断。工具跑完了说明权限已过或不需要。

还有个情况就是 Subagent 完成也给我哐哐弹。最初硬编码了 SUBAGENT_TYPES = ["general", "explore"];后来发现 actor.preStop / actor.postStop 的 input 里有 mode: "subagent" | "peer"。于是就做了个计数器

最后通知到时候带上 Session 名字,折腾了一圈 直接 sqlite3 ~/.local/share/mimocode/mimocode.db "SELECT title FROM session WHERE id = '$SESSION_ID'"

完整代码放在

https://github.com/est/snippets/blob/master/mimocode-hooks/notify-done.ts

复制到 ~/.config/mimocode/hooks/ 就可以试试效果

Posted

stdout

写作能力和 locate cost

自从自个儿琢磨出 locate cost 之后便开始关注这方面问题。最近看到两篇喷 harness 问题的

第一个是 Can Bölük https://blog.can.ac/2026/02/12/the-harness-problem/ 今年2月的时候发现:

Codex uses apply_patch: It takes a string as input, which is essentially an OpenAI-flavored diff, and instead of relying on a structured schema, the harness just expects this blob to follow a strict set of rules
Claude Code (and most others) use str_replace: find the exact old text, swap in the new text. Very simple to think about. But the model must reproduce every character perfectly, including whitespace and indentation.
Cursor trained a separate neural network: a fine-tuned 70B model whose entire job is to take a draft edit and merge it into the file correctly

如果你在 Codex 用别的模型

Grok 4’s patch failure rate in my benchmark was 50.7%, GLM-4.7’s was 46.2%.

Aider’s own benchmarks show that format choice alone swung GPT-4 Turbo from 26% to 59%, but GPT-3.5 scored only 19% with the same format because it couldn’t reliably produce valid diffs.

The Diff-XYZ benchmark from JetBrains confirmed it systematically: no single edit format dominates across models and use cases. EDIT-Bench found that only one model achieves over 60% pass@1 on realistic editing tasks.

懒得看原文的我直接说结论:大家都在争论哪个模型编程更强,但很多模型都知道要改什么,失败其实发生在具体改哪里。

他做了个实验,同样的 16 个模型,只换编辑这个 tool call,改成他自己发明的 hashline,给每行内容打一个短哈希做锚点,Grok Code Fast 1 从 6.7% 直接跳到 68.3%。

Can Bölük 这个老哥非常生猛,2021年有篇博客讲他在Intel CPU 发现一个指令可以序列化/反序列化打印所有 x86 指令集。微码立功了!

他这个 hashline 也很巧思,我也是最近才琢磨明白。你仔细想就会有个疑问,为啥不直接用 行号?

有个相关的问题一直困扰我。我经常让 Gemini 去搜我博客,我博客网址都是类似 stderr-XX 其中 XX 是数字,然后 Gemini 经常把别的文章内容给我总结批判一番。我得到的结论是 LLM 不识数。

后来在学 RAG embedding vs BM25 的时候突然顿悟了,tmd 这个基于 基于语义空间的相似度匹配 有利有弊。好处是比如你检索 汽车,它能联想到 车辆,无需 FTS 那里你自己要维护一套分词近义词表。坏处是行号 223 233 它觉得很「近似」直接搞混 😂


扯远了。

然后是前几天 Armin Ronacher 的 https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/

这老哥是 Flask/Werkzeug/Jinja2作者,现在主要在撸 Pi 这个agent(值得一题的是上面的老哥在撸 oh-my-pi 这个 fork)

他发现 Opus 4.8 和 Sonnet 5 在非 Claude Code 的 harness(比如他自己的 Pi 项目)里调用嵌套 edits[] 数组时会莫名其妙地塞进一堆乱七八糟的 <antml:function_calls> 这种内部控制字符。老的模型反而没这个毛病。他推测是 A\ 家训新模型强耦合了 Claude Code。逆向发现 Claude Code 客户端对格式错误极其宽容,有一整套别名映射、Unicode 修复、静默过滤多余字段的逻辑。结果就是模型在RL中适应了格式差不多就行,反正harness 会兜底。


看完这两篇我觉得印证了我前面 locate cost 一问的所有猜想。AI 指出,真正可能的机制更朴素:

RL 训练信号里,整段重写 往往比 精确定位再小改 更容易拿到奖励。重写不会因为空白符不匹配而报错。这跟第二篇里 Armin 讲的模型在宽容的 harness 里学会了偷懒,是同一个因果链条,不需要引入纹理/结构的形而上区分也能解释。

也就是说,LLM上课答题只给答案分,不给过程分,导致背题偷懒 🤣 这个方向在研究界是有名字的,叫 process supervision / process reward model,过程奖励模型。OpenAI 那篇《Let's Verify Step by Step》基本就是在数学推理场景做这件事。

但这条路有两个真实的代价,其一是 过程标注比结果标注贵得多;其二 如果训练时的 harness 比部署时的 harness 更宽容,学出来的好过程标准本身就是错的。

所以又回到一个老生常谈的话题。各大模型厂家都在推出自己的 CLI。观察「过程」比最终结果更值钱!

我让AI去 fact check了下。果然

Claude Code

数据使用文档里明确写着两条完全独立的通道:

  1. consumer 账号里"Help improve Claude"那个 toggle 控制的是"conversation content"——如果你打开它,用于训练的数据 包括整个相关对话,连带任何内容、自定义样式或对话偏好。这是"代码/对话内容"这条线。
  2. DISABLE_TELEMETRY 一条完全独立的遥测通道,文档原话是:Claude Code 会从用户的机器连接到 Anthropic,记录延迟、可靠性、使用模式这类运营指标。这类日志不包含任何代码或文件路径。关掉这条通道要单独操作,跟训练开关是两个开关、两套机制、两份文档。

Kiro(AWS)

设置页面里直接摆着两个并列开关

  1. Content Collection For Service Improvement,关掉它就是不许训练
  2. Usage Analytics And Performance Metrics 官方描述是"这是一个单独的、用于使用遥测的设置"。

也就是说厂商自己都承认这是两套独立治理的东西——只是大多数用户可能只会想起关第一个开关。

Codex(OpenAI)

官方文档列出了它 OTel 遥测会上报的事件类型,其中包括

  • codex.tool_decision 工具调用是被批准还是拒绝,以及这个决定来自配置还是用户
  • codex.tool_result 耗时、是否成功、外加一段输出片段
  • codex.user_prompt 默认只记录长度、内容会被打码

听起来很克制,但 工具决策 + 输出片段 + 时长 这几项拼起来 就是前面说那种 过程信号,不是代码本身。精确刻画了模型在 harness 里怎么试错、被拒了多少次、跑了多久。这条 OTel 通道是靠单独的 config.toml 开关控制的,跟 ChatGPT 账号层面的训练数据开关是两件事。

Google Antigravity

方向比较模糊,它把训练相关的退出开关本身命名为 Enable Telemetry,把两件事的名字焊在一起。

Google Groups 的讨论帖里用户在问这个开关到底关不关得掉训练,官方也没给出干脆的回答。


所以接下去的推论就很简单。利用公开语料能训练出 2025年级别的sota llm。但是往后就看各家谁能拿到更多的轨迹数据了。无论靠CLI / app 装机量,还是买数据,偷数据,各显神通。其中装机量/DAU几乎正比于以后的智力天花板。所谓的 trillion tokens models 估计就是从各种日志里来的(而不是人类语料)

继续推演下去,有意思的一点是,可预见的将来,AI 的智力增长几乎全来自于coding

因为 coding 有个编译器师爷能把关,保证产出可验证!

别的什么 具身 世界模型,我觉得难了 😆

还有一个考虑的,装机量看 2C,各行业应用 2B 也很重要。比如design类的。这种“轨迹” 如何收集改进也很讲究。

但是design想了下又挺主观的。不过可以降低一些看上去很笨的地方。

甚至如果从公平的角度来说,AI厂家,除了按成本收费之外,还应该给高价值数据返钱才对。不是之前有报道说Anthropic 和 OpenAI 都签了七位数金额的 RL 环境和人类专家数据合同,预计投入还要再涨 3-5 倍。就是拿来训练“过程”的吧。


今晚娃又沉迷pad,我给他pad锁了 😆

为啥说起这个呢,我一直让娃坚持写「语音日记

但是孩子长大了,他写得越来越不耐烦了,而且我苦恼作文没有批改,所以这个日记习惯实际上成了低水平重复。

其实把复制粘贴到deepseek,提示词 “以XX年级的标准点评改进下这篇” 就能搞定,奈何我家娃太懒。

所以我一直想给娃弄一个 作文训练 app。本来想不就是AI一问一答批改么。

但是想到 locate cost 突然觉得有点难。。。。。甚至比AI coding 还难。。

代码为什么定位相对容易。 哪怕 str_replace 因为空白符不匹配而报错,它要定位的目标本身是离散、有边界的——一行代码、一个函数,有语法(AST)天然把文档切成可寻址的单元。

或者 hashline,行号就每句话一个稳定锚点,把找位置从模糊的文本匹配变成精确的 ID 查找。这招完全可以照搬到「改病句」场景。做个 diff 也容易

但写作的问题,往往根本不是一个可以圈起来的line或者span,而是句子之间关系的性质。 “这段论证缺乏内在逻辑”,“这句话和上一句衔接生硬”,“全文的语气从第二段开始飘了”——这些反馈即便你精确指出“第3段第2句”,真正需要改的可能是第1句、连接词,或者整段重组。

代码的 bug 通常局限在一个可编辑单元里,作文的"bug"经常是分布式的、关系性的。定位到具体文字之后,改哪、怎么改这一步反而更模糊,比代码多绕一层

AI说,写作其实分两个层次

  1. 可验证层
    语法错误、拼写、用词重复、被动语态滥用、句长方差、可读性指标(类似 Flesch-Kincaid 这类公式)、有没有明确主题句
    这些跟代码的 compiler/linter 是同一类东西,规则可判定
  2. 不可验证层
    论证有没有说服力、有没有原创视角、语气是否统一、是否“有意思”?
    可以叫“品味”层。只有经验丰富的人的判断。而且专业阅卷老师之间对开放式作文打分的一致性本身就不高

怎么切入呢?

后者也有一些实践,比如借鉴 AP 阅卷没,用锚定范文(anchor papers)做少样本参照。而不是让模型凭空判断“这篇好不好”

也可以做高亮 + 提问式 而非直接改。比如第 2 段第 3 句话里,“非常开心”这个词能不能换一个更具体的?比如描述一下你当时的表情或动作?

甚至可以 示例驱动:AI 给出 改前 / 改后 小对比,只改 1-2 处;然后 多轮对话,孩子自己决定要改哪里,AI 只辅助,而不是 AI 主导大改。

要么就局部训练,针对常见作文类型(记事、写景、议论),开头、过渡、结尾分别训练

这样看来,写作文直接给娃一张无限大的白纸并不好。参考 Notion 的 Block 概念,强迫孩子在输入时就把“骨架”和“血肉”分开。比如,第一步只允许输入 3 个论点(形式);第二步再针对每个论点去填充素材(质料)。通过产品机制,人为制造出“伪 AST(语法树)”。

或者阻断直接生成,只给“反向约束”。不输出完整的句子让孩子抄,扮演“刁钻的苏格拉底”。比如,当孩子写“今天我很开心”,AI 的反馈不应该是“你可以改成:今天我心花怒放”,而应该是抛出环境约束(Harness):“你当时手里拿了什么东西?你的心跳有多快?”——逼迫孩子自己去完成“从潜能到现实”的推导。

当然,做好 diff 和版本控制是基础。记录孩子打磨一句话的过程。让孩子直观看到词汇的微调是如何让语义的边界越来越清晰的。

哎,这么一拆解又有点思路了,但比预计的感觉麻烦得多啊。不过语文教育有大问题啊。明明是工程上可以细化训练的(虽然很难,过去没AI需要大量人工精力)。上课根本不讲。全靠孩子天生悟性。以上种种,今年4月才喷过一篇《语文学习和考试

作文从小学一上来就300 字 500字,其实真应该「刻意练习」的是语言 primitive。什么 铺垫、呼应、留白、对比、节奏、悬念、感官、动作、心理、对话等等,都上手了,然后再各种变化,组合。

学编程也是从少量 keywords ,赋值语句,条件,循环这样一步一步来的嘛。

过去没太好的语文教学条件,归根结底因为一个班上50个娃只有一个语文老师。

但是现在有LLM了。

Posted

stderr

grep vs sqlite 谁更适合微信聊天记录?

一个爆火的讨论

云风 @cloudwu 2026-06-29
微信的开发人员根本就不懂该怎么储存数据。这种聊天软件,文本和媒体文件分开存,文本根本就不应该保存在什么数据库(sqlite)里, 一个对话一个文本文件追加就可以了。需要搜索的时候 grep 一下性能完全符合需求。一个对话能有多少文本?一秒一个字 24 小时不间断,一年也就 30M 个字。

网上的争论都是猜测,我呢,决定让 opus 跑一局。

首先让AI去搜微信聊录表结构

微信(Android)聊天记录存储在加密 SQLite 数据库 EnMicroMsg.db 中,使用 SQLCipher(AES-256-CBC,PBKDF2 256000 轮派生密钥)。核心 message 表:

CREATE TABLE message (
    msgId      INTEGER PRIMARY KEY,  -- 本地自增 ID
    msgSvrId   INTEGER,              -- 服务器消息 ID
    type       INTEGER,              -- 1=文字, 3=图片, 34=语音, 43=视频
    isSend     INTEGER,              -- 0=接收, 1=发送
    createTime INTEGER,              -- Unix 时间戳
    talker     TEXT,                 -- wxid 或群 chatroom ID
    content    TEXT,                 -- 消息正文
    imgPath    TEXT                  -- 附件路径
);

测试设计

  • 数据量:50 万条模拟消息(模拟中度用户 ~2 年)
  • 搜索关键词微信支付服务器数据库会议周末
  • 环境:macOS Apple Silicon, Python 3.14, ripgrep 15.1, DuckDB 1.5.4, Polars 1.42
  • 每项测试 3 轮取最小值

参赛选手

分类 方案 思路
传统文本 grep (BSD) 最朴素的逐字节匹配
SIMD文本 ripgrep AVX2/NEON 并行 + 多线程
零拷贝 mmap 直接搜索 OS page cache + Python bytes.find
压缩文本 zstd 流式解压搜索 省空间,边解压边搜
索引 倒排索引 (2-gram) 搜索引擎思路,内存索引
索引 Bloom Filter 分块 概率型预过滤
RDBMS SQLite LIKE 微信的实际方案(去掉加密)
RDBMS SQLite mmap 模式 mmap I/O 加速
RDBMS FTS SQLite FTS5 (trigram) 全文搜索引擎
列式DB DuckDB contains() OLAP 列式扫描
列式DB DuckDB FTS DuckDB 的全文搜索扩展
列式文件 Parquet(zstd) + DuckDB 列式文件直接查询
DataFrame Polars lazy scan Rust实现的极速 DataFrame
DataFrame Polars in-memory 全量载入内存
并行文本 ripgrep 多文件并行 分块文件 + rg 多线程

测试结果

关键词搜索延迟(ms, 3轮最小值)

# 方案 微信支付 服务器 数据库 会议 周末 平均
1 SQLite FTS5 (trigram) 0.65 0.46 0.37 ❌² ❌² 0.31¹
2 Polars lazy scan 3.51 2.56 2.43 2.49 2.47 2.69
3 倒排索引 (2-gram) 2.71 3.26 2.78 3.24 2.37 2.87
4 Polars in-memory 2.67 3.61 2.98 3.21 3.21 3.13
5 DuckDB contains() 3.72 3.23 3.18 3.91 4.41 3.69
6 Parquet + DuckDB 4.63 4.37 4.55 4.88 4.84 4.65
7 ripgrep 多文件并行 10.65 10.26 9.48 10.32 9.05 9.95
8 DuckDB FTS (BM25) 12.53 11.77 12.85 11.82 12.78 12.35³
9 ripgrep (SIMD, 单文件) 13.24 13.77 13.40 14.14 13.10 13.53
10 mmap 直接搜索 17.00 22.62 22.86 31.36 30.52 24.87
11 Bloom Filter + 扫描 25.34 25.25 25.06 26.68 27.01 25.87
12 SQLite mmap LIKE 37.45 37.45 37.92 37.83 37.81 37.69
13 SQLite LIKE 43.13 41.40 41.50 41.51 41.46 41.80
14 grep (BSD) 139.10 136.86 141.42 122.54 124.16 132.82
15 zstd 流式解压搜索 161.32 164.22 164.56 170.23 170.73 166.21

¹ FTS5 只对 ≥3字符 的关键词有效,取3个有效关键词平均
² trigram tokenizer 无法匹配 2 字符的中文词
³ DuckDB FTS 默认 tokenizer 不支持中文,返回 0 结果(延迟仍可参考)

视觉化排名

 1.                                                            █   0.31ms SQLite FTS5
 2.                                                            █   2.69ms Polars lazy
 3.                                                            █   2.87ms 倒排索引
 4.                                                            █   3.13ms Polars in-mem
 5.                                                            █   3.69ms DuckDB contains
 6.                                                           ██   4.65ms Parquet+DuckDB
 7.                                                         ████   9.95ms rg多文件并行
 8.                                                       ██████  12.35ms DuckDB FTS
 9.                                                       ██████  13.53ms ripgrep (SIMD
10.                                                 ████████████  24.87ms mmap 直接搜索
11.                                                 ████████████  25.87ms Bloom+扫描
12.                                           ██████████████████  37.69ms SQLite mmap LIKE
13.                                         ████████████████████  41.80ms SQLite LIKE
14. ████████████████████████████████████████████████████████████ 132.82ms grep (BSD
15. ████████████████████████████████████████████████████████████ 166.21ms zstd流式解压

复合条件查询(指定用户 + 时间范围 + 关键词"会议")

方案 延迟 (ms) 倍率(vs grep)
SQLite indexed 2.80 76x
DuckDB 4.57 47x
Polars in-memory 7.02 30x
ripgrep pipe 20.47 10x
grep pipe 213.90 1x

存储大小

格式 大小 vs TSV 说明
Parquet (zstd) 8.3 MB 0.18x 列式 + 字典编码 + 压缩
zstd 压缩 TSV 12.7 MB 0.27x 纯压缩
DuckDB + FTS 26.0 MB 0.55x 含全文索引
TSV 纯文本 47.0 MB 1.00x 基线
SQLite 69.2 MB 1.47x B-tree 开销
SQLite + FTS5 116.8 MB 2.49x trigram 索引翻倍

各方案深度分析

Tier 1: 亚毫秒级(< 1ms)

SQLite FTS5 (trigram)
- 原理:对 content 字段的每个 3 字符子串建倒排索引
- 优点:查询极快(0.3-0.6ms),无需额外依赖
- 缺点:索引体积翻倍(+68MB);trigram 无法匹配 ≤2 字符的关键词
- 适用:搜索词通常 ≥3 字符的场景

Tier 2: 低毫秒级(2-5ms)

Polars (lazy/in-memory)
- 原理:Rust 实现的 DataFrame 引擎,列式内存布局 + SIMD 字符串匹配
- 优点:Parquet 文件仅 8.3MB(最小!),查询 2-3ms,复合查询也快(7ms)
- 缺点:需要加载到内存;Python 库依赖
- 杀手锏:8MB 的 Parquet 文件 + 3ms 搜索延迟,这是存储效率和速度的最佳平衡点

倒排索引 (2-gram, 内存)
- 原理:搜索引擎最经典的思路,对所有 2-gram 建 posting list
- 优点:构建仅 0.78s,查询 2.9ms,支持任意长度关键词
- 缺点:纯内存(需要序列化/加载),索引构建需要全量遍历
- 适用:append-only 数据可以增量更新索引

DuckDB contains()
- 原理:列式存储,content 列连续存放,CPU cache 友好 + SIMD 扫描
- 优点:无需专门索引即可 3.7ms;复合查询也仅 4.6ms;文件仅 26MB
- 缺点:需要 DuckDB 运行时
- 杀手锏:不建任何索引,纯靠列式布局就比 SQLite LIKE 快 11 倍

Parquet 文件 + DuckDB 零拷贝查询
- 原理:Parquet 本身就是列式格式,DuckDB 可以直接查询不需导入
- 优点:文件仅 8.3MB,不需要数据库进程,查询 4.6ms
- 缺点:每次查询需要启动 DuckDB 连接
- 杀手锏:一个 8MB 的文件就是完整的"数据库",任何语言都能读

Tier 3: 10ms 级

ripgrep 多文件并行
- 原理:把消息分块成多个文件,ripgrep 的 work-stealing 线程池并行搜索
- 优点:比单文件 ripgrep 快 ~35%(10ms vs 13.5ms)
- 缺点:文件管理复杂
- 适用:数据天然按时间分文件存储的场景

ripgrep (SIMD, 单文件)
- 原理:AVX2/NEON 每周期处理 16-32 字节,多线程(对单文件仍用单线程)
- 优点:零配置,即装即用
- 缺点:对单文件只能单线程

Tier 4: 失败/不推荐的"邪路"

Bloom Filter 分块预过滤
- 问题:中文常用 2-gram 只有 ~690 种,每个块都包含所有 n-gram,过滤率为 0
- 结论:对高频 n-gram 的数据集完全无效,白费构建时间

zstd 流式解压搜索
- 问题:Python 解压+搜索 166ms,比不压缩的 grep 还慢
- 结论:CPU 密集的解压抵消了 I/O 节省。如果数据在 SSD 上,不如直接读原文
- 可能有用的场景:数据在网络存储/HDD 上,I/O 是瓶颈时

DuckDB FTS (BM25)
- 问题:默认 tokenizer(类似 ICU word boundary)不支持中文
- 结论:需要自定义 tokenizer 或等 DuckDB 支持 trigram/CJK

mmap 直接搜索
- 表现:24.87ms,比 ripgrep 慢 2 倍
- 原因:Python 的 mmap.find() 是朴素搜索,没有 SIMD 优化
- 如果用 C/Rust 实现的 mmap + SIMD,预期接近 ripgrep 水平

推荐方案排名

如果从零设计微信聊天存储

优先级 方案 搜索延迟 存储 理由
🥇 Parquet + Polars/DuckDB 3-5ms 8 MB 存储最小、搜索极快、格式通用、append 友好
🥈 DuckDB 数据库 3.7ms 26 MB 单文件数据库、列式、SQL 查询、FTS 索引可选
🥉 SQLite + FTS5 0.3ms 117 MB 最快搜索(有索引)、但索引太大
4 ripgrep + 分块 TSV 10ms 47 MB 最简单、无依赖、人类可读

如果要"一行命令搜聊天记录"

# 方案 A#: ripgrep 搜纯文本 (~10ms)
rg "微信支付" messages.tsv

## 方案 B: DuckDB 直接查 Parquet (~5ms)
duckdb -c "SELECT * FROM read_parquet('messages.parquet') WHERE contains(content, '微信支付')"

## 方案 C: DuckDB 复合查询 (~5ms)  
duckdb -c "SELECT * FROM read_parquet('messages.parquet')
           WHERE talker='wxid_xxx' AND createTime > 1672531200
           AND contains(content, '会议')"

最终结论

"用 grep 代替 SQLite" — 部分正确,但格局太小

  1. ripgrep 比 SQLite LIKE 快 3 倍(13ms vs 42ms)— "grep 派"的论据成立
  2. 但 DuckDB/Polars 比 ripgrep 又快 4 倍(3ms vs 13ms)— 列式才是正道
  3. Parquet 比纯文本还小 5.6 倍(8MB vs 47MB)— 省空间还更快
  4. 复合查询(按人+时间+关键词)SQLite 仍然最强(2.8ms)

真正的启示

聊天记录是 append-only 数据 → 最适合列式存储 (Parquet/DuckDB)

  • 不需要 RDBMS 的事务/锁/B-tree 开销
  • 列式布局让 content 列连续存放,CPU cache 极度友好
  • zstd 字典压缩对重复模式多的聊天文本压缩率惊人(82%)
  • 追加写入只需要 append 新的 row group

微信搜索慢的根因:不是 SQLite vs grep 的问题,而是 SQLCipher 加密的 PBKDF2 256000 轮 + 行式存储对全文扫描不友好。如果用 Parquet + 硬件 AES,可以同时做到加密和极速搜索。

脑洞:终极方案

messages.parquet (8MB, zstd压缩, 硬件AES加密)
  → DuckDB/Polars 直接查询 (3-5ms)
  → 可选: 内存倒排索引 for < 1ms 搜索
  → 追加: 新消息 append 到 staging 文件, 定期 merge
  → 导出: parquet 是开放格式, 任何语言/工具可读

复现

pip install duckdb polars zstandard cryptography
python3 benchmark.py 500000     # 基础测试 (SQLite/grep)  
python3 benchmark_v2.py 500000  # 扩展测试 (DuckDB/Polars/mmap/...)

源码放在 https://github.com/est/snippets/tree/master/grep_vs_sqlite

模拟聊天记录有500,000行 一个 50MB 的 .tsv 太大了。自己让AI根据文章和最上面表结构生成一份吧。

后记

被Polars的性能震惊了。说实话如果换我肯定不会选sqlite。聊天记录又不会改。ACID的 OLTP 明显浪费

Posted

stdout

[AI] curl -NT. 导致100% CPU原因

有AI就是好使,搁以前自己得盯半天也看不明白。。。

问题确认

curl -NT. 在连接一个持续推送数据的 streaming 服务器时,会产生一个 紧密的 pause/unpause 循环,导致 100% CPU。

根因分析

-T.(注意是点号,不是减号)将 stdin 设为非阻塞模式,然后依赖 EAGAIN + CURL_READFUNC_PAUSE 机制来避免阻塞。核心循环发生在三个回调之间:

第一步 — 上传尝试失败src/tool_cb_rea.c):

  {
    rc = read(per->infd, buffer, sz * nmemb);
    if(rc < 0) {
      if(errno == EAGAIN) {
        errno = 0;
        config->readbusy = TRUE;
        return CURL_READFUNC_PAUSE;
      }
      /* since size_t is unsigned we cannot return negative values fine */
      rc = 0;
    }
  }

stdin 没数据 → read() 返回 EAGAIN → 设 readbusy=TRUE → 暂停上传。

第二步 — 下载数据到达立刻解除暂停src/tool_cb_wrt.c):

  if(config->readbusy) {
    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);
  }

streaming 服务器持续发送数据 → tool_write_cb 被不断调用 → 每次都无等待地立刻解除上传暂停

第三步 — 进度回调只有 1ms 退避src/tool_cb_rea.c):

  if(config->readbusy) {
    if(ulprev == ulnow) {
#ifndef _WIN32
      waitfd(1, per->infd);
#else
      /* sleep */
      curlx_wait_ms(1);
#endif
    }

    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);
  }

tool_readbusy_cb 有 1ms 的 waitfd 退避,但被 tool_write_cb 的无等待解除抢先了。

完整的死循环(每次 curl_multi_perform 内)

lib/transfer.cCurl_sendrecv 中,处理顺序是先下载后上传

  if(k->keepon & KEEP_RECV) {
    result = sendrecv_dl(data, k);
    if(result || data->req.done)
      goto out;
  }

  /* If we still have writing to do, we check if we have a writable socket. */
  if(Curl_req_want_send(data)) {
    result = sendrecv_ul(data);
    if(result)
      goto out;
  }

所以单次迭代内:
1. sendrecv_dl → 读到 streaming 数据 → tool_write_cb → 看到 readbusy=TRUE立刻解除暂停(零等待)
2. sendrecv_ultool_read_cb → stdin EAGAIN又暂停readbusy=TRUE
3. 回到 curl_multi_poll → 下载 socket 有数据 → 立刻返回(不会 sleep)
4. 重复 1-3

lib/easy.c 的主循环:

  while(!done && !mresult) {
    int still_running = 0;

    mresult = curl_multi_poll(multi, NULL, 0, 1000, NULL);

    if(!mresult)
      mresult = curl_multi_perform(multi, &still_running);

curl_multi_poll 的 1000ms 超时形同虚设——因为下载 socket 始终可读,poll() 立刻返回。

结果:每秒跑上千次这个循环,每次包含多个系统调用(pollreadwrite),CPU 直接打满。

问题本质

stdin 没有被加入 curl_multi_poll 的 pollset。只有网络 socket 参与了 poll。所以 curl 无法高效地"同时等待 stdin 和网络数据",只能靠 tool_write_cb 里的盲目 unpause 来弥补,在 streaming 场景下退化成忙等。

用户侧解决方案

如果你不需要从 stdin 上传数据(只是想看 streaming 响应),不要用 -T.

# 正确的方式:只看 streaming 输出
curl -N https://streaming-server/events

# 或者重定向 stdin 为空
curl -NT. https://streaming-server/events < /dev/null

< /dev/null 会让 read() 返回 0(EOF)而不是 EAGAIN,不会进入 pause/unpause 循环。

如果确实需要双向 streaming(边上传 stdin 边接收),目前没有完美的用户侧 workaround。可以考虑:

# 用管道加一层缓冲,减轻空转(效果有限)
cat | curl -NT. https://streaming-server/events

源码级修复思路

如果要在源码层面修复,最直接的方案是tool_write_cb 中去掉盲目 unpause,改为只在 tool_readbusy_cb 中带退避地 unpause,并增大退避时间:

// tool_cb_wrt.c — 去掉这段,或加条件限制
// 不再从 write callback 无脑解除上传暂停
if(config->readbusy) {
    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);  // 删除或改为有条件触发
}
// tool_cb_rea.c — 增大退避时间
if(config->readbusy) {
    if(ulprev == ulnow) {
        waitfd(100, per->infd);  // 从 1ms 提高到 100ms
    }
    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);
}

更彻底的方案是把 stdin fd 加入 curl_multi_poll 的 extra fds,但这需要改 curl_multi_poll 的调用方式,涉及面更大。

Posted

stdout

或许「数羊」真是个有效的入睡法

无聊刷博客,《AI如何导致和修复了我的失眠问题》 这老哥真猛,通过统计规律发现自己 失眠 的罪魁祸首。(不剧透。感兴趣点开阅读)

我自己也写过 快速入睡的技巧和原理 以及 强行入睡方法 v2.0 其实我都忘记这个 2.0 方法了。都不知道自己当时怎么想到这个办法的,原来自己写的东西也能常看常新(老登健忘症😂),所以还是要多写,多记录

本文章的讨论都是基于这个 2.0方法的,接着看之前请务必点开 2.0 那个链接,不长,一会儿就看完。

然后我就无聊让AI 评价一下这个 2.0 方法是不是真的,然后AI说真有学者在搞类似的,关键词 :

  • 睡前“认知打断 / imagery distraction
  • cognitive shuffle
  • serial diverse imagining (SDI)

然后我去搜了下

TikTok和Instagram上爆火的“认知洗牌法”,火到连医生都开始推荐。选一个随机的单词(比如 cake,蛋糕),专注于这个词的第一个字母(C),然后列出一串以这个字母开头的词:cat(猫)、carrot(胡萝卜)、calendar(日历)等等,一边列举,一边在脑中想象这些词的画面。当你准备好了,就转到下一个字母(A),重复上述过程,继续进行下去(K、E),直到你睡着或者想换一个新词为止

嗯,和我的方法居然殊途同归,只是更加麻烦,需要调用大脑的语言区。

但是自媒体这个标题让我产生了兴趣 《别再数羊了》,我恰好周末刷了 西藏那曲拉姆 的视频 藏族人家里几十上百头牦牛,如何识别是不是自己家的?

然后突然发现一个被大多数人忽略的惊人的事实:放牧人的白天是极度无聊和空虚的,以至于他/她们能辨识自己每一头羊的特征、性别,甚至给每头羊起个名字。视频说牦牛和人一样,每一头都有独一无二的毛色、长相、形态等。

脸上有点黑?叫小黑;黑白相间的?叫花脸。按脾气起名字,暴躁哥,温顺妹;还有谁和谁喜欢一起吃草,小牛的母牛妈妈是谁等等。

所以这两件事就串起来了。「数羊」这事儿一定是牧区的人发明出来的,比如英国乡下,但是城里人哪里知道这些细节啊。

牧民晚上躺床上,没事干,说不定就给自己牛羊编故事造剧情啊。而且关键词是「数」,你不能陷入一个逻辑推理细节,必须不停地轮换,把羊变成高频切换的具象个体(有名字、有外形、有性格),才能保证大脑疲惫然后入睡。

AI总结:

对于古代或乡村的牧羊人来说,夜晚盘点羊群是一天结束时最让人安心的闭环。他们脑海中的羊,确实是毛发质感、体态特征各异的具象实体。
现代城市人剥离了这种生活语境,把一幅丰富的田园3D渲染图,降维成了枯燥的Excel递增表格,自然也就失去了助眠的神奇功效。

我觉得AI真有点东西的。清点生产资料也是被忽略的一环。如果我睡前都能盘点自己该做的事儿都做了,羊吃饱了,明天会更好,没啥落下的,那我肯定也睡得安稳啊。

但是现代人难就难在很多事是跨很多天的,入睡是非常不情愿的。如果有精力很多人甚至愿意熬夜。

都怪爱迪生,本来以为发明电灯泡给人类漫长的黑夜带来光明,没想到人类却用这玩意来加班和难眠!!😤 😤 😤 如果太阳下山就睡觉,就算失眠4小时你到23点也睡着了 😂

Posted

stderr