Super Library Agent:让 coding agent 学会"建公共库",一个项目组的代码不再各写各的

你有没有想过一个场景:公司让 AI 一口气生成 8 个网站——产品宣传页、CRM 系统、招聘平台——每个都能跑,功能测试也都过。但打开代码一看,8 个项目里各有 8 份长得不太一样的 useLocalStorage、8 份大同小异的导航栏、8 份表单校验逻辑。改一个设计规范?对不起,8 个仓库挨个改一遍。

这就是典型的"一个 agent 一个应用"工作流的隐性代价。功能都对,但共享逻辑被复制了 N 份,而且每个 agent 还会用自己的方式重新推导一遍同样的东西,代码越积越臃肿。KAIST 这篇被 EMNLP 2026 Findings 收录的论文把这个问题正式拎了出来,起名叫 Super Library Agent(SLA)问题,并给了一套我觉得相当务实的解法。

核心摘要:论文要解决的痛点是——让 LLM coding agent 顺序生成一组相关应用时,如何边生成边维护一个跨应用的共享库(Super Library),既不牺牲功能,又压住代码冗余。思路说起来不复杂:不要按代码长什么样来匹配复用候选,而是先把每个代码块总结成一句自然语言,按"它做什么"来匹配;抽取和迁移拆成两个 agent,中间用结构化的抽取轨迹和调用图信息做交接。效果上,WebGen-Bench 冗余度(Verbosity)从 0.1603 降到 0.0994,降了 38 个点,准确率还不降反微升(76.04 → 77.21)。我的判断:这不是什么底层突破,是三个工程组件的精心组装,但它把一个真实存在、且会随 agentic coding 普及而越来越严重的问题定义清楚了,每个组件的消融也都站得住。值得做 agentic SE 的人细读。

论文信息 - 标题:Super Library Agent: Joint Generation and Maintenance of Multiple Applications Beyond the Single Codebase - 作者:Daegyu Sung, Yukyeong Lee, Geon Park, Yumin Choi, Sung Ju Hwang(KAIST) - arXiv:https://arxiv.org/abs/2608.29310 (2026 年 8 月 29 日提交,EMNLP 2026 Findings) - 代码:https://github.com/sbigstar0310/super-library-agent


🎯 这个问题为什么值得单独定义

先说说这个问题的来源,因为它不是凭空造出来的。

真实组织里的软件很少是孤立的。一个公司可能有十几个内部系统,技术栈一样、UI 规范一样、数据处理的套路也一样——这就是软件产品线(software product line)研究了几十年的事情。放到 LLM coding agent 的语境下,麻烦就来了:现在 agent 生成单个应用已经相当能打,WebGen-Bench、SWE-Bench 这些榜单上数字一年比一年好看,但几乎所有评测都是"一个任务一个仓库"的设定。

逐个独立生成 N 个应用,会有两个叠加的坏效应。

一个是 DRY 原则被系统性违反。同样的逻辑在 N 个仓库里各写一份,后续任何一次 bug 修复或规范变更都要手工传播 N 次。

另一个更隐蔽:LLM 生成的代码本身就有"越写越烂"的倾向。SlopCodeBench(Orlanski et al. 2026)把这种现象叫 slop——功能还对,但代码日益冗长、结构纠缠。N 个互相隔离的 agent 会把这个效应放大 N 倍:每个 agent 用微妙不同的方式重推共享逻辑,各自积累死代码,每维护一轮就离兄弟仓库更远一点。

说到共享库学习,其实前置工作是有的。Cornell 团队的 Librarian(Kovačič et al. 2025)用 sample-and-rerank 加 MDL 重排,把多个独立解法重构成联合库,在 MiniCode 上压缩率比当时的 coding agent 好 1.6-2 倍。更早还有 DreamCoder、Stitch、LILO 这一脉的库学习。但注意,这些工作都假设输入文件在单个逻辑项目内。Librarian 论文自己也明确说,大规模跨仓库的库创建是个开放问题。

SLA 论文填的就是这个空:应用顺序到达,生成第 t 个应用时要用上已经建好的共享库,建完新应用后还要把新抽出来的抽象迁回老应用。这是个在线设定,比事后重构难得多。

形式上,给定请求序列 \(\mathbf{X}=(x_1,\ldots,x_N)\),agent 从空库 \(\mathcal{L}_0=\emptyset\) 出发,每步做:

