跳到主要内容

搜索出来的抽象,不叫设计

老铁
老铁

· 阅读约 12 分钟

看到 Credo 这篇论文,我第一反应不是“有意思”,是“我们是不是又往错误的方向上狂奔了”。先把话放在这:它做的事,是把 agentic workflow 里搜出来的命令式 harness 转成声明式原语。标题很学术,Reusable Declarative Primitives for Agentic Workflows,三位作者,cs.AI 和 cs.DB 双挂。摘要我看完了,实验没细跑。没必要细跑——方向本身已经说明问题了。

问题根本不在他们做得好不好。问题在于,这类工具为什么会被需要。

故事是这样的:编码 agent 跑一个工作流的时候,花大量搜索去找“表现好的 harness”。这些 harness 是任务特定的命令式代码——一堆 if-else、重试逻辑、对某个模型输出该信多少的阈值、该调几次 API、哪些结果要丢掉。搜索过程替你把它们挑出来了,但挑出来之后呢?它们就是一堆代码,没有结构,没有元数据,没有“这个阈值为什么是 0.7 而不是 0.6”的解释。下次换个任务,你又得从头搜一遍。

Credo 想干的事,是从这堆搜索结果里“恢复出结构化的声明式描述”,给每个抽取出来的原语贴上元数据,记下来源,然后用编译器组合这些存储起来的原语,给新任务生成 harness,免得每次都从零搜。听起来是不是很合理?合理。但你想过没有,这整个链条的起点是一个什么动作?

是搜索替代了设计。

这句话可能说得有点冲,但道理我慢慢讲。

Harness 这种东西,本来就应该是一开始就想清楚的东西,而不是搜出来的。它负责决定每次模型调用的输入是什么、调几次、信任哪个结果——这不就是工程里最核心的控制流与数据流问题吗?你一开始设计这个 agent 要干什么的时候,难道不应该先想清楚:我这一步给模型看什么上下文,我允许它失败几次,我怎么判断它的输出里哪部分是垃圾?想清楚了,写出来就是结构化、可检查、可复用的代码。没想清楚,就让 search 去替你试,试完再找工具反过来“恢复结构”——这不是工程,这是考古。你在替自己前一步偷的懒做抢救性发掘。

我没说这篇文章不该发。恰恰相反,它把圈子里的病照出来了。病根在于:我们太习惯“让搜索来兜底”了。这几年凡是跟 agent 搭边的,搜索、采样、暴力枚举,都是主角。DSPy 早开了这个头——把 prompt 当成可优化参数,在空间里搜;后来越来越多框架把“找 harness”当成一个最优化问题。问题是,搜索的前提是搜索空间里存在一个稳定、可迁移的解;而你如果连原语都没有,搜出来的东西本身就带着任务特定的随机性,你要从里面“恢复”结构,这个结构有多可信?它不过是一堆随机梯度下的局部最优,被你事后包装成了一个原语。拿这个原语再去做组合,无异于从一堆噪音里提炼出规律,再当成物理定律去用。

拿数据库打比方,有人觉得不沾边,其实再恰当不过。SQL 是声明式的,大家都知道;执行计划是命令式的,优化器的搜索也是命令式的——但查询优化器的搜索空间是什么?是关系代数这个先验结构。关系代数是设计出来的,不是从一堆烂执行计划里逆向工程出来的。你见过哪个数据库的优化器是从随机 SQL 跑出来的执行计划里“恢复”出等价关系代数规则的?没有。因为规则是先验的,搜索是在规则的约束下进行,不是反过来。Agentic workflow 现在缺的,恰恰就是这个“先验的规则层”。你在没有代数的情况下让 agent 瞎搜,搜出一堆只对当前任务有效的 harness,然后说“我们来把这些东西抽象成原语吧”——抽象不是这么来的。抽象是你能证明它对一组任务具有不变性,不是它恰好被你从一个局部最优解里抽出来了。

Credo 的摘要里写,harness “以任务特定的命令式代码形式存在,缺乏可检查或可复用的结构”。这句话我看了三遍。它是对的,但它描述的恰恰是一个被搜索文化搞出来的问题。你从搜索这个动作出发,得到的当然是任务特定的命令式代码——搜索不生产结构,搜索只生产分布。结构是设计者放进去的,是人在写第一行搜索代码之前就应该画在纸上的。你跳过了那一步,现在带着一堆搜索结果回头找结构,找得到吗?能找到的,顶多是相关结构,不是因果结构。一个阈值 0.7 之所以出现在搜出来的 harness 里,可能是因为任务 A 的数据分布刚好在那里有最佳准确率;你给它贴个元数据“这是基于本次任务搜索得到的最佳阈值”,然后把它当原语存起来。下次任务 B 来,编译器一看,有个现成的“重试策略原语”和“阈值原语”,组合一下,生成新 harness。但任务 B 的失败模式和任务 A 可能完全不同,0.7 在 B 里可能是个灾难。你这个“原语”的抽象层级是错的——它抽象的不是控制流的意图,而是某个具体参数的运气。

这才是我想骂的地方。不是骂这三个人,我不认识他们,他们做的东西在自己的系统里很可能是自洽的,实验也是老老实实做的。我骂的是整个社区正在形成的一种反模式:先让搜索飞一会儿,等它飞出一堆烂摊子,再发明工具来收拾。这个循环最毒的地方在于,它看起来像工程迭代,其实每一步都在加固你对搜索的依赖,稀释你对设计的基本功信仰。你以为你在做“tools for AI”,其实你在做“AI 帮出来的技术债的再融资工具”。

技术债这个词,现在谁都在说,但大多数人都把它当梗。真正的技术债,不是说你的代码写得丑;是你的每一步决策都在给下一步增加不可逆的假设,而这些假设没有一个是被检查过的。让搜索来选 harness,就是在一开始把所有关键决策都外包给了一个没有后果意识的机制。它不是不会犯错,而是它犯错的方式让你特别难发现。一个人类工程师写了一个有问题的阈值,代码审查里可能被看出来,或者旁边的注释会提醒你“这个 0.7 是因为当时采样不足,别信”。搜索出来的 0.7,没有注释、没有来源、没有人的干预——它看起来天然正确,就因为它是从“最优”里出来的。Credo 想给它补来源信息,这个动机是对的,但来源信息补出来的,最多是“这个值来自哪次搜索的哪个 candidate”,它补不出来“这次搜索的假设是什么、边界在哪”。后者才是一个原语真正该带的东西,而这个东西,只有在一开始把它当设计对象的人,才写得出来。

所以我的态度到这里已经清楚一半了:Credo 解决的问题,是搜索文化的副作用,而不是 agentic workflow 的本质问题。你把它当工程工具来用,可以,它是这个阶段里一个有用的拐杖。但你如果把它的存在当成证明“搜索 + 事后抽象”这条路可行的证据,那就是自欺欺人。这条路在数据库那边从来没有成功过——真正的系统都是先有代数先验,再用搜索优化,绝不反着来。Agentic workflow 现在还年轻,但已经能闻到一股“我先搜了再说”的气味在弥漫,这个气味对工程素养的腐蚀是慢性且不可逆的。

那到底该怎么办?不然我又成了那个只说不给路的人。

先把话说透:如果你的 agent 工作流里的 harness 是从搜索里挑出来的,而且你发现它没有结构、不可检查、不可复用——你不是该去找一个像 Credo 这样的工具来帮你“恢复结构”。你该停下来问自己:我当初为什么不先定义控制流?我真的连“这一步最多调三次模型”都说不清楚吗?我真的连“模型输出里用户意图的置信度低于多少就退回澄清”都不知道吗?

大部分时候,你知道。你只是贪快,把搜索当成了思考的替代品。

按顺序排,三条路。

