LLM护套工程为何如此困难——从测试到迭代的真实痛点
本文基于作者五个月的实战经验(104次提交),深入剖析将LLM演示转化为可靠产品时遭遇的结构性困难。核心挑战包括:无法编写确定性测试(相同输入每次输出不同)、模型失败无声且呈渐变(99%正确中隐藏1%错误)、调试对象是自然语言段落而非代码(一个形容词可能成为bug)、添加规则反而陷入困境(prompt从20行膨胀到200行导致模型相互矛盾)、示例的引导力远强于指令、模型基础不稳定(供应商更新悄然改变行为)、反馈循环慢且昂贵以及护套工程工作不可见(外人以为只是写prompt)。文章强调护套工程(prompt、校验、eval、护栏)是真正的护城河,其难度源于概率系统的本质,无法工程化消除,只能构建能吸收这些不确定性的系统。适合LLM应用开发者、AI工程师及对AI工程痛点感兴趣的技术管理者阅读。
Why Harness Engineering Is So Hard
By @WinterArc2125 · 2026-07-25T15:42:52.000Z
为什么 Harness 工程如此困难
作者 @WinterArc2125 · 2026-07-25T15:42:52.000Z


Five months, 104 commits, and one repeating lesson: harness engineering is hard.
There is an illusion that settles over almost every project built on a large language model. The illusion is that the model is the product. You wire up an API call, write a prompt, get something impressive in the first demo, and feel as though the hard part is finished.
It is not.
The difficult part is turning that impressive demonstration into behaviour you can rely on. It is getting the model to follow the same constraints across different inputs, express the right ideas without drifting, survive model updates, fail visibly instead of silently, and produce output that can be tested even though it will never be perfectly deterministic.
That behavioural layer is a large part of what I mean by the harness: the prompts, examples, schemas, validators, evals, and guardrails that sit between a probabilistic model and the person using your product. The model generates the output. The harness is what makes that output usable.
This is why harness engineering is so hard, and why the difficulty keeps surprising people who are otherwise excellent engineers. I've spent months in it, and most of the pain comes from a handful of structural facts about working with a probabilistic system that look, on the surface, like ordinary software problems, but to our dismay, are not.
五个月,104 次提交,一个反复出现的教训:Harness 工程很难。
几乎每个基于大语言模型的项目都会产生一种错觉:模型就是产品。你接上 API 调用、写一段 Prompt,第一次演示就得到惊艳的结果,感觉最难的部分已经完成了。
但事实并非如此。
真正的困难在于把那个惊艳的演示变成你能够信赖的行为。你需要让模型在不同输入下遵循相同的约束、表达正确的观点而不偏离、在模型更新后依然稳定、在出错时显式失败而非静默、并产出可以被测试的输出——尽管输出永远无法完全确定。
这个行为层很大程度上就是我所说的 Harness:提示词、示例、模式、校验器、评估和防护栏,它们位于概率模型和使用你产品的用户之间。模型生成输出,而 Harness 让输出变得可用。
这就是 Harness 工程如此困难的原因,也是为什么这种困难不断让其他方面优秀的工程师感到意外。我在这上面花了数月时间,大多数痛苦来自与概率系统合作时的一些结构性事实——它们表面上看像是普通的软件问题,但令我们沮丧的是,其实不是。