\[(c_t,\mathbf{C}^{\prime}_{<t},\mathcal{L}_t)=\mathcal{A}(x_t,\mathbf{C}_{<t},\mathcal{L}_{t-1})\]

新代码库 \(c_t\)、更新后的库 \(\mathcal{L}_t\)、以及被打补丁改用新库的老代码库 \(\mathbf{C}^{\prime}_{<t}\),三个产出一次给全。理想的 Super Library 恰好包含所有被至少两个应用使用的组件:

\[\mathcal{L}_t^{\star}=\bigg\{u\,\bigg|\sum_{i=1}^{t}\mathbf{1}[\mathrm{use}(u,c_i)]\geq 2\bigg\}\]

应用特有的留本地,共享的抽进库统一复用。目标是在功能性和可维护性之间拿一个好的 Pareto 权衡。

⚠️ 最朴素的方案为什么不行

最直接的脚手架(论文叫 SLA-Naive)长这样:每来一个请求,coding agent 先生成新应用(能复用库就复用),然后一个 library agent 干两件事——把跨应用共享的组件抽进库,再把老应用迁移到新库上。

这个方案理论上闭环了,实际跑起来栽在两个地方。

抽取召回低。功能等价的代码,写法可以天差地别。两个应用都实现了"搜索框加分类筛选",一个用 hook 一个用 class,表面相似性根本匹配不上。随着应用越攒越多,漏检越来越多,库的覆盖率上不去。

迁移脆弱。把本地实现挪进库不是一次局部编辑。import 要改、调用点要改、依赖的辅助函数要跟着走。朴素的迁移 agent 经常只换掉眼皮底下的那份实现,留下一堆失效引用、重复代码和死代码。

实验里的数字印证了这两点:Naive 变体虽然把 LOC、token 压下去一点,但结构侵蚀度(Erosion)反而比 Zero-Shot 还高——WebGen 上 Naive-Implicit 是 0.1567,Zero-Shot 只有 0.1032。作者的解释很到位:无引导的库抽取会把复杂度集中到共享组件里,库本身成了新的烂摊子。

说实话,这个结果我一点都不意外。我们在内部项目里试过让 agent 自己重构公共模块,没有结构化交接的话,改完的代码能跑纯属运气。

🧠 SLA-Full 的三个组件

SLA-Naive 与 SLA-Full 的工作流对比

图:左边是最小脚手架 SLA-Naive——生成、抽取、迁移全塞给一个 library agent;右边是 SLA-Full,把流程拆成候选抽取(代码块先总结成自然语言再匹配)和上下文感知迁移(extraction trace + 调用图)两大块,抽取与迁移由不同 agent 完成,中间用结构化信息交接。

SLA-Full 的核心动作是把那个大而全的 library agent 拆成两个专职 agent——一个管抽取、一个管迁移——再配三样东西。

组件一:按"做什么"而不是"怎么写"来匹配候选。 这是全文我最喜欢的一个设计。直觉上大家都会想到用代码相似度或 embedding 来找复用候选,但功能等价的代码表面形式差异太大,这条路漏检严重。SLA-Full 的做法是:用 AST 边界(函数、类、模块)切代码块,每块让一个轻量模型(gpt-5.4-nano)生成一句自然语言摘要,维护成索引;候选选择器(deepseek-v4-flash)在摘要索引上做两类匹配——给抽取 agent 找"多个应用里实现同一功能"的抽取候选,给迁移 agent 找"库组件与可替换的本地代码"的配对。摘要索引比原始代码紧凑得多,上下文压力也小。一句话概括:匹配语义,不匹配语法

组件二:抽取前先做一次单仓库整合。 这个设计很细。library agent 是为跨仓库抽取设计的,它看不到单个代码库内部的重复——而新应用刚生成出来时,内部往往就有重复实现了(agent 写代码经常一个模式写两三遍)。所以在跨应用匹配之前,先用同一套索引机制把新仓库内部的重复整合成共享模块,再拿去索引。消融里去掉这一步,Verbosity 从 0.0994 涨到 0.1264,LOC 也反弹,说明这一步不是可有可无的。

组件三:抽取和迁移之间的结构化交接。 拆开两个 agent 之后最大的风险是信息不对称——迁移 agent 不知道抽取 agent 刚才干了什么。论文用了两手:

