OpenClaude: Claude Code 소스 유출로 탄생한 멀티 LLM CLI 도구

서론

2025년 3월, npm 레지스트리에 게시된 Claude Code 패키지에서 치명적인 설정 실수가 발견되었습니다. 개발자들이 소스맵(Source Map)을 포함한 채로 번들링된 코드를 배포한 것입니다. 이로 인해 난독화된 JavaScript 코드의 원본이 그대로 노출되었고, 보안 연구자들은 순식간에 Claude Code의 내부 아키텍처를 들여다볼 수 있게 되었습니다.

하지만 이 사건은 단순한 보안 사고로 끝나지 않았습니다. 오픈소스 커뮤니티는 이 기회를 놓치지 않았습니다. 유출된 코드를 분석한 개발자들은 Claude Code의 세련된 CLI 인터페이스가 사실상 모델에 구애받지 않는 구조라는 점을 발견했습니다. 그리고 단 며칠 만에 OpenClaude 프로젝트가 탄생했습니다.

왜 이것이 중요할까요? 현재 AI 개발자들은 선택의 딜레마에 직면해 있습니다. Claude는 뛰어난 코딩 능력을 가지고 있지만, GPT-4o는 멀티모달 처리에 강점이 있고, DeepSeek은 비용 효율적이며, 로컬 Llama는 프라이버시를 보장합니다. 각 모델마다 고유한 강점이 있지만, 도구는 파편화되어 있습니다. OpenClaude는 이 문제를 우아하게 해결합니다. 하나의 인터페이스에서 200개 이상의 모델을 자유롭게 교체하며 사용할 수 있는 세상이 열린 것입니다.

기술적 배경: Claude Code의 아키텍처

Claude Code가 어떻게 다중 모델을 지원할 수 있었는지 이해하려면, 먼저 그 내부 구조를 살펴봐야 합니다. 유출된 소스코드 분석을 통해 밝혀진 핵심은 Provider Abstraction Layer였습니다.

아키텍처 개요

Claude Code는 처음부터 확장 가능한 구조로 설계되었습니다. 핵심 컴포넌트는 다음과 같습니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
graph TD
    A[CLI Interface] --> B[Prompt Manager]
    B --> C[Context Handler]
    C --> D[Provider Shim]
    D --> E[Claude API]
    D --> F[OpenAI Compatible APIs]
    F --> G[GPT-4o]
    F --> H[Gemini]
    F --> I[DeepSeek]
    F --> J[Ollama/Local]

이 다이어그램이 보여주는 것은 Claude Code의 핵심 설계 철학입니다. CLI 인터페이스, 프롬프트 관리, 컨텍스트 처리는 모델과 완전히 분리되어 있습니다. Provider Shim이 바로 이 분리를 가능하게 하는 핵심 계층입니다.

Provider Shim의 작동 원리

Provider Shim은 서로 다른 API 스펙을 통합된 인터페이스로 변환하는 어댑터 패턴의 구현입니다. Anthropic의 Messages API와 OpenAI의 Chat Completions API는 구조적 차이가 있습니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Anthropic Messages API 형식
{
    "model": "claude-3-opus-20240229",
    "messages": [{"role": "user", "content": "Hello"}],
    "max_tokens": 1024,
    "system": "You are a helpful assistant."
}

# OpenAI Chat Completions API 형식
{
    "model": "gpt-4o",
    "messages": [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello"}
    ],
    "max_tokens": 1024
}

OpenClaude의 Provider Shim은 이러한 차이를 런타임에 해결합니다. System 프롬프트의 위치, 토큰 제한 파라미터, 스트리밍 포맷 등을 자동으로 변환합니다.

OpenClaude 설치 및 설정 가이드

Step 1: 환경 준비

OpenClaude는 Node.js 기반으로 작성되어 있습니다. 먼저 필요한 의존성을 설치합니다:

1
2
3
4
5
6
7
8
# Node.js 18+ 필요
node --version  # v18.0.0 이상 권장

# OpenClaude 클론 및 설치
git clone https://github.com/openclaude-ai/openclaude.git
cd openclaude
npm install
npm run build

Step 2: Provider 설정

OpenClaude의 핵심은 providers.json 설정 파일입니다. 여기에 사용할 모델들의 엔드포인트와 인증 정보를 정의합니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "providers": {
    "openai": {
      "base_url": "https://api.openai.com/v1",
      "api_key_env": "OPENAI_API_KEY",
      "models": ["gpt-4o", "gpt-4o-mini", "o1-preview"]
    },
    "anthropic": {
      "base_url": "https://api.anthropic.com/v1",
      "api_key_env": "ANTHROPIC_API_KEY",
      "models": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229"]
    },
    "deepseek": {
      "base_url": "https://api.deepseek.com/v1",
      "api_key_env": "DEEPSEEK_API_KEY",
      "models": ["deepseek-chat", "deepseek-coder"]
    },
    "ollama": {
      "base_url": "http://localhost:11434/v1",
      "api_key": "ollama",
      "models": ["llama3.2", "codellama", "mistral"]
    }
  },
  "default_provider": "anthropic",
  "default_model": "claude-3-5-sonnet-20241022"
}

