热门搜索:和平精英 原神 街篮2 

您的位置:首页 > > 教程攻略 > ai教程 >关于Harness Engineering的一次实践

关于Harness Engineering的一次实践

来源:互联网 更新时间:2026-06-05 07:22

前言

Harness Engineering这个概念在圈子里热闹有一阵子了。比较宽泛的说法把它定义为Harness = Agent - Model——模型之外附加的所有能力都算Harness,像是MCP、Skill、提示词什么的。不过今天聊的是个更具体的场景:在Claude Plugins里的Harness实践。跟广义定义可能有些出入,但本质都一样——都是为了保障模型能稳定地输出结果。

关于Harness Engineering的具体概念,这里就不展开了。简单说说Prompt、Context和Harness这三种范式的区别:

维度Prompt EngineeringContext EngineeringHarness Engineering
核心定义该怎么问能让模型知道到什么给模型提供一个运行环境
实现方式自然语言描述滑动窗口、向量库搭流水线、管状态、校验、接外部API
层级关系最内层,使用模型的基础方式中间层,管理上下文信息最外层,包含上下文工程和提示词工程

为什么需要这次实践

先说说背景。团队做的是一个针对服务端代码改造的影响面分析工具Claude Plugins。之所以做这个工具,是因为Q2开始我们逐渐向AI全栈转型,挑战是用AI交付Ja va项目。现在很多模型在新项目里写代码已经不在话下,但对那些历史包袱比较重的项目,模型写代码时还是会有Bug——即使用了Sdd/SuperPowers这些增强开发范式。

目前交付的两个已上线的服务端项目中,就有一个出了线上问题。根因是AI把一个不该改的业务入口改了。所以为了更大程度保证质量,法宝箱里确实需要一件能分析改动影响面的工具。

分析代码影响面对AI来说是个复杂任务。需要全量查代码引用(已经有GodeGraph/GitNexus做了深度优化)、梳理业务链路、判断风险、整理输出等等。相信都有过类似经历:一个复杂任务直接通过提示词丢给模型,执行过程中模型可能会偷懒、偏移。无论怎么调整提示词,最后结果总跟预期有偏差——这通常是模型的认知和上下文窗口限制导致的。而Harness的作用之一,就是为了减少这种情况发生。

架构拆解

关于这个分析代码改动影响面的Plugin,做了不少工程手段上的设计。分享一下怎么做的,以及为什么要这么做。

1. 整体工作流 —— 用确定性的工程流程,管控非确定性的模型输出

从流程图可以看到,Plugin主要通过暴露Skill作为用户交互入口,Skill中用脚本驱动流程推进(Step1 → Step2 → ...)。每个Step下的创造性任务还是交给模型,模型执行完后,脚本记录结果到yaml(状态机)、校验成果、继续下一个Step,直到结束。

这样做的好处有三点:

  1. 流程可控

    :“执行哪一步”、“是否能进行下一步”完全由脚本控制,避免模型“提前收工”、“跳跃步骤”。如果直接用提示词告诉模型怎么做,即便写清楚了Step1/2/3,模型还是会因为注意力稀释、追踪失效、位置偏差等原因导致结果不尽人意。

  2. 状态可追溯

    :记录关键步骤的执行结果,用一个文件记录(这里用的是yaml)。以分析改动影响面的Skill为例,让模型直接调py脚本,在脚本中生成需要处理的所有任务,给每个任务标记status: WAITING/DONE/FAILED。脚本派发这些任务给模型并标记状态,执行过程中就能知道哪些任务完成了、哪些没有。最后执行结束时用脚本校验这个.yaml,针对没完成的任务让模型回去继续执行,确保处理了所有任务。

  3. 上下文可控

    :针对调用的脚本,模型在脚本执行完后能拿到什么上下文,完全由脚本决定。举个例子:

    ---name: xxdescription: xx---
    
    ## 作用
    xx...
    
    ## 执行步骤
    
    ### Step1 - 生成改动任务
    执行
    ```bash
    python3 ${PLUGIN_ROOT}/script/generate.py" --create 
    ```
    
    ### Step2 - xxxxxx
    ...
    
    def main():
        # ...
        print(f"脚本输出的内容会被模型读到,可以输出你想让模型知道的上下文")
    if __name__ == "__main__":
        main()
    

    模型能拿到什么上下文,完全由脚本中print输出了什么内容决定。利用这一点,可以在脚本里做文章——比如告诉模型当前执行结果、下一步该做什么、下一步任务需要的参数等。

2. 状态机 —— 全/跨流程状态管理

