Changing One Prompt Can Affect 50 Others — I Built a Prompt Dependency Graph to Find What Needs Retesting修改一个提示词可能会影响其他 50 个——我构建了一个提示词依赖图来找出需要重新测试的内容
I built a prompt dependency graph that separates everything a component can reach from the smaller set that actually needs targeted evaluation.我构建了一个提示词依赖图,它将组件可触达的所有内容与真正需要针对性评估的较小集合分离开来。

TL;DR总结
If you build with composable prompts, changing one shared component can leave you with a difficult question: what actually needs to be re-evaluated?如果你使用可组合的提示词进行构建,修改一个共享组件会让你面临一个难题:到底什么内容需要重新评估?
I built a pure Python prompt dependency graph that answers that question with two numbers:我用纯 Python 构建了一个提示词依赖图,通过两个数字来回答这个问题:
Reachable: everything downstream of the changed component—the structural ceiling.可触达(Reachable):变更组件下游的所有内容——即结构上的上限。
Candidate: the smaller set that directly depends on the changed section, plus its downstream consumers.候选(Candidate):直接依赖于变更部分的较小集合,以及其下游的消费者。
I tested the approach on a deterministic 55-node synthetic system. Depending on how selectively a component is shared, section-aware tracking narrowed the evaluation set by anywhere from 0% to 85% in my experiments.我在一个确定性的 55 节点合成系统上测试了该方法。根据组件共享的选择性程度,在我的实验中,基于部分的追踪将评估集缩小了 0% 到 85% 不等。
The important caveat: these numbers identify what should be evaluated, not what will actually fail. Behavioral impact still requires running the evaluation itself.重要的注意事项:这些数字确定的是应该评估的内容,而不是实际会失败的内容。行为影响仍然需要通过运行评估本身来确认。
The results, before the methodology: 在介绍方法之前,先看结果:
Here is the data before we get into the code. I ran a 55-node synthetic experiment across a few different change scenarios, tracking the total ceiling versus the actual evaluation set:在进入代码之前,先看看数据。我在几种不同的变更场景下运行了一个 55 节点的合成实验,追踪了总上限与实际评估集之间的对比:
Change Target | Reachable (Ceiling) | Candidate (Evaluation Set) | Narrowing |
| 45 | 24 | 47% |
| 55 | 55 | 0% |
| 55 | 35 | 36% |
| 45 | 24 | 47% |
| 15 | 13 | 13% |
The real takeaway here isn't just that a specific change can slash your test burden by nearly half. The catch is how unpredictable the graph is.这里真正的收获不仅仅是特定的变更可以将测试负担减少近一半。关键在于图的不可预测性。
Sometimes it finds a massive amount of narrowing, and other times it finds nothing. You literally do not know which outcome you are getting until you actually run it.有时它能大幅缩小范围,有时却毫无作用。在实际运行之前,你根本不知道会得到什么结果。
The Line I Changed That I Couldn't Reason About那行我无法推断影响的修改
I compose most of my production prompts out of shared pieces. There is a base-policy block inherited by a support agent, a sales agent, and an internal analyst agent. There's a tone block that almost imports everything, and a format block controlling JSON versus Markdown output. It is a completely ordinary setup once you have more than a handful of agents.我生产环境中的大多数提示词都是由共享片段组成的。有一个基础策略块(base-policy)被支持代理、销售代理和内部分析师代理继承。还有一个几乎所有内容都会导入的语气块(tone),以及一个控制 JSON 与 Markdown 输出的格式块(format)。一旦代理数量超过少数几个,这便是一种非常普通的设置。
One afternoon I changed one sentence in base-policy, extending the refund window from 30 days to 14. The problem wasn't making the edit. It was knowing which agents needed re-evaluation before I shipped it.一天下午,我修改了基础策略块中的一句话,将退款窗口从 30 天延长到 14 天。问题不在于修改本身,而在于在发布之前,我无法确定哪些代理需要重新评估。
Running the entire suite on everything was expensive. I had been guessing which agents used the policy, but I couldn’t tell when I was wrong. A correct guess and a lucky one look identical until something breaks. Shipping and waiting for a support agent to quote the old refund window to a real customer was obviously worse than either.对所有内容运行整个测试套件成本太高。我一直凭猜测判断哪些代理使用了该策略,但我无法判断何时猜错了。在出现问题之前,正确的猜测和幸运的猜测看起来是一样的。发布后等待支持代理向真实客户引用旧的退款窗口,显然比上述任何一种情况都更糟。
I wanted a structural answer to a simple question: what depends on the thing I just changed? That question already has a name in traditional software engineering, change impact analysis, which means tracing dependency relationships outward from a change to determine what else needs re-verification [1]. Prompt engineering still lacks many of the lifecycle practices that software engineering takes for granted, and a recent academic proposal for promptware engineering makes the same observation [2].我想要一个关于简单问题的结构化答案:什么依赖于我刚刚修改的东西?这个问题在传统软件工程中已经有了名字,即“变更影响分析”,这意味着从变更点向外追踪依赖关系,以确定还需要重新验证什么 [1]。提示词工程仍然缺乏许多软件工程视为理所当然的生命周期实践,最近一项关于提示词工程(promptware engineering)的学术提案也指出了这一点 [2]。
So I built the smallest version of that I could, and tested it against a system designed specifically to break my assumptions.所以我构建了尽可能小的版本,并用一个专门为了打破我的假设而设计的系统对其进行了测试。
Complete code: https://github.com/Emmimal/prompt-dependency-graph/完整代码:https://github.com/Emmimal/prompt-dependency-graph/

