LangGraph와 라즈베리파이를 활용한 AI 에이전트 로봇(PiCar-X) 자율 제어 구현
요약
본 기사는 LangGraph를 활용하여 라즈베리파이 기반 AI 에이전트 로봇(PiCar-X)의 자율 제어 시스템을 구현하는 방법을 다룹니다. 기존의 일방통행적인 LLM 체인 한계를 극복하기 위해, 상태 유지와 조건부 분기가 가능한 그래프 구조를 도입했습니다. 이를 통해 장애물 회피 등 복잡한 피드백 루프 기반의 다단계 자율 행동이 가능해졌습니다.
핵심 포인트
- LangGraph는 순환 및 상태 관리가 필요한 AI 에이전트 흐름을 그래프로 정의합니다.
- State 유지, Node(처리 단위), Edge(조건 분기)를 통해 복잡한 로직 구현이 가능합니다.
- 시스템은 LangGraph (Python/PC)와 하드웨어 제어 (Rust/Raspberry Pi)가 분리되어 동작합니다.
- Router 노드가 LLM의 판단에 따라 툴 실행 또는 종료 여부를 결정하는 핵심 역할을 합니다.
본 기사는 다음 전편 기사의 속편입니다.
Gemini API의 구조화 출력 (Structured Outputs) 활용
- LLM 출력을 JSON 스키마로 고정하여 파싱 에러 없이 안전하게 PiCar-X를 주행 제어하기
일방통행적인 지시만 가능: '지시 → 1회 JSON 생성 → 실행'으로 완료되어 버립니다.
환경 변화에 대응 불가: 이동 중에 장애물을 발견했거나, 카메라 이미지를 확인한 후 다음 동작을 결정하는 같은 '피드백 루프(feedback loop)'를 구성할 수 없습니다.
복잡한 태스크의 실패: '장애물이 없는지 확인하고, 있다면 피해서 원래 경로로 돌아가기'와 같은 다단계 자율 행동을 1회 출력으로 정의하기는 어렵습니다.
이러한 한계를 돌파하기 위해 이번에는 LangGraph를 사용한 AI 에이전트 제어로 전환했습니다.
LangGraph는 LLM을 활용하는 애플리케이션에서 순환(loop)이나 상태 관리(state management)가 필요한 처리 흐름을 그래프 구조로 정의하고 실행하기 위한 라이브러리입니다.
일반적인 LLM 체인(일방통행 파이프라인)과는 달리 다음과 같은 특징을 가집니다.
State (상태) 유지: 대화 기록이나 센서 값, 실행 결과 등의 변수를 딕셔너리 형태로 일원 관리하고 노드 간에 전달합니다.
-
Node (노드 / 처리 단위): LLM 호출, 툴(tool) 실행, 데이터 변환 등 하나의 구체적인 처리를 수행하는 Python 함수입니다.
-
Edge (엣지 / 전이와 조건 분기): 노드에서 다음 노드로의 이동을 정의합니다. LLM의 판단 결과에 따라 '툴을 실행할지', '처리를 종료할지' 등의 조건부 분기(Conditional Edge)가 가능합니다.
-
루프 구현: 'LLM이 툴 실행을 요구 → 툴이 실기를 움직여 결과를 반환 → 다시 LLM이 결과를 보고 판단'이라는 반복 처리를 명시적으로 기술할 수 있습니다.
평소에 사용하고 있는 AI 에이전트 중에서도 이 기술이 활용되는 경우가 많습니다.
로컬 PC 측에서 LangGraph를 통해 의사결정을 수행하고, 라즈베리파이 측에서 하드웨어 제어 및 안전 정지를 담당하는 구성입니다.
로컬 PC (Python / LangGraph): 대화 기록 관리
-
LLM에 의한 툴 선정 및 파라미터 결정
-
툴 실행과 결과 평가
-
Raspberry Pi (Rust / 하드웨어 제어): 모터/서보 구동
-
센서 값(초음파・그레이스케일) 취득
-
전방 장애물 감지 시 하드웨어 강제 자동 정지 (안전 가드)
원래는 모두 Rust로 구현했었지만, LangGraph를 이용하고 싶다는 관점에서 AI 부분을 분리하여 Python으로 구현했습니다.
기존 제어 메커니즘은 그대로 Rust 구현을 유지하는 형태로 했습니다.
현재 구현하고 있는 최소 구성의 에이전트 루프는 다음 그래프로 동작합니다.
Router Node: 사용자의 입력과 지금까지의 기록을 바탕으로 LLM이 '툴을 실행할지' 또는 '종료하고 답변할지'를 판정합니다.
-
Conditional Edge: 툴 호출이 필요한 경우 →
tools노드로 전이 - 불필요(답변 완료)한 경우 →END로 전이 -
툴 호출이 필요한 경우 →
-
Tool Node: 지정된 툴(전진, 카메라 촬영, 센서 취득 등)을 실행하고, 그 결과를 메시 기록(State)에 추가하여
Router로 돌아갑니다.
from typing import Annotated, List, TypedDict
import operator
from langchain_core.messages import BaseMessage
...
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_core.messages import AIMessage
...
項目 비교
| 항목 | 구조화 출력만 (이전) | LangGraph 에이전트 (이번) |
|---|---|---|
| 작동 방식 | 한 번 왕복의 고정된 JSON 생성 | 상황에 따른 반복 루프(사고 → 실행 → 재사고) |
| 환경 피드백 | 실행 전 추측만 가능 | 센서 값이나 카메라 결과를 보고 다음 동작 결정 가능 |
| 오류 자가 복구 | 실행 실패 시 그대로 정지 | 실패 결과를 보고 다른 접근 방식을 자율적으로 시도 가능 |
| 유연성 | 미리 정해진 스키마 범위 내 | 여러 툴을 조합한 복잡한 작업에 대응 가능 |
이전에는 '앞으로 전진하고 장애물이 있으면 피하라'고 지시하면, 처음에 생성된 고정 JSON대로 돌진하다가 벽 앞에서 안전하게 멈추고 그대로 정지하는 상태였습니다.
LangGraph를 사용한 후로는 '조금 전진 → 센서로 거리 12cm 감지 → 자율적으로 핸들을 꺾어 회피'와 같이, 로봇 스스로 환경 변화를 보면서 임기응변하게 복구하며 움직이게 되었습니다.
실제 구현에서도 서브 에이전트를 확장하기 쉬워서 개발 및 활용 양면에서 장점을 느꼈습니다.
LangGraph 도입을 통해 센서 결과를 받은 자율적인 반복 실행에 성공했습니다.
실용화(상업적 이용)를 위해 현재는 다음과 같은 과제들을 해결하는 데 집중하고 있습니다.
-
툴의 비대화 (Tool Bloating): 하나의 LLM이 모든 툴(주행/카메라/음성)을 가지고 있어, 툴 선택 오류나 토큰 소비가 증가합니다. -
병렬 동작 불가: 처리가 직렬적이라 '말하면서 주행하기', '주행하면서 카메라로 주변 감시하기'와 같은 것이 불가능합니다. -
중단 제어 불가: 주행 작업 도중에 장애물을 감지해도 즉시 중단하여 브레이크를 걸 수 없습니다.
-
전문 에이전트로 분리 (Separation into Specialized Agents):
Drive(주행만),Vision(카메라만),Voice(발화만)으로 툴과 프롬프트를 분할하여 정확도 향상 및 토큰 절감을 목표로 합니다. -
병렬 처리와 중단 구현 (Implementation of Parallel Processing and Interruption): 주행과 이미지 감시를 비동기적으로 병렬 구동시키고, 시각 에이전트가 이상을 감지했을 때 주행 에이전트를 즉시 정지시키는 협조 제어를 구현합니다.
소프트웨어 외적으로 이번에 절실히 느낀 점은 'AI가 아무리 똑똑해도, 움직이는 하드웨어가 따라가지 못하면 로봇은 원하는 대로 움직이지 않는다'는 현실이었습니다. 실제로 이번에 사용한 로봇(4륜 차량)의 타이어나 스티어링 메커니즘이 저렴해서, AI로 올바른 로직을 구성했더라도 깔끔하게 곡선으로 돌아가기 어려운 상황이 자주 발생했습니다. 또한, 실제 운용 과정에서는 처리 속도를 높이는 고안도 필요하다고 느꼈습니다. (이번에는 취미 수준이라 그 정도까지는 요구하지 않았지만요.) AI 로직이나 머신 스펙뿐만 아니라 메커니즘(Mechanics) 자체의 정밀도도 고려해야 한다는 점은, 차량형 로봇에 국한되지 않고 휴머노이드 같은 고도화된 로봇 (Physical AI)에서도 직면하는 과제일 것이라고 실감했습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기