news 2026/10/20 5:31:00

从Prompt到Harness:大模型工程化三代范式演进与落地实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从Prompt到Harness:大模型工程化三代范式演进与落地实践

两年前我接手一个内部订单语义解析的优化任务,当时觉得把需求文档写得足够细,模型输出就能稳定复现。结果上线第一周就翻车:同一句话换了一种问法,解析结果就多出一个假字段。那段时间到处刷Prompt技巧的文章,好像把提示词调好就能解决一切。回头折腾大半年,从一版接一版的Prompt补丁,一直做到带状态约束的编排系统,才真正想明白:大模型工程化的分水岭,从来不是"提示词写得妙",而是让模型从主角退成组件,让一套可测试、可观测、可回退的Harness来兜底。这篇文章就聊聊我从Prompt到Harness这段实践中,看到的第三代范式演进逻辑和具体落地方案。

1. 大模型工程化的两道分水岭:从"能跑"到"敢上生产"

先说结论:我把过去几年看到的工程方式粗分成三代,每一代背后都不是技术名词的变化,而是"我们到底把不确定性放在哪里"的思路变化。

第一代是纯Prompt主导,配合少量脚本和向量库。模型是主角,几乎所有业务逻辑都以自然语言写进提示词,代码只负责调用接口、把结果塞进下一个业务系统。这套方案胜在轻快,想验证一个场景,一两天就能搭出Demo。问题也明显:结果好不好,几乎只能靠"人眼抽查"来保证,prompt稍微一改,可能有几十条历史case悄悄变坏,你根本不知道。

第二代是代码优先的可编程编排。社区里出现了一批开源框架,把"调用模型"这件事从裸API变成可组合的流程对象。你可以用代码描述"先总结、再抽取、最后判断",可以在中间插工具调用、插向量检索。这比第一代强在:流程可见了、断点可调试了、分支逻辑可以写测试了。我一度觉得这就是终局。

但用着用着就发现不对劲:这类框架的思路是把"语言模型"嵌进一段一段的代码流程里,可是最终真正送到模型嘴边的,还是大段大段的Prompt字符串。所谓编排,很多时候只是把原来堆在变量里的提示词,换个地方堆进了框架的节点模板。跑一个链路,中间某个Agent忽然返回JSON聊爆了,你翻日志只能看到"模型产生了非预期输出",至于为什么、怎么补偿,框架基本不管。

第三代就是我说的Harness范式。它不是某个具体框架,而是一套工程主张:把大模型当成一个"不可靠但能力很强的组件"放入确定性系统中。系统负责解析输入、调度节点、校验输出、失败重试、超时熔断、离线回放、在线监控。模型只在一个被严格控制边界的窗口里做"语义生成",其余全部交给确定性的代码和规则。Prompt仍然存在,但它只是Harness内部一个可替换的部件配置,而不是整个系统的行为来源。

三代范式在几个关键维度上的差异,我整理成了一张表:

维度第一代:Prompt主导第二代:可编程编排第三代:Harness治理
行为来源自然语言提示词代码流程+提示词确定性约束+受控生成
可测试性靠人审流程可测,生成环节不可测离线回放+Golden Set+语义校验
可观测性只有日志和结果链路可见,心智负担高每个环节有结构化状态与指标
失败处理重试/人工介入部分重试,补偿逻辑自己写默认含重试/回退/熔断策略
变更成本改prompt影响全局改代码结构影响全局改单个节点,有回归保障
面向对象"对话""流程""持续运行的生产任务"

强调一下"敢上生产"这四个字。绝大多数团队的瓶颈根本不在模型效果,而在于你不敢对一个概率系统做SLA承诺。Harness解决的就是这种事:让不确定的部分足够小,并且被卡在确定性的盒子里。

2. 第一代范式的隐形天花板:一份订单解析Prompt的失控复盘

讲个具体的失控过程。当时某业务线的订单解析任务,输入是客户通过工单系统提交的自然语言诉求,要抽取出商品、数量、地址、加急标记、特殊备注这五个字段。团队里一位同学精心设计了一份Prompt,前面是角色设定和输出JSON格式说明,中间是五个字段各自的抽取规则,后面附了六个few-shot例子。第一版上线准确率大概有93%,大家都挺兴奋。

