半夜冲奶粉,我用码道 Agent 写了个"会吹凉奶瓶的倒计时",还顺手给牛顿冷却定律做了个 CT
一、这玩意儿是干嘛的
新手爸妈都懂那个抓狂的瞬间:奶刚烧开 100°C,宝宝哭得撕心裂肺,你手背试了半天也试不出到底还要等多久才能到能入口的 40°C。倒多了凉透,倒少了烫嘴。
我做了个网页:选杯型、填水温和室温、点开始,它给你一个实时倒计时——“再等 8 分 12 秒凉到 40°C,可以喂”。旁边一条温度曲线往下走,走到 40 那条虚线,正好归零。
听着就是个"套公式"的小工具对吧?难点不在这。难点在于:我凭什么信这个倒计时是准的?我总不能说"因为我用了牛顿冷却定律,书上这么写的"。所以这个项目真正花心思的地方,是怎么给它建一条能被别人一键复现、又骗不了人的验证链。这条链我差点自己把自己骗过去,是评审 Agent 一巴掌扇醒的。这个后面细说。
先给结论:最终这个模型对文献实测的冷却数据拟合优度 R² = 0.9999,三种独立算法互相比对最大偏差 3.4×10⁻¹² °C,数值积分器的四阶收敛比实测 16.71(理论值 16)。整个项目是喂华为云码道 CodeArts Agent一轮一轮建的,我只负责出题、验收、和填它挖的坑。说白了,代码是它写的,但"这代码到底能不能信"这件事,是我跟一个 AI 评审较真较出来的。
二、为什么做这个
我表姐家孩子那阵子,半夜三点起来冲奶是常态。她说有回把 100 度的水直接兑了点凉的就塞给宝宝,烫得哇哇哭,她比自己挨烫还难受。我那天正好在研究散热,随口说"这不就是牛顿冷却定律吗,一个指数衰减,能算"。
话一说出口我就知道活儿来了。真要算,得回答一堆问题:k(那个衰减快慢的系数)到底是多少?不同杯子差多少?凭什么说我算的时间是对的?——每一个问题背后都是一个能翻车的坑。
先说 k 这个系数。它不是一个魔法数字,是能从物理量推出来的:k = h·A / (m·c_p),h 是对流换热系数、A 是水暴露在空气里的表面积、m 是水的质量、c_p 是水的比热容。玻璃杯口大、h 高,k 就大,凉得快;保温杯 h 极低,k 小,凉得慢。所以只要给每个杯型定一组 (h, A, m),我就能反算出它专属的 k,进而算出"从 100 度凉到 40 度要几分钟"。
选题我给自己三条铁律:一秒看懂、有客观真值、别撞车。升旗、头像框、版图、中秋月相、日地月 3D、会算题的烟花这些获奖过的全划掉。"冲奶倒计时"这个角度,带真实痛点、又能拿物理定律对拍,行。
三、先跑起来看看
零运行时依赖,纯 Node 20+ ES Module,clone 三条命令:
gitclone https://atomgit.com/lskcode/formula-cooler.gitcdformula-coolernode--test# 15 项测试全绿nodebin/dev.mjs# http://localhost:3000玻璃杯、奶瓶、保温杯、纸杯四种预设,切一个,倒计时立刻变。保温杯那条线明显躺得平——凉得慢,符合常识。页面上那个大号的"剩余时间"是每秒刷新的,走到 40°C 那条虚线,数字归零,显示"可喂 · Safe to feed"。我特意按自己那套审美做的:纯深底#0b1120,只有一处冷蓝#4d9dff,没有花里胡哨的发光描边。工具嘛,别抢戏。
想验证模型准不准,一条命令跑外部锚定:
nodeevidence/external_check.mjs# k_ref = 0.024958 /min, R² = 0.99990这条命令做的事很朴素:把一组文献里别人用温度计实测出来的热水冷却数据喂给 fitK,让它反解出 k,再用闭式解去描这条曲线,算拟合优度。R² 越接近 1,说明"牛顿冷却"这个假设越贴真实世界。这一步不依赖我前面任何一段代码的自我循环,是实打实拿外部数据打的分。
四、怎么"钉"码道 Agent
跟上一篇一样的套路:码道跑在华为云上,只能在我登录的 AtomGit 账号里干活,参赛截图带我账号。提示词必须钉到函数级,结尾永远挂那句铁律:
直接建文件并
git add -A && git commit && git push,不要只描述、不要贴代码正文,只回复 git log + 文件树 + 测试 pass/fail。
第一轮的核心提示词(原样):
【R1 · 冲奶钟 formula-cooler 核心算法层】 建 public 仓库 formula-cooler。Node 20+ ES Module,零依赖,node:test。 - src/newton.mjs closedForm(T0,TAmb,k,t)=TAmb+(T0-TAmb)*exp(-k*t) timeToReach(T0,TAmb,k,Ttarget)=ln((T0-TAmb)/(Ttarget-TAmb))/k - src/numerical.mjs integrateRK4 数值积分 dT/dt=-k(T-TAmb) - src/calibrate.mjs fitK 用 ln(T-Tamb) 线性化最小二乘反解 k - test 硬断言:closedForm(100,25,0.005,60)≈68.066(±0.001) 直接建文件并 git commit && git push,只回复 git log + 文件树 + 测试 pass/fail切成 5 轮喂:R1 核心算法、R2 输入层+物理反演、R3 前端倒计时、R4 三算法对拍、R5 README。每轮跑完我本地git clone自己node --test复核——码道说绿不算绿。
五、架构:三把尺子量同一件事
牛顿冷却定律的数学内核就一个 ODE:dT/dt = -k(T - T_amb)。我让码道用三种完全不同的方式去解它,再互相比对:
| 路径 | 方法 | 独立性 |
|---|---|---|
| closedForm | 解析解T∞+(T0−T∞)e^(−kt) | 基准 |
| RK4 | 四阶龙格库塔数值积分 | 真独立(不看解析解) |
| fitK | 从数据点线性回归反解 k | 起初是同源(后修) |
方案上我也犹豫过要不要引入辐射散热的非线性项(严格说牛顿冷却只是对流主导的近似),但冲奶这个温区(40–100°C)对流占绝对主导,加辐射项是过度设计,我把它写进 README 当"已知不足"。
还有个取舍是关于"要不要联网查实时室温"。我一开始想调天气 API 拿用户所在城市的当前气温当 T_amb,后来放弃了:一是引入外部依赖破坏了"零依赖"这个我很在意的工程约束,二是室内温度和室外温度差得远,反而不准。最后让用户自己填室温,一个滑块搞定,简单可靠。做小工具最怕的就是为了"智能"把简单事情搞复杂。
六、核心算法,掰开揉碎
解析解一行搞定:
// src/newton.mjsexportconstclosedForm=(T0,TAmb,k,t)=>TAmb+(T0-TAmb)*Math.exp(-k*t);exportconsttimeToReach=(T0,TAmb,k,Ttarget)=>{if(Ttarget<=TAmb)thrownewRangeError('水不会自己凉到室温以下');if(Ttarget>=T0)thrownewRangeError('目标温度不低于初温,无需冷却');returnMath.log((T0-TAmb)/(Ttarget-TAmb))/k;};RK4 数值积分,完全不用上面的闭式解,只认微分方程本身:
// src/numerical.mjsexportfunctionrk4Step({T,Tamb,k},dt){constf=(temp)=>-k*(temp-Tamb);constk1=f(T),k2=f(T+dt/2*k1),k3=f(T+dt/2*k2),k4=f(T+dt*k3);returnT+dt/6*(k1+2*k2+2*k3+k4);}fitK 反演:把T(t)=T∞+(T0−T∞)e^(−kt)两边取对数,ln(T−T∞)=ln(T0−T∞)−kt,就成了y = b − k·t的直线,最小二乘求斜率即得 k:
// src/calibrate.mjsexportfunctionfitK(points,Tamb){constxs=points.map(p=>p.t_min);constys=points.map(p=>Math.log(p.T_C-Tamb));// 标准 OLS,k = -slopereturn-slope(ols(xs,ys));}// 最小二乘斜率:slope = Σ(x-x̄)(y-ȳ) / Σ(x-x̄)²functionols(xs,ys){constmx=avg(xs),my=avg(ys);letnum=0,den=0;for(leti=0;i<xs.length;i++){num+=(xs[i]-mx)*(ys[i]-my);den+=(xs[i]-mx)**2;}returnnum/den;}七、差点栽在"假独立"上
这一节是我这个项目最大的收获,也是被评审 Agent 逼出来的。
我第一版的得意之作为 R4:把 closedForm、RK4、fitK 三路跑同一条曲线,结果最大偏差 3.4×10⁻¹² °C,我截图的时候心里美啊——三个算法对到小数点后 12 位,这验证多硬!
然后我把代码丢给技术评审 Agent。它回了一段让我后背发凉的话,核心是:
closedForm↔RK4 是解析 vs 数值,真独立。但 fitK 是从同一个 exp 律反解出来的,你 selfcheck 里喂给它的还是"无噪声、由同一 exp 律生成"的数据,回收 k 到 1e-13 是必然,不校验物理。三路对拍对物理护城河是自证。
我愣了半天。它说得对。这就像我出三道题,其实三道题答案都抄自同一份标准答案,然后我宣布"你看三个答案完全一致,说明我算对了"——一致是应该的,但它没证明任何东西。真正该问的是:这套模型对不对得上现实世界里那杯真实在凉的水?
这个教训我觉得比代码本身值钱。它总结成一句话就是:"多个方法互相印证"只有在这些方法真的独立、且至少有一个锚定了外部现实时,才叫验证;否则只是把同一个假设复述了三遍。我后来把这条写进了项目的 README,也写进了我自己做这类"可验证小工具"的检查清单。
我当场补了一个 R6 修复轮,三件事:
第一,加外部真值锚定。让码道内置一组文献实测的热水自然冷却数据(T0=92°C、室温 21°C、每 2 分钟一个点共 20 个点),用 fitK 去反演它的 k,再用 closedForm 算拟合优度:
// evidence/external_check.mjsconstk_ref=fitK(REFERENCE_SERIES,21);// 反演实测 kconstr2=computeR2(REFERENCE_SERIES,k_ref,21);// 模型 vs 真实观测// 断言 r2 > 0.99结果:k_ref = 0.024958 /min,R² = 0.99990。这下牛顿冷却对真实观测成立,不再是三个算法关起门来自嗨。
第二,加一条真正独立于解析解的数值验证:RK4 步长减半收敛测试。四阶方法的误差应该随步长减半而降到 1/16。我让码道用 dt=0.4/0.2/0.1 各跑一遍,比对误差比:
// test/numerical.test.mjs —— 只依赖 RK4 自身,不碰 closedFormconstE=(dt)=>Math.abs(rk4(dt)-closedFormExact);assert.ok(Math.abs(E(0.4)/E(0.2)-16)<3);// 实测 16.71实测比值 16.71,四阶收敛坐实。这条是纯数值严谨性,跟物理模型对不对无关,但能证明"我的积分器没写错"。
第三,修了个功能硬伤。评审还发现:我 validate 里校验了 volume_ml,但 k 计算根本没用体积,质量取的是 preset 固定值——那"240ml 比 120ml 凉得慢"这个常识在代码里压根不成立。改成m = 密度×体积、面积按体积 2/3 次幂缩放,volume 才真正驱动 k。
八、测试:15 项
现在测试链是这样的,每一环锚不同的东西:
// 解析解基准assert.ok(Math.abs(closedForm(100,25,0.005,60)-68.066)<0.001);// 数值 vs 解析 真独立// RK4 四阶收敛 独立于解析解// fitK 从实测数据反演 + R²>0.99 外部锚定// volume 驱动 k 的单调性九、真实的坑
坑 1:假独立。上面第七节整节都是这个。这是我这个项目最值钱的教训——多算法互洽不等于验证,除非它们真的独立、且至少有一路锚定外部现实。
坑 2:收敛比不总是干净的 16。我算 E(0.4)/E(0.2)=16.71 很满意,再算 E(0.2)/E(0.1)=55.63 就懵了。后来想通:dt=0.1 时离散误差已经小到接近浮点舍入误差的量级,比值被舍入噪声搅乱了。理论只在"离散误差主导"的区间成立。这个我也如实写进 README,没藏。
坑 3:volume 是个摆设。评审揪出来的功能硬伤,校验了却不用。改完之后,我把"体积驱动 k"这条也补成了测试:
// test/physics.test.mjs —— 常识必须在数值上成立test('同杯型,水越多凉得越慢',()=>{constk120=kPerMinute(PRESETS.glass_mug,120);constk240=kPerMinute(PRESETS.glass_mug,240);assert.ok(k120>k240);// 120ml 的 k 更大 → 凉得快assert.ok(timeToReach(95,22,k240,40)>timeToReach(95,22,k120,40));});坑 4:码道配额中途耗尽 + 上下文反复顶满。跑到后面 deepseek-v4-flash 弹配额不足,切 GLM-5.2;上下文几度顶到 100% 自动压缩,压缩后它偶尔会"忘记"前面定的接口,得在提示词里把函数签名再钉一遍。这些都得盯着,不能撒手。
十、提效数据
| 环节 | 码道 Agent | 我 |
|---|---|---|
| 建仓库 + 写 20 个文件 | ✅ | 出题 |
| 三算法对拍初版 | ✅ | 验收 |
| 识破"假独立" | ❌ | ✅ 评审 + 我 |
| 外部 R² 锚定 + 收敛测试 | ✅ 实现 | 定方案 |
| volume 驱动 k 修复 | ✅ | 评审发现 |
| 写这篇文章 | ❌ | ✅ |
码道写代码是真快,你需求钉得越细它越准。但它会把"自证"当"验证"交给你——这种认知层面的坑,得靠人(或你自建的评审 Agent)兜。
具体说,这个项目从出题到能跑,码道大概花了不到一小时把 20 多个文件全建出来、测试全写出来,这个速度我自己敲至少两天。但真正让我这个项目"站得住"的那三个动作——识破假独立、决定用文献实测做外部锚定、要求 RK4 步长减半收敛测试——没有一个是码道主动想到的。它是个执行力极强的初级工程师,你让它干嘛它干嘛,但"这件事该不该这么验"这种判断,它给不了你。所以我的体会是:用 AI 干活,人的价值正在从"写代码"往"定义什么叫写对了"上移。
十一、五维自检
眼前一亮:会吹凉奶瓶的倒计时,新手爸妈一秒懂。
可验证护城河:外部实测 R²=0.9999 + RK4 四阶收敛 16.71 + 解析/数值双路独立,
node evidence/external_check.mjs一键复现。原创度:三算法交叉对拍 + 破自证的方法论,不是套模板。
工程质量:15 项测试、零依赖、分层清晰。
诚实度:假独立、收敛比噪声、volume 摆设、辐射项未建模——全写进 README。
这五条里我最看重的其实是最后一条。一个项目敢不敢把自己没修完的坑、模型不成立的边界条件明明白白写出来,比它堆了多少功能更能说明作者有没有真懂。我见过太多"一切顺利"的参赛 demo,反而不敢点进去看。我这个 R² 只有 0.9999 不是 1.0、Charleston 那种大潮差站误差会放大、dt 太小时收敛比会失真——这些我全留着,因为它们才是这个项目真实的样子。
十二、本地跑 + 写在最后
nodebin/dev.mjs# 倒计时 UInodeevidence/external_check.mjs# 对文献实测算 R²# k_ref = 0.024958 /min, R² = 0.99990最后说点掏心窝的。这个项目一开始我以为"三个算法算出同一个数"就是铁证,直到被评审点破那三道题其实抄的同一份答案。真正的验证,是你得敢把模型扔给一个它没见过的现实——那杯真在凉的水、那份文献里别人实测的温度序列——然后看它对不对得上。
R² 从"三个算法互洽"的自嗨,到"对真实观测 0.9999"的踏实,中间隔的就是评审那一巴掌。AI 能帮你把代码写得飞快,但分辨"算得一致"和"算得对"这两种截然不同的事,眼下还得靠人。
我现在的习惯是:任何"看起来在自我验证"的东西,先逼自己回答一句——"如果我把这个结论扔掉,还有什么外部证据能撑起它?"答不上来,就是自证。这个习惯放在写代码上叫交叉验证,放在别的地方,大概也叫独立思考。
仓库在这,欢迎 clone 下来跑,也欢迎拿你家那杯正在凉的奶来对:https://atomgit.com/lskcode/formula-cooler
这是我"会算 XX 的小工具"系列第二篇。上一篇算了潮水,这一篇算了奶瓶,下一篇打算去山上算"几点能登顶"。三篇用的是同一套打法:选题要一秒看懂、护城河要有客观真值、评审要真敢挑刺。如果对你有启发,点个赞收藏一下,是我继续肝的最大动力。