You can't write the test you want to write
The first thing that breaks is your testing instinct. Every testing practice you've built up over a career assumes determinism: given this input, the code produces this output, and I assert equality. But feed the same input to a language model twice, and you get two different outputs. Not always wildly different, sometimes the difference is a few words, a reordered clause, a slightly different way of phrasing the same idea, but different enough that a strict equality assertion is useless.
This sounds like a minor annoyance. It is not. It removed the floor under my whole quality strategy for my specific harness. The reflex, "write a test, run it, green means done" no longer works, and most engineers don't have a replacement reflex ready. You can't assert on exact wording, because the wording changes. You can assert on structure (does the output parse, do the required fields exist, are the counts right), and you can assert on invariants (does it violate a rule it was told to follow), but both of those are weaker than what engineers are used to, and both let real unexpected output through.
Instead of asking whether one output equals one expected answer, you begin asking whether the system satisfies a rubric across a distribution of inputs and repeated runs. That is closer to evaluation than ordinary unit testing, and the result is a confidence level rather than a proof of correctness.
This brings us to...
你无法编写你想要的测试
首先被打破的是你的测试直觉。你在整个职业生涯中建立起的每条测试实践都基于确定性:给定输入,代码产生输出,我断言相等。但将相同的输入输入到语言模型两次,你会得到两个不同的输出。并不总是天差地别,有时差异只是几个词、一个重新排序的从句、一种稍微不同的表达同一想法的方式,但差异足以让严格的相等断言失去作用。
这听起来像是一个小麻烦。其实不是。它抽走了我整个针对特定 Harness 的质量策略的基石。那种“写一个测试,运行它,绿灯表示完成”的本能不再奏效,而大多数工程师没有准备好替代的本能。你不能断言确切的措辞,因为措辞会变。你可以断言结构(输出能否解析、必需字段是否存在、计数是否正确),也可以断言不变量(是否违反了它被要求遵循的规则),但这两者都比工程师习惯的要弱,而且两者都会让真正出乎意料的输出通过。
你不再问一个输出是否等于一个预期答案,而是开始问系统在一组输入和多次运行中是否满足某个评分标准。这更接近评估而不是普通的单元测试,结果是一个置信水平,而不是正确性的证明。
这就引出了……


Failure is silent and graded, not binary
In ordinary software, a failure announces itself. There's an exception, a stack trace, a non-zero exit code, a red log line. Even when a bug is subtle, the system eventually produces a signal — a wrong number, a crash, a timeout. You know something is wrong because the program told you, or because it stopped.
Model output does not crash. A language model that is 95% correct and 5% broken produces a response that looks completely fine. There is no error symbol. There is no exception. The 5% that's wrong is woven into the 95% that's right, in the same prose, in the same format, with the same confidence. The model does not know it's wrong; it has no separate channel for "I am unsure," and even when you give it one, it uses it inconsistently. So the failure sits there, inside an apparently clean document, and nothing in your pipeline raises an alarm.
This means the most dangerous outputs are the ones that almost pass. A response that's grossly wrong is easy to catch (your validators reject it, or a human glances at it and winces). A response that's subtly wrong passes every shallow check and reaches the user. And "subtly wrong" with a language model is not a small category. It includes the model drifting off a constraint because a later instruction indirectly contradicted an earlier one, the model inventing a plausible-sounding value where it should have admitted it didn't know, and the model following the letter of a rule while breaking the spirit. None of these crash. All of them ship.
So you end up building defences against a class of failure that has no signal. You can't catch what you can't see, which means a large fraction of harness engineering is making the invisible visible: adding checks that surface graded drift, adding assertions that flag "this is structurally fine but semantically off," and adding humans in the loop at the points where confidence is low, all of which come with their own tradeoffs.
故障是静默且分级的,不是二元的
在普通软件中,故障会自我宣告。有异常、堆栈跟踪、非零退出码、红色日志行。即使 bug 很微妙,系统最终也会产生一个信号——一个错误数字、一个崩溃、一个超时。你知道出了问题,因为程序告诉了你,或者因为程序停止了。
模型输出不会崩溃。一个 95% 正确、5% 错误的语言模型会产生看起来完全正常的响应。没有错误符号,没有异常。那 5% 的错误被编织进 95% 的正确之中,用同样的行文、同样的格式、同样的自信。模型并不知道自己错了;它没有单独的“我不确定”通道,即使你给了它一个,它也会不一致地使用。于是故障就待在那里,在一个看似干净的文档内部,而你管道中的任何东西都不会发出警报。
这意味着最危险的输出是那些几乎通过的输出。明显错误的响应很容易捕捉(你的校验器拒绝它,或者人类瞥一眼就皱眉)。而细微错误的响应会通过所有浅层检查并到达用户手中。在语言模型中,“细微错误”并不是一个小类别。它包括:模型偏离约束(因为后面的指令间接与前面的冲突)、模型编造一个看似合理的值(本应承认不知道)、以及模型忠于字面但违背精神。这些都不会崩溃,但都会发布出去。
于是你最终要防御一类没有信号的故障。你无法捕捉你看不到的东西,这意味着很大一部分 Harness 工程就是把不可见变得可见:增加检查来发现分级漂移、增加断言来标记“结构上没问题但语义上不对”、并在置信度低的地方引入人工干预——所有这些都有各自的权衡。


