Back to blog

Malbut — LLM의 제안을 안전한 로봇 행동으로 바꾸기

Malbut은 집 안을 이동하며 사용자와 상호작용하는 바퀴형 AIoT 반려 로봇이다. 팀은 ROS 2와 Gazebo 기반의 메카넘 로봇, Nav2 자율주행, RGB-D 사람 인식·추적, 홈캠, 대화 에이전트를 하나의 시스템으로 연결하고 있다.

내가 최근 집중한 영역은 사람의 자연어 요청을 로봇이 실행할 수 있는 안전한 작업으로 바꾸는 대화·실행 계층이다. LLM이 만든 답을 바로 모터 명령으로 보내는 대신, 요청 분류부터 사용자 확인, 실행 직전 상태 검사, 결과 기록까지 각각의 책임을 분리했다.

왜 단순한 LLM Tool 호출로는 부족했나

“거실로 가줘”처럼 목적지가 분명한 문장도 실제 로봇에서는 단순한 함수 호출이 아니다. 현재 지도가 맞는지, 위치 추정이 최신인지, 비상 정지 상태가 아닌지, 같은 요청이 이미 실행 중인지 확인해야 한다.

더 어려운 경우도 있다.

물리 장치에서는 불확실한 결과를 성공으로 추정하거나 같은 이동을 자동으로 다시 보내는 것이 더 위험하다. 그래서 이 프로젝트에서는 대화의 자연스러움보다 실행 권한과 상태 전이를 먼저 명확하게 만드는 것을 기준으로 삼았다.

제안과 실행 권한을 분리한 흐름

사용자 텍스트
  → Front Router
  → 필요한 경우 한 번의 추가 질문
  → Robot Planner의 구조화된 제안
  → 사용자의 명시적 승인
  → durable RobotAction 생성
  → 최신 RobotState·Safety·지도 binding 재검증
  → 장소명 기반 Nav2 goal 실행
  → SUCCEEDED / BLOCKED / UNKNOWN 결과 기록

각 단계는 다음 권한만 가진다.

  1. Front Router는 요청을 일반 대화, 추가 질문, 상태 조회, 현재 작업 조회, 로봇 행동 요청의 다섯 경로로 분류한다. 분류 결과 자체에는 Tool이나 실행 권한이 없다.
  2. Robot Planner는 strict schema에 맞는 행동을 제안한다. 좌표와 로봇 상태를 임의로 소유하지 않으며, 제안만으로는 RobotAction이 생기지 않는다.
  3. Confirmation은 사용자의 의도를 기록한다. 승인 기록과 durable action 생성은 하나의 transaction으로 묶어 중복 승인과 경쟁 상태를 막는다.
  4. Approved Action Worker는 작업을 claim한 뒤 로봇 상태, Safety 정책, 지도와 목적지 binding을 다시 확인한다. 조건이 달라졌으면 Nav2 goal을 만들지 않는다.
  5. Executor는 허용된 작업만 장소명 기반 이동 façade를 통해 Nav2에 전달한다. 외부 응답이 불명확하면 결과를 UNKNOWN으로 닫고 자동 재전송하지 않는다.

이 구조에서 LLM은 문장을 이해하고 제안하는 역할만 맡는다. 실제 이동 가능성과 실행 허가는 결정론적인 상태·정책 계층이 판단한다.

최근 구현한 세 가지 묶음

1. 텍스트 요청에서 Gazebo Nav2까지

먼저 장소명 이동, 이중 확인, durable action, simulation executor를 연결했다. 이후 인증된 내부 텍스트 요청부터 승인, Robot Web 이동 façade, Small House의 Nav2 terminal 결과까지 한 명령으로 검증하는 폐루프를 만들었다.

대표 흐름은 다음과 같다.

“거실로 가줘” → navigate(거실) 제안 → “네” → RobotAction
→ Robot Web start 1회 → Nav2 goal 1개 → SUCCEEDED

승인 전에는 goal이 없어야 하고, 같은 승인을 다시 보내도 추가 effect가 생기지 않아야 한다. 이 경계와 실행 후 프로세스·소켓 정리까지 PR #71의 headless Gazebo 인수 절차로 묶었다.

2. 중복·Safety·응답 유실을 실패 우선으로 다루기

정상 경로만 통과하는 데서 끝내지 않고 실제로 문제가 될 조건을 simulation campaign으로 만들었다.

조건기대한 제품 결과Nav2 처리
중복 요청·동시 승인·worker 경쟁한 작업만 유지goal 추가 생성 없음
오래된 상태·E-stop·지도 revision 변경BLOCKEDgoal 0개
Nav2 사용 불가UNKNOWNgoal 0개
start 또는 terminal 응답 유실UNKNOWN자동 재전송 없음