Step 3: 환경 변수 설정

API 키들은 환경 변수로 관리합니다:

1
2
3
4
5
# ~/.bashrc 또는 ~/.zshrc에 추가
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."
export DEEPSEEK_API_KEY="sk-..."
export GEMINI_API_KEY="..."

Step 4: 실행 및 모델 전환

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 기본 모델로 실행
openclaude

# 특정 모델 지정
openclaude --model gpt-4o

# 로컬 Llama 사용
openclaude --provider ollama --model llama3.2

# 대화형 모델 전환 (런타임)
# /model deepseek-coder
# /provider openai

모델별 성능 비교 및 사용 시나리오

실제 개발 워크플로우에서 어떤 모델을 선택해야 할까요? 벤치마크와 실무 경험을 종합한 가이드입니다:

| 모델 | 코딩 능력 | 추론력 | 속도 | 비용 | 최적 사용 시나리오 | | :— | :—: | :—: | :—: | :—: | :— | | Claude 3.5 Sonnet | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 빠름 | 높음 | 복잡한 리팩토링, 아키텍처 설계 | | GPT-4o | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 빠름 | 높음 | 멀티모달 작업, 이미지 분석 | | DeepSeek V3 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 보통 | 낮음 | 대규모 코드베이스 분석 | | Llama 3.2 (로컬) | ⭐⭐⭐ | ⭐⭐⭐ | 느림 | 무료 | 기밀 코드, 오프라인 환경 | | Gemini 2.0 Flash | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 매우 빠름 | 보통 | 빠른 프로토타이핑 |

실제 사용 패턴

개인적으로 저는 다음과 같은 워크플로우를 사용합니다:

  1. 초기 탐색: Gemini 2.0 Flash로 빠르게 아이디어 검증 2. 심층 개발: Claude 3.5 Sonnet으로 복잡한 로직 구현 3. 코드 리뷰: DeepSeek으로 대규모 변경사항 분석 4. 기밀 작업: 로컬 Llama로 민감한 코드 처리

심화: 커스텀 Provider 구현하기

OpenClaude의 진정한 강점은 확장성입니다. OpenAI 호환 API를 제공하는 어떤 서비스든 Provider로 등록할 수 있습니다.

커스텀 Provider 예시

다음은 커스텀 Provider를 구현하는 TypeScript 코드입니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// custom-provider.ts
import { BaseProvider, ChatMessage, ChatResponse } from 'openclaude-core';

interface CustomProviderConfig {
  baseUrl: string;
  apiKey: string;
  modelMapping: Record<string, string>;
}

export class CustomProvider extends BaseProvider {
  private config: CustomProviderConfig;

  constructor(config: CustomProviderConfig) {
    super();
    this.config = config;
  }

  async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> {
    // OpenAI 호환 포맷으로 변환
    const payload = {
      model: this.config.modelMapping[options?.model || 'default'],
      messages: this.normalizeMessages(messages),
      temperature: options?.temperature ?? 0.7,
      max_tokens: options?.maxTokens ?? 4096,
      stream: options?.stream ?? false
    };

    const response = await fetch(`${this.config.baseUrl}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.config.apiKey}`
      },
      body: JSON.stringify(payload)
    });

    return this.parseResponse(await response.json());
  }

  private normalizeMessages(messages: ChatMessage[]): any[] {
    return messages.map(msg => {
      // System 메시지를 첫 번째 user 메시지 앞으로 이동
      if (msg.role === 'system') {
        return { role: 'system', content: msg.content };
      }
      return msg;
    });
  }

  private parseResponse(raw: any): ChatResponse {
    return {
      id: raw.id,
      content: raw.choices[0].message.content,
      model: raw.model,
      usage: {
        promptTokens: raw.usage.prompt_tokens,
        completionTokens: raw.usage.completion_tokens
      }
    };
  }
}

// 등록
import { ProviderRegistry } from 'openclaude-core';

ProviderRegistry.register('custom', new CustomProvider({
  baseUrl: 'https://api.custom-llm.com/v1',
  apiKey: process.env.CUSTOM_API_KEY!,
  modelMapping: {
    'default': 'custom-model-v1',
    'advanced': 'custom-model-v2'
  }
}));

API 호환성 레이어 심층 분석

OpenClaude가 200개 이상의 모델을 지원할 수 있는 이유는 OpenAI Chat Completions API가 사실상의 표준(de facto standard)이 되었기 때문입니다. 하지만 완벽한 호환성을 위해서는 여전히 해결해야 할 미묘한 차이들이 있습니다.

주요 호환성 이슈와 해결책

1
2
3
4
5
6
7
8
9
graph LR
    A[Claude Code Request] --> B[Provider Shim]
    B --> C{API Type Detection}
    C --> D[Anthropic Format]
    C --> E[OpenAI Format]
    D --> F[System Prompt Injection]
    E --> G[Direct Pass]
    F --> H[Model Endpoint]
    G --> H

1. System Prompt 처리

Anthropic API는 system 파라미터를 최상위 레벨에 두지만, OpenAI는 messages 배열 내에 role: "system" 메시지를 포함합니다. OpenClaude는 이를 자동으로 감지하고 변환합니다.

2. 토큰 카운팅

각 모델은 서로 다른 토크나이저를 사용합니다. OpenClaude는 tiktoken을 사용하여 대략적인 토큰 수를 추정하고, 컨텍스트 윈도우 초과를 방지합니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import tiktoken

def estimate_tokens(text: str, model: str) -> int:
    """모델별 토큰 수 추정"""
    encoding_name = {
        "gpt-4": "cl100k_base",
        "gpt-3.5": "cl100k_base",
        "claude": "cl100k_base",  # 근사치
    }.get(model, "cl100k_base")
    
    encoding = tiktoken.get_encoding(encoding_name)
    return len(encoding.encode(text))

def truncate_context(messages: list, max_tokens: int, model: str) -> list:
    """컨텍스트 윈도우에 맞게 메시지 잘라내기"""
    total = 0
    truncated = []
    
    for msg in reversed(messages):  # 최신 메시지 우선
        tokens = estimate_tokens(msg['content'], model)
        if total + tokens > max_tokens:
            break
        truncated.insert(0, msg)
        total += tokens
    
    return truncated

3. 스트리밍 포맷

SSE(Server-Sent Events)의 이벤트 포맷이 다릅니다:

1
2
3
4
5
6
7
8
9
# OpenAI 스트림
data: {"choices":[{"delta":{"content":"Hello"}}]}
data: [DONE]

# Anthropic 스트림  
event: content_block_delta
data: {"delta":{"text":"Hello"}}
event: message_stop
data: {}

OpenClaude의 스트림 정규화 레이어가 이를 통합합니다.

실무 활용 사례

사례 1: 비용 최적화 파이프라인

한 스타트업에서는 OpenClaude를 활용하여 개발 비용을 60% 절감했습니다. 전략은 간단합니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# cost-optimization-rules.yaml
rules:
  - name: "Simple queries to cheaper model"
    condition:
      max_tokens: 100
      complexity: "low"
    action:
      provider: "deepseek"
      model: "deepseek-chat"
      
  - name: "Complex reasoning to Claude"
    condition:
      keywords: ["architecture", "design", "refactor"]
    action:
      provider: "anthropic"
      model: "claude-3-5-sonnet"
      
  - name: "Code review to GPT-4o"
    condition:
      task: "code_review"
    action:
      provider: "openai"
      model: "gpt-4o"

사례 2: 오프라인 개발 환경

보안 규정이 엄격한 금융권에서는 Ollama를 통한 로컬 모델 실행이 필수적입니다:

1
2
3
4
5
6
7
# 로컬 모델 설정
openclaude config set provider ollama
openclaude config set model codellama:34b
openclaude config set base_url http://localhost:11434/v1

# 이제 모든 처리가 로컬에서 이루어짐
openclaude "이 코드의 취약점을 분석해줘"

결론

OpenClaude는 우연히 탄생했지만, 그 영향은 의도치 않게 AI 개발 도구의 새로운 패러다임을 보여줍니다. 하나의 인터페이스, 다중 백엔드 — 이것이 앞으로의 표준이 될 것입니다.

핵심 요약을 정리하면:

  1. 아키텍처의 승리: Claude Code의 Provider Abstraction 설계가 다중 모델 지원을 가능하게 했습니다 2. 표준의 힘: OpenAI Chat Completions API가 생태계의 표준으로 자리잡았습니다 3. 개발자 자율성: 이제 모델 선택은 비즈니스 요구사항에 맞게 유연하게 결정할 수 있습니다 4. 비용 효율성: 작업 복잡도에 따라 모델을 선택적으로 사용하여 비용을 최적화할 수 있습니다

전문가 관점에서 볼 때, OpenClaude는 단순한 도구를 넘어 LLM 벤더 락인(Lock-in) 문제의 해결책을 제시합니다. 기업들은 이제 특정 모델 제공자에 종속되지 않고, 비용, 성능, 규정 요구사항에 따라 자유롭게 모델을 교체할 수 있습니다.

앞으로의 전망은 더욱 흥미롭습니다. 멀티 모델 라우팅, 자동 모델 선택, A/B 테스트 등의 고급 기능들이 OpenClaude 위에 구축될 것입니다. 그리고 이 모든 것이 하나의 CLI 인터페이스에서 이루어집니다.


참고 자료


출처: https://news.hada.io/topic?id=28115

Hugo로 만듦
JimmyStack 테마 사용 중