시리즈로 돌아가기
대표 작업16 min

4편 에이전트의 몸 — 다중 환경 자동화 파이프라인

차은별 봇이 글을 쓰는 것만으로는 부족합니다. Linux 봇 서버에서 시작해 Windows ComfyUI로 이미지를 생성하고, 호스팅어 서버에 업로드하고, Threads API로 게시하기까지 — 네 개의 서로 다른 환경이 하나로 연결되는 파이프라인을 해부합니다.

4편 에이전트의 몸 — 다중 환경 자동화 파이프라인

3편에서는 차은별 봇의 뇌(MD 파일 6종)를 해부했습니다. 이번 편은 그 뇌가 실제로 어떻게 몸을 움직이는지 — 이미지를 생성하고 SNS에 게시하는 자동화 파이프라인을 다룹니다.

하나의 서버로 끝났다면 쉬운 작업이었다. 하지만 현실은 네 개의 다른 환경이 유기적으로 연결되어야 했다.

왜 이렇게 복잡해졌나

처음 설계할 때는 단순했습니다. "봇이 글 쓰고, 이미지 만들고, 올리면 되잖아." 하지만 실제로 구현하다 보니 세 가지 제약이 생겼습니다.

제약 1 — GPU는 Windows에 있다: ComfyUI로 이미지를 생성하려면 NVIDIA GPU가 필요합니다. GPU가 연결된 머신은 Windows 서버입니다. 봇은 Linux에서 돌아갑니다. 두 운영체제가 서로 통신해야 합니다.

제약 2 — Threads API는 공개 URL이 필요하다: Threads API에 이미지를 첨부하려면 인터넷에서 접근 가능한 공개 URL이 있어야 합니다. Windows 서버의 로컬 파일 경로(C:\images\output.png)는 Threads가 접근할 수 없습니다.

제약 3 — 봇은 24시간 돌아야 한다: 스케줄 기반으로 자동 실행되려면 항상 켜져 있는 서버가 필요합니다. Windows 데스크탑 PC는 재시작, 절전 등의 이유로 항상 켜있다고 보장할 수 없습니다.

이 세 가지 제약이 모여 "다중 환경 파이프라인"이 탄생했습니다.


전체 파이프라인 구조

[Linux 봇 서버]
      │
      │ 1. 텍스트 생성 + ComfyUI 프롬프트 구성
      │ 2. ComfyUI API 호출 (HTTP → Windows)
      ▼
[Windows ComfyUI 서버]
      │
      │ 3. 이미지 생성 (GPU)
      │ 4. 생성된 이미지 파일 → SFTP 전송
      ▼
[호스팅어 이미지 서버]
      │
      │ 5. 공개 URL 생성
      ▼
[Threads API (Meta)]
      │
      │ 6. 텍스트 + 이미지 URL로 게시물 생성
      │ 7. 게시물 발행
      ▼
    [완료]

총 7단계, 4개 환경을 거칩니다. 하나라도 실패하면 전체가 멈춥니다. 그래서 각 단계에 에러 처리와 재시도 로직이 들어갑니다.


1단계 — Linux 봇 서버: 텍스트 생성

차은별 봇은 Linux 서버의 Python 프로세스로 실행됩니다. APScheduler가 매일 아침 9시에 태스크를 트리거합니다.

from apscheduler.schedulers.blocking import BlockingScheduler

scheduler = BlockingScheduler()

@scheduler.scheduled_job('cron', hour=9, minute=0)
def daily_post():
    text = generate_post_text()       # LLM으로 텍스트 생성
    image_url = generate_image(text)  # ComfyUI 파이프라인
    post_to_threads(text, image_url)  # Threads API 게시

scheduler.start()

텍스트 생성은 Claude API를 호출합니다. SOUL.md, IDENTITY.md, USER.md, MEMORY.md를 시스템 프롬프트에 주입하고, 오늘 날짜와 날씨 정보를 추가해 컨텍스트를 완성합니다.