특히 응답 유실 상황에서는 실제 Nav2 goal이 성공했더라도 제품 원장이 그 결과를 관측하지 못했다면 성공으로 승격하지 않았다. “아마 성공했을 것”이라는 추정보다 UNKNOWN을 유지하는 편이 중복 이동을 막는 안전한 선택이기 때문이다.

마지막으로 정상·중복·Safety·응답 유실을 고정된 15개 case로 묶었다. PR #77에 기록된 결과는 15/15 통과, SUCCEEDED=9, BLOCKED=3, UNKNOWN=3이며 승인 전 goal과 replay 추가 effect는 모두 0개다.

3. Front Agent와 역할별 모델 분리

이동 목적지가 빠진 문장은 Provider에 바로 보내지 않고 서버가 한 번만 추가 질문하도록 만들었다. 예를 들어 “여기로 가줘” 다음에 같은 대화의 바로 다음 답변으로 “거실”이 들어와야 기존 제안·Safety·confirmation 흐름을 이어 간다. restart, 중간 입력, 다른 대화의 답변, 동시 응답은 fail-closed한다.

그 위에 Front Router를 연결해 필요한 요청만 전문 처리기로 보냈다. 일반 대화에는 Tool을 제거하고, 로봇 행동 요청만 strict Tool schema를 가진 Planner로 보낸다. 일반 대화 모델과 로봇 Planner 모델은 OPENAI_GENERAL_MODEL, OPENAI_ROBOT_PLANNER_MODEL로 분리했으며 한 역할의 장애가 다른 역할로 연쇄 fallback되지 않도록 circuit도 분리했다.

이 시점의 Front Router는 명시적으로 주입했을 때만 동작하고 production factory의 기본값은 여전히 OFF다. 역할별 모델을 설정하는 것만으로 Router가 활성화되지는 않는다. 실제 후보 분류 smoke도 production 임계값이나 정확도 검증으로 확대하지 않았다.

역할 분리는 응답 지연과 비용을 조절하기 위한 선택이지 안전 우회로가 아니다. PR #84의 실제 API smoke에서도 Chat과 Planner의 응답 생성까지만 확인했고, RobotAction 생성과 Nav2 호출은 0회로 유지했다.

무엇을 검증했고, 무엇은 아직 증명하지 않았나

포트폴리오에서 테스트 수보다 중요한 것은 그 테스트가 무엇을 증명하는지라고 생각한다.

확인한 것공개 기록증명하지 않는 것
텍스트 → 승인 → Nav2 terminal 폐루프headless Gazebo E2E실제 로봇 구동
정상·중복·Safety·응답 유실 처리Gazebo campaign 15/15실제 센서·네트워크 장애
설치된 패키지 회귀PR #77 기준 1,352 tests 통과장시간 운영 안정성
역할별 Chat·Planner 호출실제 API smoke 각 1회로봇 실행 권한

15-case 결과는 PR #77의 검증 commit에 결속된 증거다. 그 뒤에 추가된 clarification, Front Router, 역할별 모델 변경까지 현재 main 전체가 같은 Gazebo campaign을 통과했다는 의미는 아니다.

모든 Gazebo campaign은 simulation=true, physical_authorized=false다. 실제 메카넘 하드웨어, 마이크·STT·TTS, Homecam/AWS를 포함한 전체 시스템 검증은 별도의 증거가 필요하다. 시뮬레이션과 CI 결과를 실기기 완료로 넓혀 말하지 않는 것도 이 프로젝트에서 지키는 중요한 원칙이다.

배운 점

첫째, LLM 에이전트의 핵심은 더 많은 Tool을 주는 것이 아니라 어떤 계층도 자기 권한을 넘어가지 못하게 만드는 것이었다. Router는 분류만, Planner는 제안만, Safety는 허용 여부만, Executor는 승인된 effect만 책임질 때 실패 원인을 추적할 수 있었다.

둘째, exactly-once는 한 번의 중복 검사로 해결되지 않았다. 요청 binding, 승인 CAS, durable action, worker claim·lease·fence, 외부 effect 관측을 함께 다뤄야 했다.

셋째, 물리 시스템에서는 UNKNOWN이 정당한 terminal state다. 응답을 잃었다는 이유로 자동 재시도하면 “복구”가 아니라 두 번째 이동을 만들 수 있다.

다음 증거는 실제 로봇에서 같은 권한 경계가 유지되는지 확인하는 것이다. simulation authority와 physical authority를 분리한 현재 구조를 보존하면서, 수동 주행과 센서 상태부터 한 단계씩 실제 실행 범위를 넓힐 계획이다.