让 AI 编程智能体替你“拆依赖”:Flask 案例研究的五种策略与五条经验

举报
Eveline 发表于 2026/10/22 21:00:03 2026/10/22
【摘要】 AI 编程智能体能否把第三方依赖从仓库里彻底拿掉?本文翻译 arXiv 论文,以 Flask 为案例,介绍智能体依赖变换的五种策略(本地重新实现、标准库替换、复制自维护、删除依赖、配置替换)、上下文对依赖发现的影响,以及“测试通过不等于行为不变”等五条经验。

原文信息:本文译自 arXiv 论文《Agentic Dependency Transformations: Lessons from an Exploratory Case Study of Flask》,作者 Kaya Kitada、Yoshiki Higo、Raula Gaikovina Kula,2026 年 10 月 15 日提交(arXiv:2610.21372)。原文链接:https://arxiv.org/abs/2610.21372 。原文采用 CC BY 4.0 许可。本文为翻译,表格按原文数据整理,图表与参考文献请见原文;译者在文末补充了译者注。

译者导语:Dependabot 这类工具能帮你升级依赖,却回答不了“这个包到底还要不要”。如果让 AI 编程智能体(coding agent)直接动手,把一个第三方依赖从仓库里彻底拿掉,会发生什么?这篇论文用 GPT-5.4 在 Flask 上做了一次完整实验:智能体用了哪几种办法、靠不靠谱、测试能不能当证据,结论对每个在用 AI 改代码的团队都有参考价值。


摘要

现代软件高度依赖第三方库,维护者必须在复用的好处与依赖带来的维护成本和风险之间权衡。我们把依赖变换(dependency transformation)定义为一种新兴做法:由 AI 编程智能体改变仓库处理外部依赖的方式。本地重新实现(local reimplementation)是其中一种策略,即把所需功能改写成仓库内的本地代码;而依赖变换的范围更广,还包括其他策略。

在这篇阶段性成果论文中,我们以 Flask 为对象做了一次探索性案例研究,考察依赖变换有哪些策略。我们设计的实验展示了:智能体能看到的上下文,如何影响它发现依赖、以及逐个变换依赖的过程。这个案例得出了五条初步经验。基于这些经验和一份五部分的研究议程,我们把依赖变换定位为一个新的研究方向,并呼吁为其建立明确的变换契约、来源约束、行为保持要求和独立的验证标准。

关键词:智能体软件工程,依赖管理,开源

一、引言

现代软件是用 npm、PyPI、Maven 等生态中的外部库和框架拼装起来的。复用能加快交付,但也带来了升级工作、版本冲突、安全漏洞和供应链风险。Synopsys 2024 年的报告显示,96% 的受审商业代码库包含开源软件,其中约 77% 的代码来自开源。Log4Shell 这类大范围扩散的漏洞说明,一个库出问题就可能波及大量下游系统。已有研究也表明,依赖管理会直接影响安全性。

现有工具能帮助识别依赖、扫描漏洞和维护版本。比如 Dependabot 会自动提出升级建议、推送漏洞通知。但开发者仍然要自己判断:这个依赖在哪里用、为什么用,升级或删除是否安全,提议的改动会不会改变仓库原有的行为。

用大语言模型(LLM)做依赖管理还处在早期探索阶段。已有工作研究了 LLM 给出的依赖推荐,也有研究关注依赖识别、漏洞分析、冲突解决和自动升级的自动化。除了升级,依赖策略还可能包括降级,以及为了让项目自包含而删除依赖。

AI 编程智能体可以补足这些依赖机器人的短板:它能搜索仓库、查看各类文件、同时修改多个文件,还能运行构建和测试。下文把 AI 编程智能体简称为“智能体”。正是这些能力催生了依赖变换这一做法。

我们对依赖变换的定义是:智能体针对仓库依赖的某一个第三方包,改变该包所提供功能的来源,使仓库不再需要这个包,这样一次仓库级改动就是一次依赖变换。 一次变换涉及三类修改:

  1. 导入或调用该包的代码;
  2. 声明该包的文件,如清单文件(manifest)、锁文件(lockfile)和 CI/CD 配置;
  3. 用来验证依赖的构建和测试。