You're debugging prose, not code
Detecting a bad output is only half the problem.
When the harness breaks, the "code" that caused the failure is often a paragraph of English. The only debugger is your judgment, reading a thousand words and trying to determine what gave the model permission to do what it just did.
And here's the cruelty of it: a single word can be the bug. I have spent hours tracking a regression to one adjective in a prompt, a word that seemed innocuous when I wrote it, that the model was interpreting as permission to do something I never intended. In source code, a one-character bug is a cliché, but it's findable. In a prompt, a one-word bug is invisible. The word doesn't break anything mechanically. It just shifts the model's behaviour a few degrees, and a few degrees is enough to quietly corrupt output before anyone notices.
Worse, your prompt might have a specific order, but it does not execute like a program with explicit control flow. Earlier and later instructions can influence one another in ways that are difficult to isolate.
You can't reason about it the way you reason about a function. You reason about it the way a writer reasons about a paragraph, by feel, by reading it aloud, by asking "What is this actually telling the reader to do?" And then you test your reading by running it and squinting at the output. That is a genuinely different skill from debugging, and most engineers have not had to develop it.
你是在调试文章,而不是代码
检测到糟糕的输出只是问题的一半。
当 Harness 出问题时,导致失败的“代码”往往是一段英文。唯一的调试器是你的判断力——阅读上千个字,试图弄清楚是什么让模型有权限做它刚才做的事。
残忍之处在于:一个词就可能成为 bug。我花过好几个小时追踪一个回归问题,最终定位到 Prompt 中的一个形容词——写的时候觉得毫无害处,而模型却将其解读为允许做我从未打算过的事情。在源代码中,一个字符的 bug 是老生常谈,但它是可查找的。在 Prompt 中,一个词的 bug 是看不见的。这个词不会从机械上破坏任何东西,它只是把模型的行为偏移了几度,而几度就足以在没人注意之前悄悄腐蚀输出。
更糟的是,你的 Prompt 可能有特定的顺序,但它不会像具有显式控制流的程序那样执行。前后的指令会以难以隔离的方式相互影响。
你不能像推理函数那样推理它。你得像作家推理段落那样推理——凭感觉,大声读出来,问自己“这句话到底在告诉读者做什么?”然后通过运行并眯着眼睛看输出来验证你的解读。这跟调试完全不是同一种技能,大多数工程师从未需要发展过这种技能。