Why Composability Creates a Larger Evaluation Surface为什么可组合性会产生更大的评估面

Every shared component is a single point that many other things depend on. Every edit to it carries a blast radius.每个共享组件都是许多其他事物所依赖的单一节点。对其进行的每次编辑都带有爆炸半径。
The term comes from explosives, but it has a long second life in software engineering describing how far a change's consequences propagate outward from its source [3]. I am using it in that software-engineering sense here. It means potential downstream impact, not observed behavioral damage.这个术语源于爆炸物,但在软件工程中有着悠久的使用历史,用来描述变更的后果从源头向外传播的范围 [3]。我在这里使用的是软件工程意义上的定义。它指的是潜在的下游影响,而非观察到的行为破坏。
This doesn’t measure whether the prompt’s output actually changed. It measures how far that change could spread if it did.这并不衡量提示词的输出是否真的发生了变化。它衡量的是如果发生了变化,这种变化可能传播到多远。
Chaos engineering treats minimizing blast radius as a first-class design goal for a simple reason: not to prevent failure, but to keep its consequences bounded and legible [4].混沌工程将最小化爆炸半径视为首要设计目标,原因很简单:不是为了防止故障,而是为了将其后果限制在可控和可理解的范围内 [4]。
Build systems solve an adjacent problem the same way. Bazel maintains a dependency graph across a codebase specifically so a single file change only triggers rebuilds of what is actually downstream of it, rather than everything [5].构建系统也以同样的方式解决了类似的问题。Bazel 在代码库中维护一个依赖图,专门为了确保单个文件的变更只会触发其下游内容的重新构建,而不是全部重新构建 [5]。
Prompt chaining has its own version of this failure. Because prompts feed into each other, a small and unintended change to an upstream prompt can produce unpredictable results several steps downstream, with no compiler or type system to catch it first [6].提示词链也有其自身的故障版本。由于提示词相互馈送,对上游提示词的微小且无意的更改可能会在下游几步产生不可预测的结果,且没有任何编译器或类型系统来首先捕获它 [6]。
That is exactly what I was worried about with base-policy. I just had no way to measure it.这正是我对基础策略所担心的。我只是没有办法去衡量它。
Why a Flat Dependency Lookup Fails 为什么扁平化的依赖查找会失败
Before building anything complex, I tried the simplest approach: check who directly imports the component I edited.在构建任何复杂的东西之前,我尝试了最简单的方法:检查谁直接导入了我编辑的组件。
I set up two test cases with the exact same total impact (4 downstream nodes), but different layouts:我设置了两个测试用例,它们具有完全相同的总影响(4 个下游节点),但布局不同:
Flat:
comp-afeeds straight into 4 different agents. (4 direct dependents)扁平型:组件 A 直接馈送到 4 个不同的代理。(4 个直接依赖项)Deep:
comp-bfeeds into an agent, which feeds into a workflow, then another agent, and so on. (1 direct dependent)深度型:组件 B 馈送到一个代理,该代理馈送到一个工作流,然后是另一个代理,依此类推。(1 个直接依赖项)