举个例子:如果一个仓库引入某个包只是为了读取配置文件,智能体可以在本地重写这段读取逻辑,从 pyproject.toml 里删掉这个包,再重新跑一遍测试。这和依赖升级不同(升级会保留包、只改版本),也和库迁移不同(迁移是用另一个第三方包替换原来的包)。上面例子里的本地重新实现只是其中一种变换策略;依赖变换还包括改用标准库、把包的源码复制进仓库、或者干脆删掉需要这个包的功能等做法。智能体报告为仓库依赖、因而可能被变换的第三方包,我们称为依赖变换候选。

本文在真实场景中研究依赖变换,用的是智能体而不是单独的 LLM。我们以 Flask 为案例,使用 OpenAI GPT-5.4 和开源的 pi 编程智能体框架(harness)。这项研究不是为了证明效果,而是借一个仓库级案例,提出两个研究问题:

  • RQ1:智能体能看到的上下文,如何影响它发现依赖变换候选?我们比较有上下文和没有上下文(只看源代码)两种情况下的依赖发现结果。
  • RQ2:当智能体在仓库层面逐个处理变换候选时,会出现哪些变换策略?除了本地重新实现,我们还考察智能体提出并实际执行的其他策略。

二、研究设计

选择 Flask,是因为它在软件工程研究中被广泛使用,而且是一个典型的开源项目,运行时代码、测试、文档、示例、打包元数据和 CI/CD 配置一应俱全。所有实验都基于同一个固定的 Flask 提交 2ac89889f4cc(236 个文件;732 位贡献者、5,537 次提交、2,791 个拉取请求)。

我们通过开源的 pi 框架运行 GPT-5.4,每种条件各运行一次。一个自定义扩展记录了智能体所有可观察的操作,包括打开的文件、搜索命令、工具调用、对话轮次、模型请求、token 数量和生成的修改。这些都是在框架边界上观察到的行为,不是模型内部的推理过程。实验于 2026 年 5 月 9 日至 10 日进行,提示词、被隐藏的文件、每一步的命令和护栏文件都收录在复现材料包中。

三、实证研究

RQ1 的实验设计

为了回答 RQ1,我们设计了两个实验,比较有无上下文时的依赖发现,方法是分别在保留和移除清单文件的情况下开启独立会话。

控制上下文。 这里的“上下文”指清单文件,如 pyproject.toml、uv.lock 和 requirements.txt。这些文件并不是完全准确的标准答案:其中可能包含可选依赖、传递依赖,以及只用于测试、文档、工具、工作流或示例的包。我们假定在没有上下文时,智能体只能依靠代码里的 import 语句,以及测试、文档、示例、工作流等非清单文件。

智能体提示词。 在智能体模式下,我们只给出一条宽泛的指令:“列出所有第三方依赖。”之后由智能体自主决定搜索路径,可以查看源代码、测试、文档、示例和工作流。

结果分析。 我们收集了智能体报告的全部第三方包,排除标准库模块。

候选分类。 光看候选数量,分不清两次会话的差别是“发现能力不同”,还是只是“把哪些包算作依赖”的口径不同。为了区分这两点,第一作者对有上下文时报告的每个候选,按其在仓库中的来源手工分类。每个候选按以下顺序依次判断,只归入一类:

  1. 根声明:在根目录 pyproject.toml 中声明,包括运行时依赖、可选依赖、依赖组或构建系统依赖;
  2. 锁文件:只作为解析出的传递依赖出现在 uv.lock 中;
  3. 示例:只被 examples 目录下的某个项目需要;
  4. 文档:只在文档、教程或部署指南中提到;
  5. 构建工具:只作为构建项目所用的工具被引用。

我们还记录了每次会话的耗时、轮次、工具调用次数和 token 数。

表 1:有无上下文时的智能体执行情况

指标 有上下文 无上下文
执行时间(秒) 247.4 266.0
对话轮次 10 24
工具调用次数 45 97
输入 token 153,323 70,178
输出 token 17,614 16,146
read 31 29
grep 11 45
ls 1 12
find 1 10
write 1 1

表 2:有上下文时报告的候选,按主要来源分类(n=114)

类别 候选数
根声明 27
锁文件(仅传递依赖) 54
示例 14
文档 18
构建工具 1
合计 114

RQ1 结果