The additive instinct is a trap
Here is the most reliable failure pattern in harness engineering, and the one I fell into the most times before I learned to see it: something breaks, and the instinct is to add a rule to the prompt. The model did the wrong thing? Tell it not to. The model forgot a constraint? Repeat the constraint louder. The model invented something? Add a line that says "do not invent." Same with the classic "MAKE NO MISTAKES!" Each addition feels responsible, even careful. You are, after all, responding to a real failure.
The prompt grows... and grows... and keeps on growing. But the prompt growing is the disease, not the cure.
Every rule you add narrows the space of outputs the model is allowed to produce. That's the point! But there's a major issue. The rules are written in natural language, and natural language rules interact with the models in ways you and I cannot fully predict. A rule that fixes one failure contradicts a rule that fixed an earlier one. A rule that says "be concise" fights a rule that says "include all of these details." A rule that says "never do X" makes the model think about X, and now X appears in outputs where it never appeared before. You don't get a contradiction error. You get a model that is now over-constrained, pulled in enough directions that it can no longer satisfy all of them at once, so it picks one to violate quietly, and you've traded one visible bug for a different, subtler bug.
I have watched a prompt grow from twenty lines to two hundred, with each line added in good faith to fix a real failure, until the prompt was so heavy that it could barely produce anything good anymore. The solution, every time, was to subtract. Yes, to remove rules, to collapse duplicates, to delete the instructions that were subtly fighting each other, to take a constraint out of prose and enforce it in code where it couldn't be contradicted. The jumps in quality came from deletion, not addition. But deletion is the move that feels wrong in the moment, because you are "removing a safeguard," and nothing in your engineering training rewards you for throwing away a rule that once fixed a bug.
加法本能是一个陷阱
这是 Harness 工程中最可靠的失败模式,也是我在学会发现它之前陷入最多次的模式:有些东西出错了,直觉反应就是在 Prompt 中加一条规则。模型做错了事?告诉它不要做。模型忘了约束?更大声地重复约束。模型编造了东西?加一行“不要编造。”还有经典的“不要出错!”每一条添加都显得负责任,甚至小心翼翼。毕竟,你在应对一个真实的失败。
Prompt 不断膨胀……不断膨胀……并且持续增长。但 Prompt 的增长是病,而不是药。
你添加的每条规则都缩小了模型允许产生的输出空间。这没问题!但有一个重大问题。规则是用自然语言写的,而自然语言规则与模型的交互方式,你和我都无法完全预测。一条修复了之前失败的规则可能会与修复更早失败的规则相矛盾。一条“要简洁”的规则与一条“包含所有细节”的规则打架。一条“永远不要做 X”的规则让模型想到 X,于是 X 出现在之前从未出现过的输出中。你不会得到矛盾错误。你得到的是一个过度受限的模型——被拉向多个方向,以至于无法同时满足所有规则,于是它悄悄地选择违反其中一条。你用一个可见的 bug 换来一个不同且更微妙的 bug。
我看过一个 Prompt 从二十行增长到两百行,每一行都是出于善意添加来修复真实的失败,直到 Prompt 变得如此沉重,几乎无法再产生任何好东西。每次的解决方法是做减法。是的,删除规则、合并重复项、删除那些暗中相互矛盾的指令、把约束从文字中移出来用代码执行(这样就不会被矛盾)。质量的飞跃来自于删除,而不是添加。但删除在当时感觉不对,因为你在“移除一个安全措施”,你的工程训练中没有任何东西鼓励你扔掉一条曾经修复过 bug 的规则。
Examples steer harder than rules
There's a corollary that makes the additive trap worse: the model learns from examples far more powerfully than it learns from instructions. If you write a rule that says "don't do X" and then include an example that happens to do X, the model will do X. The example wins. You can write the rule in caps, you can repeat it, you can beg, but the example is a demonstration, and a demonstration is a stronger signal than a prohibition.
The deeper lesson is that everything in a prompt is a behavioural program: instructions, examples, the order of the sections, the words you chose, (maybe) even the formatting. There is no "neutral" part of a prompt. Every token pushes the model somewhere. Which means maintaining a prompt is not like maintaining a config file; it's like maintaining a program where every line is executable, with a range of outputs.
示例比规则更有影响力
有一个推论让加法陷阱变得更糟:模型从示例中学习的能力远比从指令中学习强大。如果你写了一条规则说“不要做 X”,然后包含了一个碰巧做了 X 的示例,模型就会去做 X。示例获胜。你可以把规则写成大写,你可以重复它,你可以恳求,但示例是一个演示,而演示比禁令更强的信号。
更深层的教训是:Prompt 中的一切都是行为程序——指令、示例、各部分的顺序、你选择的词语,(可能)甚至是格式。Prompt 中没有“中性”的部分。每个 token 都在把模型推向某个方向。这意味着维护一个 Prompt 不像维护一个配置文件;它就像维护一个程序,其中每一行都是可执行的,且输出范围不确定。