一是 extraction trace。每次抽取完,抽取 agent 为每个新库符号写一份结构化记录:这个符号从哪来的(where)、这个模式为什么能跨应用泛化(why)、怎么用库 API 替换原代码(how)。迁移 agent 拿着这份记录,不用从零重新发现对应关系。

二是调用图条件化。对每个迁移候选,在 prompt 里附一个 also-refactor 清单,列出相关的 import 声明和本地的 caller/callee 关系。迁移 agent 照着清单把依赖的调用点、import 一起改掉,顺手删掉废弃的本地实现。

这两手的必要性在消融里看得很清楚:去掉调用图条件化,准确率从 77.21 掉到 74.48,Verbosity、Erosion、LOC 全面反弹——迁移 agent 又开始留死代码了。

📊 实验:功能不掉队,可维护性全面压制

实验在两个基准上做。WebGen-Bench 是从自然语言生成网站,论文构造了 3 个不相交的 8 任务套件(内容展示、数据管理 CRUD、用户交互平台各一套);PaperBench 是复现 ICML 2024 Spotlight/Oral 论文的代码,用 Code-Dev 变体,构造了 5 个 4 任务套件,共享抽象是 training loop、data pipeline、model wrapper 这类。所有方法的 backbone 都是 deepseek-v4-flash,harness 用 mini-SWE-agent,3 次试验取平均。

主表(Table 1)结果,WebGen-Bench:

方法 Acc. ↑ Appr. ↑ LOC ↓ Tok ↓ MDL ↓ Eros. ↓ Verb. ↓
Zero-Shot 76.04 3.84 9393 70364 34875 0.1032 0.1603
Librarian (K=8) 75.78 3.89 8973 68159 33919 0.1352 0.1472
Naive-Implicit 76.95 3.89 9133 69777 35103 0.1567 0.1408
Naive-Ward 76.07 3.90 8786 67774 34675 0.1250 0.1370
SLA-Full 77.21 3.86 8552 65633 34195 0.0987 0.0994

PaperBench(Score 即 Code-Dev Score):

方法 Score ↑ LOC ↓ Tok ↓ MDL ↓ Eros. ↓ Verb. ↓
Zero-Shot 0.4687 6578 68603 30342 0.2558 0.8665
Librarian (K=8) 0.4591 6551 68297 30400 0.2527 0.8628
Naive-Implicit 0.4802 6445 66539 29850 0.2882 0.8582
SLA-Full 0.4809 6252 63514 29489 0.2291 0.8490

几个值得展开说的点。

功能指标全部打平,这才是关键前提。 配对显著性检验(WebGen n=24、PaperBench n=20,bootstrap 95% CI 加双侧配对 t 检验)显示,任何方法之间的功能性差异都不显著。也就是说可维护性的收益不是拿正确性换的——这在重构类工作里其实是最大的坑,Librarian 当年在 repo 级重构上也栽在正确性保不住。

Verbosity 的降幅最狠。 WebGen 上相对 Zero-Shot 降 38 个点(0.1603 → 0.0994),p=0.001。对 Librarian 和 Naive-Ward 也都显著。冗余被真正挤出去了,不只是把代码挪了个地方。

Librarian 的 MDL 最低,但这是个有水分的第一。 注意 Librarian 是跑 8 次库构建、在通过功能门槛的候选里挑 MDL 最低的那个——它是专门为 MDL 这个指标做了 best-of-K 选择的。即便如此,它的 Erosion(0.1352)比 Zero-Shot 还高,说明事后重构并没有让结构更健康。而 SLA-Full 没有任何一个指标显著输给 Librarian。说实话,用 K=8 采样换来的 MDL 优势,工程上不太能当真。

PaperBench 上 Naive-Implicit 的 Score 其实和 SLA-Full 几乎一样(0.4802 vs 0.4809),但它的 Erosion 高达 0.2882——复杂度全堆进库里了。这再次说明只看功能分数会漏掉多少东西。

维护实验是最贴近真实场景的。 作者让一个独立的 agent(claude-opus-4.8,且看不到已生成的代码)撰写共享策略更新,再让各方法打补丁。结果 SLA-Full 的总补丁量只有 256 行,Zero-Shot 是 936 行——共享变更集中到了库里,应用级编辑大幅减少。这个数我觉得比主表更有说服力:建库的收益会在每一次后续维护里复利。