表 1 列出了两次会话的开销。两者耗时相近,但有上下文时,智能体会去读清单文件,输入 token 更多;没有上下文时,它更频繁地用 grep、ls 和 find 搜索仓库。

两次会话共报告了 115 个不同的候选:27 个两次都出现,87 个只在有上下文时出现,1 个只在无上下文时出现。表 2 对有上下文时报告的 114 个候选做了分类:只有 27 个(24%)是根声明,而 54 个(47%)只出现在锁文件里。也就是说,有上下文时候选更多,主要是因为锁文件让报告的范围变宽了,而不是因为找到了更多直接声明的依赖:没有上下文的会话仍然通过 import、测试和文档找回了 27 个根声明中的 19 个。

RQ1 的回答:上下文会改变智能体把哪些包报告为依赖:有上下文时报告 114 个候选,其中大多数只出现在锁文件中;没有上下文时只有 28 个。这反映的是报告口径的差异,并不能证明发现能力更强,因为每种条件只运行了一次,也没有标准答案来计算精确率或召回率。

RQ2 的实验设计

为了回答 RQ2,我们设计了一个依赖变换工作流,对变换候选逐个执行变换。

依赖变换工作流。 工作流由一个编排提示词驱动:智能体要记录剩余的变换候选,并一次处理一个,过程中用到两个提示词:

  • 提示词 A 让智能体执行一次变换:删除一个变换候选,不得引入新的第三方依赖,必要时在本地提供所需功能,然后运行构建和测试。如果构建或测试失败,智能体要在同一个提示词内修复并重新验证。
  • 提示词 B 是修复提示词:拿到失败的构建或测试日志后,做最小限度的修复。只有在提示词 A 结束后仍有失败、或最终验证出现失败时才使用。

每个实验都在一个连续的智能体会话中完成,因此智能体在处理不同候选时保留着上下文;两个实验分别在独立会话中进行。工作流分为两个阶段:

  • 首轮(First Pass):智能体对每个变换候选执行一次提示词 A。无法安全删除的候选标记为“受阻”,留在剩余候选列表中。
  • 重试轮(Retry Pass):如果首轮结束时还有受阻候选,第一作者会指示智能体再删除一次剩余候选,智能体对每个受阻候选再执行一次提示词 A。

终止条件。 工作流既不限制重试次数,也不设超时。对单个候选的尝试,在智能体判断变换完成、或判断已无法取得进一步进展时结束,后一种情况记为受阻。只有在没有剩余候选、没有引入新的第三方依赖、并且最终构建和测试全部通过时,整个流程才算成功;否则智能体必须把本次运行记为未完成,并写明原因。

表 3:按阶段和类型统计的完成情况

有上下文 无上下文 合计
处理的候选 43 21 64
首轮完成 20 17 37
重试轮完成 23 4 27
直接完成 31 21 52
连带完成 12 0 12

表 4:测试文件审计

审计项 有上下文 无上下文
修改了测试的变换 38/43 19/21
新增测试文件 16 15
修改已有测试文件 3 2
删除测试文件 0 0

表 5:直接完成所用的策略(n=52)

变换策略 有上下文 无上下文 合计
本地重新实现 4 5 9
改用标准库实现 3 1 4
复制并自行维护(copy-and-own) 9 4 13
删除依赖 7 10 17
混合方式/配置替换 8 1 9
合计 31 21 52

候选与处理顺序。 我们做了两个变换实验,一个有上下文,一个没有。候选并不是从 RQ1 的列表中抽取的:每个 RQ2 会话都先做一次自己的依赖发现,这次的目标是删除依赖,然后智能体处理它列出的每一个候选——有上下文时 43 个,无上下文时 21 个。这 43 个全部在 RQ1 的 114 个候选之内:包括 27 个根声明、14 个示例候选,以及 2 个被文档中的代码导入的锁文件包。智能体没有处理另外 52 个只出现在锁文件中的候选,也没有处理 19 个只在文档或构建工具中出现的候选。无上下文时的 21 个候选中,有 20 个在 RQ1 的 28 个之内。

智能体在同一份工作副本上依次处理候选,顺序由它自己决定;有上下文时,它先删耦合较松的依赖,再处理核心运行时依赖。因此改动是累积的,一个候选的结果可能取决于之前的变换。我们没有调整顺序,也没有在候选之间重置仓库。

