模型不是变量,记录才是
模型不是变量,我认为记录才是。
让模型直接查看你的代码仓库,问它某件事为什么会出问题,它会给出一个答案。答案条理清晰,能指出具体机制,还能讲清问题是怎么一步步走到这一步的。问题恰恰在于它“解释得通”,而不是它真的可靠。无论答案最终落在记录里的某个具体数值上,还是落在“通常就是这样”的推断上,看起来都差不多——可这两者里,只有前者算证据。
我在两个系统上、用三个模型,把同一个问题放在四个不同级别的证据下去问。我问的从来都不是“帮我找 bug”,而是“这件事已经发生了,找出它是怎么发生的”。
让我担心的是那种“证据差一点点就够用”的输出。它手里的信息,足够构建一个像样的假设,却不足以区分这是真实原因,还是一个看起来合理的猜测。它给了两个猜测,让我去查之前没查过的地方。其中一次是死胡同,另一次是我确实不知道的真实问题。可两次的答案里,没有任何信息能告诉我哪个是哪个。
一个碰巧猜对的假设,和一条真实原因,追查起来的成本完全一样。你只有到最后才能知道答案属于哪一种。
四个层级,以及每一层消除了什么
我不再把这看作“增加上下文”。每一层实际上是在拿掉模型本需要去猜测的某样东西,这样理解更有用。
第一层:宽泛的代码仓库访问。 把整个仓库丢给它,然后直接提问。这一层什么也没拿掉。得到的答案在结构上说得通,会点名一些并未牵涉其中的组件,而且它给出的自信程度,和三级之后的正确答案几乎没有差别。如果你的团队试过一次,得到一个自信但错误的答案,然后断定“这工具还没成熟”,你们大概就是停在了这一层。
第二层:三个有边界的仓库,外加一份书面地图。 哪个服务跟哪个服务通信,走什么协议,带有哪些投递和顺序保证。这一层拿掉的是“重新发现”。真正起作用的其实不是地图,而是让模型把地图当作既定事实,不要再自己去核实。这换来了专注,代价是放弃了验证。地图会悄无声息地过期,而我恰恰删掉了那个本该让它发现过期的步骤。
第三层:加上覆盖真实执行过程的链路追踪和日志。 这一层拿掉了对执行顺序、以及哪些数据跨过服务边界的猜测。这是它在没有状态证据的情况下,离真相最近的一层,也是文章开头提到的那一层。
第四层。
再加上一小批能复现问题的记录——客户字段已移除、标识符已替换,但数据结构保持原样。正是这批记录能告诉你代码实际走了哪条路径,而事实证明,这才是整个问题的关键。
最后一轮跑在 Sonnet 上——它是之前三个模型里表现最差的一个:给出的判断更含糊,也更倾向于乐观猜测,而不是依据证据说话。Opus 和 Fable 在证据更少的情况下,倒是把方向指得更接近正确答案一些。但最终没有一个模型真正解决问题。
然而,当没有什么可猜的时候,那个此前猜得最差的模型,反而只凭这些记录就搞定了。
我并不是说模型选择不重要。它确实影响猜得准不准,但它不改变一个事实——究竟需不需要猜。
为什么记录能起作用
第一个缺陷我在之前就已经诊断出来了,所以每一级提示里,我可能都选取了恰好指向那个问题的证据。第二个缺陷是我自己项目里的一个真实 bug,我开始测试时还没解决。那是老代码,几年下来每遇到一个边界情况就堆一个条件判断,导致一个入口进去,可能走出一大堆不同路径。
第三级提示时,模型做的是正向推理:代码可能做什么,大概发生了什么。第四级时,它开始反向推理:给定这份记录里的这些值,这个分支执行了,而那些分支没执行。然后它针对那条路径写了一个测试,跑通,确认了行为,并交回一个可复现的用例。
原因其实很朴素。一个你没有记录日志的分支,就是一个你无法排除的分支。对于一个积累了多年条件的服务,每个分支都加日志,这种事没人会做——因为每条改动都要付出存储、延迟和评审注意力的成本,而它的价值在派上用场之前,始终只是个假设。
但来自受影响运行的一条记录,事后来看就能定论:记录里的值决定了哪些条件为真。当然这不绝对——两个分支可能收敛到同样的存储状态,后续的写入可能抹掉证据,起决定作用的值可能压根没有落库。但只要这些情况都不存在,记录就能告诉你哪个分支执行了。
这正是我本来该写的那行日志能做的事。这也是我两轮实验真正的差别所在,而且这个差别与技术无关。
变量不是模型。我认为是记录。
在我负责的项目里,拿到记录只是跑一条查询的事。在别的地方,这是一个法律问题——多年前有人回答过,却不知道自己当时回答的正是这个问题。那是另一个话题,我欠你一篇。
我可能错在哪里
两次运行,三个模型,还出自同一家厂商。这算不上基准测试。有人会说,到了第四层就能直接给出诊断了。我不这么认为。那些记录,人打开也是一样的——打开记录不等于看懂记录。这个论点能否成立,全看一件事:记录是直接给你答案,还是给你找到答案所需的东西。
什么能证伪这个论点:一个缺陷,单凭代码和调用轨迹就能确定它走了哪个分支,没有任何条件歧义。若真如此,第四层就不该再增加任何信息。
我现在常备的东西
每个服务一份简报,外加一张通信关系图;写给模型看的,合并代码时自动重新生成,而不是手工维护。投递和顺序保证也都写进去,因为没有哪个代码库会把这些说明白。图上标注最后核验日期,因为我是在让模型信任这张图。还有一个感知字段的提取脚本,剥掉敏感值,保留字段名、时间戳和连接关系原样保留。趁没着火的时候写好的。再就是养成一个习惯:问它认为跑的是哪个分支,数据里哪一点让它这么说。这个问题是第三层和第四层的分水岭,而你在任何一层都能问。
你有没有追查过模型给出的某个看似合理的附带发现,结果发现是真的?或者结果什么都不是——花的代价一模一样。而如果你把模型指向一份你告诉过它可以信任的文档,你又怎么知道那份文档现在仍然可信?
我为那些不是 Google 的公司写架构和 AI 工程方面的内容的项目里,拿记录就是一条查询语句。别的地方,这通常是某人多年前当作法律问题随口答过的事,而他根本不知道自己在回答这个问题。那是另一个话题,我欠你一篇文章。