자연어 명령을 작업 순서로 바꾸기
말은 부드럽지만 로봇 계획은 단단해야 한다
사람의 말은 놀라울 만큼 유연하다. 우리는 "저 컵 좀 치워줘"라고 말하면서도 상대가 컵을 찾고, 적당한 곳으로 옮기고, 주변 물건을 건드리지 않기를 기대한다. 하지만 로봇에게 이 문장은 너무 많은 빈칸을 가진 명령이다. 어떤 컵인지, 어디로 치워야 하는지, 컵을 집을 수 있는지, 목적지가 비어 있는지, 옮긴 뒤 성공을 어떻게 확인할지 모두 따로 정해야 한다.
LLM 로봇 에이전트의 첫 번째 실전 과제는 바로 이 빈칸을 다루는 것이다. 사용자의 자연어 명령을 곧바로 로봇 스킬에 넣는 것이 아니라, 먼저 실행 가능한 계획으로 바꿔야 한다. 여기서 실행 가능한 계획이란 로봇이 실제로 호출할 수 있는 스킬 이름과 필요한 인자, 실행 순서, 실패했을 때의 대응까지 포함한 구조를 말한다.
예를 들어 사용자가 이렇게 말한다고 해 보자.
가까운 컵을 조심히 집어서 노트북 오른쪽에 놓아줘.
사람에게는 자연스러운 문장이지만, 로봇에게는 네 가지 질문이 숨어 있다. '가까운 컵'은 무엇을 기준으로 가까운가? '조심히'는 속도를 낮추라는 뜻인가, 힘을 약하게 쓰라는 뜻인가, 아니면 깨지기 쉬운 물체로 취급하라는 뜻인가? '노트북 오른쪽'은 카메라 화면 기준 오른쪽인가, 사용자가 보는 방향의 오른쪽인가, 노트북 자체의 오른쪽인가? 그리고 컵을 놓을 공간은 실제로 비어 있는가?
이 장에서는 이런 문장을 로봇이 실행할 수 있는 단계적 작업 계획으로 바꾸는 방법을 설계한다. 핵심은 간단하다. 자연어는 모호하므로, 로봇은 움직이기 전에 목표와 조건을 명확히 해야 한다.