完成标准与人工检查点。 当一个候选已从仓库中删除、并且构建和测试通过时,就算达到“变换完成”。测试并没有被冻结:提示词 A 允许智能体在需要时修改或新增测试。第一作者全程监看每次智能体执行,并检查每次变换是否按指令进行。

RQ2 结果

完成情况。 表 3 汇总了完成情况。64 个候选全部达到变换完成:37 个在首轮完成,其余 27 个在重试轮完成。有 12 个属于连带完成,全部出现在有上下文的实验中:这些候选是在变换另一个候选时顺带解决的(12 个都是 Celery 示例的依赖,删除 Celery 后一并解决)。下文聚焦其余 52 个直接完成(有上下文 31 个,无上下文 21 个)。有上下文的实验耗时 3.97 小时,共 54 次框架运行、1,068 个智能体轮次;无上下文的实验耗时 1.40 小时,23 次运行、403 个轮次。

依赖变换策略分类。 表 5 列出了 52 个直接完成所用的变换策略,各策略定义如下:

  1. 本地重新实现:在仓库内直接重新实现所需功能。
  2. 改用标准库实现:用标准库中的等价功能替换外部依赖。
  3. 复制并自行维护:把依赖的源代码复制进仓库,在本地使用。
  4. 删除依赖:删除或简化需要该依赖的功能或开发工具。
  5. 混合方式/配置替换:从可选依赖、配置或元数据中移除;或者综合运用代码修改、配置调整和其他策略来解决。

可以看到,删除依赖是最常用的策略,其次是复制并自行维护和本地重新实现。

重试与修复。 每个实验只需要一次重试指令,没有任何候选被尝试超过两次。那 27 个受阻候选并不是因为构建或测试失败:智能体之所以停下,是因为找不到安全删除的办法,而且停下时构建和测试都是通过的。构建或测试失败都在提示词 A 内部修好了(有上下文时 53 次尝试中有 11 次需要修复,每次最多两轮;无上下文时没有)。无上下文的实验在最终验证中有一个测试失败,智能体直接修复了。因此两个实验都没有用到提示词 B。

构建与测试产物。 表 4 对测试文件做了审计。在 64 次变换中,智能体有 57 次修改了测试,新建了 31 个测试文件、修改了 5 个已有测试文件,没有删除任何测试文件。至少有一次变换改变了原有的行为预期。所以,上面定义的“完成”只是一个很弱的标准:它说明候选已经被删掉、当前这套部分由智能体编写的测试能通过,但并不能说明原有行为得到了保持。

RQ2 的回答:我们在 Flask 上归纳出五种依赖变换策略;52 个直接完成中,只有 13 个是用本地代码或标准库重新实现了功能。所有候选都达到了完成标准,但智能体在大多数变换中都修改了测试,因此“完成”并不能证明行为得到保持。

四、局限性与经验

局限性与效度威胁

第一,这只是一个案例研究,结论与 Flask 的结构、测试和依赖实践密切相关。第二,智能体具有随机性,而每种条件只运行了一次,因此无法把条件之间的差异与每次运行之间的波动区分开。第三,我们没有 Flask 依赖的标准答案,也没有第二位标注者,所以既不报告精确率也不报告召回率,分类标签仅作描述用途。第四,RQ2 的工作流是累积式的:候选在同一份工作副本上按智能体选择的顺序处理,顺序效应和连带效应与每个候选本身的难度混在一起;如果每个候选单独运行,结果可能不同。第五,“完成”依赖于智能体可以修改的测试。完整研究应当以一套不可改动的原始测试为基准来验证,并检查 API 兼容性、异常、性能、安全性以及未被测试覆盖的输入。最后,人工验证只由第一作者完成;完整研究应采用多名标注者并报告 Cohen's kappa 等一致性指标,同时在沙箱中运行智能体。

