1. 从模型崇拜到系统思维:为什么“更好的系统”正在取代“更强的模型”
过去两年,我接触过不少做机器人方向的朋友,大家聊天的开场白往往是“你用的哪个模型”“参数多大”“跑分多少”。但到了2024年下半年,话题明显变了——越来越多人开始问“你的Harness怎么搭的”“VLA推理延迟压到多少”“Agent的失败恢复策略是什么”。这个转变不是偶然的,它背后是一条非常清晰的工程逻辑线。
先说结论:在物理Agent和机器人领域,模型能力已经不再是瓶颈,系统能力才是。一个70B的LLM配上精心设计的Harness,在真实任务中的完成率可以碾压一个GPT-4级别的模型裸跑。这不是玄学,而是因为物理世界的任务天然具有长程性、不确定性和不可逆性,单次推理再强也扛不住连续20步的误差累积。
那什么是Harness?这个词在软件工程里原本指“测试夹具”或“驱动框架”,在Agent语境下,我倾向于把它定义为:包裹在模型外面、负责感知输入、组织上下文、调度工具、校验输出、处理异常、维护状态的那一整套工程系统。模型是发动机,Harness是变速箱、底盘、刹车和方向盘的总和。你发动机再好,没有变速箱,车也跑不起来。
热词里有个问题问得很好——“harness和agent区别”。我的理解是:Agent是一个概念,指的是“能自主感知、决策、行动的智能体”;Harness是实现这个概念的工程载体。就像“自动驾驶”是一个概念,但“传感器融合模块+路径规划器+控制执行器”才是真正让车动起来的东西。你不可能只买一个“自动驾驶”回家,你买的是一整套系统。
再看VLA(Vision-Language-Action)模型。热词里有人问“vla模型是一个模型还是2个模型”,这个问题本身就暴露了一个常见误解。VLA不是简单的“视觉模型+语言模型+动作模型”拼接,而是一个端到端训练的统一架构,输入是图像和指令,输出是动作序列。但即便是VLA,裸跑的效果也远远不够——你需要Harness来处理观测噪声、动作平滑、安全边界、失败回滚。这就是为什么“派0 vla”这类项目在实际部署时,大量精力花在了Harness层而不是模型层。
所以这篇文章想做的事情很明确:把“机器人/物理Agent Harness”这件事拆开揉碎,从设计思路、核心模块、实操要点、常见坑位四个维度,给出一份可以直接参考的系统级方案。不管你是刚入门机器人学习的新手,还是已经在做Agent开发的老手,都能从中找到可复用的东西。
2. 物理Agent Harness的整体架构设计:分层解耦与状态管理
2.1 为什么不能把一切塞进一个循环
我见过最简陋的Agent实现是这样的:一个while循环,每轮把摄像头图像和任务描述拼成prompt丢给LLM,LLM输出一个动作,执行,然后继续。这种写法在demo里能跑,但一上真实机器人就崩。原因很简单:LLM的上下文窗口是有限的,而物理任务是无限的。你不可能把过去1000步的观测和动作全部塞进prompt,那样既慢又贵,而且模型会迷失在长上下文里。
正确的做法是分层。我的经验是至少分四层:
- 感知层:负责原始数据的采集和预处理。摄像头、激光雷达、力传感器、关节编码器,这些数据先经过滤波、对齐、降采样,变成结构化的观测向量。
- 状态层:维护一个“世界状态”的紧凑表示。不是原始像素,而是“物体A在位置X,机械臂在位置Y,任务进度Z”这样的符号化或向量化状态。
- 决策层:LLM或VLA在这里工作。它接收状态层的摘要,输出高层动作意图(比如“抓取红色方块”),而不是底层关节角度。
- 执行层:把高层意图翻译成具体的控制指令,处理轨迹规划、碰撞检测、力控等底层问题。
这个分层的好处是:每一层都可以独立替换和调试。你换一个更好的LLM,只需要改决策层;你换一个更精确的传感器,只需要改感知层。如果全部揉在一起,牵一发而动全身。
2.2 状态管理的核心:什么该记,什么该忘
物理Agent和纯软件Agent最大的区别在于:物理世界的状态是不可逆的。你把杯子打碎了,不能按Ctrl+Z。所以状态管理必须区分“可恢复状态”和“不可恢复状态”。
我的做法是维护两个缓冲区:
- 短期缓冲区:最近N步的观测-动作对,N一般取5到10。这个缓冲区用于给LLM提供即时上下文,让它知道“我刚才做了什么,结果如何”。
- 长期记忆:任务级别的关键事件日志。比如“已经完成了抓取”“已经到达目标位置”“遇到了障碍物并绕开”。这些事件用自然语言或结构化JSON存储,在需要时检索出来注入prompt。
注意:长期记忆不要存原始数据,要存“结论”。存“机械臂在t=3.2s时关节角度为[0.1, 0.5, ...]”没有意义,存“机械臂在3.2秒时成功抓取了方块”才有意义。
2.3 工具调用的设计原则:少而精,幂等优先
Agent Harness里最容易被滥用的就是工具调用。很多人喜欢给LLM挂几十个工具,觉得这样“能力强”。但实际上,工具越多,LLM选错的概率越大。我的经验是:核心工具控制在5到8个,每个工具的功能要正交,不要有重叠。
更重要的是幂等性。物理世界里,一个动作执行两次可能造成灾难。比如“向前移动10厘米”,如果因为网络重试执行了两次,机器人就撞墙了。所以每个工具都要设计成幂等的,或者至少要有执行前的状态检查。
# 非幂等工具的反例 def move_forward(distance): robot.move(distance) # 重试会移动两次 # 幂等工具的正例 def move_to_position(target_pos): current = robot.get_position() if abs(current - target_pos) < 0.01: return "already there" robot.move_to(target_pos) return "moved"这个细节看起来小,但在实际部署中能避免大量诡异bug。
3. 核心模块拆解:从VLA推理到LLM调度
3.1 VLA模型的Harness适配:延迟、频率与动作平滑
VLA模型(Vision-Language-Action)是当前机器人领域的热点,但很多人拿到模型后直接部署,发现效果远不如论文。问题往往出在Harness层没有做好适配。
首先是推理频率。VLA模型通常推理一次需要100到500毫秒,而机器人的控制循环可能需要10到50毫秒。这意味着你不能每一步都调用VLA,而是要用VLA输出一个“动作块”(action chunk),然后由底层控制器插值执行。
我的做法是:VLA每200毫秒推理一次,输出未来1秒的动作序列(比如10个动作点),底层控制器以50Hz的频率在这10个点之间做样条插值。这样既保证了响应速度,又避免了频繁推理带来的抖动。
其次是动作平滑。VLA输出的动作序列往往有噪声,直接执行会导致机器人抖动。我通常会在执行层加一个低通滤波器或卡尔曼滤波器,对动作做平滑处理。参数上,截止频率一般设在5到10Hz,太高了滤不掉噪声,太低了会导致响应迟钝。
3.2 LLM在Harness中的角色:调度器而非执行器
很多人把LLM当成“大脑”,指望它输出底层控制指令。这是典型的误用。LLM在物理Agent里最适合的角色是调度器:理解任务、分解步骤、选择工具、处理异常。它不应该直接输出关节角度。
举个例子,任务“把桌上的苹果放进篮子”。LLM的工作是:
- 分解为子任务:定位苹果、抓取苹果、移动到篮子上方、释放。
- 为每个子任务选择合适的工具:
detect_object("apple")、grasp()、move_to("basket")、release()。 - 监控执行结果,如果抓取失败,决定重试还是换策略。
而具体的抓取力度、移动轨迹、释放时机,由底层控制器和VLA模型负责。这样分工,LLM的推理负担小,响应快,而且不容易出错。
3.3 上下文工程:如何让LLM“记住”关键信息
LLM没有持久记忆,每次调用都是无状态的。所以Harness必须负责上下文的构建。我的经验是,每次调用LLM时,prompt里应该包含以下部分:
| 组成部分 | 内容 | 长度控制 |
|---|---|---|
| 系统提示 | 角色定义、可用工具列表、输出格式要求 | 200-500 token |
| 任务描述 | 当前任务的完整描述 | 50-100 token |
| 世界状态 | 当前观测的符号化摘要 | 100-300 token |
| 近期历史 | 最近3-5步的动作和结果 | 200-400 token |
| 长期记忆 | 检索到的关键事件 | 100-200 token |
| 当前指令 | 本轮需要决策的具体问题 | 50-100 token |
总长度控制在1000到1500 token之间。太短了信息不足,太长了模型注意力分散。这个平衡点需要根据具体模型和任务调优。
实操心得:我习惯在系统提示里加一句“如果你不确定,选择最安全的动作”。这句话在物理场景里能救命。LLM有时候会“自信地犯错”,加这句话能显著降低危险动作的概率。
4. 实操过程:从零搭建一个可用的Harness
4.1 环境准备与依赖选型
假设你要从零搭一个物理Agent Harness,我的推荐技术栈是这样的:
- 语言:Python 3.10+。生态最全,调试方便。
- 机器人中间件:ROS2(如果做研究)或直接厂商SDK(如果做产品)。ROS2的好处是工具链成熟,坏处是学习曲线陡。
- LLM接入:用OpenAI兼容的API接口,这样可以在不同模型之间切换。本地部署的话,vLLM或Ollama都不错。
- VLA模型:如果做研究,可以从开源的VLA项目入手;如果做产品,建议先用传统视觉+规划方案,等VLA成熟再替换。
- 状态存储:Redis或SQLite。Redis适合高频读写,SQLite适合持久化日志。
安装依赖的时候有个坑:不同厂商的机器人SDK经常有版本冲突。我的建议是用Docker把每个机器人的驱动隔离开,Harness通过gRPC或ROS2 topic跟驱动通信。这样换机器人只需要换容器,不用动Harness代码。
4.2 核心循环的实现
Harness的主循环大概是这样的:
class AgentHarness: def __init__(self, llm, vla, robot, memory): self.llm = llm self.vla = vla self.robot = robot self.memory = memory self.state = WorldState() def run(self, task): self.memory.log_event("task_started", task) while not self.task_complete(task): # 1. 感知 obs = self.robot.get_observation() self.state.update(obs) # 2. 构建上下文 context = self.build_context(task) # 3. LLM决策 action_intent = self.llm.decide(context) # 4. 执行 if action_intent.type == "high_level": result = self.execute_high_level(action_intent) elif action_intent.type == "vla": result = self.execute_vla(action_intent) # 5. 记录 self.memory.log_step(obs, action_intent, result) # 6. 异常处理 if result.failed: self.handle_failure(result) self.memory.log_event("task_completed", task)这个循环看起来简单,但每个环节都有讲究。比如build_context要做上下文裁剪,execute_high_level要做工具调用的参数校验,handle_failure要有重试上限和降级策略。
4.3 参数调优:那些文档里不会写的数字
以下是我在实际项目中总结的一些关键参数,供参考:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| LLM调用频率 | 1-5 Hz | 太高了浪费算力,太低了响应慢 |
| VLA推理频率 | 3-10 Hz | 取决于模型大小和硬件 |
| 底层控制频率 | 50-1000 Hz | 取决于机器人类型 |
| 短期缓冲区长度 | 5-10步 | 覆盖最近的动作历史 |
| 重试上限 | 3次 | 超过就降级或求助 |
| 动作平滑截止频率 | 5-10 Hz | 滤除高频抖动 |
| 安全边界检查频率 | 每步 | 不能省 |
注意:这些数字不是绝对的,要根据具体硬件和任务调整。比如做精细装配,控制频率要更高;做移动导航,LLM调用频率可以更低。
4.4 安全层:物理Agent的保命符
物理Agent和软件Agent最大的区别是:它会伤人。所以安全层不是可选项,是必选项。我的做法是在执行层前面加一个独立的安全检查模块,它不依赖LLM,而是用硬编码的规则做最后一道防线。
安全检查包括:
- 关节限位:每个关节的角度不能超过机械限位。
- 速度限位:末端执行器的速度不能超过安全阈值。
- 力限位:检测到异常力反馈立即停止。
- 工作空间限位:机械臂不能进入人类活动区域。
- 超时检查:任何动作超过预期时间立即中止。
这个模块要用独立的线程或进程运行,不能和主循环共享状态。因为主循环可能因为LLM推理而阻塞,但安全检查不能阻塞。
5. 常见问题与排查技巧实录
5.1 LLM输出格式错误:最常见也最烦人
问题现象:LLM返回的JSON格式不对,或者工具名拼错,或者参数类型错误。
排查思路:
- 先检查prompt里的格式说明是否清晰。我习惯用JSON Schema明确告诉LLM每个字段的类型和取值范围。
- 加一个输出解析器,对LLM的输出做容错处理。比如用正则提取JSON,或者用Pydantic做校验。
- 如果错误率还是高,考虑用function calling或structured output功能,让模型在解码层面就保证格式。
我的经验:格式错误率超过5%就说明prompt有问题,不要靠重试硬扛。重试只能解决偶发错误,解决不了系统性问题。
5.2 VLA动作抖动:机器人像得了帕金森
问题现象:VLA输出的动作序列不连续,机器人执行时抖动明显。
排查思路:
- 检查VLA的输入图像是否有噪声。图像质量差会导致动作预测不稳定。
- 检查动作序列的时间对齐。VLA输出的动作可能是相对于不同时间点的,直接拼接会跳变。
- 加动作平滑滤波器。我通常用Savitzky-Golay滤波器,窗口长度5到11,多项式阶数2到3。
我的经验:抖动问题80%出在输入侧,20%出在输出侧。先把摄像头固定好、光照调均匀,比调滤波器参数有效得多。
5.3 任务卡死:Agent陷入死循环
问题现象:Agent反复执行同一个动作,任务永远完不成。
排查思路:
- 检查状态更新逻辑。如果状态没有正确更新,Agent会以为任务还没完成。
- 加一个“动作重复检测器”。如果连续3步执行相同动作且结果相同,强制触发异常处理。
- 在prompt里加入“如果你已经尝试过某个动作且失败,请换一种策略”的提示。
我的经验:死循环往往是因为LLM的上下文里缺少“我已经试过了”的信息。在短期缓冲区里明确标注“已尝试且失败的动作”,能显著降低死循环概率。
5.4 常见问题速查表
| 问题 | 可能原因 | 快速排查方法 | 解决方案 |
|---|---|---|---|
| LLM输出格式错误 | prompt不清晰 | 打印原始输出 | 加JSON Schema校验 |
| VLA动作抖动 | 输入噪声 | 可视化输入图像 | 滤波+固定摄像头 |
| 任务卡死 | 状态未更新 | 打印状态变化 | 加重复检测器 |
| 工具调用失败 | 参数类型错误 | 检查工具签名 | 加参数校验层 |
| 响应延迟高 | LLM调用太频繁 | 打时间戳 | 降低调用频率 |
| 安全问题 | 缺少安全检查 | 审查执行层 | 加独立安全模块 |
5.5 独家避坑技巧
技巧一:用“影子模式”做回归测试。在真实执行之前,让Harness在仿真环境里跑一遍,对比仿真和真实的结果差异。差异大的地方就是Harness需要改进的地方。
技巧二:给LLM加“置信度”输出。让LLM在输出动作的同时输出一个0到1的置信度。置信度低于0.6时,Harness自动切换到保守策略或请求人工干预。这个技巧在复杂任务里特别有用。
技巧三:日志要记“为什么”而不只是“是什么”。不要只记录“执行了抓取动作”,要记录“因为检测到苹果在位置X,所以执行抓取”。这样出问题时才能回溯决策链路。
技巧四:定期做“故障注入”测试。人为制造传感器噪声、网络延迟、工具失败,看Harness能不能正确处理。这比等真实故障发生再修要主动得多。
6. 从“更强的模型”到“更好的系统”的工程哲学
6.1 模型能力的天花板与系统能力的杠杆
我做过一个对比实验:同一个抓取任务,用GPT-4裸跑,成功率大概60%;用一个小得多的模型配上精心设计的Harness,成功率能到85%。这个差距不是模型能力带来的,而是系统能力带来的。
Harness的杠杆效应体现在几个方面:
- 错误恢复:模型犯错是必然的,Harness能在错误发生后及时纠正。
- 上下文管理:模型记不住的东西,Harness帮它记。
- 安全兜底:模型可能输出危险动作,Harness能拦住。
- 工具编排:模型不擅长多步规划,Harness能把复杂任务拆解成简单步骤。
所以我的观点很明确:在物理Agent领域,与其花大力气追求更强的模型,不如花同样的力气打磨更好的系统。模型能力提升10%可能需要几百万美元的训练成本,但系统能力提升10%可能只需要几周的工程优化。
6.2 可复用的Harness设计模式
经过多个项目的迭代,我总结出几个可复用的设计模式:
模式一:状态机+LLM混合决策。对于流程固定的任务,用状态机控制主流程,LLM只在关键决策点介入。这样既保证了可靠性,又保留了灵活性。
模式二:工具分层。把工具分成“安全工具”和“危险工具”。安全工具(如查询状态)可以自由调用,危险工具(如移动)需要额外校验。
模式三:渐进式自主。新任务先让人类示范几次,Harness记录轨迹;然后让Agent在人类监督下执行;最后逐步放开自主权。这个模式在工业场景里特别实用。
模式四:双通道输出。LLM同时输出“动作”和“解释”。动作用于执行,解释用于日志和调试。解释还能帮助人类理解Agent的决策逻辑。
6.3 未来扩展方向:从单Agent到多Agent协作
单个Agent的能力终究有限。下一步的扩展方向是多Agent协作:一个Agent负责导航,一个负责抓取,一个负责质检,它们通过共享状态层通信。这时候Harness的角色就变成了“通信中间件+协调器”。
多Agent的挑战在于:如何避免冲突、如何分配任务、如何处理一个Agent失败时的连锁反应。这些问题目前还没有标准答案,但我觉得这是物理Agent领域接下来最有意思的方向。
我个人在实际操作中的体会是:不要等到模型完美了再去做系统。模型永远在迭代,但系统架构是可以提前设计的。一个好的Harness能让平庸的模型发挥出超常的水平,而一个糟糕的Harness会让最好的模型也寸步难行。先把系统搭起来,让模型在里面跑,边跑边优化,这才是务实的做法。
最后分享一个小技巧:每次调试Harness的时候,把LLM的输入输出完整打印出来,存成文件。过一周再回头看,你会发现很多当时没注意到的模式。这个习惯帮我省了无数个小时的debug时间。