전화로 홈랩(Homelab)에 전화를 걸면 AI 에이전트가 전화를 받아 인프라를 운영합니다
요약
홈랩의 분산된 실험실들을 Telegram 음성 통화 기반의 AI 에이전트 'clau'로 통합하여 인프라 운영 인터페이스로 구축한 사례를 소개합니다. 에이전트가 화면을 공유하고 도구를 실행하며, 필요시 다른 에이전트에게 권한을 위임하는 오케스트레이션 패턴을 다룹니다.
핵심 포인트
- 음성 에이전트를 인프라 운영을 위한 통합 인터페이스로 활용
- 멀티모달 RAG, Claude Code 등 개별 도구를 에이전트의 네이티브 도구로 연결
- 화면 공유를 통해 에이전트와 사용자가 동일한 작업 맥락을 공유
- 복잡한 작업 수행 시 권한이 높은 다른 에이전트에게 위임하는 구조
원래 제 블로그(pereyra.ar/blog/clau-tg)에 (스페인어로) 게시된 글입니다.
요약 (TL;DR)
수개월 동안 저는 홈랩(Homelab)에 흩어진 실험실들을 구축해 왔습니다: 멀티모달 RAG, WhatsApp 라우터, GPU 스위치보드, K3s에서 실행되는 Claude Code, 실시간 립싱크 아바타 등입니다. 각각은 단일 문제를 해결하며 독립적으로 존재했습니다. 이번 주에 저는 이 모든 것들을 하나의 음성 에이전트인 clau 뒤에 연결했습니다. 그리고 제가 이 에이전트와 대화하는 방식은 문자 그대로입니다: 바로 Telegram 전화 통화입니다.
흥미로운 점은 음성 에이전트 그 자체가 아니었습니다 (그런 에이전트는 이미 수천 개가 있습니다). 모든 실험실이 연결되자, clau는 단순한 "음성 봇"을 넘어 **제 인프라를 운영하기 위한 기본 인터페이스(Primary Interface)**로 변모했다는 점입니다. 저는 clau와 대화하고, clau는 제 화면을 보며, 자신의 화면을 저에게 보여줍니다. 클러스터에 대해 kubectl을 실행하고, 웹을 브라우징하며, 자신의 권한 밖의 일이 생기면 **더 많은 권한을 가진 다른 에이전트에게 위임(Delegate)**한 뒤 음성으로 답변을 가지고 돌아옵니다.
이 포스트에서는 아키텍처, "오케스트레이션 레이어(Orchestration Layer)로서의 음성 에이전트" 패턴, 그리고 이를 온프레미스(On-prem)로 구축할 때의 솔직한 트레이드오프(Tradeoffs)를 다룹니다.
문제점: 서로 소통하지 못하는 실험실들
지난 1년 동안 저는 각기 다른 목적을 가진 요소들을 홈랩에 계속 추가해 왔습니다:
| 실험실 | 역할 | 스택 |
|---|---|---|
| 멀티모달 RAG (Multimodal RAG) | 내 문서/이미지 검색 | FastAPI + Qdrant + Gemini |
| ... |
전형적인 문제입니다: 각각 호출되는 방식이 달랐습니다. 무언가를 "엔드 투 엔드(End-to-end)"로 수행하려면 결국 세 개의 터미널을 열고, WhatsApp 채팅을 확인하며, 대시보드를 봐야 했습니다. 마찰(Friction)은 기능의 부재가 아니라 — 기능들은 모두 갖춰져 있었습니다 — 바로 **그 기능들 사이의 접착제(Glue)**에 있었습니다.
아이디어: 오케스트레이션 레이어로서의 음성 에이전트
clau는 제 워크스테이션에서 실행되며, 제가 전화를 걸 때(또는 능동적으로 저에게 전화를 걸 때) 실제 Telegram 전화를 받고, Gemini Live를 통해 자신의 목소리로 말합니다. 지금까지는 그저 또 다른 음성 에이전트일 뿐이었습니다.
그 전환점은 제가 이미 보유하고 있던 랩(Labs)들을 하나씩 연결하는 것이었습니다. 각 랩은 에이전트 상에서 **네이티브 도구 (native tool)**로 노출됩니다. 현재 약 50개의 네이티브 도구가 있으며, 여기에 무거운 작업은 Claude Code에 위임할 수 있는 능력까지 갖추고 있습니다. 이 모든 것은 저의 자격 증명(credentials), 저의 RAG, 저의 기술을 사용하여 온프레미스(on-prem) 환경에서 이루어집니다.
챗봇과의 차이점은 에이전트에서 흔히 볼 수 없는 두 가지 능력으로 요약됩니다:
- 제가 무엇을 하는지 봅니다. 저는 제 화면이나 카메라를 공유하며, 에이전트는 그것을 바탕으로 추론(reasoning)합니다.
- 자신이 무엇을 하는지 보여줍니다. 에이전트는 통화 중 비디오를 통해 자신의 터미널이나 브라우저를 공유하므로, 우리 둘은 동일한 작업에 대해 함께 작업할 수 있습니다.
실제 작동 방식
영상에서 저는 에이전트에게 전화를 걸어 자신을 보여달라고 요청합니다. 실제 진행 과정은 다음과 같습니다:
클러스터(cluster)를 운영합니다. 저는 에이전트에게 터미널을 공유하고 명령어를 실행하라고 요청합니다:
kubectl get nodes
# k3s-master, k3s-worker-01, ubuntu → 모두 Ready 상태
kubectl get ns
...
에이전트는 단순히 명령을 실행하는 데 그치지 않습니다. 공유된 터미널에서 출력 결과(output)를 직접 확인하며, 우리는 화면에 나타난 내용에 대해 대화를 나눕니다. 대화를 통해 인프라를 운영하는 것입니다.
웹을 브라우징합니다. 저는
RAG를 멀티모달(multimodally) 방식으로 쿼리합니다. 저는 RAG 위에 제가 좋아하는 디테일을 하나 구축했습니다. 클래식한 벡터 검색(Qdrant + Google embeddings)과 병렬로, 동일한 정보를 LLM-as-a-wiki 구조(Andrej Karpathy가 언급했던 방식)로 저장합니다. 그 상단의 레이어가 쿼리를 분석합니다. 만약 쿼리가 일반적이거나 개방형(open-ended)이라면 위키를 탐색하고, 매우 구체적이라면 벡터로 이동하며, 혹은 두 가지를 결합합니다. 즉, 먼저 위키 방식으로 접근한 다음 세밀한 검색(fine-grained search)을 수행합니다. WhatsApp을 통해 Apple 로고를 보냈더니, 시스템이 생성했던 설명을 반환했습니다. 트리거 문구는 "use the RAG to…"입니다.
서비스를 스케줄링하고 확인합니다. Google Calendar 이벤트를 생성하고, AI 서버의 FLUX 서비스 상태를 알려주며, 카메라를 보고 제 설정을 설명합니다(네, 제 동료도 찾아냈습니다).
가장 도움이 된 패턴: 위임(delegation)
clau는 통화 중에 모든 것을 해결하지 않습니다. 긴 추론(reasoning)이 필요하거나 음성 에이전트에게 권한이 없는 작업의 경우, claupod에 위임합니다 — claupod는 K3s에서 실행되는 Claude Code 인스턴스입니다. claupod가 작업(특정 데이터를 가져오기 위해 구성된 외부 서버에 SSH로 접속하는 것 포함)을 수행하고 결과를 반환하면, clau는 동일한 통화 내에서 이를 음성으로 저에게 전달합니다.
작업이 위임되기 때문에 시간이 조금 더 걸리지만, 이 패턴이 핵심입니다: 음성 에이전트는 대화형 인터페이스(conversational face) 역할을 하고, 권한을 가진 에이전트들이 실제 작업(dirty work)을 수행합니다. 음성 레이어는 민감한 자격 증명(credentials)에 절대 직접 접근하지 않으며, 오케스트레이션(orchestrates)만 수행합니다.
아바타 (그리고 지연 시간이 발생하는 이유)
마지막 부분은 clau가 실시간 웹캠 아바타와 함께 나타난다는 점입니다. AI 서버에서 TensorRT를 사용하여 립싱크(lip-sync)를 생성하는 MuseTalk를 사용합니다. 솔직히 말해서 전체 파이프라인에서 가장 비용이 많이 드는 부분입니다. 많은 실시간 연산(realtime compute)이 필요하기 때문에 오디오와 **약 1초의 오프셋(offset)**이 발생합니다. 비디오는 실시간이지만, 미세한 동기화(fine sync)는 개선해야 할 과제로 남아 있습니다. 아이디어를 보여주기에는 충분하므로 프로덕션용은 아니지만 현재 상태로 유지합니다.
솔직한 트레이드오프(tradeoffs)
- 지연 시간(Latency)은 네트워크가 아니라 모델의 문제입니다. claupod로의 왕복 시간(round-trip)을 측정해 본 결과, 99%의 시간은 Gemini의 추론(reasoning) 과정(도구 사용에 따라 4~8초)에서 발생하며, 내부 네트워크는 5ms 미만입니다. 여기서 최적화를 한다는 것은 인프라가 아니라 프롬프트(prompt)와 도구(tools)를 개선하는 것을 의미합니다.
- 모델을 길들여야 합니다. Gemini Live는 혼란스러운 질문을 받으면 무작위로 도구를 실행하며, 때때로 식별자(identifiers)를 환각(hallucinate)하기도 합니다. 프롬프트에 명시적이고 위반 불가능한 규칙을 추가하고 폴백(fallback)을 설정하여 이를 해결할 수 있지만, 매우 까다로운 작업입니다.
- GPU 공유 = 웜업(warmups). 모든 작업은 시분할 방식(time-multiplexing)을 통해 단일 RTX 3090에 연결되어 있습니다. 새로 교체된 모델에 대한 첫 번째 호출은 로드 비용(load cost)을 지불해야 합니다. 저의 경우(단일 사용자, 즉 나 자신)에는 완벽하지만, 실제 동시성(concurrency)을 처리하기에는 적합하지 않습니다.
- 브라우저는 2FA/SSO를 처리하지 못합니다. 영상에서 볼 수 있듯이, 2단계 인증(two-factor)을 통한 Grafana 로그인은 작동을 중단시킵니다. 이는 브라우저 공유(browser share)의 알려진 한계입니다.
- 약 1초의 오프셋(offset)이 발생하는 아바타. 이미 언급한 바와 같습니다.
이 중 어느 것도 "혁명적"이지는 않습니다. 이미 작동하고 있던 조각들 위에 적절하게 배치된 접착제(glue)일 뿐이며, 단일 사용자용 홈랩(homelab)에서는 그 가치가 매우 큽니다.
스택 (Stack)
- 음성 (Voice): Telegram 통화(hydrogram + pytgcalls)를 통한 Gemini Live (Aoede 음성).
- 오케스트레이션 (Orchestration): 에이전트의 네이티브 도구(native tools) + K3s 상의 Claude Code (claupod)로 위임.
- 인프라 (Infra): Proxmox 상의 K3s, PV를 위한 NAS, GPU Switchboard를 갖춘 단일 RTX 3090.
- RAG: FastAPI + Qdrant + Google embeddings + LLM-as-a-wiki 레이어.
- 메시징 (Messaging): Evolution API + ws-router (WhatsApp).
- 브라우저 (Browser): Chromium + Playwright (headless CDP screencast).
- 아바타 (Avatar): MuseTalk + TensorRT.
- 보조 STT/TTS: 자체 호스팅된 Whisper + Piper.
마무리 (Wrap-up)
핵심 요점은, "또 하나의" 에이전트를 만드는 것이 아니라, 이미 보유하고 있는 랩(labs)에 **단일 대화형 프런트 도어(single conversational front door)**를 제공하는 데 가치가 있었다는 점입니다. 결과적으로 전화 통화는 집 안을 돌아다니는 동안 홈랩을 운영하기 위한 최고의 인터페이스가 되었습니다.
개선해야 할 사항들(아바타 오프셋 (avatar offset), 모델을 더 잘 길들이는 것, 브라우저에서의 SSO 처리 등)이 남아 있으므로, 더 많은 영상이 제작될 예정입니다.
만약 이와 유사한 것을 구축하고 있거나 온프레미스 (on-prem) 접근 방식에 대해 논의하고 싶다면 연락해 주세요. 기꺼이 대화에 응하겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기