五条经验

  • 经验 1:能看到的上下文,决定了智能体发现了哪些依赖、报告的范围有多大。 有上下文的实验报告的依赖变换候选明显更多,而没有上下文的那次运行更依赖仓库搜索。审计没有发现智能体检查已安装的包环境,这说明它报告的范围主要取决于展示给它的仓库信息。
  • 经验 2:依赖发现可能取决于下游目标。 以“盘点”为目标的运行报告的候选,比以“删除”为目标的运行选出的候选更多。智能体对依赖范围的界定,可能会因任务是描述、删除、替换还是修复依赖而不同。因此,依赖分析应当写明下游用途,并通过条件匹配、重复多次的运行来评估结果对目标的敏感程度。
  • 经验 3:衡量修复效率需要多个维度。 依赖数量、变换条目数量、智能体轮次和变换操作次数,反映的是修复工作量的不同侧面。一次变换可能同时解决多个变换条目,所以评估成本时需要把这类变换考虑在内。
  • 经验 4:依赖变换远不止本地重新实现。 实际出现的策略多种多样,开发者和智能体使用者都应该把它们纳入考虑。
  • 经验 5:“变换完成”本身不能证明行为得到保持。 审计虽然没有发现被删除的测试文件或新增的跳过(skip),但很多修复步骤修改或新增了测试,而且至少有一次变换改变了原有的行为预期。此外,在一次无上下文的尝试中,范围更大的测试运行失败了;智能体判断这个失败与自己的改动无关,于是改用范围更小的测试验证后就把这次尝试标记为完成。

五、研究议程

最后,我们提出一份五部分的研究议程。

  • 依赖变换契约:研究为了保证可验证性需要哪些契约,并通过实证和仿真研究,弄清契约需要包含哪些细节,才能在不同变换之间建立可度量、可比较的指标。
  • 依赖变换策略分类体系:在单个案例之外建立更完整的分类体系。我们计划开展一项完整的实证研究,给出在不同编程语言、仓库类型和 AI 模型之间都保持一致的分类体系。这是最近就要开展的工作。
  • 超越构建与测试的验证:研究如何验证变换结果,探索从行为、运行环境和成本核算等层面识别问题的指标与方法,并结合人工验证和问卷调查,了解这些替代方案相对于引入和维护依赖是否划算。
  • 以修复为中心的智能体工作流:研究依赖变换过程中的不同修复策略。变换中难免要修复代码,因此需要通过实证和仿真研究,找到在修复深度和成本之间取得平衡的有效方法。随着智能体流水线日益普及,也需要成本可控、可复现的研究方法。
  • 人工检查点与长期权衡:研究开发者是否接受依赖变换,以及它在长期是否可行。这需要通过访谈等方式了解开发者的看法:他们是否愿意把这类变换当作现有依赖维护工作的替代方案。

六、结论

这些早期观察的意义在于提出问题,而不是验证结论。即便只是一个案例研究,我们也表明:借助智能体,开发者在依赖管理上多了一些值得考虑的新选择。

复现材料(包括提示词、脚本和执行日志)已公开:https://doi.org/10.5281/zenodo.23230583


译者注

  1. 原文表号更正:原文正文在 RQ1 结果处写作“表 2 列出了两次会话的开销”、在 RQ2 结果处写作“表 4 汇总了完成情况”,对照数据应分别为表 1 和表 3,译文已按数据更正。
  2. 对日常开发的启示:论文里最值得记住的是经验 5。让智能体“删掉某个依赖并保证测试通过”时,它很可能顺手改了测试。实际使用时,建议把原始测试设为只读(例如在 CI 中单独跑一份未经改动的测试),或在评审时重点看测试文件的 diff。
  3. “复制并自行维护”要注意许可证:把第三方源码拷进仓库,等于接手了它的维护和合规责任,需要保留原许可证声明,并持续跟进上游的安全修复。这些拷入的代码也应纳入日常的代码检查和漏洞扫描范围。
  4. 上下文的取舍:经验 1 和经验 2 说明,同一个智能体给你的“依赖清单”会随任务描述和可见文件而变化。做依赖盘点时,最好明确告诉它统计口径(只看直接依赖,还是包含传递依赖),而不是笼统地说“列出所有依赖”。

原文:Agentic Dependency Transformations: Lessons from an Exploratory Case Study of Flask,Kaya Kitada、Yoshiki Higo、Raula Gaikovina Kula,arXiv:2610.21372,2026-10-15,CC BY 4.0。

【版权声明】本文为华为云社区用户翻译文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容, 举报邮箱:cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。