状态机主要解决两个问题。

问题一:记录任务状态,兜底模型提前收工

面对复杂且任务多的需求时,模型可能因为上下文窗口限制、追踪失效等问题提前收工。具体表现是:模型在对话框里宣布所有任务已完成,但手动review时发现有的地方并没有改到。反问“这块是不是没有改到”,模型才意识到还有任务没完成。

src/main/ja va/com/example/OrderService.ja va:
analysis_steps:
  - step1_分析影响面: WAITING/PROCESSING/DONE/FAILED
  - step2_分析风险点: WAITING/PROCESSING/DONE/FAILED

解决办法是给每一个任务加上状态标记,任务执行完后通过验证门校验状态,校验通过才放行,否则打回重新执行。工作流如下:

(图片占位)

问题二:跨Agent(mainAgent、subAgent)协作时的任务状态追溯 / 跨Skill工作流时的任务状态追溯

将关键步骤的执行结果记录到.yaml文件中,其他Agent/Skill执行时也能读到这个记录,从而实现跨Agent/Skill协作。试想一个全流程需求交付的Plugin,从PRD → 系分 → 开发 → 测试每个阶段都对应不同的Skill,下一步一定依赖前一步的执行结果,状态记录就显得很有必要。

3. Diff切割 —— 减少模型不确定性

这一层的核心思路是:拆分出模型容易产生幻觉的任务,用脚本辅助完成,或者直接全部用脚本完成,减少模型的不确定性。

比如在这个Plugin中,获取“当前分支相比origin/master改动了哪些文件、哪些代码块”对模型来说是容易产生幻觉的任务。所以直接选择用脚本梳理好再喂给模型。当每一个模型有概率出错的小任务都加一层保障,整个工作流自然就稳定了。

4. SubAgent并发 —— 避免上下文窗口限制

执行复杂任务时,把所有任务都交给一个Agent执行容易触发上下文窗口限制、注意力分散的问题。Anthropic也遇到过类似情况:上下文达到一定数量时(40%),模型会变得犹豫,甚至倾向于提前收工,即使任务还没完成。

一些AI IDE支持开启subAgent,比如Claude CodeCursor。所以可以换一种思路:用一个mainAgent控制主流程,复杂任务则让mainAgent派发subAgent执行。这样subAgent执行任务时有干净的上下文,不会出现提前收工的问题,整个工作流也不会上下文溢出。

这里可能有人疑惑mainAgent如何拿到subAgent的运行结果。其实IDE会自动把subAgent的结果返回给mainAgent,这点无需担心。但从稳定性和管理状态统一性的角度来说,通过回写状态机的方式会更合适。工作流如下:

(图片占位)

5. 验证门 —— 把控模型交付质量

验证机制是Harness理念中关键的一层。一般的Prompt工程里如果想验证结果,可能会写这样一些提示词:“Review你的成果,识别有没有问题”。但仔细想想,让本就带有不确定性的模型来Review自己的成果,这跟让人用同一双眼睛检查自己的作业没什么区别。曾经也做过一些尝试——让模型生成一份方案文档,然后新建会话保持干净上下文,继续让模型Review这份方案有没有问题,重复这个动作。最终结论是每次Review模型都会提出新的问题。让模型自己Review自己,不是一个趋于稳定的方案。

这次实践中关于验证的部分,做法是将一个大任务进行拆解。比如在一次代码改动分析任务中,diff里的每一个文件都可以拆成一个小任务,而每一个文件里的改动又有不同维度需要分析(调用链、影响面、逻辑错误、性能风险等),每个维度也可以拆成一个小任务,依次类推。任务也分不同类型,有的任务只需要执行过就好,有的任务有好几个方面需要模型执行得全面。最终验证门要做两件事:

  1. 结合状态机里任务的status校验任务是否执行完
  2. 结合状态机里任务执行结果.yaml文件校验模型是否执行到位

校验不通过不允许放行,让模型重新执行任务。具体实现也是通过脚本来控制,只需要在脚本执行完后print结论,模型就会拿到这个结论做后续处理。不过建议针对整个工程设计一套结构化的输出范式,加强模型关注脚本执行结果的心智。工作流和状态机类似,只是更细粒度的校验,这里不再贴图。

6. Skill Prompt设计

虽然有了不少工程化手段,但Prompt设计依然很重要。这方面踩了一些坑,总结了一些实践经验。

简单Prompt vs 复杂Prompt

