ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

物理Agent Harness系统设计:从VLA推理到LLM调度的工程实践

物理Agent Harness系统设计:从VLA推理到LLM调度的工程实践

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的工作是:

  1. 分解为子任务:定位苹果、抓取苹果、移动到篮子上方、释放。
  2. 为每个子任务选择合适的工具:detect_object("apple")、grasp()、move_to("basket")、release()。
  3. 监控执行结果,如果抓取失败,决定重试还是换策略。

而具体的抓取力度、移动轨迹、释放时机,由底层控制器和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格式不对,或者工具名拼错,或者参数类型错误。

排查思路:

  1. 先检查prompt里的格式说明是否清晰。我习惯用JSON Schema明确告诉LLM每个字段的类型和取值范围。
  2. 加一个输出解析器,对LLM的输出做容错处理。比如用正则提取JSON,或者用Pydantic做校验。
  3. 如果错误率还是高,考虑用function calling或structured output功能,让模型在解码层面就保证格式。

我的经验:格式错误率超过5%就说明prompt有问题,不要靠重试硬扛。重试只能解决偶发错误,解决不了系统性问题。

5.2 VLA动作抖动:机器人像得了帕金森

问题现象:VLA输出的动作序列不连续,机器人执行时抖动明显。

排查思路:

  1. 检查VLA的输入图像是否有噪声。图像质量差会导致动作预测不稳定。
  2. 检查动作序列的时间对齐。VLA输出的动作可能是相对于不同时间点的,直接拼接会跳变。
  3. 加动作平滑滤波器。我通常用Savitzky-Golay滤波器,窗口长度5到11,多项式阶数2到3。

我的经验:抖动问题80%出在输入侧,20%出在输出侧。先把摄像头固定好、光照调均匀,比调滤波器参数有效得多。

5.3 任务卡死:Agent陷入死循环

问题现象:Agent反复执行同一个动作,任务永远完不成。

排查思路:

  1. 检查状态更新逻辑。如果状态没有正确更新,Agent会以为任务还没完成。
  2. 加一个“动作重复检测器”。如果连续3步执行相同动作且结果相同,强制触发异常处理。
  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时间。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进