> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 써드파티 통합 기술 가이드

>  `@aihubmix/ai-sdk-provider`, `dify-plugin-aihubmix`, `cherry-studio` 프로젝트의 핵심 구현 로직을 기반으로, 이 문서는 개발자들이 aihubmix 통합 게이트웨이를 빠르게 통합할 수 있도록 기술 가이드를 제공합니다. 

## 1. 핵심 통합 패턴

### 1.1 통합 인증 및 라우팅

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Core logic: Replace API Key, Base URL, add APP-Code
    const config = {
      apiKey: 'your-aihubmix-api-key',  // Replace with aihubmix API Key
      baseURL: 'https://aihubmix.com',   // Replace with aihubmix gateway
      headers: {
        'APP-Code': 'App Code'  // App Code can be obtained from https://aihubmix.com/appstore
      }
    };

    // Model routing rules
    function routeModel(modelName: string) {
      if (modelName.startsWith('claude')) {
        // Claude models: Use Anthropic SDK
        return 'anthropic';
      } else if (modelName.startsWith('gemini') && !modelName.endsWith('-nothink') && !modelName.endsWith('-search')) {
        // Gemini models: Use Google SDK, endpoint https://aihubmix.com/gemini
        return 'gemini';
      } else {
        // Other models: Use OpenAI compatible interface
        return 'openai';
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Core logic: Replace API Key, Base URL, add APP-Code
    credentials = {
        "api_key": "your-aihubmix-api-key",  # Replace with aihubmix API Key
        "base_url": "https://aihubmix.com",   # Replace with aihubmix gateway
        "extra_headers": {
            "APP-Code": "App Code"  # App Code can be obtained from https://aihubmix.com/appstore
        }
    }

    # Model routing rules
    def route_model(model_name: str):
        if model_name.startswith("claude"):
            # Claude models: Use Anthropic SDK
            return "anthropic"
        elif model_name.startswith("gemini") and not model_name.endswith(("-nothink", "-search")):
            # Gemini models: Use Google SDK, endpoint https://aihubmix.com/gemini
            return "gemini"
        else:
            # Other models: Use OpenAI compatible interface
            return "openai"
    ```
  </Tab>
</Tabs>

### 1.2 특수 처리 사항

* **빈 Tools 수정**: `tools=[]`이고 `tool_choice`가 존재하는 경우 자동으로 `tool_choice` 제거
* **파일 확장자**: `mediaType`에 따라 올바른 파일 확장자 자동 설정
* **캐시 제어**: `<cache>` 태그를 통한 캐시 제어 지원

## 2. 통합 구현

### 2.1 핵심 클라이언트 래퍼

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    class AihubmixModelClient {
      private config: {
        apiKey: string;
        baseURL: string;
        appCode: string;
      };

      constructor(apiKey: string) {
        this.config = {
          apiKey,
          baseURL: 'https://aihubmix.com',
          appCode: 'App Code can be obtained from https://aihubmix.com/appstore'
        };
      }

      async chatCompletion(model: string, messages: any[], options: any = {}) {
        // Automatically route to corresponding SDK based on model name
        if (model.startsWith('claude')) {
          return this.claudeCompletion(model, messages, options);
        } else if (model.startsWith('gemini')) {
          return this.geminiCompletion(model, messages, options);
        } else {
          return this.openaiCompletion(model, messages, options);
        }
      }

      private async claudeCompletion(model: string, messages: any[], options: any) {
        const { Anthropic } = await import('@anthropic-ai/sdk');
        const client = new Anthropic({
          apiKey: this.config.apiKey,
          baseURL: this.config.baseURL,
          defaultHeaders: { 'APP-Code': this.config.appCode }
        });
        return client.messages.create({ model, messages, ...options });
      }

      private async geminiCompletion(model: string, messages: any[], options: any) {
        const { GoogleGenerativeAI } = await import('@google/generative-ai');
        const genAI = new GoogleGenerativeAI(this.config.apiKey, {
          baseURL: `${this.config.baseURL}/gemini/v1beta`,
          defaultHeaders: { 'APP-Code': this.config.appCode }
        });
        const genModel = genAI.getGenerativeModel({ model });
        return genModel.generateContent(messages);
      }

      private async openaiCompletion(model: string, messages: any[], options: any) {
        const OpenAI = await import('openai');
        const client = new OpenAI.default({
          apiKey: this.config.apiKey,
          baseURL: `${this.config.baseURL}/v1`,
          defaultHeaders: { 'APP-Code': this.config.appCode }
        });
        return client.chat.completions.create({ model, messages, ...options });
      }
    }

    // Usage example
    const client = new AihubmixModelClient('your-aihubmix-api-key');
    await client.chatCompletion('gpt-4o-mini', messages);
    await client.chatCompletion('claude-3-5-sonnet-20241022', messages);
    await client.chatCompletion('gemini-2.5-flash', messages);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    class AihubmixModelClient:
        def __init__(self, api_key: str):
            self.api_key = api_key
            self.base_url = "https://aihubmix.com"
            self.headers = {
                "Authorization": f"Bearer {api_key}",
                "APP-Code": "App Code can be obtained from https://aihubmix.com/appstore"
            }
      
        def chat_completion(self, model: str, messages: list, **kwargs):
            """Unified chat completion interface"""
            if model.startswith("claude"):
                return self._claude_completion(model, messages, **kwargs)
            elif model.startswith("gemini"):
                return self._gemini_completion(model, messages, **kwargs)
            else:
                return self._openai_completion(model, messages, **kwargs)
      
        def _claude_completion(self, model: str, messages: list, **kwargs):
            import anthropic
            client = anthropic.Anthropic(
                api_key=self.api_key,
                base_url=self.base_url,
                extra_headers={"APP-Code": "App Code can be obtained from https://aihubmix.com/appstore"}
            )
            return client.messages.create(model=model, messages=messages, **kwargs)
      
        def _gemini_completion(self, model: str, messages: list, **kwargs):
            import google.generativeai as genai
            genai.configure(
                api_key=self.api_key,
                client_options={"api_endpoint": f"{self.base_url}/gemini/v1beta"}
            )
            genai._client._http_client._session.headers.update({"APP-Code": "App Code can be obtained from https://aihubmix.com/appstore"})
            model_instance = genai.GenerativeModel(model)
            return model_instance.generate_content(messages)
      
        def _openai_completion(self, model: str, messages: list, **kwargs):
            import openai
            client = openai.OpenAI(
                api_key=self.api_key,
                base_url=f"{self.base_url}/v1",
                extra_headers={"APP-Code": "App Code can be obtained from https://aihubmix.com/appstore"}
            )
            return client.chat.completions.create(model=model, messages=messages, **kwargs)

    # Usage example
    client = AihubmixModelClient("your-aihubmix-api-key")
    await client.chat_completion("gpt-4o-mini", messages)
    await client.chat_completion("claude-3-5-sonnet-20241022", messages)
    await client.chat_completion("gemini-2.5-flash", messages)
    ```
  </Tab>
</Tabs>

### 2.2 특수 처리 및 유틸리티 함수

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Empty tools fix
    function fixToolChoice(requestBody: any): any {
      if (requestBody.tools?.length === 0 && requestBody.tool_choice) {
        delete requestBody.tool_choice;
      }
      return requestBody;
    }

    // File extension mapping
    function setFileExtension(mediaType: string): string {
      const mimeToExt: Record<string, string> = {
        'audio/mpeg': 'mp3', 'audio/wav': 'wav', 'audio/flac': 'flac'
      };
      return mimeToExt[mediaType] || 'bin';
    }

    // Cache control
    function processCacheTags(content: string): { content: string; cacheControl?: any } {
      if (content.includes('<cache>')) {
        return { content: content.replace('<cache>', ''), cacheControl: { type: 'ephemeral' } };
      }
      return { content };
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Empty tools fix
    def fix_tool_choice(request_body: dict) -> dict:
        if request_body.get("tools") == [] and "tool_choice" in request_body:
            del request_body["tool_choice"]
        return request_body

    # File extension mapping
    def set_file_extension(media_type: str) -> str:
        mime_to_ext = {
            'audio/mpeg': 'mp3', 'audio/wav': 'wav', 'audio/flac': 'flac'
        }
        return mime_to_ext.get(media_type, 'bin')

    # Cache control
    def process_cache_tags(content: str):
        if "<cache>" in content:
            return {
                "content": content.replace("<cache>", ""),
                "cache_control": {"type": "ephemeral"}
            }
        return {"content": content}
    ```
  </Tab>
</Tabs>

## 3. 배포 및 구성

### 3.1 환경 변수

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const config = {
      apiKey: process.env.AIHUBMIX_API_KEY || '',
      baseURL: process.env.AIHUBMIX_BASE_URL || 'https://aihubmix.com',
      appCode: process.env.AIHUBMIX_APP_CODE || 'App Code can be obtained from https://aihubmix.com/appstore'
    };
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os

    config = {
        "api_key": os.getenv("AIHUBMIX_API_KEY", ""),
        "base_url": os.getenv("AIHUBMIX_BASE_URL", "https://aihubmix.com"),
        "app_code": os.getenv("AIHUBMIX_APP_CODE", "App Code can be obtained from https://aihubmix.com/appstore")
    }
    ```
  </Tab>
</Tabs>

### 3.2 오류 처리

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    class AihubmixError extends Error {
      constructor(message: string, public code?: string, public status?: number) {
        super(message);
        this.name = 'AihubmixError';
      }
    }

    function handleAihubmixErrors(error: any): AihubmixError {
      const message = error.message || 'Unknown error';
      if (message.toLowerCase().includes('rate limit')) {
        return new AihubmixError('Rate limit exceeded', 'RATE_LIMIT', 429);
      } else if (message.toLowerCase().includes('unauthorized')) {
        return new AihubmixError('Authentication failed', 'AUTH_ERROR', 401);
      } else {
        return new AihubmixError(message, error.code, error.status);
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    class AihubmixError(Exception):
        def __init__(self, message: str, code: str = None, status: int = None):
            super().__init__(message)
            self.code = code
            self.status = status

    def handle_aihubmix_errors(e: Exception) -> AihubmixError:
        message = str(e).lower()
        if "rate limit" in message:
            return AihubmixError("Rate limit exceeded", "RATE_LIMIT", 429)
        elif "unauthorized" in message:
            return AihubmixError("Authentication failed", "AUTH_ERROR", 401)
        else:
            return AihubmixError(f"Request failed: {e}")
    ```
  </Tab>
</Tabs>

## 4. 참조 구현 및 정합성 체크리스트

### 4.1 cherry-studio 클라이언트 참조 (TypeScript)

`cherry-studio`의 `AihubmixAPIClient.ts`의 다음 핵심 요점은 TypeScript 측에서 aihubmix와 통합하는 써드파티 프론트엔드/데스크톱 애플리케이션의 적용 패턴으로 사용할 수 있습니다:

* **통합 할인 코드 추가**: Provider 수준에서 `extra_headers`를 병합하고 `APP-Code`를 설정 (프로젝트에서 `MLTG2087` 사용)
* **다중 클라이언트 라우팅**:
  * `claude*` → Anthropic 클라이언트 사용
  * `-nothink`/`-search`로 끝나지 않고 `embedding`을 포함하지 않는 `gemini*`/`imagen*` → Gemini 클라이언트 사용 (`apiHost: https://aihubmix.com/gemini`)
  * OpenAI 시리즈 (`gpt-oss` 제외) → OpenAI 호환 response 클라이언트 사용
  * 기타 → 기본 OpenAI 클라이언트로 대체
* **BaseURL 검색**: 현재 라우팅된 특정 클라이언트에서 내보내, 각 공급자에 대한 엔드포인트 차이 유지

### 4.2 dify-plugin-aihubmix 참조 (Python)

`dify-plugin-aihubmix` 구현의 다음 핵심 요점은 Python 도구가 aihubmix와 통합하는 써드파티의 적용 패턴으로 사용할 수 있습니다:

* **통합 할인 코드 추가**: Provider 수준에서 `extra_headers`를 병합하고 `APP-Code`를 설정 (프로젝트에서 `Dify2025` 사용)
* **다중 클라이언트 라우팅**:
  * `claude*` → Anthropic 클라이언트 사용
  * `-nothink`/`-search`로 끝나지 않고 `embedding`을 포함하지 않는 `gemini*`/`imagen*` → Gemini 클라이언트 사용 (`apiHost: https://aihubmix.com/gemini`)
  * OpenAI 시리즈 (`gpt-oss` 제외) → OpenAI 호환 response 클라이언트 사용
  * 기타 → 기본 OpenAI 클라이언트로 대체
* **BaseURL 검색**: 현재 라우팅된 특정 클라이언트에서 내보내, 각 공급자에 대한 엔드포인트 차이 유지

### 4.3 정합성 체크리스트

* Provider 진입점에서 `extra_headers`를 통합 병합하고 `APP-Code`를 주입
* Gemini 클라이언트는 `https://aihubmix.com/gemini`를 `apiHost`로 사용
* `claude*`, `gemini*/imagen*`, OpenAI 시리즈 (`gpt-oss` 제외)와 일치하는 라우팅 규칙
* 기본적으로 OpenAI 클라이언트로 대체하여, OpenAI 호환 인터페이스 동작 유지
* `getBaseURL()`은 항상 현재 라우팅된 클라이언트에서 내보내어 하드코딩 방지

## 5. 마이그레이션 체크리스트

* API Key를 aihubmix API Key로 교체
* Base URL을 `https://aihubmix.com`로 교체
* 할인을 위해 `APP-Code` 헤더 추가
* 모델 라우팅 로직 구현 (claude/gemini/openai)
* tools가 비어 있을 때 `tool_choice` 수정 처리
* 파일 업로드를 위한 MIME 타입 처리 구성
* 다양한 모델 호출 테스트

***

마지막 업데이트: 2026-06-01
