环境可供性:智能的一半是取到正确数据
没有工具的模型,只能靠提示词获得额外信息。提示词工程在那个阶段几乎是唯一杠杆:把背景写清楚,把例子给够,把格式约束好,让模型在已有上下文里尽量选对路径。 一旦模型拥有主动提取外部数据的能力,问题就变了。智能不再只取决于模型内部能处理多少信息,也取决于它能不能从环境里拿到正确的信息。 Prompt 是喂信息,工具是取信息 prompt-only 系统的工作方式像开卷考试,但试卷、教材和草稿纸都必须提前塞进同一个信封。用户没塞进去的信息,模型默认看不见。它可以猜,可以泛化,可以从训练数据里补常识,但无法可靠知道当前项目、当前日志、当前数据库、当前 API 文档发生了什么。 工具调用改变了边界。ReAct 把 reasoning 和 acting 交织起来,让模型一边推理,一边对外部知识库或环境采取动作获取观察结果。Toolformer 进一步证明,语言模型可以学习在合适时机调用 API,并把结果纳入后续预测。SWE-agent 的核心贡献也不是“又写了一个 coding prompt”,而是强调 Agent-Computer Interface:给 agent 一个适合读文件、改...
Context Paging:Compact、外部化、恢复与 Memory 生命周期
长程 Agent 的限制不只来自 context window 大小。窗口里装了什么、旧材料怎样退出、compact 后哪些内容恢复、外部记忆何时失效,同样决定任务能否连续推进。 操作系统用 paging 把有限内存接到更大的外部存储。Agent 的上下文管理也在形成类似结构:当前窗口是工作集,文件、索引和 memory 是外部状态,compact 负责压缩历史,检索和恢复机制负责按需换入。Prompt cache 则位于另一条轴上,影响相同前缀的成本和延迟,却不减少它占用的 context window。 Context Paging 管理的是工作集 长窗口解决“能否放入”,没有保证“能否稳定使用”。《Lost in the Middle》显示,模型利用长上下文中的信息时会受到位置影响。Coding 会话还会积累失败命令、过时文件内容、被否决方案和大段日志。即使它们仍在窗口内,也未必属于当前任务需要的工作集。 Context paging 的目标不是保存所有历史,而是在每次推理前组织一个可执行工作集: 当前目标与完成条件; 仍然有效的约束; 关键证据及其来源; 修改状态与未...
裸模型为什么像抽卡
大模型写代码有一种很危险的爽感:同一个问题,上一轮胡说八道,下一轮突然给出一段漂亮实现。失败的时候像差一点,成功的时候像中奖了。 这种体验很像抽卡。不是因为使用 AI 等同于赌博,而是因为裸模型的反馈结构具备几个相似特征:结果有波动,高分样本偶尔出现,用户很难提前判断下一轮是不是高分,于是自然产生“再试一次”的冲动。 把这种冲动看清楚,比单纯争论模型聪不聪明更重要。很多 AI coding 的燃尽感,不来自模型太弱,而来自人把自己的注意力押在一次又一次低可见度的重试上。工程工作被悄悄改造成了抽卡循环。 输出是分布,不是一个点 OpenAI 的 reproducible outputs 文档说得很直接:Chat Completions 和 Completions API 默认是非确定性的;设置 seed、固定参数和 system_fingerprint 后,也只是接近可复现,仍不能保证完全相同。 这意味着模型能力不是一个确定点,而是一段分布。一个平均能写出 70 分答案的模型,在具体一次调用里可能落到 50 分,也可能冲到 90 分。用户看到的不是“模型真实能力”,而是一次采...
Harness 的本质:把随机模型锁进可验证的箱体
把大模型当成一个确定性函数,很容易高估它。更合适的工程抽象是:模型在当前上下文、参数和工具环境给出的概率空间里,采样出一个看起来最合适的结果。 这意味着同一个模型在同一类任务上的能力并不是一个点,而是一段分布。一个平均能做 70 分的模型,在某个具体任务上可能落到 50 分,也可能冲到 90 分。Harness Engineering 的价值,不是把 70 分模型魔法般变成 95 分模型,而是把它的行为压进一个更小、更可验证的区间,让低分尾部少出现,让高分路径更容易复现。 这个区间可以叫“箱体”。Spec、测试、工具、循环、权限、外部状态、子 Agent、hook,都是箱体的不同面。没有箱体时,模型在荒野里寻路;有箱体时,模型在一条被标出边界和检查点的路线上前进。 模型不是函数,而是概率场 OpenAI 的 reproducible outputs 文档把一个事实说得很直白:Chat Completions 和 Completions API 默认是非确定性的;即使设置 seed、保持参数和 system_fingerprint 一致,也只是“mostly determin...
AI 编程让人类更累——从 Vibe Coding 到 Brain Fry 的认知负荷真相
导语 2025 年初,Andrej Karpathy 在 X 上定义了一个词——Vibe Coding:"完全交给 vibes,拥抱指数级增长,忘记代码的存在。"他用语音输入加上 Cursor Composer 与 Claude Sonnet 协作,"Accept All"不读 diff,代码超出自己理解范围。这个定义专门标注了适用场景:throwaway weekend projects。 一年后,这个词被 Collins Dictionary 评为 2025 年度词汇。Y Combinator W25 批次 25% 的创业公司代码库 95% 由 AI 生成。Vibe Coding 从周末实验变成了产业现实。 与此同时,另一条叙事线在英文科技世界悄悄展开:用 AI 写代码的人,开始报告一种新类型的疲惫。不是传统意义上"加班太久"的 burnout,而是一种此前在编程工作中几乎不曾出现的认知消耗。 Andrew Ng 在 LangChain Interrupt 会议(2025 年 6 月)说了两句话,一句承认疲惫——“W...
回到工程:计算理论怎样改变写代码的眼光
这个系列从《计算的本质》重新出发,用 Python 重写核心实验,再把每个模型映射到 Java 程序员熟悉的工程结构。一路走下来,主题并不是记住更多术语,而是换一种方式观察程序。 程序可以是语法树,执行可以是规约,协议可以是自动机,嵌套可以靠栈,通用计算可以由解释器承载,分析工具也可以承认不精确并保持有用。 本文不再引入新模型,只把前面十几篇文章收束成一张工程地图。 概念地图 先把系列里最重要的模型压成一段可运行的概念地图。 12345678910concept_map = [ ("AST / 语义", "解释器、DSL、规则执行"), ("DFA / NFA / 正则", "协议状态、校验器、词法分析"), ("PDA / 栈", "parser、嵌套结构、调用栈"), ("图灵机 / 通用性", "VM、脚本引擎、程序作为数据"), ("停机问题 / 抽象解释", &...
抽象解释:用保守近似理解程序
停机问题说明,对任意程序做完备且正确的停机判断是不可能的。工程分析工具并没有因此失去意义。它们换了目标:不追求精确理解所有程序,而是用可计算的抽象信息给出有用结论。 抽象解释就是这个方向的代表。它把具体值集合映射到抽象域里,用保守规则模拟程序执行。只要抽象规则覆盖了所有可能的具体行为,分析结果就可以用于告警、优化或拒绝高风险程序。 本文用区间分析做一个最小模型:变量不再保存单个整数,而是保存可能取值范围。 从具体值到抽象值 具体执行关心单个值: 123x = 3y = 5z = x + y = 8 抽象执行关心值的集合。若只知道 x 在 0 到 10 之间,y 等于 5,就可以推出: 123x in [0, 10]y in [5, 5]z = x + y => z in [5, 15] 这个结果不精确。z 不一定等于区间里的每个数,但所有可能结果都被包含在 [5, 15] 里。保守性比精确性更重要:不能漏掉可能发生的值。 区间抽象域 区间可以用两个端点表示。 12345678910111213141516from dataclasses import dataclass@...
停机问题:为什么某些判断没有通用程序
上一篇文章用寄存器机解释器说明了通用性:程序可以编码成数据,解释器可以读取这份数据并模拟执行。通用性带来强大表达能力,也带来一个边界问题:能不能写一个程序,判断任意程序在任意输入上是否会停机。 答案是否定的。停机问题说明,不存在一个对所有程序和输入都正确的通用停机判定器。工程里可以做超时、步数限制、静态分析和资源预算,但这些方法要么不完整,要么会保守拒绝,要么只覆盖有限范围。 本文先用一个可运行的有界检查器展示工程近似,再用自引用反证解释为什么通用判定器不存在。 问题形式 停机问题可以写成一个理想函数: 1halts(program, input_data) -> True / False 它的承诺是: 返回值 含义 True program(input_data) 最终会停机 False program(input_data) 会一直运行 难点在“任意程序”和“任意输入”。判断某个具体程序很容易;判断所有程序则会遇到自引用。 工程里常见的近似:有界执行 最朴素的方法是只运行有限步。如果步数内停机,就返回 True;如果步数用完还没停,就返回 Fa...
通用性:一种机器怎样模拟另一种机器
前面的文章已经走过两条计算模型路线。图灵机用纸带和指令循环表达计算,lambda 演算用函数应用和表达式规约表达计算。Church 编码进一步说明,数字和布尔值也可以由函数行为构造出来。 这些模型看起来差异很大,却都能表达同一类可计算过程。通用性的核心在于:一种机器可以把另一种机器的程序和数据编码成自己的数据,然后按规则解释这份编码。 本文用一个小型寄存器机解释器展示“程序作为数据”这件事。示例不会追求指令集完整性,只展示通用解释器的基本形状。 从专用机器到通用机器 DFA、PDA、某个固定规则表的图灵机,都像专用机器。规则写死以后,它只能识别或执行某一类任务。 通用机器多了一层编码: 1encoded_program + input_data + interpreter -> output_data 程序不再只是宿主语言里的代码,也可以是一段数据。解释器读取这段数据,根据其中的指令改变配置。只要编码方式足够表达指令和数据,解释器就能模拟很多不同程序。 这个结构正是虚拟机、脚本引擎和 DSL runtime 的共同骨架。 场景 程序数据 解释器 JVM by...
不用数字也能计算:Church 编码
lambda 演算的基本语法只有变量、函数和调用。前一篇已经写出规约器,说明函数应用可以通过替换一步步化简。 接下来的问题是数据从哪里来。日常编程语言有数字、布尔值、条件表达式和集合;纯 lambda 演算没有这些原语。Church 编码给出一种极端答案:数据可以表示成行为。布尔值是“选择左边还是右边”的函数,数字是“把某个函数重复执行多少次”的函数。 本文用 Python 高阶函数实现 Church boolean 和 Church numeral,再把它们映射回工程直觉。 数据也可以是行为 普通编程里,数字 2 看起来是一块数据。Church 编码换一个定义: 12 = 接收函数 f 和初始值 x,然后执行 f(f(x)) 也就是说,数字不再保存为整数对象,而是保存为“重复应用函数的次数”。 布尔值也类似: 12true = 选择第一个分支false = 选择第二个分支 这个视角把“值是什么”改成“值能做什么”。在 lambda 演算里,只要函数和调用足够表达这些行为,就可以构造出数字、布尔值和条件选择。 Church 布尔值 Church boolean 的定义很短。 ...