If you only check direct imports, the flat system reports 4 impacted nodes, but the deep system reports 1. It misses three-quarters of the actual impact.如果你只检查直接导入,扁平系统报告 4 个受影响节点,但深度系统只报告 1 个。它漏掉了四分之三的实际影响。
The reachable downstream set didn't change; both graphs contain 4 downstream nodes. What changed was what a one-hop lookup could see. In the deep case, it finds only the first dependent and misses the three nodes further downstream. That is the reason transitive analysis matters in traditional change impact analysis: dependencies propagate through chains, so looking only at direct dependents can systematically undercount the downstream evaluation surface [1].可触达的下游集合并没有改变;两个图都包含 4 个下游节点。改变的是单跳查找所能看到的内容。在深度情况下,它只找到了第一个依赖项,而漏掉了下游更远的三个节点。这就是传递分析在传统变更影响分析中很重要的原因:依赖关系通过链条传播,因此只看直接依赖项可能会系统性地低估下游评估面 [1]。
In prompt systems, this happens often because prompts are layered like organizational charts. A base policy goes to an agent, which goes to a workflow, which goes to a router. Every layer is another hop.在提示词系统中,这种情况经常发生,因为提示词像组织结构图一样分层。基础策略进入代理,代理进入工作流,工作流进入路由。每一层都是新的一跳。
If a dependency check only looks one level deep, it can miss risks in prompts further down the chain, where important logic often lives.如果依赖检查只看一层深度,它可能会漏掉链条更深处的提示词风险,而重要的逻辑往往就存在于那里。
Component 1: The Data Model组件 1:数据模型
A PromptComponent isn't modeled as an opaque blob of text. It is a versioned collection of named sections.PromptComponent 不应被建模为不透明的文本块。它是一个带有版本号的命名片段集合。
The section-level approach is the most important design choice because it helps us define “candidate” more narrowly than “reachable.” Agents declare which specific sections they use, and with what relationship (imports, inherits, references, formats-with):片段级方法是最重要的设计选择,因为它帮助我们比“可触达”更狭义地定义“候选”。代理会声明它们使用了哪些特定片段,以及具有何种关系(导入、继承、引用、格式化):
I didn’t use dependency type in the impact calculation for v1, but I still needed the model to keep track of the difference.我在 v1 的影响计算中没有使用依赖类型,但我仍然需要模型来跟踪其中的差异。
This was a deliberate choice: I made the data model flexible enough for future needs, instead of adding a new field later and changing all the existing dependency declarations. Adding a type system later is harder than including one extra field that you don’t use yet.这是一个深思熟虑的选择:我使数据模型足够灵活以满足未来需求,而不是以后再添加新字段并更改所有现有的依赖声明。以后再添加类型系统比现在包含一个你暂时还没用到的额外字段要困难得多。
Component 2: The Dependency Graph组件 2:依赖图
The graph needs to answer two simple questions: who uses this component directly, and who is reachable further down the line?该图需要回答两个简单的问题:谁直接使用了该组件,以及在下游还有谁是可触达的?
This dependency walk fixes the blind spot we saw earlier. It treats agent-to-agent and agent-to-workflow connections as normal graph links and uses a simple breadth-first search to follow them outward:这种依赖遍历修复了我们之前看到的盲点。它将代理到代理以及代理到工作流的连接视为正常的图链接,并使用简单的广度优先搜索向外追踪它们:
I called this structural_blast_radius in the code, but I just label it Reachable in the output. Saying structural feels a bit too confident since we do not know the full behavioral impact yet. Reachable is a cleaner description of what it actually is: everything downstream that could get touched, rather than everything that definitely will be.我在代码中将其称为 structural_blast_radius(结构化爆炸半径),但在输出中我将其标记为“可触达”(Reachable)。说“结构化”感觉有点过于自信,因为我们还不知道完整的行为影响。Reachable 是对其本质更清晰的描述:所有可能被触及的下游内容,而不是必然会被触及的内容。
I considered limiting how far the search could go in larger systems, but the results didn’t show a need for it. With 55 nodes, the search takes less than a millisecond. Testing it on larger production systems is still future work.我考虑过在更大的系统中限制搜索深度,但结果显示没有必要。对于 55 个节点,搜索耗时不到一毫秒。在更大的生产系统上进行测试仍是未来的工作。
Component 3: Reachable versus Candidate组件 3:可触达与候选
A section is considered changed when its text is different between two versions. There are no embeddings or semantic checks, just a direct text comparison that ignores extra spaces:当一个片段在两个版本之间的文本不同时,它被视为已更改。这里没有嵌入或语义检查,只有忽略额外空格的直接文本比较:
That is one half of the mechanism. The other half runs the same graph traversal, but starts from the specific nodes that declared the changed section instead of the whole component:这是机制的一半。另一半运行相同的图遍历,但从声明了已更改片段的特定节点开始,而不是从整个组件开始:
That’s the whole process. We run the same breadth-first search twice: once for everything connected to the component, and once only for nodes connected to the section that changed.这就是整个过程。我们运行两次相同的广度优先搜索:一次针对与该组件连接的所有内容,另一次仅针对与已更改片段连接的节点。
Defining the Two Numbers定义这两个数字
This gives us two clear terms to work with:这为我们提供了两个清晰的术语:
Reachable set: The maximum possible impact: every node connected to the changed component, no matter which section changed. It comes only from the graph structure.可触达集(Reachable set):最大可能的冲击范围:与已更改组件连接的每个节点,无论哪个片段发生了变化。它仅源于图结构。
Candidate set: The proposed evaluation boundary: nodes that directly depend on the changed section, plus everything downstream from them. It is based on the section diff and graph traversal.候选集(Candidate set):建议的评估边界:直接依赖于已更改片段的节点,加上它们下游的所有内容。它基于片段差异和图遍历。
Once a node is selected, everything downstream from it is included. For example, if a changed section affects an agent and that agent affects a workflow, the workflow becomes a candidate too. After the first step, we track nodes rather than individual sections.一旦选中一个节点,其下游的所有内容都会被包含在内。例如,如果已更改的片段影响了一个代理,而该代理又影响了一个工作流,那么该工作流也成为候选者。第一步之后,我们追踪的是节点而不是单个片段。
Important Caveats on the Numbers关于这些数字的重要注意事项
Because the reachable ceiling is computed per component rather than per section, it treats every section the same. If a component has one widely used section and one rarely used section, the reachable number stays identical no matter which one you edit. The candidate set is the only metric that actually shifts based on the specific edit.由于可触达上限是按组件计算的,而不是按片段计算的,因此它对待每个片段的方式都是一样的。如果一个组件有一个广泛使用的片段和一个很少使用的片段,无论你编辑哪一个,可触达数字都保持不变。候选集是唯一真正根据具体编辑而变化的指标。
To keep things precise, I stick to specific terms for these outputs so the tool is never misunderstood:为了保持精确,我为这些输出坚持使用特定的术语,以确保工具不会被误解:
Avoid | Use Instead | Why |
Structural blast radius | Reachable blast radius | "Structural" implies a level of precision the ceiling doesn't have. |
Semantic blast radius | Candidate blast radius | Nothing semantic is happening; it is just a section-level diff. |
Safe prompts | Candidates for evaluation | Skipping the candidate list is not the same as proving safety. |
Unaffected prompts | Outside the candidate set | The graph only shows declared dependencies, not actual behavior. |
Broken prompts | Potentially affected prompts | The graph never confirms behavior changed; only evaluation does. |
Evaluation reduction | Evaluation narrowing | "Reduction" implies a target; "narrowing" just describes the mechanism. |
Component 4: The 55-Node Test System组件 4:55 节点测试系统
To test this, I built a deterministic 55-node system consisting of 50 agents across five roles (support, sales, analyst, operations, and marketing, with ten teams each) plus 5 workflow nodes. Those workflow nodes depend on agents rather than components directly, which creates a real transitive and diamond-shaped dependency structure. There are also five shared components (base-policy, tone, format, domain, safety), each with two to four sections.为了测试这一点,我构建了一个确定性的 55 节点系统,包含分布在五个角色(支持、销售、分析师、运营和营销,每组十个团队)中的 50 个代理,以及 5 个工作流节点。这些工作流节点直接依赖于代理而不是组件,这创建了一个真实的传递性和菱形依赖结构。此外还有五个共享组件(基础策略、语气、格式、领域、安全),每个组件有两到四个片段。
Role | Agents | Depends on base-policy? | Notes |
support | 10 | Yes ( | Heaviest policy consumer. |
sales | 10 | Yes ( | Narrower policy surface than support. |
analyst | 10 | Yes ( | Reads policy but doesn't own it. |
operations | 10 | Yes ( | Also inherits both safety sections. |
marketing | 10 | No | Deliberately disconnected. Real systems always have agents that share nothing with a given policy. |
workflows | 5 | Reachable transitively through 3 constituent agents each | Includes |
The key rules behind this table are simple: support imports the refunds and privacy sections, sales only imports privacy, and marketing doesn't touch the base policy at all.该表格背后的关键规则很简单:支持导入退款和隐私片段,销售仅导入隐私,而营销完全不触及基础策略。
That last rule is deliberate. If every node in a synthetic system depended on every shared component, it would hide the exact pattern I wanted to find. You need some agents with zero exposure to a given component to test whether section-aware tracking actually works.最后一条规则是刻意为之的。如果合成系统中的每个节点都依赖于每个共享组件,它就会掩盖我想要寻找的确切模式。你需要一些对给定组件零暴露的代理,以测试片段感知追踪是否真的有效。
The full generator covering all five roles and the workflow layer lives in the repository. I left out the 50 near-identical configuration blocks here to save space.涵盖所有五个角色和工作流层的完整生成器位于代码库中。为了节省空间,我省略了那 50 个几乎相同的配置块。
Every number in the results table at the top of this article comes from running compute_impact() against this exact graph. These figures describe one deliberately built, synthetic 55-node system rather than a universal law for every production suite. Whether a real system shows this same pattern depends entirely on its actual dependency structure, which is what this tool exists to measure rather than assume.本文顶部结果表中的每个数字都来自针对这个确切图运行 compute_impact() 的结果。这些数字描述的是一个刻意构建的 55 节点合成系统,而不是适用于所有生产套件的普遍规律。真实系统是否表现出同样的模式完全取决于其真实的依赖结构,而这正是该工具旨在衡量而非假设的内容。
Walking Through One Change, Start to Finish从头到尾走一遍变更流程
With the system in place, it is worth tracing what happens during a real edit because the mechanism is easy to lose track of in the abstract.系统就位后,值得追踪一次实际编辑过程中会发生什么,因为在抽象层面很容易忽略其机制。
Say I edit the refunds section of base-policy:假设我编辑了基础策略中的退款片段:
v1: "
Customers may request refunds within 30 days of purchase."v1:“客户可在购买后 30 天内申请退款。”v2: "
Customers may request refunds within 14 days of purchase."v2:“客户可在购买后 14 天内申请退款。”
changed_sections() compares every section of v1 against v2. It sees that refunds differs, while privacy and escalation do not.changed_sections() 比较 v1 和 v2 的每个片段。它发现退款片段不同,而隐私和升级片段没有变化。
Next, structural_blast_radius("base-policy") walks the graph outward through every node depending on it for any reason, plus everything downstream of those. That gives us 45 nodes.接下来,structural_blast_radius("base-policy") 通过所有因任何原因依赖它的节点以及这些节点下游的所有内容向外遍历图。这给了我们 45 个节点。
Meanwhile, section_dependents("base-policy", "refunds") narrows that list down to only the nodes that explicitly declared a dependency on refunds, extended downstream. That gives us 24 nodes.同时,section_dependents("base-policy", "refunds") 将该列表缩小到仅包含明确声明依赖于退款的节点,并扩展到下游。这给了我们 24 个节点。
Why the Difference Matters为什么差异很重要
The gap between those two sets is concrete. Every sales-* agent is in the reachable set of 45 because they depend on base-policy through the privacy section. However, none of them are in the candidate set of 24 because they never declared a dependency on refunds.这两组之间的差距是具体的。每个销售代理都在 45 个节点的“可触达集”中,因为它们通过隐私片段依赖于基础策略。然而,它们都不在 24 个节点的“候选集”中,因为它们从未声明依赖于退款片段。
A sales agent uses the privacy section because it handles customer data, but it does not use the refund section. Without section-level declarations, we cannot tell the difference, so the sales agent is incorrectly included in the refund check.销售代理使用隐私片段是因为它处理客户数据,但它不使用退款片段。如果没有片段级的声明,我们就无法区分差异,因此销售代理被错误地包含在退款检查中。
Splitting the full 55-node system three ways for this change clarifies the actual footprint:将完整的 55 节点系统按此变更分为三部分,可以阐明其实际影响范围:
Group | Count | What it Means |
Candidate | 24 | Declared a dependency on refunds specifically. |
Reachable, not candidate | 21 | Downstream of |
Not reachable at all | 10 | No dependency on |
Adding those up: 24 + 21 + 10 = 55, and every single node lands in exactly one bucket.加起来:24 + 21 + 10 = 55,每个节点都恰好落入一个桶中。
Instead of checking all 55 agents or guessing, we get a clear first-pass set of 24 agents and can see why the other 31 were left out.我们不需要检查所有 55 个代理或进行猜测,而是得到了一个清晰的 24 个代理的初步筛选集,并且可以清楚地看到为什么其他 31 个被排除在外。

The Sharing-Level Curve共享水平曲线
That first change happened to land on 47% narrowing. Is that number unique to base-policy, or would any component in this system land somewhere similar?第一次变更恰好实现了 47% 的缩减。这个数字是基础策略独有的,还是该系统中的任何组件都会落在类似的位置?
To find out, I changed just one thing: how many agents shared a component. Everything else stayed the same. I tested one component shared by about 10%, 24%, 50%, and 76% of the agents, plus the existing tone component shared by all 100% of agents.为了找出答案,我只改变了一件事:有多少代理共享一个组件。其他一切保持不变。我测试了一个由大约 10%、24%、50% 和 76% 的代理共享的组件,加上现有的由 100% 代理共享的语气组件。
Sharing level (% of agents) | Direct | Reachable | Candidate | Narrowing |
10.0% | 5 | 54 | 8 | 85% |
24.0% | 12 | 54 | 15 | 72% |
50.0% | 25 | 54 | 29 | 46% |
76.0% | 38 | 54 | 43 | 20% |
100.0% (tone) | 50 | 55 | 55 | 0% |

One detail is worth noting: the four sharing tiers reach 54 of the 55 nodes because one node is not assigned to any tier. The tone component reaches all 55 because every agent depends on it. These are just two different components with different real-world coverage.值得注意的一个细节是:四个共享层级覆盖了 55 个节点中的 54 个,因为有一个节点未分配到任何层级。语气组件覆盖了所有 55 个,因为每个代理都依赖于它。这只是两个具有不同真实覆盖范围的组件。
In this synthetic experiment, the relationship is monotonic: as component sharing increases, the opportunity for section-aware narrowing decreases. Put simply, the more universally a component is shared, the less there is for dependency-aware evaluation to narrow.在这个合成实验中,关系是单调的:随着组件共享的增加,片段感知缩小的机会就会减少。简而言之,组件共享得越普遍,依赖感知评估所能缩小的空间就越小。
This experiment demonstrates that relationship inside one controlled synthetic system. It doesn't prove that production systems will yield the exact same percentages.该实验在一个受控的合成系统内展示了这种关系。它并不能证明生产系统会产生完全相同的百分比。
I built a separate component for this test instead of using the five results from earlier. Those earlier components had different sharing levels by chance, not by design. Using them would make it hard to know if the results were caused by sharing level or something else. So I tested one component with four sections at planned sharing levels to measure only the effect of sharing.我为这个测试构建了一个单独的组件,而不是使用之前五个结果中的组件。那些早期的组件是偶然具有不同的共享水平,而不是设计使然。使用它们将很难判断结果是由共享水平还是其他原因引起的。因此,我测试了一个具有四个片段且处于计划共享水平的组件,以仅衡量共享的影响。
Notice that Reachable stayed at 54 for every sharing level from 10% to 76%. This shows the limitation of measuring one component. Only the candidate set changed with the sharing level.注意,从 10% 到 76% 的每个共享水平,Reachable 都保持在 54。这显示了衡量单个组件的局限性。只有候选集随共享水平而变化。
The Result That Mattered More Than the Percentages比百分比更重要的结果
tone/professional: Reachable 55, Candidate 55, Narrowing 0%.语气/专业:可触达 55,候选 55,缩减 0%。
Every agent inherits tone. When it changed, every agent was a legitimate candidate. The graph did not fail to find a shortcut; there simply isn't one when a component is genuinely universal.每个代理都继承了语气。当它发生变化时,每个代理都是合理的候选者。图表并没有未能找到捷径;当一个组件真正通用时,根本不存在捷径。
This is the result that keeps the tool honest. It would be simple to build something like this and imply that dependency-aware evaluation always saves work. It doesn't.这是让该工具保持诚实的结果。构建这样一个东西并暗示依赖感知评估总是能节省工作量是很简单的。但事实并非如此。
Sometimes running all 55 evaluations is the correct answer. A tool built to always report a smaller number would have quietly forced one here too. Instead, this one returns 55 without apology, because that is the honest answer for a component every agent genuinely depends on.有时运行所有 55 个评估才是正确的答案。一个旨在总是报告较小数字的工具也会在这里悄悄强行输出一个较小数字。相反,这个工具会毫不掩饰地返回 55,因为对于每个代理都真正依赖的组件来说,这是诚实的答案。
There’s a practical takeaway here. If every consumer uses a shared component in the same way, section-level tracking does not help. For example, tone is used by everyone, so there is no smaller group to exclude.这里有一个实际的收获。如果每个消费者都以相同的方式使用共享组件,片段级追踪就没有帮助。例如,语气被所有人使用,因此没有更小的组可以排除。
The tool is most useful for components that are used by different groups of agents. That is what the sharing curve measures. I would rather have a tool that sometimes says there is no shortcut than one that always pretends there is.该工具最适用于被不同代理组使用的组件。这就是共享曲线所衡量的。我宁愿要一个有时说没有捷径的工具,也不要一个总是假装有捷径的工具。
How These Numbers Were Verified这些数字是如何验证的
Every number in this article comes from an actual run of the code in the repository rather than a hand-calculated estimate.本文中的每个数字都来自代码库中代码的实际运行,而不是人工计算的估计。
The synthetic system is generated deterministically with random.seed(7), so the 50-agent configuration, 5 workflows, and their dependency relationships are reproduced consistently across runs. I also reproduced the full pipeline independently on a separate Windows machine in a clean virtual environment. The results matched the original run, including:合成系统是使用 random.seed(7) 确定性生成的,因此 50 个代理的配置、5 个工作流及其依赖关系在多次运行中是一致的。我还独立地在另一台 Windows 机器上的干净虚拟环境中完整重现了整个流水线。结果与原始运行相匹配,包括:
The 45/24 split on
base-policyand refunds.基础策略和退款上的 45/24 分割。The 55/55 on
tone.语气上的 55/55。The 54-node ceiling across all four sharing tiers.所有四个共享层级上的 54 节点上限。
The 4-node blast radius on both the flat and deep shape comparison.扁平与深度形状比较上的 4 节点爆炸半径。
That reproducibility is not incidental. If a dependency graph's numbers can't be reproduced deterministically on a second machine, there's no reason to trust anything downstream of them.这种可重复性并非偶然。如果依赖图的数字不能在第二台机器上确定性地重现,那么就没有理由信任其下游的任何内容。
A Gap Caught During Verification验证过程中发现的差距
The verification pass exposed a modeling issue in the first version of the workflow layer. Workflows were copying their constituent agents' component dependencies instead of declaring those agents as graph nodes. That meant the 55-node experiment was not actually testing the intended transitive traversal.验证过程暴露了工作流层第一版中的一个建模问题。工作流复制了其组成代理的组件依赖项,而不是将这些代理声明为图节点。这意味着 55 节点实验实际上并没有测试预期的传递遍历。
I changed the model so workflows depend directly on their constituent agents and re-ran the experiments. The Reachable, Candidate, and Narrowing results did not change because the flattened and transitive representations happened to produce the same sets for this synthetic graph.我修改了模型,使工作流直接依赖于其组成代理,并重新运行了实验。可触达、候选和缩减结果没有改变,因为扁平化表示和传递表示恰好为这个合成图产生了相同的集合。
The direct dependency counts did change. That’s why the sharing curve uses the 50 agents as the base, not all 55 nodes. The experiment measures how many agents share the component, while workflows are downstream nodes.直接依赖计数确实发生了变化。这就是为什么共享曲线使用 50 个代理作为基础,而不是所有 55 个节点。该实验衡量的是有多少代理共享该组件,而工作流是下游节点。
Runtime Characteristics运行特性
Measured on Python 3.12, CPU only, standard library for the core mechanism (matplotlib and networkx are used only for the figures):在 Python 3.12 上测量,仅 CPU,核心机制使用标准库(matplotlib 和 networkx 仅用于图表):
Operation | Latency | Notes |
Build graph (55 nodes, 5 components) | 0.229 ms | Average of 200 builds. |
| 0.0375 ms | Average of 2,000 calls. |
| 31.91 ms | Single timed block, no caching. |
For this 55-node experiment, graph construction and impact calculation were negligible compared with the cost of running the resulting evaluation set. The measured compute_impact() latency was 0.0375 ms on average, while the 1,000-call batch completed in 31.91 ms.对于这个 55 节点的实验,图构建和影响计算的成本与运行最终评估集的成本相比可以忽略不计。测得的 compute_impact() 延迟平均为 0.0375 毫秒,而 1,000 次调用的批处理在 31.91 毫秒内完成。
I have not yet tested the implementation on graphs containing thousands of prompts, so these measurements should not be treated as production-scale performance results. The core operations, set unions and breadth-first searches, are lightweight, but we still need to test how well they scale to larger systems.我尚未在包含数千个提示词的图上测试该实现,因此这些测量结果不应被视为生产规模的性能结果。核心操作(集合并集和广度优先搜索)是轻量级的,但我们仍需要测试它们在更大系统中的扩展性。
Honest Design Decisions诚实的设计决策
Mechanical section diffing: Section diffing is purely mechanical. A one-character fix and a full rewrite are treated the same because the check only compares the old and new text. This is a real limitation, not an oversight. Using an LLM to decide whether an edit is meaningful would add a judgment that is hard to verify. A dependency graph should show what changed and what could be affected, not decide whether the change is important.机械式片段差异比较:片段差异比较纯粹是机械式的。一个字符的修复和完全重写被视为相同,因为检查只比较旧文本和新文本。这是一个真正的限制,而不是疏忽。使用 LLM 来判断编辑是否有意义会增加难以验证的判断。依赖图应该显示发生了什么变化以及可能受到什么影响,而不是决定变更是否重要。
No section rename inference: Section renames aren't inferred, and I went back and forth on this. Renaming
refund_policytorefundsis not treated as the same section under a new name. It is reported as a removed section and an added section, flagged as high-risk, with continuity explicitly marked as unresolved. Guessing that two differently named sections mean the same thing is unreliable. I would rather the tool say it cannot tell than give the wrong answer.没有片段重命名推断:片段重命名不会被推断,我在这方面反复斟酌过。将 refund_policy 重命名为 refunds 不会被视为新名称下的相同片段。它被报告为一个删除的片段和一个添加的片段,被标记为高风险,且连续性被明确标记为未解决。猜测两个不同名称的片段意味着相同的事情是不可靠的。我宁愿工具说它无法判断,也不愿给出错误的答案。Unused dependency kinds: Dependency kinds (
imports,inherits,references,formats-with) are modeled but not load-bearing yet. The impact calculator treats all four identically, even though I do not think they should produce identical evaluation requirements. Aformats-withdependency on a JSON schema section probably should not trigger the same re-evaluation as animportsdependency on a refund policy section. I left this out of v1 on purpose rather than building a distinction I had not actually tested. It is the most obvious next increment, not a gap I overlooked.未使用的依赖类型:依赖类型(导入、继承、引用、格式化)已建模但尚未发挥作用。影响计算器对所有四种类型一视同仁,尽管我认为它们不应该产生相同的评估要求。对 JSON 模式片段的“格式化”依赖可能不应该触发与对退款策略片段的“导入”依赖相同的重新评估。我故意在 v1 中省去了这一点,而不是构建一个我尚未实际测试过的区别。这是最明显的下一个增量,而不是我忽略的差距。Toy system scope: This is a 55-node toy system, not a production benchmark, and every number in this piece is scoped to it specifically. I built it to isolate one variable at a time (sharing level, dependency depth, section granularity) cleanly enough to see real patterns, not to simulate what a production prompt library looks like.玩具系统范围:这是一个 55 节点的玩具系统,而不是生产基准,本文中的每个数字都专门针对它。我构建它是为了足够清晰地隔离每个变量(共享水平、依赖深度、片段粒度)以观察真实模式,而不是为了模拟生产提示词库的样子。
Section-level structure requirement: Section-level structure is the main requirement for this to work. If a prompt is not split into named sections, there is nothing for the system to compare. In that case, it tracks the whole component and gives the full Reachable number without narrowing it down.片段级结构要求:片段级结构是此方法有效的主要要求。如果提示词没有拆分为命名片段,系统就没有东西可以比较。在这种情况下,它会跟踪整个组件并给出完整的可触达数字,而不会将其缩小。
In practice, this usually does not require a big migration. Most system prompts already have sections, such as policy, tone, and formatting instructions. You just need to make those sections explicit using simple Markdown headers or comment blocks. That is most of the work needed to adopt this approach.在实践中,这通常不需要大规模迁移。大多数系统提示词已经有了片段,例如策略、语气和格式指令。你只需要使用简单的 Markdown 标题或注释块明确这些片段即可。这就是采用这种方法所需的大部分工作。
Trade-offs and What's Missing权衡与缺失的内容
The current implementation deliberately stops at structural, section-aware dependency analysis. Several useful capabilities remain outside v1:当前的实现故意停留在结构化、片段感知的依赖分析上。几个有用的功能仍然在 v1 之外:
Missing | Why it's out of scope for v1 |
Dependency-type-aware filtering | Kind is modeled but not yet used in impact calculation. |
Evaluation harness integration | This tool says what to check, not how to check it. |
Circular dependency handling | System was built without cycles; real graphs with them need this. |
Production-scale validation | 55 nodes proves the mechanism, not throughput at scale. |
Make the Cost of Change Visible使变更成本可见
The useful output of a prompt dependency graph is not a prediction of failure. It is a defensible evaluation boundary.提示词依赖图的有价值输出不是对故障的预测。它是一个可辩护的评估边界。
When base-policy/refunds changes, this system says: start with these 24 candidates. It does not say those 24 prompts will break. When tone changes, it says all 55 are candidates because every agent genuinely depends on that component.当基础策略/退款发生变化时,该系统会说:从这 24 个候选者开始。它并没有说这 24 个提示词会崩溃。当语气发生变化时,它会说所有 55 个都是候选者,因为每个代理都真正依赖于该组件。
A dependency graph doesn't make a prompt safer. It makes the cost of changing it visible before you pay it.依赖图并不能使提示词更安全。它是在你付出代价之前,使更改它的成本变得可见。
Complete code: https://github.com/Emmimal/prompt-dependency-graph/
Related Reading相关阅读
Two earlier pieces cover adjacent ground:两篇早期的文章涵盖了相关领域:
"Prompt Engineering Is Solved—Prompt Management Isn't" looks at contract validation for individual prompt changes.《提示词工程已解决——提示词管理尚未解决》着眼于单个提示词变更的契约验证。
"Prompt Engineering Fails Quietly—Prompt Regression Is Why" covers runtime behavioral regression detection after a change ships.《提示词工程悄无声息地失败——提示词回归是原因》涵盖了变更发布后的运行时行为回归检测。
This piece sits upstream of both. It is about knowing what to check before you run either of those checks, rather than the checking process itself.本文位于这两者之上。它是关于在运行这些检查之前知道要检查什么,而不是检查过程本身。
References参考文献
[1] Change impact analysis. Wikipedia. https://en.wikipedia.org/wiki/Change_impact_analysis[1] 变更影响分析。维基百科。https://en.wikipedia.org/wiki/Change_impact_analysis
[2] Chen, Z., Wang, C., Sun, W., Liu, X., Zhang, J. M., & Liu, Y. (2026). Promptware engineering: Software engineering for prompt-enabled systems. ACM Transactions on Software Engineering and Methodology. https://doi.org/10.1145/3796535[2] Chen, Z., Wang, C., Sun, W., Liu, X., Zhang, J. M., & Liu, Y. (2026). Promptware engineering: Software engineering for prompt-enabled systems. ACM Transactions on Software Engineering and Methodology. https://doi.org/10.1145/3796535
[3] Blast radius. Wikipedia. https://en.wikipedia.org/wiki/Blast_radius[3] 爆炸半径。维基百科。https://en.wikipedia.org/wiki/Blast_radius
[4] Rosenthal, C., Hochstein, L., Blohowiak, A., Jones, N., & Basiri, A. (2017). Minimize blast radius. In Chaos engineering. O’Reilly Media. https://www.oreilly.com/library/view/chaos-engineering/9781491988459/ch07.html[4] Rosenthal, C., Hochstein, L., Blohowiak, A., Jones, N., & Basiri, A. (2017). Minimize blast radius. In Chaos engineering. O’Reilly Media. https://www.oreilly.com/library/view/chaos-engineering/9781491988459/ch07.html
[5] Bazel overview. https://bazel.build/[5] Bazel 概述。https://bazel.build/
[6] Greyling, C. These Are The Challenges When Creating A LLM Based Conversational Interface. https://cobusgreyling.medium.com/these-are-the-challenges-when-creating-a-llm-based-conversational-interface-4c8bdf018c24[6] Greyling, C. 创建基于 LLM 的对话界面时面临的挑战。https://cobusgreyling.medium.com/these-are-the-challenges-when-creating-a-llm-based-conversational-interface-4c8bdf018c24
Disclosure披露
All code, experiments, and figures in this article are my own work, developed and tested on Python 3.12. The synthetic 55-node system, all five experiments, the sharing-curve test, and three of the four diagrams (the direct-vs-transitive dependency chain, the candidate-vs-reachable subgraph, and the sharing-level curve) were generated by the linked repository's scripts, run on my own machine, with numbers reproduced independently, including the runtime table above, calculated from timed local runs. The pipeline diagram is a hand-built schematic, not a script output. The featured image was generated with ChatGPT (DALL·E). No production data, real customer prompts, or proprietary systems were used; the entire system is synthetic and illustrative. I have no financial relationship with any tool, library, or company mentioned in this. 本文中的所有代码、实验和图表均为我个人工作,在 Python 3.12 上开发和测试。合成的 55 节点系统、所有五个实验、共享曲线测试以及四个图表中的三个(直接与传递依赖链、候选与可触达子图、共享水平曲线)均由链接代码库的脚本生成,在我自己的机器上运行,数字经过独立重现,包括上面的运行时表(根据本地计时运行计算)。流水线图是手工构建的示意图,而非脚本输出。特色图片由 ChatGPT (DALL·E) 生成。未使用任何生产数据、真实客户提示词或专有系统;整个系统是合成且具有说明性的。我与本文中提到的任何工具、库或公司均无财务关系。