之后两周开始迭代:运营反馈某些地域简称识别错了,就在Prompt里加"注意以下地址别名映射";紧接着又有漏抽规格的情况,再加一条"如果出现规格关键词,务必抽取到spec字段"。到第四版,光输出格式部分已经写了十几条约束。问题开始批量出现:修好了地址别名,几天后一个新的few-shot例子让模型在另外一个case上把数量填进了价格字段。最崩溃的是有一次只删掉一个示例,全量回归直接掉了三个百分点。

复盘时我们意识到,第一代范式的天花板不在于提示词技巧,而在于几个结构性缺陷。

第一是上下文漂移。Prompt越写越长,模型注意力的重心分布不可见。你以为新增一条规则只是"添加信息",实际上它在改变模型对已有信息的权重排序。没有任何工具能告诉你"这条规则到底影响了哪部分注意力"。

第二是输出契约不可强制。你可以要求"请只返回合法JSON",但当模型返回一段插在Markdown代码块里的JSON注释时,整条链路就断了。代码里要么堆一堆容错正则,要么直接失败人工处理。这不是个别现象,这是概率系统下必然存在的长尾。

第三是回归测试只能靠人眼。自然语言的改动没有diff概念。你不能说"这版Prompt相对上版只改了第13行到第17行",因为模型不是按行执行的。想验证改动的副作用,唯一的办法就是把历史case全部跑一遍然后肉眼对比,这是纯劳动力,还带主观性。

后来我们给这份Prompt外挂了RAG,建了一个客户备注常见用语的向量库,每次请求先检索相似表达作为上下文注入。RAG的确把一部分低频表达覆盖掉了,准确率回到94%左右。但这解决的是"资料外挂",不是"行为控制"。模型仍然可能无视检索到的内容直接按自己的理解输出,也仍然可能伪造一个看起来合理的字段。基本盘不稳,资料再全也没用。

这段经历让我形成一个判断:第一代路径里,人一直在迁就模型。你把规则写成自然语言、把示例排好顺序、把输出格式期望写进角色设定,本质上是在一个不可测试的表面上持续打补丁。这种模式适合快速验证,不适合持续交付。

3. 第二代范式的得与失:可编程编排把复杂度外置之后

第一代走到头之后,我把目光转向当时已经火起来的一类开源编排框架。这类框架的核心贡献我现在依然承认:它第一次把"调用大模型"从"发一个请求"变成"描述一段流程"。你可以在代码层面声明输入、输出、工具、内存,甚至把多个模型角色串成一个图状流水线。这对工程团队的吸引力是巨大的,毕竟代码是能review、能单测、能上CI的。

做一个具体对比就知道差别在哪。第一代方案里,"地址校验"只能靠Prompt写一句"请确认地址格式是否正确,如不正确请给出修正建议"。模型回一句"地址看起来没问题",然后就没有然后了。在第二代框架里,你可以挂一个真实的地址解析函数,让模型只负责抽取原始地址字符串,标准化处理交给确定性的算法。光是这一点,地址字段的错误率就下降了一大截。

所以我说第二代范式是"复杂度外置"。框架逼着你把一段模糊的任务拆成多个可命名、可调用的环节,每个环节有输入输出定义,这本身就是工程化的一大步。尤其对我这种习惯写后端的开发者来说,用代码描述AI流程,就像从写自由文本的说明书进步到写有类型的函数,整个体验是代际级别的。

但第二代也留下了两笔很重的债务。

一是"节点内部黑盒"。框架让你拆出了节点,但每个节点内部做的事,依然是"把几段Prompt拼接一下发给模型"。节点可以命名成Summarizer、Extractor、Router,可从外部看它就是一个不可控的生成函数。你不清楚它内部Prompt长什么样,不知道它在什么条件下会跑偏,也不知道它输出多大体积的文本。调试的时候,你面对的还是"模型说了一堆废话"这种没有结构化原因的异常。

二是"编排逻辑与调试心智的撕裂"。越复杂的图状链路,越容易藏着隐式依赖。A节点的输出格式是B节点Prompt里的示例来源,C节点期待D节点在某种分支下返回空列表,这些约束散落在代码各个文件里,运行时才暴露。框架本身的抽象能力强,不代表使用者的业务复杂度能被自动消化。

我在第二代框架上做了一个多节点的工单分类器,最后代码结构长得像一棵圣诞树:策略路由、模型节点、工具节点、校验节点层层嵌套,每个节点里都是一大段Prompt模板加解析逻辑。线上出问题时,我经常要同时打开四五个文件才能定位一次异常输出发生在哪个环节。那段时间我深刻体会到,编排框架能外置复杂度,却不能自动降低复杂度;它只是把"提示词不可调试"的问题,暂时转移成了"代码流程不可见但有迹可循"的问题。

也正是这些债,催生了第三代思路:既然模型是不可靠组件,那就不该让它承载系统级的行为决策。系统的行为应该由可测试的结构定义,模型只负责在特定约束窗口里做生成。

4. 第三代范式Harness的第一性原理:把模型从"主角"降为"组件"

先给个我自己的定义:Harness是一套围绕大模型推理建立的确定性控制结构,它接管输入、输出、校验、纠偏、路由、重试、监控等全部周边能力,让模型生成只发生在最小的必要环节。它的设计出发点不是"如何让模型更聪明",而是"如何让一套包含不可靠模型的系统仍然有可靠行为"。

我在落地时把它拆成四个物理层,每一层都有明确职责:

4.1 解析层:用确定性代码处理入口信号

所有外部输入先走解析层,把原生文本、工单信息、日志片段改造成结构化的中间表示。这个阶段不使用大模型。比如对订单解析任务,我先用模板匹配和正则在入口处把"客户ID""提交时间""渠道来源"这些元信息剥出来,剩下的自然语言诉求才进入模型窗口。这样做的好处是:模型永远看不到整包脏数据,它只处理被裁剪好的核心片段。

4.2 语义层:模型只在受限窗口内作业

语义层就是真正调用大模型的节点,但它的输入不是"用户原始全文",而是解析层输出的受控中间表示。我在这一层强制指定输出schema,并且用结构约束让模型只能输出JSON对象,任何多余解释、Markdown包裹、对话式语气都会在下一层被拒绝。Prompt仍然存在,但它的角色降级为"面向单一任务的作业指导书",不再承担全局规则。

4.3 策略层:校验、纠偏、路由、回退

模型输出出来后,立即进入策略层。第一步是schema校验:字段缺失、类型错误、枚举越界,直接判定失败。第二步是语义校验:用规则或小模型检查字段值是否符合业务约束,比如加急标记只能是true/false,地址必须包含省市区三级结构。第三步是路由:通过校验的结果进入正常出口;失败的结果根据失败类型进入不同处理分支——格式错误就原样重试一次,语义错误就带着校验错误信息让模型重新生成,连续失败则降级为规则兜底或人工队列。

4.4 控制层:状态、度量、回放、熔断

控制层是Harness区别于普通代码编排的本质所在。它给每个任务分配状态和上下文ID,记录从进入到出口的每一跳事件。每一跳都打点:模型调用耗时、tokens消耗、校验结果、重试次数。控制层还负责超时熔断和全局频率限制。更重要的是,它把所有交互记录成结构化的事件流,喂给离线回放系统。任何一次线上异常,都能在回放环境里复现,成为下一轮调整的输入。

这套分层设计回答了我在第二代框架时期最大的困惑:当模型行为不符合预期时,系统应该有一个"异常被拦截、被诊断、被纠正"的内建机制,而不是依赖开发者写一堆散落的if-else去猜。

从"对话"到"运行单元"的转变也很关键。第一代、第二代本质上都在为"一次问答"设计:进来一段文本,返回一段文本。Harness则为一个持续运行的任务设计:它关心这个任务今天处理了多少量、失败率有没有超过1%、某类输入是否在最近一次模型更新后表现退化。对话是一次性的,任务是可追踪、可改进、可决定继续还是下线的。

5. 从Prompt到Harness的一次完整迁移:日志解析Agent实战

光讲概念容易飘,拿我们落地过的一个日志解析Agent来拆解。

场景是这样:某运维平台需要从几十种历史遗留服务的日志中提取时间段、错误级别、模块名、影响范围、建议动作五个字段,原先由一个同学维护一份超长Prompt,准确率长期在85%左右。85%意味着每100条日志有15条需要人工修正,周均人工量非常大。平台并不要求这个Agent能做复杂的推理,要求只有一个:高准确率地把结构化字段稳定提取出来,新格式日志接入时,修改成本要低。

第一代方案为什么只有85%?因为几十种日志格式差异太大,Prompt里塞的few-shot例子再多也覆盖不了长尾。格式稍有变化,比如时间戳从ISO变成"2024-05-01 13:22:33.123+08:00",模型就可能把时区信息塞进错误级别字段。纯Prompt方案对输入的"排列组合爆炸"无能为力。

迁移到Harness的步骤我按顺序记录一下:

第一步,定出口契约。五个字段的类型、取值范围、缺失策略全部写成JSON Schema,在策略层强制生效。没有这一步后面全是虚的。

第二步,拆解析层。先用正则识别日志的公共骨架:时间戳、级别、服务名、消息体。有一类老服务日志没有标准级别关键词,需要模型判断严重程度,这部分单独标记为一个"严重度推断子任务"。这一步让大部分输入根本不进模型。

第三步,写知识兜底。针对常见的时间格式、服务名缩写、错误码,建立字典映射。校验失败时,策略层先走字典兜底,兜底不了再让模型重试。顺序有讲究:宁可让模型减少承担,也不让它用猜测覆盖已知事实。

第四步,搭状态机。每个日志解析任务经历"入口归一化 → 规则预提取 → 模型抽取(按需) → schema校验 → 语义校验 → 出口格式化"。任何节点失败都会带着失败原因回到上一跳,但重试次数有上限,超限直接进人工队列。

代码骨架大致长这样(示意):

class LogParseHarness: def __init__(self, validator, retry_policy): self.validator = validator self.retry_policy = retry_policy def run(self, raw_log: str) -> ParseResult: skeleton = preprocess(raw_log) # 解析层:正则提取骨架 fields = rule_extract(skeleton) # 规则预提取 if not fields.need_model_inference(): return self.finish(fields, skeleton) with self.trace("model_inference"): candidate = self.infer_with_schema(skeleton) # 语义层:模型按schema输出 for attempt in range(self.retry_policy.max_attempts): errors = self.validator.validate(candidate) # 策略层:schema+语义校验 if not errors: break if errors.is_fatal(): return self.fallback(candidate, errors) candidate = self.infer_with_feedback(errors) # 带着错误信息重试 return self.finalize(candidate, skeleton)

第五步,搭离线回放与回归集。我们把历史上三个月的人工修正结果整理成Golden Set,每条日志带标准答案。任何一次Prompt或规则变更,必须先跑全量回放,准确率低于99%不允许上线。这一步直接把"改一版Prompt心里没底"的问题消灭了。

改完上线跑了两个月,实际效果对比如下:

指标原Prompt方案Harness方案
字段级准确率85%99.3%
人工修正比例15%0.7%
新日志格式接入时间2~3人天1小时左右
失败任务可定位性靠日志猜事件流直接定位节点
Prompt平均长度约1800词约200词/子任务

一个很有意思的细节:迁移后模型本身没换,Prompt能力也没发生质变,准确率提升主要来自"模型只处理它擅长的部分,其余部分由确定性规则兜底"。过去模型什么都得干,现在它只干一件事——在几个候选值里选最可能的那个,选完还有校验和回退盯着它。

6. 落地Harness期间最容易踩的三类坑

说点实际的,迁移过程中我自己和身边团队踩过的坑,至少有三个很典型。

6.1 把Prompt模板塞进代码,就以为做好了Harness