The foundation rewrites itself
Most software is built on foundations that hold still. Your language version bumps occasionally. Your framework releases breaking changes every year or two, with a migration guide and a deprecation cycle. The contract between your code and the platform is, for the most part, stable on the timescale of a sprint.
The model is not a stable foundation. The vendor updates it (sometimes without announcement) and the update changes behaviour. A prompt that produced clean output last quarter produces subtly different output this quarter. Not because you changed anything. Because the statistical surface shifted under you. A phrase the old model read as a hard constraint, the new model reads as a suggestion. A format the old model produced reliably, the new model produces with occasional gaps. Your harness was tuned to the old model's failure modes, and the new model has different failure modes, and your defences now guard against problems that no longer occur while leaving the new problems unguarded.
This has become especially evident with the release of Opus 5. Many people are talking about how their previously reliable skills files are just not working with the new model.
This is the part that makes harness engineering fundamentally different from ordinary engineering. You are not building on a platform; you are building on a probabilistic system that rewrites its own behaviour on someone else's schedule. Every contract you write is with a moving target. Your tests can be green and your product can be regressing, because your tests assert against the old model's behaviour and the model moved. There is no "lock the version" escape hatch that holds forever; vendors deprecate old versions, prices change, and eventually you are forced onto the new surface whether you're ready or not.
The practical consequence is that a harness is never "done." It is in a state of continuous adaptation to a foundation that shifts beneath it. You will spend time you didn't budget re-tuning prompts against a model you didn't necessarily choose to upgrade. You will ship a fix for a regression you didn't cause. This is not a failure of planning; it is a structural fact of the substrate, and the only honest response is to build harnesses that are robust to it — minimal prompts that depend on as few model-specific quirks as possible, validation that catches drift, and the humility to expect the next update to break something you can't yet name.
地基在自我重写
大多数软件建于稳定的基础上。你的语言版本偶尔升级。你的框架每一两年发布一次破坏性更新,并配有迁移指南和弃用周期。你的代码与平台之间的契约在大多数情况下在冲刺周期内是稳定的。
模型不是一个稳定的基础。供应商会更新它(有时没有通知),更新会改变行为。一个在上个季度能产生干净输出的 Prompt,在这个季度会输出微妙不同的结果。不是因为你改变了什么,而是因为你脚下的统计表面发生了移动。旧模型视为硬约束的短语,新模型视为建议。旧模型可靠生成的格式,新模型偶尔会出现缺口。你的 Harness 是为旧模型的失败模式调优的,而新模型有不同的失败模式,你的防御现在防护着不再发生的问题,却对新问题毫无防备。
这在 Opus 5 发布后变得尤为明显。很多人在讨论他们之前可靠的技能文件与新模型不再兼容。
这就是让 Harness 工程从根本上区别于普通工程的部分。你不是在建造一个平台,而是在建造一个概率系统,它按照别人的时间表重写自己的行为。你写的每一个契约都是一个移动的目标。你的测试可能全部变绿,但你的产品却可能退化,因为你的测试是针对旧模型行为的,而模型已经变了。没有永远有效的“锁定版本”逃生口;供应商弃用旧版本,价格变动,最终无论你是否准备好,你都会被推到新的表面上。
实际后果是:一个 Harness 永远不会“完成”。它处于不断适应其下移基础的状态。你会花费你没有预算的时间,针对一个你未必选择升级的模型重新调优 Prompt。你会发布一个修复,修复的不是你造成的回归。这不是规划失败,这是底层的结构性事实。唯一诚实的回应是构建对它鲁棒的 Harness——尽可能少依赖模型特定怪癖的最小化 Prompt、能够捕捉漂移的验证、以及谦逊地预期下一次更新会破坏一些你还未命名的东西。
Feedback loops are slow and expensive
A normal edit-compile-test cycle takes seconds. You change a line, you run the test, you see the result, you change it again. The tightness of that loop is what makes engineering bearable; it's what lets you iterate your way to a correct solution without holding the whole system in your head.
A harness feedback loop is nothing like that. One run of a model call costs money, maybe not much, but enough that you can't run it a thousand times the way you run a unit test. If your pipeline has multiple model calls, a single end-to-end test can take from several minutes to several hours.
So you iterate slower. You hold more state in your head. You can't brute-force a solution by trying twenty variations in a minute; you try three, wait, read the outputs carefully, form a hypothesis, try one more. The whole shape of problem-solving changes. Engineers who are used to tight loops start to feel like they're working in slow motion, and the temptation is to make changes blindly, to edit the prompt, push it, and check back later, which is exactly how regressions get introduced, because you stopped observing before you stopped editing.
The cost has a second, subtler effect: it makes you lazy about verification. When each check is expensive, you start checking less. You convince yourself the change is small enough to skip the full run. It is almost never small enough. The expensive loop trains you to under-test, and under-testing with a probabilistic system is how you ship drift.
反馈循环缓慢且昂贵
一个正常的编辑-编译-测试周期只需几秒钟。你改一行代码,运行测试,看到结果,再改。循环的紧凑性让工程变得可忍受;它可以让你迭代到正确方案,而无需将整个系统记在脑中。
Harness 的反馈循环完全不同。一次模型调用需要花钱,可能不多,但足够让你不能像运行单元测试那样运行一千次。如果你的管道中有多次模型调用,一次端到端测试可能需要几分钟到几个小时。
所以你迭代得更慢。你需要在脑中保持更多状态。你不能在一分钟内尝试二十个变体来暴力破解;你试三次,等待,仔细阅读输出,形成一个假设,再试一个。整个问题解决的形式都改变了。习惯紧凑循环的工程师会感到自己仿佛在慢动作中工作,而诱惑是盲目地做出改变——修改 Prompt、推送、之后再检查——这正是引入回归的方式,因为你在停止编辑之前就停止了观察。
成本还有第二个更微妙的影响:它让你在验证上变得懒惰。当每次检查都很昂贵时,你开始减少检查。你说服自己改动足够小,可以跳过完整运行。实际上几乎从不够小。昂贵的循环训练你去少测试,而在概率系统中少测试就是引入漂移的方式。
Nobody can see the work
The last pain is social, and it might be the most draining. "Prompt engineering" is a phrase that makes the work sound trivial. It implies the job is writing prompts, a task that, to anyone who hasn't done it, seems about as hard as writing an email. The months of tightening, the removed contradictions, the silent failures caught before they shipped, all of that is invisible. It looks like you barely did anything.
There's no clean fix for this. The best you can do is make the invisible visible on purpose: track the failure modes you caught, measure the drift you prevented, write down the regressions that didn't ship, and accept that a portion of the value you create will never be legible to the people around you. That's a tax the work carries, and it's worth naming so you don't internalize the false message that you aren't doing much.
没人能看到你的工作
最后的痛苦是社会性的,可能是最消耗人的。“Prompt 工程”这个词让工作听起来微不足道。它暗示工作就是写 Prompt,对于没有做过的人来说,这看起来跟写一封电子邮件差不多。几个月的收紧、删除矛盾、在发布前拦截下来的静默失败——所有这些都不可见。看起来你几乎什么都没做。
这个问题没有干净的解决方案。你能做的最好的事就是有意让不可见变得可见:记录你捕捉到的失败模式、衡量你预防的漂移、写下没有发布的回归、并接受你创造的一部分价值永远不会被周围的人理解。这是这项工作所携带的税,值得说出来,这样你就不会内化“你没做多少事”的错误信息。
The difficulty is the point
Here's the thing I kept circling back to while I was living in this. Every one of these pain points is structurally hard. They aren't hard because the tools are immature or because we haven't figured out best practices yet. They're hard because they come from the nature of the substrate, a probabilistic system with no stack trace, no determinism, no stable contract, and no separation between "the program" and "the data it was trained on." You can't engineer those facts away. You can only build a harness that absorbs them.
And that, honestly, is the moat. If harness engineering were easy, the model would be the product, and anyone with an API key could compete with you. The reason a real application is hard to build on a language model is the same reason it's hard to copy: the work of turning a probabilistic output into a shippable, reliable, tested thing is invisible, painful, and not transferable by reading your prompt.
The pain is the work, and the work is the moat.
困难正是关键
这是我在身处其中时不断绕回来的要点。这里的每一个痛点都是结构性的困难。它们之所以困难,不是因为工具不成熟,也不是因为我们还未找到最佳实践。它们之所以困难,是因为它们源于底层的本质——一个没有堆栈跟踪、没有确定性、没有稳定契约、没有“程序”与“训练数据”之分的概率系统。你无法通过工程手段把这些事实消除。你只能构建一个能够吸收它们的 Harness。
而说实话,这就是护城河。如果 Harness 工程很容易,模型本身就是产品,任何拥有 API 密钥的人都能与你竞争。真正的应用之所以难以在语言模型上构建,正是因为它难以复制:将概率输出转化为可发布、可靠、可测试的东西,其工作是不可见的、痛苦的,并且无法通过阅读你的 Prompt 来转移。
痛苦就是工作,而工作就是护城河。