基于ROS与LLM的智能体协作框架:从架构设计到实战部署
1. 项目概述:从代码仓库到智能体协作框架的深度解构
最近在开源社区里,一个名为agenticros/agenticros的仓库引起了我的注意。乍一看,这像是一个典型的个人或组织项目,名字本身“agenticros”就充满了暗示——它很可能指向“智能体”(Agent)与“机器人操作系统”(ROS)的结合。作为一名长期在机器人、自动化与AI交叉领域摸爬滚打的从业者,我深知将前沿的智能体(Agent)技术与成熟、稳定的机器人中间件(如ROS)进行深度融合,是当前解决复杂机器人任务从“自动化”迈向“自主化”的关键一步。这个项目标题背后,潜藏的正是一个极具前瞻性的技术方向:构建一个基于ROS的智能体协作框架。
简单来说,agenticros瞄准的痛点非常明确。传统的ROS(Robot Operating System)为机器人提供了强大的通信、硬件抽象、工具和库,但它本质上是一个“反应式”或“脚本化”的系统。机器人的行为通常由预先编写好的节点(Node)和话题(Topic)、服务(Service)通信来定义,缺乏高层级的、基于目标的自主决策与任务分解能力。而近年来兴起的AI智能体(Agent),尤其是基于大语言模型(LLM)的智能体,展现了惊人的任务理解、规划与工具调用能力。agenticros的目标,很可能就是架起这座桥梁,让ROS机器人能够被一个或多个具备高级认知能力的智能体所驱动和管理,从而执行更开放、更动态、更复杂的任务。
这个项目适合谁呢?我认为有三类开发者会对此特别感兴趣:一是正在研究具身智能(Embodied AI)或机器人自主导航与操作的科研人员和工程师;二是希望为自己的ROS机器人项目注入“大脑”,实现更灵活任务编排的机器人应用开发者;三是任何对多智能体系统(Multi-Agent System, MAS)在实体世界落地应用感到好奇的技术爱好者。接下来,我将基于我对机器人系统、ROS架构以及智能体技术的理解,对这个项目可能涉及的核心设计、关键技术、实操路径以及潜在挑战进行一次深度拆解。
2. 核心架构设计:智能体如何与ROS世界对话
要理解agenticros,我们必须先厘清其最核心的架构思想。它绝非简单地将一个聊天机器人接口接到ROS上,而是需要设计一套让“认知层”(智能体)与“执行层”(ROS节点网络)能够高效、可靠、安全交互的中间件或框架。
2.1 分层架构与职责边界
一个合理的agenticros架构至少应包含以下三层:
智能体层(Agent Layer):这是系统的“大脑”。它可能包含一个或多个智能体。这些智能体具备自然语言理解、任务规划、状态评估和决策能力。它们接收高级别的人类指令(如“去厨房拿一杯水”),并将其分解为一系列可执行的子目标。智能体的实现可以基于LLM(如GPT、Claude等模型的API或本地部署版本),也可以是基于符号AI或强化学习的传统智能体。这一层的核心输出是“行动计划”或“技能调用序列”。
协调与适配层(Orchestration & Adaptation Layer):这是整个框架的“中枢神经系统”,也是最体现
agenticros项目价值的部分。它负责:- 任务分解与映射:将智能体层生成的抽象计划,映射到ROS系统中已知的“技能”(Skill)或“动作原语”(Action Primitive)。例如,计划中的“导航到厨房”会被映射到调用
move_base导航栈的Action客户端。 - 状态管理与监控:持续订阅ROS中的各类话题(如传感器数据、节点状态),将其转化为智能体能够理解的“世界状态”描述,并反馈给智能体层,用于后续决策和异常处理。
- 安全与约束检查:在执行任何命令前,检查其是否符合预设的安全规则和物理约束(如机械臂关节限位、碰撞避免)。这是防止智能体“胡思乱想”导致危险操作的关键。
- 多智能体协调:如果系统内有多个智能体(例如一个负责导航,一个负责操作),这一层需要管理它们之间的通信、资源竞争和任务同步。
- 任务分解与映射:将智能体层生成的抽象计划,映射到ROS系统中已知的“技能”(Skill)或“动作原语”(Action Primitive)。例如,计划中的“导航到厨房”会被映射到调用
ROS执行层(ROS Execution Layer):这就是我们熟悉的ROS 1或ROS 2生态系统。它由大量的节点、话题、服务、动作(Action)构成,直接控制机器人硬件或仿真环境。
agenticros框架会通过标准的ROS客户端库(如rospy或rclpy)与这一层交互,调用服务、发布话题消息或发送动作目标。
注意:这里的“层”是逻辑概念,在实际部署中,它们可能以不同的ROS节点或进程形式存在。协调层本身很可能就是一个或多个高度专业化的ROS节点。
2.2 通信协议与数据流设计
智能体与ROS世界的数据交换是设计的难点。智能体通常处理自然语言或结构化的JSON数据,而ROS使用其自有的.msg格式进行强类型通信。因此,需要一个高效的“翻译”机制。
- 动作与技能抽象:最核心的抽象是“技能”。框架应定义一个统一的技能接口(例如,一个ROS服务或Action),每个技能对应一个ROS中可执行的具体功能模块(如
NavigateToPose,PickObject,Speak)。协调层维护一个“技能注册表”,智能体通过技能名称和参数来调用它们。 - 状态观察抽象:同样,需要将复杂的ROS传感器数据(激光雷达点云、图像、关节状态)抽象成智能体可理解的符号化状态,如
{“robot_location”: “living_room”, “cup_is_visible”: true, “battery_level”: 85}。这可能需要专门的“感知抽象节点”来处理。 - 事件与回调:ROS是异步事件驱动的。框架需要将ROS的回调(如动作结果、服务响应、特定话题消息)封装成事件,并能够通知到智能体层,触发其重新规划或决策。
一个简化的数据流可能是:用户输入“打开那扇门” -> 智能体层生成计划[‘定位门’, ‘移动到门前’, ‘识别门把手’, ‘操作门把手’]-> 协调层将‘定位门’映射为技能ObjectDetection(door),并调用对应的ROS服务 -> ROS层的物体检测节点处理并返回门的位置 -> 协调层将结果格式化为状态更新,并触发下一个技能‘移动到门前’的执行。
3. 关键技术实现与核心模块剖析
基于上述架构,我们可以深入探讨agenticros项目需要实现或整合的几个关键技术模块。这些模块共同决定了框架的实用性、性能和易用性。
3.1 技能定义与注册系统
这是框架的基石。我们需要一种方式来定义、描述和注册ROS中已有的或新开发的功能为智能体可调用的“技能”。
- 技能描述文件:可以采用YAML或JSON格式。一个技能描述至少应包括:
name: “navigate_to_pose” description: “通过2D导航栈将机器人移动到指定位姿” interface_type: “action” # 也可以是 ‘service’ 或 ‘topic’ interface_name: “/move_base” # ROS Action或Service的名称 input_schema: # 输入参数的模式定义,用于智能体参数填充和验证 type: “object” properties: target_pose: type: “object” properties: x: {type: “number”} y: {type: “number”} theta: {type: “number”} output_schema: # 输出结果的模式定义 type: “object” properties: success: {type: “boolean”} message: {type: “string”} - 技能注册与发现:可以设计一个中心化的“技能管理器”节点。其他节点启动时,向该管理器注册自己提供的技能。协调层查询管理器,获取当前可用的技能列表及其描述。这种方式支持动态的技能添加和移除。
3.2 基于LLM的智能体核心集成
如何将LLM的能力嵌入到这个框架中是项目的亮点。这里有几个关键决策点:
- 提示工程(Prompt Engineering):设计一套高效的提示词模板,将机器人状态、可用技能列表、任务历史和环境上下文组织成LLM能理解的文本。提示词需要引导LLM输出结构化的计划,例如JSON格式:
{“plan”: [{"skill": “navigate_to_pose”, “args”: {...}}, ...]}。 - 规划与重规划:LLM根据当前状态和任务生成初始计划。协调层按序执行计划中的技能。当技能执行失败(如导航超时)或环境状态发生意外变化时,需要将新的状态和失败信息反馈给LLM,请求其生成新的调整计划(Re-planning)。
- 工具调用(Function Calling)的适配:许多现代LLM API支持“工具调用”功能。我们可以将每个“技能”定义为一个工具(Tool),将技能描述中的
input_schema直接作为工具的JSON Schema。这样,LLM在规划时,可以更规范、更可靠地“思考”需要调用哪个工具(技能)并生成正确的参数。这是目前最主流和稳定的集成方式。 - 本地化与成本考量:使用云端LLM API(如OpenAI)虽然方便,但存在延迟、成本和网络依赖问题。对于实时性要求高的机器人任务,可能需要部署本地轻量级模型(如Llama 3.1、Qwen等)。
agenticros框架应设计为可插拔的LLM后端,允许用户根据需求切换不同的模型提供商或本地模型。
3.3 状态管理与世界模型维护
智能体做出合理决策的前提是对世界有准确的认知。在动态、实时的机器人环境中,维护一个轻量级、符号化的世界模型至关重要。
- 状态聚合器:需要专门的节点来订阅各种ROS话题(如
/amcl_pose获取位置,/camera/object_detections获取物体列表,/battery_state获取电量),并按照固定的频率或事件,将这些信息融合成一个统一的世界状态表示。 - 状态更新与通知:世界模型不是静态的。当状态聚合器检测到关键状态变化(如“目标物体被移走”、“电量低于20%”),它需要主动通知协调层或智能体层。这可以通过ROS内置的“条件”(Condition)机制或自定义的事件话题来实现。
- 历史与上下文:框架需要维护一定长度的任务执行历史和状态变化历史,以便在向LLM提供上下文时,能让LLM理解任务的进展和过往的决策依据。
4. 实操部署:从零搭建一个简易的Agent-ROS智能体
理论讲了很多,现在我们动手搭建一个最小可行版本,来直观感受agenticros框架的工作流程。我们将实现一个简单的场景:让机器人在仿真环境中,根据语音命令移动到指定位置。
4.1 环境准备与依赖安装
假设我们使用ROS Noetic和Python3。首先创建一个工作空间。
mkdir -p ~/agenticros_ws/src cd ~/agenticros_ws/src catkin_init_workspace接下来,创建我们的核心功能包。我们将其命名为agenticros_core。
cd ~/agenticros_ws/src catkin_create_pkg agenticros_core rospy std_msgs actionlib cd agenticros_core安装必要的Python依赖。我们将使用openai库(或其他兼容OpenAI API的库)作为LLM接口,同时需要pyyaml来解析技能描述。
pip install openai pyyaml # 如果你使用其他LLM,如通过ollama运行本地模型,则安装对应客户端库 # pip install ollama4.2 定义第一个技能:导航
我们在agenticros_core包中创建skills文件夹,并创建第一个技能描述文件navigate_to_pose.yaml。
# skills/navigate_to_pose.yaml name: “navigate_to_pose” description: “Command the robot to navigate to a specified 2D pose (x, y, theta).” interface_type: “action” interface_name: “/move_base” input_schema: type: “object” required: [“x”, “y”, “theta”] properties: x: type: “number” description: “Target X coordinate in meters.” y: type: “number” description: “Target Y coordinate in meters.” theta: type: “number” description: “Target yaw angle in radians.” output_schema: type: “object” properties: success: type: “boolean” message: type: “string”这个YAML文件定义了一个名为navigate_to_pose的技能,它对应ROS中标准的/move_base导航动作。输入是x, y, theta三个数字,输出包含成功标志和信息。
4.3 实现技能执行器与协调节点
现在,我们创建框架的核心——协调节点orchestrator_node.py。这个节点负责加载技能、与LLM交互、并执行技能。
#!/usr/bin/env python3 # orchestrator_node.py import rospy import yaml import json import os from actionlib import SimpleActionClient from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal from geometry_msgs.msg import PoseStamped, Quaternion from tf.transformations import quaternion_from_euler import openai # 或其他LLM客户端 class Skill: def __init__(self, desc): self.name = desc[‘name’] self.desc = desc self.client = None if desc[‘interface_type’] == ‘action’ and desc[‘interface_name’] == ‘/move_base’: self.client = SimpleActionClient(‘/move_base’, MoveBaseAction) rospy.loginfo(f“Waiting for action server {desc[‘interface_name’]}...”) self.client.wait_for_server() def execute(self, args): “”“执行技能”“” if self.name == “navigate_to_pose”: return self._execute_navigation(args) else: return {“success”: False, “message”: f“Skill {self.name} not implemented.”} def _execute_navigation(self, args): goal = MoveBaseGoal() goal.target_pose.header.frame_id = “map” goal.target_pose.header.stamp = rospy.Time.now() goal.target_pose.pose.position.x = args[‘x’] goal.target_pose.pose.position.y = args[‘y’] # 将偏航角theta转换为四元数 q = quaternion_from_euler(0, 0, args[‘theta’]) goal.target_pose.pose.orientation = Quaternion(*q) self.client.send_goal(goal) wait = self.client.wait_for_result(rospy.Duration(30)) if not wait: return {“success”: False, “message”: “Navigation action timed out.”} state = self.client.get_state() if state == 3: # SUCCEEDED return {“success”: True, “message”: “Navigation succeeded.”} else: return {“success”: False, “message”: f“Navigation failed with state {state}.”} class OrchestratorNode: def __init__(self): rospy.init_node(‘agenticros_orchestrator’) self.skills = {} self._load_skills() # 初始化LLM客户端,这里以OpenAI为例,实际使用时请设置环境变量OPENAI_API_KEY self.llm_client = openai.OpenAI() rospy.loginfo(“Agent-ROS Orchestrator Started.”) def _load_skills(self): skills_dir = os.path.join(os.path.dirname(__file__), ‘skills’) for filename in os.listdir(skills_dir): if filename.endswith(‘.yaml’) or filename.endswith(‘.yml’): with open(os.path.join(skills_dir, filename), ‘r’) as f: desc = yaml.safe_load(f) skill = Skill(desc) self.skills[skill.name] = skill rospy.loginfo(f“Loaded skill: {skill.name}”) def _call_llm_for_plan(self, user_command, available_skills): “”“调用LLM,根据用户指令和可用技能生成计划。”“” # 构建提示词 skills_desc = “\n”.join([f”- {name}: {self.skills[name].desc[‘description’]}” for name in available_skills]) prompt = f“”” 你是一个机器人任务规划器。机器人有以下可用的技能: {skills_desc} 当前用户指令是:{user_command} 请将指令分解为一个技能执行序列(计划)。每个技能必须来自上面的列表。 请严格按照以下JSON格式输出,不要有任何其他文字: {{ “plan”: [ {{“skill”: “skill_name_1”, “args”: {{“arg1”: value1, “arg2”: value2}}}}, {{“skill”: “skill_name_2”, “args”: {{...}}}} ] }} “”” try: response = self.llm_client.chat.completions.create( model=“gpt-3.5-turbo”, # 或 gpt-4 messages=[{“role”: “user”, “content”: prompt}], temperature=0.1 # 低温度以获得更确定的输出 ) content = response.choices[0].message.content.strip() # 尝试解析JSON plan_data = json.loads(content) return plan_data[‘plan’] except Exception as e: rospy.logerr(f“Failed to get plan from LLM: {e}”) return [] def execute_command(self, user_command): “”“主执行流程”“” rospy.loginfo(f“Received command: {user_command}”) # 1. 获取计划 plan = self._call_llm_for_plan(user_command, list(self.skills.keys())) if not plan: rospy.logerr(“No plan generated.”) return rospy.loginfo(f“Generated plan: {plan}”) # 2. 按序执行计划中的每个技能 for step in plan: skill_name = step[‘skill’] args = step.get(‘args’, {}) if skill_name in self.skills: rospy.loginfo(f“Executing skill: {skill_name} with args {args}”) result = self.skills[skill_name].execute(args) rospy.loginfo(f“Result: {result}”) if not result.get(‘success’, False): rospy.logwarn(f“Skill {skill_name} failed. Stopping plan.”) break else: rospy.logerr(f“Unknown skill: {skill_name}”) break def run(self): “”“简单循环,等待用户输入(模拟)”“” # 在实际应用中,这里可能连接语音识别或Web接口 while not rospy.is_shutdown(): try: # 模拟从某个话题(如/voice_command)接收指令 # 这里简化为从标准输入读取 command = input(“Enter command for robot (or ‘quit’): “) if command.lower() == ‘quit’: break self.execute_command(command) except EOFError: break except Exception as e: rospy.logerr(f“Error in main loop: {e}”) if __name__ == ‘__main__’: node = OrchestratorNode() node.run()这个节点做了以下几件事:
- 启动时加载
skills目录下的所有YAML技能描述。 - 初始化LLM客户端(这里用了OpenAI)。
- 提供了一个
execute_command方法,接收用户指令,调用LLM生成包含技能和参数的计划。 - 按顺序执行计划中的技能,并处理执行结果。
4.4 运行与测试
首先,确保你的ROS环境中有可用的导航栈(例如,在Gazebo仿真中运行TurtleBot3)。然后运行我们的协调节点。
cd ~/agenticros_ws catkin_make source devel/setup.bash # 在一个终端运行导航仿真环境 # roslaunch turtlebot3_gazebo turtlebot3_world.launch # roslaunch turtlebot3_navigation turtlebot3_navigation.launch # 在另一个终端运行我们的节点 rosrun agenticros_core orchestrator_node.py节点启动后,在终端输入指令,例如:“请移动到坐标 (2.0, 1.0, 0.0)”。LLM会解析这个指令,并生成类似{“plan”: [{“skill”: “navigate_to_pose”, “args”: {“x”: 2.0, “y”: 1.0, “theta”: 0.0}}]}的计划。协调节点随后会调用/move_base动作,驱动机器人移动到指定位置。
实操心得:在首次测试时,LLM生成的参数格式可能与技能期望的格式不完全匹配。例如,LLM可能生成
“theta”: “0”(字符串)而不是0.0(浮点数)。因此,在技能执行器的execute方法中,加入参数类型转换和验证是必不可少的。一个更健壮的做法是,利用技能描述中的input_schema,使用像jsonschema这样的库在调用前进行严格的参数验证。
5. 进阶挑战与深度优化方向
上面实现了一个极其简化的原型。一个真正可用的agenticros框架需要解决一系列复杂问题。
5.1 处理不确定性与部分可观察环境
真实世界对机器人来说是不确定且部分可观察的。LLM生成的计划可能基于错误或过时的状态信息。
- 重规划与恢复机制:当技能执行失败(如导航超时、抓取失败),不能简单地停止。框架需要将失败信息(错误码、当前传感器快照)作为新的上下文,连同原始任务,再次请求LLM进行重规划。这形成了一个“感知-规划-执行-监控”的闭环。
- 子目标验证:在执行一个技能后,应有机制验证子目标是否达成。例如,“移动到门前”完成后,可以通过视觉检测确认门是否在视野中。这需要将感知技能也纳入技能库,并在计划中插入验证步骤,或由协调层自动插入。
5.2 多智能体协作与资源管理
在更复杂的场景中,可能需要多个智能体分工协作。例如,一个“导航专家”智能体负责路径规划,一个“操作专家”智能体负责机械臂控制。
- 通信协议:需要定义智能体间的通信协议。可以采用黑板模型(Blackboard)、消息传递或基于ROS话题/服务的定制协议。每个智能体将自己视为一个“服务提供者”,对外发布自己能处理的子任务类型。
- 冲突解决:多个智能体可能竞争同一资源(如机械臂、底盘)。框架需要引入资源锁或优先级调度机制。这通常由协调层中的一个“仲裁器”模块来处理。
5.3 长期记忆与经验学习
为了让机器人变得更“聪明”,它需要从过往的经验中学习。
- 技能库扩展:框架可以记录成功执行的任务序列,并将其抽象为新的“宏技能”(Macro-Skill)存入技能库。未来遇到类似任务时,可以直接调用这个复合技能,而无需LLM从头规划。
- 参数优化:记录技能执行时的参数和结果(如不同物体抓取时的夹持力),通过数据分析或在线学习,优化下次执行的默认参数。
- 失败案例库:记录导致失败的指令、上下文和计划,用于后续分析,或作为提示词的一部分警告LLM避免类似错误。
5.4 安全性与可靠性保障
这是将AI智能体部署到物理机器人上最严峻的挑战。
- 动作前安全检查:在执行任何底层ROS命令前,必须经过一层“安全过滤器”。例如,发送移动指令前,检查目标点是否在预设的可通行区域内;发送机械臂关节角度前,进行碰撞检测仿真(如果可用)。
- 人类监督与干预:设计“人在回路”(Human-in-the-loop)机制。对于高风险操作,或当智能体置信度较低时,框架应暂停并请求人类确认。可以设计一个监控界面,实时显示智能体的计划、状态和意图。
- 可解释性与调试:框架必须提供丰富的日志和可视化工具,让开发者能够追溯智能体的决策过程:它为什么生成这个计划?它感知到了什么状态?这对于调试和建立信任至关重要。
6. 常见问题与实战排坑指南
在实际开发和集成agenticros这类框架时,你会遇到许多预料之外的问题。以下是我总结的一些典型问题及其解决思路。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| LLM生成的计划格式错误,无法解析JSON。 | 1. 提示词设计不佳,LLM输出了额外解释文本。 2. LLM对技能描述理解有偏差,生成了不存在的技能名或参数。 | 1.强化提示词约束:在提示词中明确要求“只输出JSON,不要有任何其他文字”。使用LLM的“JSON模式”功能(如果API支持)。 2.后处理与重试:在代码中添加健壮的JSON解析,尝试从回复中提取JSON块。如果失败,将错误和原始回复反馈给LLM,要求其修正。 |
| 技能执行成功,但任务整体失败。 | 1. 子目标验证缺失,机器人自以为完成了,实际未达到预期状态。 2. 技能执行后的世界状态未及时更新,后续步骤基于旧状态规划。 | 1.引入验证技能:为关键步骤设计验证技能(如verify_object_at_location),并在计划中显式加入,或由协调层在特定技能后自动调用。2.状态订阅与同步:确保状态聚合器节点及时更新世界模型,并在状态变化时发布事件。协调层在执行下一步前,应等待或检查相关的状态更新。 |
| 机器人执行动作缓慢,响应延迟高。 | 1. LLM API调用网络延迟高。 2. 每次规划都调用LLM,没有利用缓存或历史。 3. 技能执行本身耗时。 | 1.本地模型部署:对于实时性要求高的场景,考虑部署本地轻量化LLM(如通过Ollama)。 2.计划缓存:对常见的、成功的任务指令-计划对进行缓存。下次收到相同或相似指令时,直接使用缓存计划。 3.异步执行与流式响应:将LLM调用、规划与技能执行放在不同的线程/进程中,避免阻塞。对于长任务,可以让LLM先生成一个粗略计划,然后逐步细化执行。 |
| 智能体在复杂环境中陷入“死循环”或做出荒谬决策。 | 1. 提示词上下文窗口有限,丢失了早期的重要决策信息。 2. 世界模型不准确或过于简化。 3. LLM本身在复杂逻辑推理上存在局限性。 | 1.精炼历史上下文:不要将完整的对话历史都喂给LLM。设计一个摘要函数,将长历史压缩成关键决策点和当前状态的摘要。 2.增强感知与状态表示:投入更多精力在状态聚合器上,提供更丰富、更准确的环境符号化描述。 3.分层规划与人工规则兜底:对于已知的、确定性的子任务,使用传统的规划器或脚本,而非全部依赖LLM。为智能体的决策设置规则边界,超出边界则触发人工干预。 |
| 多技能调用时,资源(如话题、服务)冲突。 | 多个智能体或同一智能体的连续计划试图同时访问独占资源。 | 1.资源管理器:实现一个资源管理节点,对关键资源(如“底盘控制权”、“机械臂控制权”)进行加锁管理。技能执行前需要先申请锁。 2.串行化执行队列:在协调层维护一个全局任务队列,确保同一时间只有一个任务在访问冲突资源。 |
我个人在实际操作中的体会是,构建agenticros这样的框架,最难的不是让机器人动起来,而是建立一个稳定、可靠、可预测的“认知-执行”循环。初期,你会花费大量时间在“对齐”上——对齐LLM的输出与ROS的输入,对齐符号化的世界模型与真实的传感器数据。一个非常实用的技巧是:从最简单的、闭环的单个技能开始。先让“打开灯”这个指令能稳定可靠地通过LLM规划并调用ROS服务实现,然后再逐步增加技能复杂度、环境复杂度和任务长度。每增加一个变量,就充分测试其边界情况。另一个深刻教训是日志的重要性,必须记录下每一轮交互中用户指令、LLM原始回复、生成的计划、每一步技能执行的结果和最终的世界状态。这些日志是后期调试、优化提示词、分析失败案例的黄金数据。最后,永远不要完全信任AI的决策,在物理机器人上运行前,务必在仿真环境中进行 exhaustive 的测试,并设计好紧急停止和手动接管的后路。这个领域正在飞速发展,今天的实验性框架,很可能就是明天智能机器人的标准配置。
