comfyui openwebui 랑 연동하여 api 로 이미지 생성하기

comfyui openwebui 랑 연동하여 api 로 이미지 생성하기

comfyui openwebui 랑 연동하여 api 로 이미지 생성하기(우분투,arm64, 도커)

ComfyUI + OpenWebUI API 연동을 통한 이미지 생성 가이드 (Ubuntu, ARM64, Docker)

이 가이드는 Docker 환경에서 ComfyUI와 OpenWebUI를 연동하여 API로 이미지를 생성하는 완전한 설정 방법을 제공합니다.

1. Docker Compose 설정

먼저 필요한 디렉토리 구조와 docker-compose.yml을 설정합니다.

version: '3.8'
services:
  comfyui:
    image: comfyui:arm64  # ARM64 기반 이미지 사용
    container_name: comfyui-server
    restart: unless-stopped
    environment:
      - NVIDIA_VISIBLE_DEVICES=all  # NVIDIA GPU 사용 (지원 시)
    ports:
      - "8188:8188"  # ComfyUI 웹 인터페이스
    volumes:
      - ./comfyui-data/models:/comfyui/models
      - ./comfyui-data/input:/comfyui/input
      - ./comfyui-data/output:/comfyui/output
      - ./comfyui-data/custom_nodes:/comfyui/custom_nodes
    networks:
      - ai-network
    command: >
      python -m comfyui.server
      --listen 0.0.0.0
      --port 8188
      --disable-auto-launch
  openwebui:
    image: ghcr.io/open-webui/open-webui:arm64  # ARM64 버전
    container_name: openwebui
    restart: unless-stopped
    ports:
      - "3000:8080"  # OpenWebUI 웹 인터페이스
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434  # Ollama 연동 (선택)
      - OPENAI_API_KEY=${OPENAI_API_KEY}  # 환경 변수로 설정
    volumes:
      - ./openwebui-data:/app/backend/data
    networks:
      - ai-network
    depends_on:
      - comfyui
networks:
  ai-network:
    driver: bridge
volumes:
  comfyui-models:
  openwebui-data:

2. ComfyUI ARM64 Docker 이미지 빌드

ARM64 시스템(예: Raspberry Pi, 다른 ARM 보드)에서 ComfyUI를 실행하려면 커스텀 Dockerfile이 필요합니다.

# Dockerfile.comfyui (ComfyUI ARM64)
FROM python:3.11-slim-bullseye
# ARM64 의존성 설치
RUN apt-get update && apt-get install -y \
    git \
    build-essential \
    libssl-dev \
    libffi-dev \
    wget \
    curl \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /comfyui
# ComfyUI 저장소 클론
RUN git clone https://github.com/comfyanonymous/ComfyUI.git /comfyui
# Python 의존성 설치 (ARM64 호환성)
RUN pip install --no-cache-dir \
    torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 \
    numpy \
    pillow \
    scipy \
    scikit-image \
    opencv-python-headless \
    aiohttp \
    websockets
# 포트 노출
EXPOSE 8188
# 기본 커맨드
CMD ["python", "-m", "comfyui.server", "--listen", "0.0.0.0", "--port", "8188"]

빌드 명령:

docker build -f Dockerfile.comfyui -t comfyui:arm64 .

3. ComfyUI API를 통한 이미지 생성

ComfyUI API 호출을 위한 Python 스크립트:

# generate_image.py
import requests
import json
import time
import base64
from pathlib import Path
from typing import Dict, Optional
class ComfyUIClient:
    def __init__(self, base_url: str = "http://localhost:8188"):
        self.base_url = base_url
        self.api_url = f"{base_url}/api"
        
    def get_system_stats(self) -> Dict:
        """시스템 상태 조회"""
        response = requests.get(f"{self.api_url}/system")
        return response.json()
    
    def queue_prompt(self, workflow: Dict) -> str:
        """프롬프트 큐에 추가"""
        response = requests.post(
            f"{self.api_url}/prompt",
            json=workflow
        )
        data = response.json()
        return data.get("prompt_id")
    
    def get_history(self, prompt_id: str) -> Dict:
        """실행 결과 조회"""
        response = requests.get(f"{self.api_url}/history/{prompt_id}")
        return response.json()
    
    def wait_for_completion(self, prompt_id: str, timeout: int = 300) -> Optional[Dict]:
        """작업 완료 대기"""
        start_time = time.time()
        
        while time.time() - start_time < timeout:
            history = self.get_history(prompt_id)
            
            if prompt_id in history:
                return history[prompt_id]
            
            time.sleep(1)
        
        raise TimeoutError(f"작업이 {timeout}초 내에 완료되지 않음")
    
    def download_image(self, filename: str, subfolder: str = "output") -> bytes:
        """생성된 이미지 다운로드"""
        response = requests.get(
            f"{self.api_url}/view",
            params={"filename": filename, "subfolder": subfolder},
            stream=True
        )
        return response.content
