-
n8n으로 로컬 AI 챗봇 오케스트레이션 이전하기 — 하루 종일 삽질한 실전 기록IT 2026. 4. 18. 21:00
코드 속에 갇힌 AI 에이전트
로컬 서버에서 Gemma 4 모델을 vLLM으로 서빙하고, LangGraph로 ReAct 에이전트를 만들어 RAG 검색, 웹 검색, 캘린더, 메모리 저장 등 7개 도구를 연결해 쓰고 있었습니다. 코드로는 잘 돌아가지만 몇 가지 불만이 있었습니다.
- 에이전트가 어떤 도구를 호출했는지 확인하려면 로그 파일을 grep해야 합니다
- 시스템 프롬프트 한 줄 바꾸려면 Python 코드를 수정하고 서버를 재시작해야 합니다
- 새 도구를 추가하려면 코드 작성 → 테스트 → 배포 사이클을 돌아야 합니다
- 실행 흐름을 파악하려면 코드를 머릿속으로 따라가야 합니다
이걸 해결할 방법을 찾다가 n8n을 떠올렸습니다. 이미 셋업은 해놨지만 실제로 AI 에이전트 워크플로우에 써본 적은 없었거든요.
n8n이 뭔가요?
n8n은 오픈소스 워크플로우 자동화 플랫폼입니다. Zapier나 Make(Integromat)의 셀프호스팅 대안이라고 보면 됩니다. 핵심 차이점은:
- 셀프호스팅 — 데이터가 외부로 나가지 않습니다. 로컬 서버에서 돌릴 수 있습니다
- AI Agent 노드 내장 — OpenAI-compatible API를 지원하므로 로컬 vLLM 서버에 바로 연결됩니다
- 코드 노드 — JavaScript/Python 코드를 노드 안에서 직접 실행할 수 있습니다
- 비주얼 에디터 — 노드를 드래그해서 연결하면 워크플로우가 만들어집니다
- 실행 로그 — 모든 실행의 입출력을 노드별로 시각적으로 확인할 수 있습니다
최종 아키텍처
하루 종일 작업한 결과물의 구조는 이렇습니다:
chat.html → proxy.py (SSE 중계) → n8n Webhook → AI Agent → vLLM (Gemma 4) n8n Chat UI ──────────────────→ n8n Webhook → AI Agent → vLLM ↓ Tool Bridge (FastAPI, 포트 8091) ├─ RAG 검색 (Qdrant) ├─ 텔레그램 메모리 ├─ 웹 검색 (DuckDuckGo) ├─ 메모리 저장/조회 ├─ 캘린더 (Google Calendar) ├─ Claude CLI └─ GPU 관리 (acquire/release)기존 chat.html 프론트엔드를 그대로 유지하면서, 백엔드 에이전트 로직만 n8n으로 옮겼습니다. n8n 자체 Chat UI로도 접근 가능합니다.
핵심 노드별 설명
1. Chat Trigger — 대화의 시작점
사용자 메시지를 받는 입구입니다. n8n 내장 Chat UI를 쓸 수도 있고, 외부에서 webhook으로 호출할 수도 있습니다.
Make Chat Publicly Available옵션을 켜면/webhook/{id}/chat엔드포인트가 생깁니다.2. HTTP Request — GPU 준비 및 메모리 로드
AI Agent를 호출하기 전에 두 가지 사전 작업을 합니다:
- GPU Ensure Ready — vLLM 서버가 꺼져있으면 GPU를 확보하고 시작합니다
- Load Memories — 저장된 사용자 정보를 불러와서 시스템 프롬프트에 주입합니다
이 HTTP Request 노드들은 Tool Bridge(별도 FastAPI 서버)의 엔드포인트를 호출합니다.
3. Code 노드 — 시스템 프롬프트 조립
JavaScript로 시스템 프롬프트를 동적으로 조립합니다. 현재 시각, 저장된 메모리, 도구 사용 지침을 하나로 합칩니다. 이 부분을 n8n UI에서 직접 수정할 수 있다는 게 큰 장점입니다.
4. AI Agent 노드 — 핵심 두뇌
n8n의 AI Agent 노드는 OpenAI-compatible API를 지원합니다. 설정값:
- LLM:
Gemma 4 26B(vLLM 서버,http://host.docker.internal:8000/v1) - Temperature: 0.3
- Max Tokens: 4096
- Max Iterations: 5 (도구 호출 최대 횟수)
하위에 LLM 모델, 메모리, 도구들이 서브 노드로 연결됩니다.
5. Tool Workflow — 도구를 서브 워크플로우로 분리
각 도구(RAG 검색, 웹 검색, 메모리 등)를 별도의 워크플로우로 만들었습니다. AI Agent 노드에서
Call n8n Workflow Tool로 연결하면, AI가 알아서 필요할 때 호출합니다.서브 워크플로우 구조는 단순합니다:
Execute Workflow Trigger → HTTP Request (Tool Bridge 호출) → Format Response이 구조의 장점은 도구를 독립적으로 테스트하고 수정할 수 있다는 것입니다.
6. Respond to Webhook — 응답 반환
AI Agent의 출력을 외부 호출자(chat.html)에게 반환합니다. Chat Trigger의 Response Mode를
responseNode로 설정하고, 이 노드에서{{ $('AI Agent').first().json.output }}으로 응답을 지정합니다.7. Schedule Trigger — GPU 아이들 모니터링
메인 워크플로우와는 별개로, 60초마다 실행되는 GPU 모니터링 워크플로우를 만들었습니다:
매 60초 → vLLM 헬스체크 → 실행 중이면 idle 시간 확인 → 5분 초과 시 GPU 반환기존에 Python 코루틴으로 구현했던 것을 n8n 스케줄 워크플로우로 대체한 것입니다.
Tool Bridge — 기존 코드를 재활용하는 접착제
기존 Python 도구 코드(RAG 검색, 텔레그램 메모리, DuckDuckGo 웹 검색 등)를 버리지 않고 FastAPI로 감싸서 HTTP 엔드포인트로 노출했습니다. n8n의 HTTP Request 노드에서 이 엔드포인트를 호출하는 구조입니다.
# Tool Bridge 엔드포인트 예시 POST /tools/search_knowledge_vault → RAG 검색 POST /tools/web_search → DuckDuckGo 검색 POST /tools/save_memory → 사용자 정보 저장 POST /tools/calendar → Google Calendar 조회 GET /gpu/health → vLLM 상태 확인 POST /gpu/ensure-ready → GPU 확보 및 vLLM 시작이 접근의 장점은 기존 Python 코드를 한 줄도 다시 작성하지 않아도 된다는 것입니다. systemd 서비스로 등록해서 서버 부팅 시 자동 시작됩니다.
삽질 포인트 — 이것만은 알고 시작하세요
1. AI Agent는 도구에
{"input": "..."}만 보낸다n8n의 AI Agent가 도구를 호출할 때 파라미터를 개별 필드로 보내지 않고,
input이라는 단일 문자열 필드로 보냅니다. Tool Bridge에서 이걸 각 도구의 필드(query,content,question등)로 변환해야 합니다.2. n8n은 빈 값을
null로 보낸다서브 워크플로우의 HTTP Request에서
{{ JSON.stringify($json) }}으로 body를 보내면, 정의되지 않은 필드가null로 들어갑니다. Pydantic 모델에서str타입으로 선언하면 422 에러가 납니다. 모든 필드를Optional로 선언하고 기본값 처리를 해야 합니다.3. Draft와 Published 버전이 분리되어 있다
n8n에서 워크플로우를 수정한 후 반드시 Publish를 해야 production 환경에 반영됩니다. 저장(Ctrl+S)만으로는 부족합니다. 이걸 몰라서 "분명 수정했는데 왜 안 되지?"를 여러 번 겪었습니다.
4. Expression 모드에서
=이 자동으로 붙는다n8n의
fx(Expression) 모드를 켜면 값 앞에=가 자동으로 붙습니다. 그래서={{ $json.chatInput }}이라고 입력하면 실제로는=={{ $json.chatInput }}이 됩니다. Expression 모드에서는{{ }}만 입력해야 합니다.5. 한글 노드 이름은 도구 이름 충돌을 일으킨다
Tool Workflow 노드의 이름을 한글로 지으면 n8n이 내부적으로 도구 이름을
_로 변환합니다. 여러 도구가 모두_라는 같은 이름이 되어 충돌이 납니다. 도구 노드 이름은 반드시 영문으로 지어야 합니다.6. CLI import에 credential 버그가 있다
n8n import:workflowCLI 명령어로 워크플로우를 import하면 "credential with ID undefined" 에러가 발생합니다. n8n UI에서 직접 Import from File로 하는 게 안전합니다.이전 전후 비교
이전 (LangGraph) 이후 (n8n) 실행 모니터링 터미널 로그 grep 비주얼 실행 이력 UI 도구 호출 확인 로그 파일 검색 노드 클릭으로 입출력 확인 프롬프트 수정 코드 수정 → 서버 재시작 n8n UI에서 바로 수정 에러 추적 로그 파일 분석 실패 노드 빨간색 표시 도구 추가 Python 코드 작성 서브 워크플로우 드래그 실행 재현 불가 이전 실행을 다시 실행 가능 궁극적으로 얻는 것
가장 큰 변화는 "코드를 읽어야 이해하는 시스템"에서 "보면 이해되는 시스템"으로 바뀐 것입니다.
n8n 워크플로우 에디터를 열면 에이전트의 전체 흐름이 한눈에 보입니다. 사용자 메시지가 들어오면 GPU를 준비하고, 메모리를 로드하고, 시스템 프롬프트를 조립하고, AI Agent가 필요한 도구를 호출하고, 결과를 반환하는 과정이 노드와 연결선으로 시각화됩니다.
실행 이력을 클릭하면 각 노드에 어떤 데이터가 오가는지 전부 확인할 수 있습니다. "왜 이 검색 결과가 이상하지?" 싶으면 RAG Search 노드를 클릭해서 입력값과 출력값을 바로 볼 수 있습니다.
그리고 Langfuse와 연동하면 LLM 호출의 토큰 사용량, 응답 시간, 비용까지 추적됩니다. AI 시스템을 운영하려면 관측성(observability)이 필수인데, n8n + Langfuse 조합이 이걸 깔끔하게 해결해줍니다.
물론 단점도 있습니다. n8n의 Draft/Published 분리는 직관적이지 않고, AI Agent의 도구 파라미터 전달 방식(
input단일 필드)은 제약이 있습니다. 하지만 "코드 수정 → 배포 → 로그 확인" 사이클에서 벗어나 "UI에서 수정 → 바로 테스트 → 시각적 확인"으로 바뀌는 개발 경험의 차이는 상당합니다.로컬 AI 에이전트를 운영하고 있다면, 코드에 모든 걸 담기보다 n8n 같은 워크플로우 엔진을 오케스트레이션 레이어로 두는 것을 추천합니다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
텔레그램으로 GitHub 이슈 관리 자동화하기 (1) 2026.04.21 6개월 만에 4세대, 智谱AI GLM 모델 패밀리 완전 정리 (1) 2026.04.20 Google One 해지와 클라우드 탈출기 — 구글에 남긴 건 메일과 캘린더뿐 (1) 2026.04.19 Microsoft 365 구독 해지와 AI 마이그레이션 — 556GB를 구출한 일주일 (0) 2026.04.19 ChatGPT 유료 구독 취소 후 달라진 3가지 (0) 2026.04.19 AI 모델 리더보드, Chatbot Arena 완전 해부 (1) 2026.04.17 LangGraph 에이전트에 Langfuse 붙이기 — LLM 앱의 블랙박스를 유리상자로 (0) 2026.04.16 Chain이 아니라 Graph — LangGraph로 AI 에이전트를 만드는 이유 (0) 2026.04.16 아카라이브 알파카 채널과 AI 모델 이름의 계보 (0) 2026.04.14 Gemma 4 로컬 AI 스택 완전 정복 — DGX Spark에서 돌려본 솔직한 후기 (1) 2026.04.13