1. 从模型崇拜到系统思维为什么现在要聊 Harness过去两年我接触过不少做机器人和 Agent 的团队发现一个特别有意思的现象大家一开始都在卷模型。谁的 VLA 参数大、谁的 LLM 推理强、谁微调的数据多仿佛只要模型够强一切问题都能迎刃而解。但真正把系统跑起来之后情况往往不是这样——模型在 benchmark 上表现亮眼到了真实场景里却频繁翻车换个任务就得重新调一遍换个硬件就得重写一遍逻辑。这个落差本质上不是模型能力的问题而是系统能力的问题。而 Harness 这个词恰恰就是从这个落差里长出来的。所谓 Harness直译是“马具”“挽具”在工程语境里它指的是一套包裹在模型外面的运行框架负责把模型的输出翻译成真实世界的动作把真实世界的反馈整理成模型能理解的输入同时处理异常、重试、状态管理、安全边界这些“脏活累活”。你可以把它理解成模型和物理世界之间的“翻译官 调度员 安全员”。为什么现在这个话题突然热起来因为大家逐渐意识到从“更强的模型”到“更好的系统”是机器人 Agent 落地的必经之路。一个 70 分的模型配一套 90 分的 Harness实际表现往往好过一个 90 分的模型配一套 50 分的 Harness。这不是玄学而是大量工程实践反复验证过的结论。这篇文章我想聊的就是这件事Harness 到底是什么、它和 Agent 的区别在哪、VLA 和 LLM 在系统里各自扮演什么角色、一套靠谱的 Harness 该怎么搭、踩过哪些坑。内容会偏工程实操适合正在做机器人 Agent、VLA 应用、或者准备入门这个方向的朋友。不管你是刚听说 Harness 这个词还是已经在写自己的调度框架应该都能找到能直接抄作业的部分。2. 概念厘清Harness、Agent、VLA、LLM 到底谁是谁在动手之前必须先把几个高频词的关系理清楚。我发现很多人聊了半天其实对这几个概念的理解是混的导致方案设计时职责边界模糊最后写出来的代码又臭又长。2.1 Harness 和 Agent 的区别一个管“怎么跑”一个管“做什么”这是被问得最多的问题。我的理解是Agent关注的是决策给定当前状态和目标下一步该做什么。它可以是基于 LLM 的比如让大模型规划任务序列也可以是基于规则的比如状态机甚至可以是强化学习策略。Harness关注的是执行Agent 决定了“做什么”之后Harness 负责“怎么把它安全、稳定、可观测地跑起来”。打个比方Agent 像是车里的导航系统负责规划路线Harness 像是底盘、刹车、油门、仪表盘这一整套负责让车真的能按导航走还能在爆胎时稳住、在油量低时报警、在司机走神时兜底。所以你会看到一个 Agent 可以换不同的 Harness 来跑一个 Harness 也可以承载不同的 Agent。它们不是替代关系而是分层协作关系。很多项目一开始把两者揉在一起写后期想换模型或者换硬件时改一处崩一片就是因为没有做这层解耦。2.2 VLA 是一个模型还是两个模型别被名字骗了VLA 是 Vision-Language-Action 的缩写字面看是三个模态。经常有人问“VLA 是一个模型还是两个模型”这个问题本身就暴露了对架构的误解。严格来说VLA 描述的是一种能力形态而不是固定的模型数量。常见的实现方式有两类架构类型组成方式典型特点适用场景端到端单模型一个网络同时吃图像、语言、输出动作参数共享、延迟低、训练难固定任务、算力受限模块化多模型视觉编码器 语言模型 动作头可分开训练灵活、可替换、调试方便多任务、快速迭代我个人的经验是早期项目优先选模块化。原因很实际端到端模型一旦效果不好你根本不知道是视觉没看懂、语言没理解还是动作头映射错了。模块化虽然延迟高一点但每一段都能单独测、单独换调试成本低太多。等系统稳定了再考虑往端到端收敛。2.3 LLM 在机器人系统里的真实位置LLM 在机器人 Agent 里通常不直接输出电机指令它更多承担高层语义理解和任务分解。比如用户说“把桌上那个红色的杯子拿给我”LLM 负责把它拆成“定位桌子 → 识别红色杯子 → 规划抓取 → 移动到用户位置”这样的子任务序列具体每个子任务怎么执行交给 VLA 或者专门的技能模块。这里有个关键认知LLM 的输出是“意图”不是“动作”。Harness 的职责之一就是把 LLM 输出的意图翻译成下游模块能执行的指令格式。这个翻译层做得好不好直接决定了系统稳不稳。3. Harness 的核心职责拆解一套系统到底要管多少事很多人以为 Harness 就是个“调用模型的壳”写个函数包一下就行。真做过项目就知道这个壳里装的东西远比想象的多。我把它拆成六个核心职责每一个都对应着实际项目里踩过的坑。3.1 输入归一化让模型永远看到“干净”的世界真实世界的输入是脏的。摄像头有噪声、传感器有漂移、用户指令有歧义、网络请求会超时。如果这些原始数据直接喂给模型模型再强也扛不住。Harness 的第一件事就是输入归一化把各种来源的数据统一成模型期望的格式。具体包括图像统一分辨率、色彩空间、时间戳对齐语言指令做意图澄清和歧义消解传感器数据做滤波、去噪、单位统一缺失字段用默认值或上一次有效值填充提示归一化层一定要做幂等设计。同一份输入重复处理结果必须一致。否则重试机制会引入难以复现的 bug。3.2 输出校验模型说的不一定能做模型输出的动作指令不能直接下发到执行器。原因很简单模型可能输出超出关节限位的角度、可能给出不存在的物体 ID、可能生成格式错误的 JSON。Harness 需要在输出和执行之间加一道校验层格式校验JSON schema、字段类型、必填项范围校验角度、速度、力矩是否在安全区间语义校验引用的物体、位置、技能是否真实存在冲突校验多个子任务之间是否有资源竞争校验不通过怎么办不是简单报错而是要走降级策略能修正的自动修正不能修正的退回给 Agent 重新规划实在不行就安全停机。3.3 状态管理机器人不是无状态的函数LLM 调用可以当成无状态请求但机器人不行。机器人有位置、有电量、有正在执行的任务、有历史动作。这些状态如果管理不好就会出现“模型以为杯子还在桌上其实已经被拿走了”这种经典问题。我的做法是维护一个分层状态机全局状态机器人位置、电量、模式空闲/执行/故障任务状态当前任务 ID、进度、子任务栈世界状态已知物体、位置、可操作区域每次调用模型前把相关状态序列化进 prompt模型返回后根据执行结果更新状态。状态更新必须是原子操作避免并发写导致的不一致。3.4 异常处理与重试稳定性的真正来源系统跑久了异常是常态而不是意外。网络抖动、模型超时、执行器卡死、传感器掉线这些都会发生。Harness 的价值很大一部分体现在异常处理上。我总结的异常处理原则分类处理可重试的网络超时、可降级的模型不可用走规则、必须停机的安全相关指数退避重试间隔递增避免雪崩熔断机制连续失败达到阈值直接切换到备用方案全程留痕每次异常都记录上下文方便事后复盘3.5 可观测性看不见的系统等于不可控一个没有日志、没有指标、没有追踪的 Harness出问题时你只能靠猜。可观测性不是锦上添花而是必需品。至少要覆盖三个层面日志结构化日志包含时间戳、模块、输入、输出、耗时、结果指标调用次数、成功率、延迟分布、异常分类统计追踪一次完整任务的全链路 trace能还原每一步的决策依据3.6 安全边界最后一道防线这是最容易被忽视、但最不能省的部分。安全边界包括硬件层急停、力矩限制、工作空间限制软件层指令白名单、频率限制、危险动作二次确认逻辑层任务超时、死循环检测、资源耗尽保护注意安全边界必须独立于模型和 Agent 之外。不能指望模型“自己知道不该做什么”那是把安全交给了最不可控的一环。4. 从零搭一套 Harness完整实操流程理论讲完进入实操。下面这套流程是我在几个项目里反复打磨过的可以直接参考。为了讲清楚我用一个具体场景贯穿让机器人根据自然语言指令在桌面上找到指定物体并抓取。4.1 环境准备与依赖选型先说选型思路。Harness 本身不挑语言Python 生态最全适合快速迭代如果对延迟敏感核心调度可以用 C 或 RustPython 只做上层编排。我常用的组合组件选型理由主语言Python 3.10生态全、调试快异步框架asyncio天然适合 IO 密集的模型调用消息队列Redis / 内存队列解耦模块、支持重试配置管理Pydantic YAML类型安全、易校验日志structlog结构化、易检索追踪OpenTelemetry标准协议、生态好安装依赖时有个小技巧把模型推理、硬件驱动、Harness 核心分成三个独立的依赖组。这样换模型或换硬件时不用动核心代码的依赖。pip install pydantic structlog opentelemetry-sdk redis4.2 定义统一的数据契约Harness 内部模块之间怎么通信靠数据契约。这一步做扎实后面所有模块都能解耦。我通常定义三类核心结构from pydantic import BaseModel from typing import Literal, Optional class Observation(BaseModel): timestamp: float image: Optional[bytes] instruction: Optional[str] robot_state: dict class Action(BaseModel): skill: str params: dict timeout: float 5.0 class ExecutionResult(BaseModel): success: bool error_type: Optional[str] detail: dict duration: float这三个结构分别对应“看到什么”“要做什么”“做得怎么样”。所有模块只认这三个契约内部实现随便换。4.3 搭建主循环感知-决策-执行-反馈Harness 的主循环是系统的骨架。我习惯写成显式的状态机而不是隐式的回调地狱。async def harness_loop(agent, executor, observer): while True: obs await observer.get() obs normalize(obs) try: action await agent.decide(obs) action validate(action, obs) except ValidationError as e: log.warn(action_invalid, errorstr(e)) continue result await executor.run(action) if not result.success: result await handle_failure(result, action, agent) await update_state(obs, action, result) await report_metrics(obs, action, result)这个循环看起来简单但每一行背后都有讲究。normalize要做幂等validate要做多层校验handle_failure要区分可重试和不可重试update_state要保证原子性。4.4 参数计算超时和重试怎么定超时和重试参数不能拍脑袋要有依据。我的计算方法超时时间 模型 P99 延迟 × 1.5 执行器 P99 延迟 × 1.5比如模型 P99 是 800ms执行器 P99 是 2s那超时设 4.2s 左右比较合理。设太短会误杀正常请求设太长会拖慢故障发现。重试次数 ceil(log(可接受失败率) / log(单次失败率))假设单次失败率 10%希望整体失败率低于 1%那需要重试 2 次0.1² 0.01。但要注意只有幂等操作才能重试抓取这种有副作用的动作重试前必须先确认上一次的状态。4.5 实操现场一次完整抓取任务的执行记录我把一次真实执行的关键日志整理出来方便你对照理解[t0.000] observe: instruction拿红色杯子, image640x480 [t0.012] normalize: image resized to 224x224, instruction cleaned [t0.015] agent.decide: calling LLM... [t0.842] agent.decide: returned [locate_cup, grasp, move_to_user] [t0.845] validate: all actions passed schema check [t0.846] executor.run: locate_cup [t1.203] executor.run: locate_cup success, bbox[120,80,200,160] [t1.205] executor.run: grasp [t2.891] executor.run: grasp success, force3.2N [t2.893] executor.run: move_to_user [t4.102] executor.run: move_to_user success [t4.105] state updated, metrics reported整个流程 4.1 秒其中模型决策占 0.83 秒执行占 3.26 秒。这个比例很典型模型不是瓶颈执行才是。所以优化系统时先看执行链路别一上来就换更大的模型。5. 常见问题与排查技巧实录这一节是我踩坑最多的地方整理成速查表希望能帮你少走弯路。5.1 模型输出格式不稳定怎么办这是最高频的问题。同一个 prompt模型有时返回 JSON有时返回带解释的自然语言有时字段名还变。我的处理策略分三层Prompt 层明确要求输出格式给出 schema 示例用 few-shot 固定风格解析层用宽松解析器能提取 JSON 就提取提取不到走正则兜底修复层解析失败时把错误信息回传给模型让它自我修正一次实测下来这三层能把格式错误率从 15% 降到 2% 以下。剩下 2% 走降级策略用规则引擎兜底。5.2 执行器卡死导致整个系统挂起机器人执行动作时卡住是另一个经典问题。如果 Harness 没有超时保护整个循环就停在那里了。解决方案是所有执行调用都包在超时里并且超时后要主动查询执行器状态而不是直接认为失败。因为有时候动作其实完成了只是反馈没回来。try: result await asyncio.wait_for(executor.run(action), timeoutaction.timeout) except asyncio.TimeoutError: actual_state await executor.query_state() result infer_result_from_state(actual_state, action)5.3 状态不一致引发的“幽灵任务”有时候会出现“模型以为任务没完成其实已经完成了”的情况导致重复执行。根因通常是状态更新和执行结果之间有时间差。我的做法是引入版本号机制每次状态更新递增版本号模型决策时带上版本号执行前校验版本号是否匹配。不匹配就丢弃这次决策重新感知。5.4 常见问题速查表问题现象可能原因排查方向解决手段模型输出格式错乱prompt 不明确检查 schema 示例加 few-shot 宽松解析执行超时频繁超时设置过短看 P99 延迟按公式重算超时任务重复执行状态更新延迟查版本号引入版本校验系统整体卡死缺少熔断看异常日志加熔断和降级抓取成功率低视觉定位漂移对比 bbox 和实际加视觉反馈闭环5.5 几个容易被忽视的避坑技巧日志里别打完整图像体积大、检索慢存路径或哈希就行重试要带抖动固定间隔重试容易造成同步雪崩加随机抖动降级方案要提前测别等主方案挂了才发现降级方案也是坏的安全边界要独立进程和主循环同进程主循环崩了安全边界也崩了6. 系统优化的进阶方向把基础 Harness 跑通之后还有不少可以深挖的地方。这部分聊聊我观察到的几个有效方向。6.1 从串行到并行能并行的绝不串行很多 Harness 默认串行执行其实感知、决策、部分执行是可以并行的。比如在移动到目标位置的同时就可以开始下一轮的视觉感知。并行化能显著降低端到端延迟但要注意共享状态的并发安全。6.2 缓存与复用别重复算一样的东西视觉特征、语言 embedding、常见任务的规划结果这些都可以缓存。我见过一个项目加了视觉特征缓存之后整体延迟降了 30%。缓存的关键是失效策略场景变化了必须失效否则会用过期数据做决策。6.3 反馈闭环让系统自己变好每次执行的结果都是宝贵的训练数据。把成功的轨迹、失败的案例都收集起来定期回灌到模型微调或者规则优化里系统会越跑越稳。这个闭环不需要多复杂一个简单的数据收集脚本就能起步。6.4 模块化替换为未来留好接口技术迭代很快今天用的模型明天可能就被淘汰了。Harness 设计时把模型、执行器、感知模块都做成可替换的接口换的时候只改配置不改代码。这个投入在项目中期会带来巨大回报。我个人在实际操作中的体会是Harness 这件事没有银弹也没有一步到位的完美设计。它更像是在项目推进过程中不断把踩过的坑固化成机制。一开始可能只有几十行代码随着问题暴露慢慢长出校验、重试、状态管理、可观测性这些模块。所以别想着一次设计到位先跑起来让问题驱动演进反而更靠谱。
阅读完成 · 觉得有帮助?