尝试了两种风格的Prompt。一种是写得复杂,把各个场景下正确的case、错误的case都列出来,用全面的自然语言举例什么是对的什么是错的,附加一些边界情况。另一种是写得简单,整个Prompt只包含需要执行的Step。最后发现Prompt越精简,模型执行过程越稳定——过多的Prompt会导致模型注意力分散。不过这一点可能与模型有关,各家对此观点也不一,比如OpenAI倾向详细的Prompt,Anthropic则倾向于定期精简Prompt。

直接上代码 > 文字描述

这点很好理解。某些模型内部编码、预训练数据、分词逻辑天生偏英文,在解析中文上可能有偏差。测试发现即使很短的描述也有概率产生幻觉,而代码是通用的,不会有歧义。

维度不要太分散,否则模型每次执行的重点都不一样

起初在代码中放了一份风险点列表的md,一共34个风险点,让模型逐一分析。测试次数多了发现有时模型最后输出的风险点不一致,甚至偏差很大。排查下来是风险点太分散了。后来把34个具体的风险点整合到10个,并且从具体的点转变成给模型引导方向。

以下是几个主要Skill的最终提示词设计:

(Skill Prompt模板占位)

7. 整体目录设计

Plugin和单个Skill不同,Plugin可以是一个Skill集合,所以目录设计要考虑通用性。

├── .claude-plugin/plugin.json          # 插件元信息
├── .cc-version                         # 版本号 (1.0.0)
├── README.md                           # 项目文档
├── constants/
│   ├── analysis_diff_risk/
│   │   └── filter-rules.yaml           # 文件过滤规则
│   └── common/
│       ├── runtime-constants.yaml      # 公用运行时配置
│       └── output-messages.yaml        # 输出消息模板
├── docs/
│   └── analysis_diff_risk_point.md     # 风险检查文档
│   └── analysis-diff-risk-step2.md     # 风险检查文档
├── script/analysis_diff_risk/
│   ├── analyze_diff.py                 # Git diff 解析 + YAML 生成
│   ├── execute_analysis.py             # 状态机引擎(列表/收集/汇总)
│   └── cleanup_state.py                # 清理临时状态
├── state/analysis_diff_risk/           # 运行时状态机
│   └── impact-analysis-${hash_id}.yaml # 待执行的任务列表
│   └── results_${hash_id}_batch.yaml   # subAgent执行任务的结果
├── skills/fe-analysis-diff-risk/
│   └── SKILL.md                        # Skill 入口定义
└── template/
    └── analysis_diff_risk_result.md    # 输出报告模板
    └── subagent_analyze_prompt.md      # subagent提示词

踩过的坑

  1. 使用脚本输出执行结果时,注意print里的文字描述需要精确,否则也会造成模型偏移。最好能附带下一步要做什么。

  2. 能力一般的模型在统计diff行数时会有偏差。这里选择直接用脚本算好每一段diff的start_line、end_line再喂给模型。

  3. 改动量比较大时,模型的检索时间会比较久。这是因为模型的常用检索方式是grep / ripgrep(rg)命令,这些命令的本质是用正则模糊匹配项目里的代码字符,匹配完成后还要依赖模型判断是否是正确的场景。

举个例子:告诉模型“把项目中所有的start换成star”,模型开始执行grep命令。因为只是正则模糊匹配,它分不清语境,可能会匹配出各种情况。然后模型再挨个分析上下文,判断哪些才是预期的场景,循环下来就拖慢了检索速度。

好消息是目前社区已有开源库能解决这些问题,比如codegraph。它会先把代码解析成结构图(AST + 符号关系图),记录“谁是什么角色”。开始检索时,它可以直接查索引,知道哪些是变量定义、哪些是函数引用,甚至能知道代码在哪些地方被真正使用了。实测下来能减少30%的耗时,而且比模型更稳定,后续也打算集成到Plugin中。

效果与局限

  1. 测试了Sonnet 4.6Opus 4.8deepseek-v4-proQwen3.6 Plus,都能按预期执行。
  2. 服务端会有跨上下游的场景。此时上游或下游代码在其他项目中,Skill读取不到会造成上下文不够完整。简单的解法是把上下游项目放在同一个文件目录下。

总结:Harness的核心启示

  1. 我们当然期望能力更强大的模型能一键帮我们做完所有事情,但模型的稳定性和边界也不可忽视。
  2. 除了会用模型,也应该探索有关模型更深层次的东西,提升在未来的竞争力。
  3. 在前端/服务端学会的那些工程化能力,在AI时代一样有用。平时也要注重基础的积累。

热门手游

相关攻略

手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc