JIN.PROC // v3.1
~/ / 프로젝트 / 2026-04-29-lazyclaude-deep-7-multi-provider

[LazyClaude 심층 7] Multi-provider 워크플로우 — 한 캔버스, 네 개의 CLI

노드의 assignee가 CLI를 정한다. claude · gemini · ollama · codex를 한 그래프에서 조립

DATE 2026.04.29 UPDATED 2026.04.29 READ ~ 3 MIN WORDS 565

LazyClaude 심층 7편. 한 모델에 모든 일을 시키는 시대 는 비싸고 느리다. n8n 스타일 캔버스에 네 가지 프로바이더를 같이 두면, 작업 단위로 맞는 모델 을 고를 수 있다.

왜 멀티 프로바이더인가

각 모델의 강점이 명확히 다르다:

모델 강점 약점
Claude (Opus/Sonnet) 긴 추론, 코드 편집, 도구 사용 비용
Gemini 2.5 Pro 이미지/비디오, 긴 컨텍스트 도구 호출 일관성
Ollama (로컬) 비용 0, 프라이버시 작은 모델 = 약한 추론
Codex / GPT 계열 코드 빠른 생성, 다양한 SDK 도메인 따라 편차

n8n 스타일 워크플로우 캔버스는 노드 를 단위로 작업을 조립한다. 노드마다 다른 모델 을 붙일 수 있다면, 각 단계에 가장 싼/빠른 옵션을 선택할 수 있다.

노드의 assignee 문법

LazyClaude 워크플로우 노드는 assignee 필드 하나로 어떤 CLI를 부를지 정한다.

@claude:opus-4-7
@claude:sonnet-4-6
@gemini:gemini-2.5-pro
@ollama:llama3.1
@codex:o4-mini

@<provider>:<model>. 단순한 문법인데, 라우터는 이걸 보고 다른 바이너리 를 띄운다.

라우터 구현

from dataclasses import dataclass

@dataclass
class Assignee:
    provider: str
    model: str

def parse(a: str) -> Assignee:
    p, m = a.lstrip("@").split(":", 1)
    return Assignee(p, m)

def cli_for(a: Assignee) -> list[str]:
    if a.provider == "claude":
        return ["claude", "--model", a.model, "--print"]
    if a.provider == "gemini":
        return ["gemini", "--model", a.model]
    if a.provider == "ollama":
        return ["ollama", "run", a.model]
    if a.provider == "codex":
        return ["codex", "--model", a.model]
    raise ValueError(f"unknown provider: {a.provider}")

라우팅의 책임은 단 하나: 어떤 binary를 어떤 인자로 띄울지 결정. 그 외 (요청 본문 변환, 응답 파싱) 는 모두 노드 실행기 의 책임.

CLI가 없을 때 — 친절한 fallback

설치되지 않은 CLI를 호출하면 사용자에게 명확한 에러를 줘야 한다. 침묵하지 않는다.

import shutil

def ensure_or_warn(cmd: list[str]) -> list[str]:
    bin_name = cmd[0]
    if shutil.which(bin_name) is None:
        emit_toast(f"'{bin_name}' not installed. Falling back to claude.",
                   level="warn")
        return ["claude", "--print"]
    return cmd

LazyClaude의 정책: 요청한 CLI가 없으면 claude로 fallback + warning 토스트. 워크플로우 자체가 멈추지 않게 하되, 사용자가 모를 수 없게 한다.

프롬프트는 배너로

대화형 REPL CLI(예: gemini, ollama run, codex)는 프로그램이 계속 켜져 있다. 자동으로 종료시키면 사용자가 인터랙션을 잃는다.

LazyClaude의 디자인 결정:

  • 노드 spawn 시 터미널을 연다 (Terminal.app)
  • 프롬프트는 자동 입력하지 않고 배너로 출력
  • 사용자가 그 배너를 보고 직접 붙여 넣거나, 이어서 대화
# emitted as a banner in the spawned terminal
==============================================
NODE: classify-tickets
PROMPT (paste this or it's already on clipboard):

Classify the following tickets into ...
==============================================
$ ollama run llama3.1
>>> _

REPL CLI를 그대로 살려 둠. 프롬프트는 보조 채널로.

워크플로우 그래프의 의미

노드와 엣지로 표현하면 다음을 그래프 그대로 담을 수 있다:

[load CSV]──▶[classify w/ ollama]──▶[summarize w/ claude]──▶[write report w/ codex]

각 단계가 다른 모델 인 게 자연스러워진다. 모든 단계를 Opus로 돌릴 이유 가 없는 것을 시각적으로 알게 한다.

비용/품질 트레이드오프 — 노드 단위

LazyClaude의 Advisor Lab 패턴(다음 편에서 더 다룸)을 응용하면, 같은 노드를 두 모델 로 동시 실행하고 cost/quality delta를 비교할 수 있다:

            ┌─▶[ollama:llama3.1]─┐
[input]─────┤                      ├─▶[delta]
            └─▶[claude:sonnet-4-6]┘

작은 모델로 충분한 단계인지 측정으로 결정한다. 의견이 아니라 데이터.

라이브 상태 — 캔버스의 색깔

워크플로우가 돌고 있을 때 캔버스에 진행 중 이 보여야 한다. LazyClaude는:

  • Running: 노드 펄싱 + ● Running 배지
  • Done: 노드 그린 색
  • Failed: 노드 레드 색 + 에러 토스트

캔버스를 다시 열어도 마지막 run 상태를 자동 복원 — 활성 run은 라이브 폴링, 완료된 run은 one-shot 색상 hydration.

워크플로우의 유지보수

워크플로우 정의는 JSON으로 저장된다. 단순한 스키마:

{
  "id": "wf-2026-04-29-classify",
  "nodes": [
    {"id":"n1","kind":"prompt","assignee":"@claude:sonnet-4-6","prompt":"…"},
    {"id":"n2","kind":"prompt","assignee":"@ollama:llama3.1","prompt":"…"}
  ],
  "edges": [{"from":"n1","to":"n2"}]
}

JSON이라서 git에 들어간다. 워크플로우 자체가 코드처럼 리뷰 가능한 자산이 된다.

한 줄 요약

한 모델에 모든 일을 시키지 마라. 노드 단위 라우팅 이 가장 큰 절약을 만든다.


다음 편: LazyClaude 심층 8 — Hook Detective: 90MB jsonl을 마이닝해 차단의 원인을 찾기 이전 편: LazyClaude 심층 6 — Auto-Resume