def generate_post_text():
    system_prompt = load_md_files([
        "SOUL.md", "IDENTITY.md", "USER.md", "MEMORY.md"
    ])
    response = anthropic.messages.create(
        model="claude-sonnet-4-6",
        system=system_prompt,
        messages=[{
            "role": "user",
            "content": f"오늘({today_date()}) 감성 포스팅 1개 작성해줘."
        }]
    )
    return response.content[0].text

TOOLS.md와 SKILLS.md는 어디서 쓰이나?

TOOLS.md는 코드 작성 시 참조 문서로 씁니다. "어떤 API를 어떻게 호출해야 하는지" 기억하기 위한 운영자 메모입니다. SKILLS.md는 LLM에게 특정 작업을 지시할 때 시스템 프롬프트에 추가합니다. "이미지 프롬프트를 작성해줘"라고 할 때 SKILLS.md의 절차를 함께 주입합니다.


2단계 — ComfyUI API 호출 (Linux → Windows)

이 단계가 가장 까다로운 부분입니다. Linux에서 Windows에 있는 ComfyUI 서버로 HTTP 요청을 보냅니다.

ComfyUI API의 상세한 내용은 ComfyUI API 파이프라인 구축 가이드에서 19단계로 정리되어 있습니다. 여기서는 봇 통합 관점에서 핵심만 짚습니다.

import requests
import json
import uuid

COMFYUI_HOST = "http://[windows-server-ip]:8188"

def generate_image(post_text: str) -> str:
    # 1. 텍스트로 이미지 프롬프트 생성 (Claude API)
    prompt = text_to_image_prompt(post_text)

    # 2. ComfyUI 워크플로우에 프롬프트 주입
    workflow = load_workflow("chaeunbyul_workflow.json")
    workflow["6"]["inputs"]["text"] = prompt  # CLIPTextEncode 노드

    # 3. ComfyUI에 작업 제출
    client_id = str(uuid.uuid4())
    response = requests.post(
        f"{COMFYUI_HOST}/prompt",
        json={"prompt": workflow, "client_id": client_id}
    )
    prompt_id = response.json()["prompt_id"]

    # 4. 완료 대기 (폴링)
    image_filename = wait_for_completion(prompt_id)

    # 5. 생성된 이미지 다운로드
    return download_image(image_filename)

폴링 vs 웹소켓: ComfyUI는 웹소켓으로 실시간 진행 상황을 받을 수 있고, /history/{prompt_id} 엔드포인트로 폴링도 됩니다. 봇에서는 폴링 방식을 채택했습니다. 웹소켓은 연결 유지 관리가 필요해 봇 프로세스에 복잡도를 더하기 때문입니다. 이미지 생성은 보통 15~30초 걸리므로 5초 간격 폴링으로도 충분합니다.


3단계 — Windows ComfyUI: 이미지 생성

Windows 서버에서는 ComfyUI가 항상 실행 중이어야 합니다. Windows 서비스 또는 시작 프로그램으로 등록해두면 재시작 후에도 자동 실행됩니다.

차은별 봇의 ComfyUI 워크플로우는 z_image_turbo_nvfp4 모델 기반입니다. 자세한 워크플로우 구성은 ComfyUI 파이프라인 가이드의 1단계에서 확인할 수 있습니다.

워크플로우 설정

  • 모델: z_image_turbo_nvfp4
  • 해상도: 1080×1080 (SNS 정사각형)
  • 샘플링 스텝: 15
  • CFG: 1 (turbo 모델)
  • 생성 시간: 약 15~20초

자동화 고려사항

  • API 서버 모드로 실행 필요 (--listen 플래그)
  • 방화벽에서 8188 포트 개방
  • Windows Defender 예외 설정
  • 절전 모드 비활성화

생성된 이미지는 ComfyUI의 output/ 폴더에 저장됩니다. Linux 서버는 이 이미지를 /view 엔드포인트로 다운로드합니다.


4단계 — 이미지 호스팅어 업로드 (SFTP)

Threads API는 이미지를 직접 업로드하지 않습니다. 공개 URL을 받아서 자체적으로 이미지를 가져갑니다(pull). 따라서 이미지를 공개 서버에 먼저 올려야 합니다.

호스팅어 웹 서버를 이미지 CDN으로 활용합니다. SFTP로 이미지를 업로드하고, 공개 URL을 만들어 Threads에 넘깁니다.

import paramiko
from pathlib import Path

SFTP_HOST = "files.hostinger.com"
SFTP_USER = "username"
SFTP_PASS = "password"
SFTP_PATH = "/public_html/images/bot/"
PUBLIC_URL = "https://mydomain.com/images/bot/"

def upload_to_hostinger(local_path: str) -> str:
    filename = Path(local_path).name

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(SFTP_HOST, username=SFTP_USER, password=SFTP_PASS)

    sftp = ssh.open_sftp()
    sftp.put(local_path, SFTP_PATH + filename)
    sftp.close()
    ssh.close()

    return PUBLIC_URL + filename

왜 호스팅어인가? 이미 운영 중인 웹 서버가 있었기 때문입니다. 별도 이미지 CDN 서비스를 쓰거나 S3를 써도 됩니다. 중요한 것은 Threads API가 접근 가능한 공개 HTTPS URL이어야 한다는 점입니다.

이미지 파일명은 UUID로 생성합니다. 같은 시간에 여러 게시물을 올릴 때 파일명이 충돌하지 않도록 하기 위해서입니다.


5~7단계 — Threads API: 게시물 발행

Meta의 Threads API는 2단계로 게시물을 만듭니다. 먼저 컨테이너를 생성하고, 그다음 발행합니다.

import requests

THREADS_TOKEN = "YOUR_ACCESS_TOKEN"
THREADS_USER_ID = "YOUR_USER_ID"
BASE_URL = "https://graph.threads.net/v1.0"

def post_to_threads(text: str, image_url: str) -> str:
    # 1단계: 미디어 컨테이너 생성
    container_res = requests.post(
        f"{BASE_URL}/{THREADS_USER_ID}/threads",
        params={
            "media_type": "IMAGE",
            "image_url": image_url,
            "text": text,
            "access_token": THREADS_TOKEN,
        }
    )
    container_id = container_res.json()["id"]

    # 컨테이너 처리 대기 (보통 3~5초)
    import time
    time.sleep(5)

    # 2단계: 게시물 발행
    publish_res = requests.post(
        f"{BASE_URL}/{THREADS_USER_ID}/threads_publish",
        params={
            "creation_id": container_id,
            "access_token": THREADS_TOKEN,
        }
    )
    return publish_res.json()["id"]

Threads API에서 자주 발생하는 실수가 있습니다. 컨테이너를 만들고 바로 발행하면 PENDING 상태 에러가 납니다. 컨테이너가 처리될 때까지 기다려야 합니다. 경험상 이미지 크기와 서버 상태에 따라 3~8초 정도 걸립니다. 안전하게 5초 대기를 넣어두는 것이 좋습니다.


에러 처리와 재시도 전략

4개 환경을 연결하면 각 단계에서 오류가 날 수 있습니다. 단순히 try-except로 묶는 것이 아니라, 단계별로 다른 전략이 필요합니다.

재시도 가능한 오류

  • ComfyUI 서버 일시 응답 없음 (네트워크 지연)
  • 호스팅어 SFTP 연결 실패 (순간적 불안정)
  • Threads API 컨테이너 PENDING 상태 → 3회까지 지수 백오프로 재시도

재시도 불가한 오류

  • ComfyUI 워크플로우 에러 (프롬프트 형식 오류)
  • Threads API 인증 실패 (토큰 만료)
  • 이미지 생성 실패 (GPU 메모리 부족) → 즉시 중단 + 알림 발송
import time