库里抽出来的东西质量更高。 按导入应用数统计,SLA-Full 平均构建 13.3 个导出,naive 变体只有 7-8 个;而且对高复用符号分类后发现,naive 变体抽的基本是 Header、Footer 这种浅层组件,SLA-Full 额外抽出了 useRouter、useFilteredList 这类行为 hook 和 FeatureCardGrid 这种组合的领域组件。抽象的层次明显不一样。

还有一个很妙的消融:把建好的库直接当先验用。让一个普通 coding agent 带着 SLA-Full 的最终库去生成新应用,准确率从 80.95 涨到 84.35,涨了 3.4 个点,总 LOC(算上库本身)还降了约 11 个点。

有库与无库条件下生成网页的定性对比

图:Library-as-prior 实验的定性对比,每组左边是无库生成、右边是带库生成。8 个任务里多数持平或提升,比如个人 CV 页从 71.4 提到 100.0,但也有翻车的——设计师作品集从 100.0 掉到 83.3。库是个有用的先验,不是万能的先验。

这张图我喜欢它不放水的部分:8 个案例里有 2 个准确率是掉的(临床办公信息掉 10 个点、设计师作品集掉 16.7 个点)。带库生成不是稳赢,库里的约定偶尔会约束掉本该有的灵活性。作者敢把这种案例放出来,比只挑成功案例的论文可信得多。

🤔 我的判断

这篇论文的定位要放准:它不是底层方法论的突破,三个组件单独看都不算新——语义摘要做检索、先整合再抽取、用调用图辅助迁移,每一件在各自领域都有先例。它真正的贡献有三层。

一是问题定义。把"agent 顺序生成并维护一组相关应用"形式化成一个在线的库构建问题,给了理想库的形式化目标和一组可维护性度量(LOC、token、依赖感知 MDL、Erosion、Verbosity)。这个定义本身比方法更有长期价值——Librarian 留的那个开放问题,现在有人把坑占了。

二是失败模式的拆解足够诚实。抽取召回和迁移正确性这两个挑战,都有消融数据背书,不是拍脑袋说的。

三是组件之间的因果链完整。NL 候选匹配抽得更多但引入 bug(Accuracy 72.95)→ 所以加调用图条件化把 Accuracy 救回 77.21;跨仓库抽取看不到库内重复 → 所以加预整合。每个组件都对应一个被实证过的失败模式,这种"为问题找方案"而不是"为方案找问题"的结构,在 agent 论文里不算多见。

局限也得说清楚。基准是作者自己构造的套件(WebGen 24 个任务、PaperBench 20 个),样本量不大,PaperBench 上 LOC 的改善(p=0.097)其实没过显著性线。维护实验只做了一轮更新,长期多轮维护下库会不会腐烂,没有答案。还有作者在伦理部分自己提到的风险:一个库符号被多个应用导入后,一次错误编辑会同步传播到所有应用——共享库放大了复用的收益,也放大了错误的爆炸半径。这点在金融、医疗这类场景里是要命的。

另外一个我看完没完全想明白的点:整个流水线用了 deepseek-v4-flash、gpt-5.4-nano、claude-opus-4.8、gpt-5-mini 四五个模型分工,推理成本比 Zero-Shot 高多少,正文没给(在附录里,我没能拿到数字)。如果成本高一个数量级,那"功能持平、可维护性更好"的账就要重新算了。

💡 工程上的启发

如果你在做 agentic coding 的产品,这篇论文有三个可以直接拿走的动作。

生成完一批相关项目后,不要让 agent 直接重构——先给代码块建语义摘要索引,按摘要做候选匹配,这比 embedding 相似度靠谱。

重构迁移时,给 agent 的 prompt 里附上调用图上下文(import 和 caller/callee 清单),这是论文里性价比最高的单点改进,直接决定迁移后留不留死代码。

评估重构效果时,别只看测试通过率。加两个结构指标(复杂度侵蚀、冗余度),否则你会像 Naive 基线一样,看着功能全绿、代码悄悄烂掉。

更长远一点看,"给 agent 的产出建共享记忆、并且让记忆随使用进化"这个范式,大概率会从代码扩展到 agent 的其他产出物——prompt 库、工具库、workflow 模板。代码只是第一个被形式化的战场。


觉得有启发的话,欢迎点赞、在看、转发。跟进最新AI前沿,关注我