def create_txt2img_workflow(
    prompt: str,
    negative_prompt: str = "",
    steps: int = 20,
    cfg_scale: float = 7.5,
    width: int = 512,
    height: int = 512,
    seed: int = 0,
    checkpoint: str = "sd15.safetensors"
) -> Dict:
    """텍스트-이미지 워크플로우 생성"""
    
    workflow = {
        "1": {
            "inputs": {
                "seed": seed,
                "steps": steps,
                "cfg": cfg_scale,
                "sampler_name": "euler",
                "scheduler": "normal",
                "denoise": 1,
                "model": ["4", 0],
                "positive": ["3", 0],
                "negative": ["3", 1],
                "latent_image": ["5", 0]
            },
            "class_type": "KSampler",
            "_meta": {"title": "KSampler"}
        },
        "3": {
            "inputs": {
                "text": prompt,
                "clip": ["4", 1]
            },
            "class_type": "CLIPTextEncode",
            "_meta": {"title": "CLIP Text Encode (Prompt)"}
        },
        "4": {
            "inputs": {
                "ckpt_name": checkpoint
            },
            "class_type": "CheckpointLoaderSimple",
            "_meta": {"title": "Load Checkpoint"}
        },
        "5": {
            "inputs": {
                "width": width,
                "height": height,
                "length": 1,
                "batch_size": 1
            },
            "class_type": "EmptyLatentImage",
            "_meta": {"title": "Empty Latent Image"}
        },
        "8": {
            "inputs": {
                "samples": ["1", 0],
                "vae": ["4", 2]
            },
            "class_type": "VAEDecode",
            "_meta": {"title": "VAE Decode"}
        },
        "9": {
            "inputs": {
                "filename_prefix": "ComfyUI",
                "images": ["8", 0]
            },
            "class_type": "SaveImage",
            "_meta": {"title": "Save Image"}
        }
    }
    
    # Negative prompt 추가 (있는 경우)
    if negative_prompt:
        workflow["3"]["inputs"]["text"] = {
            "positive": prompt,
            "negative": negative_prompt
        }
    
    return workflow
# 사용 예제
if __name__ == "__main__":
    client = ComfyUIClient(base_url="http://localhost:8188")
    
    # 시스템 상태 확인
    print("ComfyUI 시스템 상태:", client.get_system_stats())
    
    # 워크플로우 생성
    workflow = create_txt2img_workflow(
        prompt="a beautiful landscape with mountains",
        negative_prompt="blurry, low quality",
        steps=30,
        cfg_scale=7.5,
        width=768,
        height=512
    )
    
    # 프롬프트 큐에 추가
    prompt_id = client.queue_prompt(workflow)
    print(f"작업 ID: {prompt_id}")
    
    # 완료 대기
    result = client.wait_for_completion(prompt_id)
    print(f"완료됨: {result}")
    
    # 이미지 다운로드
    if result and "outputs" in result:
        for node_id, output in result["outputs"].items():
            if "images" in output:
                for image in output["images"]:
                    image_data = client.download_image(image["filename"])
                    with open(f"output_{image['filename']}", "wb") as f:
                        f.write(image_data)
                    print(f"이미지 저장됨: output_{image['filename']}")

4. OpenWebUI와 ComfyUI 연동

OpenWebUI에서 ComfyUI API를 호출하도록 설정합니다.

# .env 파일 (docker-compose와 같은 디렉토리)
OPENAI_API_KEY=your_api_key_here
COMFYUI_API_URL=http://comfyui:8188

OpenWebUI 커스텀 필터 스크립트:

# openwebui_comfyui_filter.py
from typing import Optional
import requests
import json
class ToolFilter:
    def __init__(self):
        self.comfyui_url = "http://comfyui:8188"
    
    async def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
        """OpenWebUI 요청 전 처리"""
        return body
    
    async def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
        """OpenWebUI 응답 후 처리 - 이미지 생성 요청 감지"""
        
        # 사용자 메시지에서 이미지 생성 키워드 감지
        if "generate image" in body.get("content", "").lower():
            try:
                # ComfyUI API 호출
                workflow = self._create_workflow(body.get("content"))
                response = requests.post(
                    f"{self.comfyui_url}/api/prompt",
                    json=workflow
                )
                
                if response.status_code == 200:
                    prompt_id = response.json().get("prompt_id")
                    body["content"] += f"\n\n[Image generation started: {prompt_id}]"
            
            except Exception as e:
                body["content"] += f"\n\n[Error: {str(e)}]"
        
        return body
    
    def _create_workflow(self, prompt: str) -> dict:
        """프롬프트에서 워크플로우 생성"""
        return {
            # 워크플로우 JSON (위 코드 참고)
        }

5. 실행 및 배포

컨테이너 시작

# docker-compose 실행
docker-compose up -d
# 로그 확인
docker-compose logs -f comfyui
docker-compose logs -f openwebui
# ComfyUI 접속: http://localhost:8188
# OpenWebUI 접속: http://localhost:3000

헬스 체크

# ComfyUI 상태 확인
curl http://localhost:8188/api/system
# OpenWebUI 상태 확인
curl http://localhost:3000/api/status

6. ARM64 최적화 팁

성능 개선:

# GPU 메모리 할당 최적화 (docker-compose에 추가)
environment:
  - PYTORCH_ENABLE_MPS_FALLBACK=1
  - OMP_NUM_THREADS=4
# 리소스 제한 설정
deploy:
  resources:
    limits:
      cpus: '4'
      memory: 4G
    reservations:
      memory: 2G

모델 최적화:

  • INT8/INT4 양자화 모델 사용
  • 낮은 해상도(256x256, 512x512)로 시작
  • 스텝 수 감소 (15-20 스텝)
  • CPU 기반 추론은 느릴 수 있으므로 GPU 가속 권장
    이 설정으로 Ubuntu ARM64 도커 환경에서 ComfyUI와 OpenWebUI를 완벽하게 연동할 수 있습니다!
개인정보보호링크