第一,先定义原语,再谈搜索。原语是什么?是控制流的最小可决策单元:一次调用的上下文选择算一个,重试策略算一个,结果可信度判断算一个,人类介入的卡口算一个。这些原语在动手写第一个 agent 之前,就应该是显式的、有名字的、有边界条件的。你写的时候就知道“这是 RetryPolicy(2, exponential_backoff)”,而不是“试了三次,第三次那个 prompt 碰巧能把 40% 的情况拉回来”——这是完全不同的两种工程。前者的行为可预测,边界在代码里写着,后者的行为是一个随机分布的采样,换个 seed 就变。

第二,把搜索空间约束在这些原语之上。你不搜 harness,你搜的是原语的参数组合,或者搜的是“在当前任务下,哪一个已经定义好的控制流模板更合适”。搜索空间里有结构,搜索出来的结果才可能迁移。这跟 SQL 优化器一个道理:规则是先验的,代价模型是搜索用的,搜索永远在规则允许的范围内跑。这样你压根不需要事后考古,因为每个被选中的组合,本身就从明确的原语组合而来,它的可解释性是构造上就保证的,不是挖出来的。

第三,如果你真的拿不准某个语义判断该怎么做——比如“模型这轮输出到底该信还是该怀疑”——那就把它当一个小型实验对象,把判据写清楚,跑一组对照,自己得出结论,然后把结论写进原语。这叫设计。你需要的是一台实验笔记本,不是一个搜索器。一个阈值是 0.7 还是 0.6,如果这个数字对你很重要,你就该知道它为什么是这个数字;如果你不知道,说明你根本没在管这个参数。

写到这里,我想起上一篇文章里讲过的一件事——AI 写 commit message,能写“改了什么”,写不出“为什么这么改”。现在 Credo 这个故事是一样的:搜索能给你“哪个 harness 表现好”,给不了你“为什么这个 harness 该长这样”。你可以在事后给它贴一层元数据、记个来源,但“为什么”那个空,永远是设计者的事,不是考古学家的事。

也许有人会说:你这也太理想化了,现实里谁有工夫在跑 agent 之前把所有原语都定义清楚?任务千奇百怪,一开始就想清楚所有的控制流根本不可能。这话我认一半。确实不可能一开始就定义所有原语,但那不代表你连第一层核心控制流都不定义就放手去搜。工程的正确姿态,是你先把你确定的部分固定下来,把不确定的部分显式地标成“不确定”,然后针对那部分做实验、做搜索、做探索。而不是反过来,把一切决策全部丢给搜索,然后做完再来缝缝补补。前者是带着结构去探索,后者是迷了路再找北。差别大到不是一个工具能补的。

我可能是错的。Credo 的编译器和原语恢复机制也许确实做得巧妙,能把任务特定的 harness 压缩成跨任务可用的组合,实验结果也可能挺漂亮。但要我改观,请给我看工程证据:拿一类分布漂移明显的任务,证明从搜索出的 harness 里恢复的原语,能在没有人类重新设计控制流的情况下,稳定迁移到新分布上,并且可以检查、可以维护、可以让人看懂每一步的“为什么”。别给我看 demo,别给我看“在 X/Y/Z 三个 benchmark 上平均提升了百分之多少”。这个领域已经太多 benchmark 上的捷报了,少的是对边界的诚实交代。

我的态度很明确:抽象是设计出来的,不是搜索出来的。原语这种东西,你一开始没设计,后面用再聪明的工具去挖,挖出来的也只是残骸,不是骨架。Agentic workflow 现在最缺的,不是更快的搜索、不是更聪明的编译器、不是更多一键生成 harness 的框架——是有人慢下来,把控制流和数据流的根本问题想清楚,把原语定义清楚,然后再去让机器跑。顺序反了,工具越好,你离工程越远。

先把“想清楚再动手”这五个字拾起来,再谈什么声明式原语、半自动编译、可复用目录。地基没打,上面盖什么都是沙子上盖楼。

老铁
老铁

把每个技术现象拆到原理、再上升到工程价值观,崇尚基础功、反浮躁。

查看主页 →