한 문장을 바로 실행하지 않는다
자연어 명령을 받은 로봇이 가장 먼저 해야 할 일은 행동이 아니라 해석이다. 사람은 명령을 들으면 대략적인 의미를 추측해서 움직일 수 있지만, 로봇은 추측만으로 움직이면 위험하다. 특히 로봇팔은 실제 공간을 차지하며 움직이고, 그리퍼는 물체를 누르거나 밀 수 있다. 잘못된 해석은 단순한 오답이 아니라 충돌이나 파손으로 이어질 수 있다.
그래서 자연어 명령 로봇은 보통 다음 흐름을 따른다.
사용자 문장
↓
의미 추출
↓
환경 정보와 연결
↓
모호성 해결
↓
실행 계획 생성
↓
안전 검사
↓
스킬 실행
↓
결과 확인이 흐름에서 LLM은 주로 의미 추출, 모호성 판단, 계획 생성, 질문 생성에 관여한다. 객체 검출, 좌표 변환, 경로 계획, 그리퍼 제어는 각각의 로봇 모듈이 담당한다. 즉 LLM은 "무엇을 어떤 순서로 시도할지"를 정하고, 실제 동작은 검증된 스킬이 수행한다.
좋은 계획은 처음부터 완벽한 긴 계획일 필요가 없다. 오히려 로봇에서는 너무 긴 계획이 위험할 때가 많다. 컵을 집는 동안 컵 위치가 조금 바뀔 수 있고, 사람이 중간에 손을 넣을 수도 있으며, 목적지에 새로운 물체가 놓일 수도 있다. 따라서 계획은 한 번 만든 뒤 끝나는 문서가 아니라, 실행 결과를 보며 갱신되는 살아 있는 목록이어야 한다.
명령문에서 무엇을 뽑아야 하는가
사용자의 문장에서 가장 먼저 추출해야 하는 것은 로봇이 실행에 필요한 의미 요소다. 이것을 슬롯이라고 부를 수 있다. 슬롯은 문장을 일정한 칸에 맞춰 정리한 것이다. 사용자가 문장을 자유롭게 말하더라도, 로봇 내부에서는 일정한 구조로 바꿔야 한다.
예를 들어 다음 명령을 보자.
빨간 컵을 집어서 책상 오른쪽 빈 공간에 놓아줘. 노트북은 건드리지 말고.
이 문장에서 로봇이 추출해야 할 요소는 다음과 같다.
대상 객체: 빨간 컵.
동작: 집기, 이동하기, 놓기.
목적지: 책상 오른쪽 빈 공간.
금지 조건: 노트북을 건드리지 않기.
우선순위: 노트북 회피와 안전이 컵 이동보다 우선.
이 정보를 구조화하면 다음과 같은 형태가 된다.
{
"task_type": "pick_and_place",
"target_object": {
"category": "cup",
"attributes": {
"color": "red"
}
},
"actions": ["pick", "move", "place"],
"destination": {
"reference_object": "desk",
"relation": "right_empty_area"
},
"constraints": [
{
"type": "avoid_object",
"object": "laptop"
}
],
"priority": ["safety", "avoid_laptop", "complete_task"]
}이런 구조를 만들면 이후 단계가 훨씬 분명해진다. 객체 검출 모듈은 빨간 컵과 노트북, 책상을 찾으면 된다. 공간 검사 모듈은 책상 오른쪽의 빈 영역을 찾으면 된다. 계획기는 노트북 근처 경로를 피하면서 컵을 옮기는 순서를 만들 수 있다.
여기서 중요한 점은 명령문에 없는 내용을 마음대로 채우지 않는 것이다. 사용자가 목적지를 말하지 않았다면 "기본 정리 위치" 같은 규칙이 있는지 확인해야 한다. 그런 규칙이 없다면 질문해야 한다. 사용자가 "컵을 치워줘"라고만 말했다면, 로봇은 컵을 싱크대로 옮겨도 되는지, 책상 한쪽으로 옮기면 되는지 알 수 없다.
목표, 대상, 목적지, 동작, 우선순위
명령 해석에서 가장 기본이 되는 다섯 가지 축은 목표, 대상, 목적지, 동작, 우선순위다. 이 다섯 가지가 분명하면 대부분의 작업을 계획으로 바꿀 수 있다.
목표는 사용자가 최종적으로 원하는 상태다. "컵을 오른쪽에 놓아줘"의 목표는 컵이 오른쪽 위치에 놓여 있는 상태다. "책상 위를 정리해줘"의 목표는 여러 물체가 정해진 위치로 이동한 상태다. 로봇 계획은 동작 목록처럼 보이지만, 실제로는 목표 상태를 향해 움직인다.
대상은 로봇이 찾고 조작해야 할 물체다. 대상은 보통 이름과 속성을 함께 가진다. "컵"은 범주이고, "빨간 컵"은 색상 속성이 붙은 대상이다. "가장 왼쪽 컵"은 위치 조건이 붙은 대상이고, "내 컵"은 사용자별 사전 정보가 필요할 수도 있다.
목적지는 물체가 이동해야 할 위치다. 목적지는 절대 위치일 수도 있고, 상대 위치일 수도 있다. "싱크대 옆"은 장소 기준 상대 위치이고, "노트북 오른쪽"은 물체 기준 상대 위치다. "여기"나 "저기"처럼 지시어가 들어가면 음성 방향, 사용자의 손짓, 최근 대화 맥락 같은 추가 정보가 필요할 수 있다.
동작은 로봇이 수행해야 할 행동의 종류다. 집기, 놓기, 밀기, 열기, 닫기, 이동하기, 확인하기가 여기에 들어간다. 같은 목표라도 동작은 달라질 수 있다. "컵을 오른쪽으로 옮겨줘"는 pick and place가 적합할 수 있지만, 아주 가까운 거리라면 밀기가 가능할 수도 있다. 다만 어떤 동작이 허용되는지는 로봇 스킬 목록에 의해 제한된다.
우선순위는 조건이 충돌할 때 무엇을 먼저 지킬지 정한다. 로봇에서는 보통 안전이 가장 위에 있다. 그다음 사용자가 명시한 금지 조건, 작업 성공, 효율성, 미관이나 선호 같은 조건이 뒤따른다. 예를 들어 "빨리 가져와, 하지만 깨지지 않게 조심해"라는 명령에서는 빠르게 움직이는 것보다 깨지지 않게 다루는 것이 우선이다.
이를 실제 시스템에서는 다음처럼 정리할 수 있다.
{
"goal": "red_cup_on_right_side_of_laptop",
"object": "red_cup",
"destination": "right_side_of_laptop",
"required_actions": ["detect", "approach", "pick", "move", "place", "verify"],
"priority": ["safety", "fragile_handling", "task_success", "speed"]
}이 구조는 LLM이 만들 수 있지만, 그대로 실행해서는 안 된다. 다음 단계에서 반드시 환경 정보와 연결해야 한다.
단어를 좌표와 상태에 연결하기
자연어 해석만으로는 로봇이 움직일 수 없다. "빨간 컵"이라는 말은 카메라 속 특정 물체와 연결되어야 하고, "노트북 오른쪽"이라는 말은 실제 3D 공간의 후보 위치로 바뀌어야 한다. 이 과정을 그라운딩이라고 한다. 쉽게 말해, 말의 의미를 현실의 물체와 위치에 붙이는 과정이다.
예를 들어 LLM이 "빨간 컵"을 추출했다면, 다음 단계에서는 객체 검출 스킬이 실행된다.
call: detect_object(category="cup", color="red")결과가 하나만 나오고 신뢰도가 충분히 높다면 대상이 정해진다.
{
"success": true,
"candidates": [
{
"object_id": "obj_17",
"category": "cup",
"color": "red",
"confidence": 0.93,
"position": [0.42, -0.18, 0.76]
}
]
}하지만 빨간 컵이 두 개라면 이야기가 달라진다. 이때 LLM은 둘 중 하나를 마음대로 고르면 안 된다. 명령에 "가까운"이나 "왼쪽" 같은 추가 조건이 있는지 확인하고, 그래도 구분되지 않으면 사용자에게 질문해야 한다.
빨간 컵이 두 개 보입니다. 로봇에 더 가까운 컵과 책상 왼쪽의 컵 중 어느 것을 옮길까요?목적지도 마찬가지다. "노트북 오른쪽"은 노트북을 먼저 찾아야 계산할 수 있다. 노트북의 위치와 방향을 알면 그 오른쪽에 일정 거리 떨어진 후보 영역을 만들 수 있다. 그 영역이 비어 있는지 확인한 뒤, 컵을 놓을 수 있는 목표 자세를 만든다.
call: detect_object(category="laptop")
call: estimate_relative_region(reference="laptop", relation="right")
call: check_empty_space(region="laptop_right_region", object_size="cup")이렇게 단어는 곧바로 좌표가 되지 않는다. 단어는 후보를 만들고, 센서는 후보를 확인하며, 계획기는 그 후보 중 실행 가능한 것을 선택한다.
모호한 표현을 다루는 규칙
자연어 명령에서 가장 어려운 부분은 모호한 표현이다. 사람은 상황을 보고 쉽게 해석하지만, 로봇은 기준을 정하지 않으면 같은 말을 여러 방식으로 해석할 수 있다. 좋은 로봇 에이전트는 모호한 표현마다 기본 해석 규칙을 갖고 있어야 한다. 그리고 그 규칙으로도 안전하게 결정할 수 없으면 질문해야 한다.
"가까운 컵"이라는 표현부터 생각해 보자. 가까움의 기준은 여러 가지일 수 있다. 로봇팔 베이스에 가까운 컵일 수도 있고, 사용자에게 가까운 컵일 수도 있고, 카메라 화면에서 가까워 보이는 컵일 수도 있다. 조작 작업에서는 보통 로봇이 실제로 집기 쉬운 컵, 즉 로봇 기준 3D 거리와 접근 가능성을 함께 고려한 컵을 선택하는 편이 자연스럽다. 하지만 사용자가 가리키는 손짓이나 "내 쪽에 있는 컵" 같은 표현이 함께 있다면 사용자 기준으로 바꿔야 한다.
"오른쪽"도 조심해야 한다. 책상 오른쪽, 노트북 오른쪽, 로봇 오른쪽, 사용자 오른쪽은 모두 다를 수 있다. 참조 대상이 있는 표현, 예를 들어 "노트북 오른쪽"은 노트북을 기준으로 해석한다. 참조 대상이 없는 "오른쪽에 놓아줘"는 기본 좌표계를 정해 두어야 한다. 데스크톱 로봇팔에서는 카메라 화면 기준 오른쪽을 기본값으로 쓸 수도 있고, 로봇 베이스 좌표계의 오른쪽을 기본값으로 쓸 수도 있다. 중요한 것은 시스템마다 이 기준을 문서화하고, 애매하면 질문하는 것이다.
"조심히"는 더 흥미롭다. 이 말은 좌표가 아니라 실행 방식에 영향을 준다. 로봇은 "조심히"를 다음과 같은 제어 파라미터로 번역할 수 있다.
접근 속도를 낮춘다.
그리퍼 힘 또는 닫힘 속도를 제한한다.
충돌 여유 거리를 크게 잡는다.
집은 뒤 들어 올리는 높이와 안정성 확인 단계를 추가한다.
놓은 뒤 물체가 넘어지지 않았는지 검증한다.
사용자가 "조심히"라고 말했을 때 LLM이 해야 할 일은 감성적인 문장으로만 응답하는 것이 아니다. 이 말을 실제 스킬 인자로 바꿔야 한다.
{
"handling_mode": "careful",
"max_speed_scale": 0.4,
"gripper_force_limit": "low",
"collision_margin": "large",
"extra_verification": true
}물론 이런 값은 LLM이 임의로 정하기보다, 시스템에 미리 정의된 모드 중 하나를 선택하는 것이 안전하다. 예를 들어 normal, careful, fragile, fast 같은 모드를 만들고, 각 모드의 실제 속도와 힘 제한은 로봇 제어 계층에서 관리한다.
모호성은 위험도에 따라 처리한다
모든 모호성에 매번 질문하면 로봇은 답답한 비서가 된다. 반대로 모든 모호성을 추측하면 위험한 로봇이 된다. 따라서 모호성은 위험도에 따라 다뤄야 한다.
예를 들어 "컵을 조금 오른쪽으로 옮겨줘"라는 명령에서 컵이 하나뿐이고 오른쪽에 충분한 빈 공간이 있다면, 시스템 기본 규칙에 따라 10cm 오른쪽으로 옮길 수 있다. 하지만 컵이 두 개 있거나 오른쪽에 노트북이 있다면 질문해야 한다. "조금"이라는 표현은 기본 이동 거리 규칙으로 해석할 수 있지만, 그 결과가 충돌 위험을 만들면 실행해서는 안 된다.
모호성 처리 기준은 다음처럼 정할 수 있다.
1. 기본 규칙으로 해석해도 안전하고 결과가 명확하면 실행한다.
2. 후보가 여러 개지만 위험이 낮으면 사용자에게 선택지를 제시한다.
3. 안전, 파손, 사람과의 거리, 금지 조건에 영향을 주면 반드시 확인한다.
4. 시스템이 수행할 수 없는 작업이면 가능한 대안을 제시한다.예를 들어 로봇이 "저 컵 치워줘"라는 명령을 받았고 컵이 하나뿐이며 기본 정리 위치가 싱크대 옆으로 설정되어 있다면 계획을 진행할 수 있다. 그러나 컵이 세 개라면 질문해야 한다.
컵이 세 개 보입니다. 빨간 컵, 흰 컵, 투명 컵 중 어떤 컵을 치울까요?목적지가 없을 때 기본 위치가 없다면 질문해야 한다.
컵을 어디로 옮기면 될까요? 싱크대 옆, 책상 오른쪽 빈 공간, 또는 사용자가 지정한 위치 중에서 선택할 수 있습니다.좋은 질문은 막연하지 않다. "어떻게 할까요?"라고만 묻는 대신, 로봇이 파악한 상황과 가능한 선택지를 함께 말해야 한다. 그래야 사용자가 짧고 정확하게 답할 수 있다.
작업을 스킬 단계로 분해하기
이제 구조화된 명령을 실제 실행 순서로 바꿔 보자. 로봇팔의 물체 이동 작업은 대체로 다음 단계로 나눌 수 있다.
detect → localize → approach → pick → lift → move → place → retreat → verify여기서 detect는 물체를 찾는 단계이고, localize는 물체의 3D 위치와 자세를 추정하는 단계다. approach는 그리퍼가 물체에 접근할 수 있는 준비 자세로 이동하는 단계다. pick은 실제로 잡는 단계이고, lift는 잡은 뒤 안정적으로 들어 올리는 단계다. move는 목적지 근처로 이동하는 단계이며, place는 내려놓기다. retreat는 그리퍼를 안전한 위치로 빼는 단계이고, verify는 작업 결과를 확인하는 단계다.
사용자 명령이 짧아도 내부 계획은 이렇게 여러 단계로 나뉜다.
빨간 컵을 노트북 오른쪽에 놓아줘.
이 문장은 다음 계획으로 바뀔 수 있다.
{
"plan_id": "plan_042",
"goal": "place red cup on right side of laptop",
"steps": [
{
"id": 1,
"skill": "detect_object",
"args": { "category": "cup", "color": "red" },
"on_fail": "ask_user_or_stop"
},
{
"id": 2,
"skill": "detect_object",
"args": { "category": "laptop" },
"on_fail": "ask_user_or_stop"
},
{
"id": 3,
"skill": "compute_place_region",
"args": { "reference": "laptop", "relation": "right" },
"on_fail": "ask_user"
},
{
"id": 4,
"skill": "check_workspace",
"args": { "region": "computed_place_region" },
"on_fail": "find_alternative_or_ask_user"
},
{
"id": 5,
"skill": "pick",
"args": { "object_id": "from_step_1", "mode": "normal" },
"on_fail": "retry_with_new_grasp_or_ask_user"
},
{
"id": 6,
"skill": "place",
"args": { "target_region": "from_step_3" },
"on_fail": "replan"
},
{
"id": 7,
"skill": "verify",
"args": { "condition": "red_cup_in_target_region" },
"on_fail": "recheck_scene"
}
]
}이 계획에서 눈여겨볼 부분은 on_fail이다. 로봇 계획은 성공 순서만 적어서는 부족하다. 실제 로봇은 자주 실패하기 때문에 각 단계마다 실패했을 때 어떻게 할지 정해야 한다. 컵을 찾지 못하면 집을 수 없고, 목적지가 막혀 있으면 놓을 수 없다. 실패 처리가 없는 계획은 현실 세계에 나가자마자 멈춰 버린다.
순서는 왜 중요한가
같은 스킬을 쓰더라도 순서가 잘못되면 위험하거나 비효율적인 계획이 된다. 예를 들어 컵을 먼저 집은 뒤 목적지를 확인하면 문제가 생길 수 있다. 컵을 든 상태에서 "놓을 곳이 없네?"라는 상황이 되면 로봇은 불안정한 상태로 멈춰야 한다. 따라서 가능하면 목적지가 비어 있는지 먼저 확인하고, 그다음 물체를 집는 편이 안전하다.
일반적으로 물체를 옮기는 작업에서는 다음 원칙이 유용하다. 먼저 대상과 목적지를 모두 확인한다. 그다음 접근 가능성과 빈 공간을 검사한다. 위험 조건이 없을 때만 집기 동작을 시작한다. 물체를 든 뒤에는 중간에 오래 멈추지 않도록 놓기 계획을 준비해 둔다. 마지막으로 결과를 확인한다.
사용자가 여러 작업을 한 문장에 넣으면 순서 선택이 더 중요해진다.
컵은 싱크대 옆에 두고, 책은 왼쪽에 쌓고, 휴대폰은 건드리지 마.
이 경우 로봇은 컵과 책, 휴대폰을 모두 인식해야 한다. 휴대폰이 책 가까이에 있다면 책을 먼저 집는 것이 위험할 수 있다. 컵을 먼저 옮겨 작업 공간을 넓히고, 그다음 책 주변을 다시 확인하는 순서가 더 안전할 수 있다. 만약 휴대폰이 책 위에 놓여 있다면 사용자의 금지 조건과 책 이동 목표가 충돌한다. 이때는 실행하지 말고 질문해야 한다.
책 위에 휴대폰이 놓여 있어 책을 옮기려면 휴대폰을 건드릴 가능성이 있습니다. 휴대폰을 그대로 둔 상태에서는 책을 이동할 수 없습니다. 어떻게 할까요?이런 판단은 로봇 계획에서 매우 중요하다. 계획은 단순한 할 일 목록이 아니라, 조건과 위험을 고려한 실행 전략이다.
우선순위는 계획의 브레이크다
우선순위는 로봇이 헷갈릴 때 멈추게 하는 브레이크다. 사용자가 여러 조건을 동시에 말하면 조건끼리 충돌할 수 있다.
최대한 빨리 유리컵을 가져와. 절대 흔들리지 않게.
여기에는 빠르게 움직이라는 요구와 흔들림을 줄이라는 요구가 함께 들어 있다. 두 조건을 모두 완벽히 만족하기 어렵다면 무엇을 우선해야 할까? 로봇에서는 안전과 파손 방지가 속도보다 앞선다. 따라서 계획은 fast가 아니라 careful 또는 fragile 모드를 선택해야 한다. 응답도 분명해야 한다.
유리컵이 깨지기 쉬운 물체이므로 속도보다 안정성을 우선해 천천히 이동하겠습니다.우선순위는 보통 다음 순서로 둔다.
사람 안전.
로봇과 환경의 물리적 안전.
사용자가 명시한 금지 조건.
작업 목표 달성.
속도, 미관, 편의 같은 선호 조건.
이 순서를 정해 두면 LLM이 계획을 만들 때 기준이 생긴다. 예를 들어 "가장 가까운 컵을 빨리 치워줘"라고 했는데 가까운 컵이 사람 손 옆에 있다면, 로봇은 무작정 빠르게 접근하지 않아야 한다. 사람 감지 영역이 있으면 속도를 낮추거나 사용자에게 손을 빼 달라고 요청해야 한다.
컵 근처에 손이 감지되어 바로 접근하지 않겠습니다. 손을 치우면 컵을 옮기겠습니다.이처럼 우선순위는 로봇의 행동을 보수적으로 만든다. 보수적인 로봇은 때로 느려 보이지만, 실제 환경에서는 훨씬 믿을 수 있다.
필요한 정보가 부족할 때 질문하기
좋은 로봇 에이전트는 질문을 잘한다. 질문은 실패가 아니라 안전한 협업의 일부다. 특히 자연어 명령에서 정보가 빠졌거나, 후보가 여러 개이거나, 조건이 충돌할 때는 사용자에게 확인해야 한다.
질문이 필요한 대표 상황은 다음과 같다.
대상이 여러 개라서 하나로 정할 수 없을 때.
목적지가 명령에 없고 기본 규칙도 없을 때.
"여기", "저기", "그거" 같은 지시어를 해석할 근거가 없을 때.
명령이 로봇의 능력 밖일 때.
금지 조건과 작업 목표가 충돌할 때.
안전 위험이 감지되어 실행 여부를 확인해야 할 때.
예를 들어 사용자가 "그 컵 좀 치워줘"라고 했는데 최근 대화나 손짓 정보가 없다면 로봇은 "그 컵"을 알 수 없다. 이때는 이렇게 묻는 편이 좋다.
어떤 컵을 말씀하시는지 확인이 필요합니다. 빨간 컵, 흰 컵, 투명 컵이 보입니다. 어느 컵을 옮길까요?사용자가 "책상 위를 정리해줘"라고 했는데 정리 규칙이 없다면 질문해야 한다.
정리 기준을 알려주세요. 컵은 싱크대 옆, 책은 왼쪽, 쓰레기는 휴지통 근처로 옮기는 방식으로 진행할까요?질문은 가능한 한 짧고 선택 가능해야 한다. 사용자가 다시 긴 설명을 해야 한다면 대화가 복잡해진다. 로봇은 자신이 본 것과 가능한 선택지를 제시해야 한다. 그러면 사용자는 "빨간 컵", "싱크대 옆", "응, 그렇게 해"처럼 짧게 답할 수 있다.
조건이 충돌할 때 멈추는 법
정보 부족보다 더 중요한 것은 조건 충돌이다. 조건 충돌은 사용자의 명령을 그대로 실행하면 어떤 조건을 반드시 위반하게 되는 상황이다.
예를 들어 이런 명령을 생각해 보자.
노트북은 절대 건드리지 말고, 노트북 아래에 있는 종이를 꺼내줘.
종이가 노트북 아래에 깊이 들어가 있다면, 종이를 꺼내려면 노트북을 움직이거나 누를 가능성이 있다. 이때 로봇은 "종이 꺼내기"와 "노트북 건드리지 않기"를 동시에 만족하지 못할 수 있다. 이런 경우 LLM은 그럴듯한 계획을 억지로 만들지 말고 충돌을 설명해야 한다.
종이가 노트북 아래에 있어 꺼내는 과정에서 노트북을 건드릴 가능성이 높습니다. 노트북을 잠시 이동해도 될까요, 아니면 작업을 중단할까요?또 다른 예를 보자.
유리컵을 빠르게 집어서 높은 선반 위에 올려줘.
로봇팔이 높은 선반에 도달할 수 없거나, 유리컵을 든 상태에서 안정적인 경로가 없다면 실행하면 안 된다. 가능한 대안을 제시해야 한다.
현재 로봇팔로는 높은 선반의 목표 위치에 안전하게 도달할 수 없습니다. 낮은 선반이나 책상 오른쪽 빈 공간에 놓을 수 있습니다.조건 충돌 처리의 핵심은 세 가지다. 먼저 충돌 원인을 명확히 설명한다. 다음으로 가능한 선택지를 제시한다. 마지막으로 사용자가 승인하기 전까지 위험한 행동을 하지 않는다.
계획은 실행 전 검사를 통과해야 한다
LLM이 만든 계획은 초안이다. 실행 계획으로 사용하려면 검사 단계를 거쳐야 한다. 이 검사는 LLM의 언어적 타당성을 보는 것이 아니라, 로봇 시스템이 실제로 실행할 수 있는지 확인하는 과정이다.
검사해야 할 항목은 크게 네 가지다. 첫째, 스킬 이름이 실제로 존재하는가. 둘째, 각 스킬의 인자 타입이 맞는가. 셋째, 물체와 목적지가 현재 장면에서 확인되었는가. 넷째, 안전 규칙을 위반하지 않는가.
예를 들어 LLM이 다음과 같은 계획을 만들었다고 하자.
{
"skill": "pick",
"args": {
"object": "red_cup"
}
}이 계획은 사람에게는 이해되지만, 스킬 서버에는 부족할 수 있다. pick 스킬이 object_id와 grasp_candidate를 요구한다면, 단순히 red_cup이라는 문자열만으로는 실행할 수 없다. 먼저 detect_object와 estimate_grasp가 필요하다.
따라서 계획 검사는 다음처럼 반려할 수 있어야 한다.
{
"valid": false,
"reason": "pick requires object_id and grasp_candidate",
"suggested_missing_steps": ["detect_object", "estimate_grasp"]
}이 결과를 받은 LLM은 계획을 수정한다. 좋은 에이전트는 한 번에 완벽한 계획을 만드는 모델이 아니라, 검사를 통과할 때까지 계획을 다듬는 시스템이다.
작은 계획 언어 만들기
실전 프로젝트에서는 자연어 명령을 바로 코드로 바꾸기보다, 중간 표현을 하나 두는 것이 좋다. 중간 표현은 사람의 말과 로봇 스킬 사이에 놓인 작은 계획 언어다. 복잡한 프로그래밍 언어일 필요는 없다. 어떤 목표를 위해 어떤 스킬을 어떤 순서로 호출할지 표현할 수 있으면 된다.
예를 들어 다음과 같은 형식을 사용할 수 있다.
{
"task": "pick_and_place",
"target": {
"query": {
"category": "cup",
"color": "red"
},
"selection_rule": "nearest_to_robot"
},
"destination": {
"reference": {
"category": "laptop"
},
"relation": "right",
"require_empty_space": true
},
"mode": "careful",
"constraints": [
{
"type": "avoid",
"query": { "category": "phone" }
}
],
"steps": []
}이 표현은 아직 실제 스킬 호출 목록이 아니다. 대신 사용자의 의도를 안정적으로 보관한다. 그다음 계획 생성기가 이 의도를 현재 장면과 스킬 목록에 맞춰 단계로 확장한다.
{
"steps": [
{ "skill": "detect_object", "args": { "category": "cup", "color": "red" } },
{ "skill": "select_object", "args": { "rule": "nearest_to_robot" } },
{ "skill": "detect_object", "args": { "category": "laptop" } },
{ "skill": "compute_relative_region", "args": { "relation": "right" } },
{ "skill": "check_empty_space", "args": { "min_size": "target_object_size" } },
{ "skill": "estimate_grasp", "args": { "mode": "careful" } },
{ "skill": "pick", "args": { "mode": "careful" } },
{ "skill": "place", "args": { "mode": "careful" } },
{ "skill": "verify", "args": { "condition": "target_at_destination" } }
]
}중간 표현을 두면 장점이 많다. LLM의 출력이 조금 달라져도 시스템 내부 형식은 일정하게 유지할 수 있다. 계획 검사를 자동화하기 쉽고, 로그를 남겨 나중에 왜 그런 행동을 했는지 분석할 수 있다. 또한 같은 자연어 해석 결과를 로봇팔, 이동 로봇, 시뮬레이터 등 서로 다른 실행기에 맞게 변환할 수도 있다.
예제: 가까운 컵을 조심히 옮기기
이제 처음의 문장을 전체 흐름으로 처리해 보자.
가까운 컵을 조심히 집어서 노트북 오른쪽에 놓아줘.
먼저 LLM은 의미를 추출한다.
{
"task_type": "pick_and_place",
"target_object": {
"category": "cup",
"selection_rule": "nearest"
},
"destination": {
"reference_object": "laptop",
"relation": "right"
},
"handling_mode": "careful"
}다음으로 모호성을 점검한다. "가까운"은 기본적으로 로봇 기준 가까움으로 해석한다고 시스템 규칙이 정해져 있다면 진행할 수 있다. "노트북 오른쪽"은 노트북이라는 참조 대상이 있으므로 비교적 명확하다. "조심히"는 careful 모드로 변환한다.
그다음 장면 정보를 수집한다.
call: detect_object(category="cup")
call: detect_object(category="laptop")
call: get_robot_state()컵이 세 개 발견되었다고 하자.
{
"cups": [
{ "object_id": "cup_1", "distance_to_robot": 0.42, "reachable": true },
{ "object_id": "cup_2", "distance_to_robot": 0.55, "reachable": true },
{ "object_id": "cup_3", "distance_to_robot": 0.38, "reachable": false }
]
}가장 가까운 컵은 cup_3이지만 도달할 수 없다. 이때 단순 거리만 보면 cup_3을 선택해야 하지만, 실제 작업 가능성을 고려하면 cup_1이 더 적합하다. 따라서 선택 규칙은 "도달 가능한 컵 중 로봇에 가장 가까운 컵"으로 적용한다. 필요하다면 사용자에게 이렇게 말할 수 있다.
가장 가까운 컵은 현재 장애물 때문에 집기 어렵습니다. 도달 가능한 컵 중 가장 가까운 컵을 옮기겠습니다.이후 목적지를 계산한다. 노트북 오른쪽에 빈 공간이 있으면 계획을 진행한다. 빈 공간이 없다면 대안을 찾거나 질문한다.
노트북 오른쪽에 컵을 놓을 빈 공간이 부족합니다. 노트북 앞쪽 빈 공간에 놓아도 될까요?빈 공간이 있다면 최종 계획은 다음처럼 된다.
1. 컵 후보를 인식한다.
2. 도달 가능한 컵 중 가장 가까운 컵을 선택한다.
3. 노트북을 인식한다.
4. 노트북 오른쪽의 빈 영역을 계산한다.
5. 컵을 조심 모드로 집을 수 있는 후보 자세를 만든다.
6. 낮은 속도와 큰 충돌 여유로 접근한다.
7. 컵을 집고 들어 올린다.
8. 노트북 오른쪽 영역으로 이동한다.
9. 컵을 천천히 내려놓는다.
10. 컵이 목표 영역에 안정적으로 놓였는지 확인한다.이 과정에서 LLM은 단어를 해석하고 순서를 만들었지만, 실제 거리 계산과 도달 가능성 판단, 경로 계획은 로봇 모듈이 수행한다.
예제: 책상 위를 정리해줘
조금 더 복잡한 명령도 살펴보자.
책상 위를 정리해줘. 컵은 싱크대 옆으로 옮기고, 책은 왼쪽에 쌓아줘. 휴대폰은 건드리지 마.
이 명령은 단일 물체 이동이 아니라 여러 하위 작업을 포함한다. 먼저 전체 목표를 분해한다.
{
"task_type": "multi_object_cleanup",
"subtasks": [
{
"target": { "category": "cup" },
"destination": { "location": "beside_sink" },
"action": "move"
},
{
"target": { "category": "book" },
"destination": { "reference": "desk", "relation": "left_stack_area" },
"action": "stack"
}
],
"constraints": [
{
"type": "do_not_touch",
"target": { "category": "phone" }
}
]
}그다음 로봇은 컵, 책, 휴대폰을 모두 찾아야 한다. 금지 대상인 휴대폰은 작업 대상이 아니지만 반드시 인식해야 한다. 건드리지 말아야 할 물체의 위치를 알아야 피할 수 있기 때문이다.
이 상황에서 계획 순서는 장면에 따라 달라진다. 컵이 책 앞을 막고 있다면 컵을 먼저 옮긴 뒤 책을 쌓는 것이 좋다. 휴대폰이 책 위에 있다면 책을 쌓는 작업은 바로 실행할 수 없다. 컵 목적지인 싱크대 옆이 비어 있지 않다면 컵 이동도 보류해야 한다.
따라서 처음 계획은 다음처럼 조건부가 된다.
1. 책상 위 물체를 인식한다.
2. 휴대폰 위치를 금지 영역으로 등록한다.
3. 컵과 싱크대 옆 공간을 확인한다.
4. 컵 이동이 안전하면 먼저 수행한다.
5. 컵 이동 후 책과 휴대폰의 상대 위치를 다시 확인한다.
6. 책을 집어도 휴대폰을 건드리지 않으면 책 쌓기를 수행한다.
7. 휴대폰과 책이 겹쳐 있으면 사용자에게 확인 질문을 한다.
8. 모든 작업 뒤 책상 상태를 다시 확인한다.여기서 핵심은 중간 재인식이다. 컵을 옮긴 뒤 장면은 바뀐다. 따라서 처음에 만든 좌표를 끝까지 믿으면 안 된다. 각 하위 작업이 끝날 때마다 필요한 물체와 금지 영역을 다시 확인해야 한다.
사용자 답변을 계획에 반영하기
질문을 했으면 답변을 다시 계획에 반영해야 한다. 대화형 로봇에서 사용자 답변은 새로운 명령이 아니라 기존 계획의 빈칸을 채우는 정보일 때가 많다.
예를 들어 로봇이 이렇게 물었다고 하자.
컵이 두 개 보입니다. 빨간 컵과 흰 컵 중 어느 컵을 옮길까요?사용자가 "빨간 거"라고 답하면, 로봇은 새 계획을 처음부터 만들 필요가 없다. 기존 계획의 target_object를 빨간 컵으로 확정하면 된다.
{
"target_object": {
"category": "cup",
"color": "red",
"object_id": "cup_red_1"
}
}또 다른 예를 보자.
노트북 오른쪽에 빈 공간이 부족합니다. 앞쪽 빈 공간에 놓아도 될까요?사용자가 "응"이라고 답하면 목적지를 대체 위치로 바꾼다.
{
"destination": {
"reference_object": "laptop",
"relation": "front",
"confirmed_by_user": true
}
}이런 대화 상태 관리는 생각보다 중요하다. 로봇이 매번 사용자의 짧은 답변을 독립적인 새 명령으로 해석하면 맥락을 잃는다. "응", "그거", "왼쪽 거", "아니 오른쪽" 같은 답변은 직전 질문과 연결해서 이해해야 한다.
실행 결과를 다시 계획으로 돌려보내기
계획은 실행되면서 계속 수정된다. 스킬은 성공 또는 실패 결과를 반환하고, LLM은 그 결과를 읽어 다음 행동을 결정한다.
예를 들어 pick이 실패했다고 하자.
{
"success": false,
"stage": "grasp",
"reason": "object_slipped"
}이 결과는 단순한 실패가 아니다. 실패한 단계와 이유가 들어 있으므로 다음 계획을 세울 수 있다. 물체가 미끄러졌다면 더 넓은 grasp 후보를 선택하거나, 그리퍼 힘 제한 안에서 조금 더 안정적인 잡기 모드를 시도할 수 있다. 하지만 같은 실패가 반복되면 멈춰야 한다.
컵이 한 번 미끄러졌습니다. 다른 방향에서 한 번 더 시도하겠습니다.두 번째 실패 후에는 질문할 수 있다.
컵을 안정적으로 잡지 못했습니다. 표면이 미끄럽거나 위치가 불안정할 수 있습니다. 계속 시도할까요, 아니면 작업을 중단할까요?이때 재시도 횟수는 시스템 규칙으로 제한해야 한다. LLM이 "될 때까지 계속 시도"하도록 두면 물체를 밀어 떨어뜨리거나 로봇에 무리를 줄 수 있다.
실행 결과가 실패가 아니라 불확실성일 수도 있다.
{
"success": true,
"confidence": 0.58,
"warning": "target_partially_occluded"
}신뢰도가 낮다면 바로 집기보다 카메라 위치를 바꾸거나 다시 인식하는 계획을 넣는 편이 안전하다. 로봇은 모르는 상태에서 자신 있게 움직이는 것보다, 한 번 더 확인하는 것이 낫다.
좋은 계획의 조건
자연어 명령을 작업 순서로 바꾼 결과가 좋은 계획인지 확인하려면 몇 가지 기준을 적용할 수 있다. 좋은 계획은 실행 가능해야 하고, 검증 가능해야 하며, 실패했을 때 멈출 수 있어야 한다.
먼저 실행 가능해야 한다. 계획에 포함된 모든 단계는 실제 스킬 목록에 있어야 한다. 로봇이 open_drawer 스킬을 갖고 있지 않은데 서랍을 열 계획을 만들면 안 된다. 그럴 때는 "현재는 서랍을 열 수 없습니다"라고 말해야 한다.
다음으로 검증 가능해야 한다. "책상을 예쁘게 정리하기"는 좋은 사용자 목표지만, 로봇이 바로 검증하기 어렵다. 내부 계획에서는 "컵은 싱크대 옆", "책은 책상 왼쪽 영역", "휴대폰은 원래 위치 유지"처럼 확인 가능한 조건으로 바꿔야 한다.
마지막으로 멈출 수 있어야 한다. 각 단계는 실패 조건과 중단 조건을 가져야 한다. 사람이 작업 공간에 들어오거나, 경로가 막히거나, 대상 신뢰도가 낮거나, 금지 물체와 너무 가까워지면 로봇은 멈추고 재계획해야 한다.
이를 간단한 점검표로 만들면 다음과 같다.
대상 객체가 하나로 확정되었는가?
목적지가 좌표나 영역으로 변환되었는가?
모든 스킬이 실제 사용 가능한 스킬 목록에 있는가?
안전 제약과 금지 조건이 계획에 반영되었는가?
각 단계의 성공과 실패를 판단할 방법이 있는가?
정보가 부족한 부분은 질문으로 남겼는가?
이 점검을 통과하지 못한 계획은 실행하지 않는다. LLM이 아무리 자연스럽게 설명해도, 로봇 계획은 검사를 통과해야 한다.
구현 흐름으로 정리하기
실제 에이전트에서는 이 장의 내용을 다음과 같은 함수 흐름으로 만들 수 있다.
parse_command(user_text)
→ extract slots: target, action, destination, constraints, priority
ground_to_scene(slots, scene_state)
→ match objects, compute regions, resolve references
check_ambiguity(grounded_task)
→ proceed, ask_user, or reject
make_plan(grounded_task, available_skills)
→ ordered skill steps with arguments and failure handlers
validate_plan(plan, safety_rules, skill_schema)
→ valid or invalid with reason
execute_step_by_step(plan)
→ observe result, replan if needed이 구조의 장점은 각 부분을 따로 개선할 수 있다는 점이다. 명령 해석이 부족하면 프롬프트나 슬롯 스키마를 개선한다. 객체 연결이 약하면 비전 모델이나 캘리브레이션을 개선한다. 계획 검사가 약하면 스킬 스키마와 안전 규칙을 강화한다. 실패 복구가 부족하면 실행 결과의 reason을 더 자세히 만들면 된다.
중요한 것은 자연어 명령을 곧바로 행동으로 보내지 않는 것이다. 로봇은 먼저 문장을 구조화하고, 환경에 연결하고, 애매한 점을 해결하고, 가능한 스킬로 나누고, 안전 검사를 통과한 뒤 움직여야 한다.
자연어 계획의 핵심
자연어 명령 로봇의 매력은 사용자가 복잡한 버튼을 누르지 않아도 된다는 데 있다. 하지만 사용자가 편해질수록 로봇 내부는 더 신중해져야 한다. "가까운 컵을 조심히 오른쪽에 놓아줘"라는 부드러운 말 뒤에는 대상 선택, 기준 좌표, 속도 제한, 충돌 회피, 빈 공간 검사, 성공 확인이 숨어 있다.
이 장에서 설계한 흐름을 기억하자. 먼저 문장에서 대상 객체, 목적지, 동작, 우선순위를 뽑는다. 다음으로 "가까운", "오른쪽", "조심히" 같은 표현을 환경 정보와 시스템 규칙에 연결한다. 그다음 작업을 detect, approach, pick, move, place, verify 같은 단계로 나눈다. 마지막으로 정보가 부족하거나 조건이 충돌하면 사용자에게 확인 질문을 한다.
좋은 로봇 에이전트는 말을 잘 알아듣는 로봇이 아니다. 말을 들은 뒤, 무엇을 알고 무엇을 모르는지 구분하고, 안전하게 실행 가능한 순서로 바꿀 줄 아는 로봇이다. 다음 장에서는 이렇게 만들어진 계획이 실제로 호출할 수 있는 로봇 스킬로 이어지도록, move_to, detect_object, pick, place, stop, check_status 같은 스킬을 어떻게 설계할지 살펴본다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.