这是最常见的误解。有些同学把策略层的校验函数写出来,把模板放进一个配置文件,就觉得已经从Prompt范式升级到Harness范式了。但Harness的关键不是"代码里有校验",而是"模型输出被校验结果驱动地纠偏"。如果校验失败后你只是记一条日志然后返回失败,那这还是第一代方案,只是穿了件代码外套。校验必须和路由、重试、回退形成闭环,让每次失败都成为系统自我修正的输入。

6.2 状态机过度设计,把简单任务拆成一棵圣诞树

我刚做日志解析Agent时,一度想模仿某种通用Agent框架,把链路拆成十几个更细的节点,甚至为每个节点配了独立的记忆策略。结果调试成本比第一代方案还高。后来复盘发现,Harness分层的核心动机是"把不确定性关在笼子里",不是追求流程图的漂亮。如果一个环节用规则就能做好,就绝不引入模型;如果一个重试就能解决,就绝不设计三种失败模式。简单的确定性结构永远优先于复杂的弹性结构。

6.3 跳过离线回放,直接在线调整

还有一次,为了赶一个上线窗口,我没等回归集跑完就改了语义校验规则,结果新规则对某类历史日志的误杀率极高,线上人工队列直接爆掉。那次之后我定了条铁律:所有变更必须先进回放系统,golden set全绿才能走发布流程。回放系统不需要很复杂,核心是事件流存档加对比脚本,但它给每一次Prompt或规则调整提供了可追溯的基准线,这是Harness范式最值得的投入之一。

个人体会是,大模型工程化推进到这个阶段,"从Prompt到Harness"并不是说提示词不重要了,它依然在语义理解环节发挥作用。改变的其实是责任边界:模型不再需要对整个任务的成败负责,它只需要对"给定受控输入、生成符合类型约束的输出"这件事负责,其他的交给确定性的管道。这个思路听起来朴素,但真正做到位,你会发现那种"改一行Prompt就提心吊胆"的日子,基本到头了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 5:30:23

显示效果好的平板有推荐吗?2026华为MatePad Air把屏幕当主角来做

“显示效果好的平板,有推荐吗”,这个问题看似简单,实际考验的是厂商对屏幕的综合打磨能力。显示效果好,意味着亮度够、对比度深、色彩准、刷新快、还护眼,任何一项短板都会被眼睛捕捉到。2026款华为MatePad Air最大的亮…

作者头像 李华
网站建设 2026/10/20 5:29:58

基于Laravel框架开发响应式网站的设计与实现

如果你正在为计算机毕设选题发愁,又不想随大流做那种满大街的“XX信息管理系统”,那“基于Laravel框架开发响应式网站的设计与实现”这个题目确实值得认真看一眼。它属于典型的PHP方向毕业设计,技术栈明确、应用场景宽、演示效果好&#xff0…

作者头像 李华
网站建设 2026/10/20 5:29:52

Code Agent 能直接接进 API 请求链路吗?先算清在线执行的时延账

把 Code Agent 接进业务 API,先要确定它承担哪一种职责:是提前生成代码,经过验证后由常规服务执行;还是每次请求到来,都让 Agent 现场编写、运行和调试代码。 这两条路径都可能提供业务能力,但它们的响应时…

作者头像 李华
网站建设 2026/10/20 5:29:28

PAT The World‘s Richest:从暴力超时到分桶多路归并的Top K优化实战

最近刷题碰到一道名字很吓人的题:PAT——The Worlds Richest,题面开头还专门标了一行小字“注意运行超时的问题”。这个名字翻译过来就是“世界上最富有的人”,听起来像财经新闻,实际是一道很典型的算法题:给你一群人的…

作者头像 李华
网站建设 2026/10/20 5:29:28

JavaFX 家谱管理开发实战:TreeView 递归渲染与避坑指南

简介:这是一份基于Java与JavaFX实现的家谱管理系统完整工程源码,适合Java桌面应用开发者、课程设计学习者参考。项目核心包含Person、Family等数据模型及家庭成员关系维护逻辑,界面采用JavaFX TreeView展示家谱树,并集成了SQLite数…

作者头像 李华