def with_retry(func, max_retries=3, base_delay=2):
    for attempt in range(max_retries):
        try:
            return func()
        except TemporaryError as e:
            if attempt == max_retries - 1:
                raise
            delay = base_delay * (2 ** attempt)
            time.sleep(delay)
        except PermanentError:
            send_alert("자동화 실패: 즉시 확인 필요")
            raise

알림은 Slack 웹훅이나 텔레그램 봇으로 보냅니다. 아침에 일어났을 때 봇이 어젯밤에 실패했는지 알 수 있어야 하기 때문입니다.


이 파이프라인에서 배운 것

19단계에 걸쳐 이 자동화를 구축하면서 얻은 통찰을 정리합니다.

첫째 — 환경의 이질성이 복잡도의 주범이다. Linux와 Windows가 섞이고, 로컬 네트워크와 공인 인터넷이 섞이면 예상치 못한 문제가 계속 나옵니다. 방화벽 설정, 경로 구분자 차이(/ vs \), 줄바꿈 문자 차이(\n vs \r\n) 등 작은 것들이 시간을 잡아먹습니다.

둘째 — 단순한 방법이 항상 옳지는 않다. kie.ai 같은 이미지 API를 쓰면 ComfyUI 연동 없이 바로 이미지 URL을 받을 수 있습니다. 훨씬 단순합니다. 하지만 로컬 ComfyUI를 연동하는 것은 다양한 환경을 유기적으로 연결하는 능력을 기르기 위한 의도적인 선택이었습니다. 비용도 절감되고 커스터마이즈도 자유롭습니다.

셋째 — 파이프라인은 가장 약한 링크만큼만 강하다. 7단계 중 하나가 깨지면 전체가 멈춥니다. 따라서 각 단계의 신뢰성을 높이는 것이 전체 시스템의 신뢰성을 높이는 유일한 방법입니다. 모니터링과 알림이 핵심입니다.

넷째 — 다중 환경 자동화는 좋은 훈련이다. 단일 서버에서 끝나는 자동화와 다르게, 다중 환경 자동화는 네트워크, API 설계, 에러 처리, 운영 모니터링까지 전 영역을 다룹니다. 복잡하지만 그만큼 배우는 것이 많습니다.


전체 시리즈 정리

1편 — 조직도 설계

12명의 에이전트를 4대 서버에 배치하는 멀티에이전트 조직 구조를 설계한 이야기

2편 — 첫 에이전트 만들기

터미널에서 에이전트를 생성하고, OAuth 인증을 마치고, SOUL/IDENTITY/USER를 주입하는 온보딩 흐름

3편 — MD 파일 구조 해부

SOUL, IDENTITY, USER, MEMORY, TOOLS, SKILLS — 에이전트의 뇌를 구성하는 여섯 개 문서의 역할과 작성 원칙

4편 — 다중 환경 파이프라인

Linux → Windows ComfyUI → 호스팅어 → Threads API로 이어지는 네 환경의 자동화 파이프라인 (현재 편)

이 시리즈는 차은별 봇 하나를 만드는 이야기였지만, 그 안에는 멀티에이전트 설계, LLM 컨텍스트 관리, 다중 환경 통합, API 자동화라는 네 가지 핵심 주제가 담겨 있습니다. 에이전트를 직접 만들어보는 것이 이 모든 개념을 가장 빠르게 이해하는 방법입니다.

리도 프로필

리도 인사이트

기술을 현장 언어로 다시 풀어 쓰는 사람

3D 설계, 광통신 인프라 장비 개발, 글로벌 현장 교육을 19년 넘게 다뤄왔고, 요즘은 AI 자동화, 꿈꾸는 카메라, 실무 채널 운영을 연결해 복잡한 일을 더 쉽게 만드는 방법을 기록하고 있습니다.

다음 대화

읽고 끝내지 말고, 실제 문제로 이어가도 좋습니다.

자동화, 설계, 교육, 콘텐츠 중 무엇이든 지금 필요한 문제부터 같이 정리해볼 수 있습니다.

편하게 문의하기