모델 제공자 플러그인 구축
모델 제공자 플러그인은 Hermes가 AIAgent 호출을 라우팅할 수 있는 추론 백엔드(OpenAI 호환 엔드포인트, Anthropic 메시지 서버, Codex 스타일 응답 API 또는 Bedrock 기본 표면)를 선언합니다. 모든 내장 제공자(OpenRouter, Anthropic, GMI, DeepSeek, Nvidia 등)는 이러한 플러그인 중 하나로 제공됩니다. 제3자는 저장소를 변경하지 않고 $HERMES_HOME/plugins/model-providers/ 아래에 디렉터리를 삭제하여 자신만의 디렉터리를 추가할 수 있습니다.
모델 제공자 플러그인은 제공자 플러그인의 세 번째 종류입니다. 다른 것들은 메모리 제공자 플러그인(교차 세션 지식) 및 컨텍스트 엔진 플러그인(컨텍스트 압축 전략)입니다. 세 가지 모두 동일한 "디렉토리 삭제, 프로필 선언, 저장소 편집 없음" 패턴을 따릅니다.
검색 작동 방식
providers/__init__.py._discover_providers()은 코드가 get_provider_profile() 또는 list_providers()을 처음 호출할 때 느리게 실행됩니다. 검색 순서:
- 번들 플러그인 —
<repo>/plugins/model-providers/<name>/— Hermes와 함께 제공 - 사용자 플러그인 —
$HERMES_HOME/plugins/model-providers/<name>/— 임의의 디렉토리에 놓습니다. 후속 세션에는 다시 시작할 필요가 없습니다. - 레거시 단일 파일 —
<repo>/providers/<name>.py— 트리 외부 편집 가능한 설치를 위한 하위 호환
사용자 플러그인은 동일한 이름의 번들 플러그인을 재정의합니다. register_provider()이 최종 작성자 승리이기 때문입니다. 저장소를 건드리지 않고 내장 GMI 프로필을 바꾸려면 $HERMES_HOME/plugins/model-providers/gmi/ 디렉터리를 삭제하세요.
디렉토리 구조
plugins/model-providers/my-provider/
├── __init__.py # Calls register_provider(profile) at module-level
├── plugin.yaml # kind: model-provider + metadata (optional but recommended)
└── README.md # Setup instructions (optional)
유일한 필수 파일은 __init__.py입니다. plugin.yaml은 hermes plugins에서 내부 검사를 위해 사용되며 일반 PluginManager에서는 플러그인을 올바른 로더로 라우팅하는 데 사용됩니다. 이것이 없으면 일반 로더는 소스 텍스트 경험적 방법으로 대체됩니다.
최소한의 예 - 간단한 API 키 제공자
# plugins/model-providers/acme-inference/__init__.py
from providers import register_provider
from providers.base import ProviderProfile
acme = ProviderProfile(
name="acme-inference",
aliases=("acme",),
display_name="Acme Inference",
description="Acme — OpenAI-compatible direct API",
signup_url="https://acme.example.com/keys",
env_vars=("ACME_API_KEY", "ACME_BASE_URL"),
base_url="https://api.acme.example.com/v1",
auth_type="api_key",
default_aux_model="acme-small-fast",
fallback_models=(
"acme-large-v3",
"acme-medium-v3",
"acme-small-fast",
),
)
register_provider(acme)
````yaml
# plugins/model-providers/acme-inference/plugin.yaml
name: acme-inference
kind: model-provider
version: 1.0.0
description: Acme Inference — OpenAI-compatible direct API
author: Your Name
그게 다야. 이 두 파일을 삭제한 후 다른 편집 없이 다음 자동 연결이 실행됩니다.
| 통합 | 어디에 | 그것이 얻는 것 |
|---|---|---|
| 자격 증명 확인 | hermes_cli/auth.py | PROVIDER_REGISTRY["acme-inference"] 프로필에서 채워짐 |
--provider CLI 플래그 | hermes_cli/main.py | acme-inference 허용 |
hermes model 선택기 | hermes_cli/models.py | CANONICAL_PROVIDERS에 표시되며, {base_url}/models에서 가져온 모델 목록입니다. |
hermes doctor | hermes_cli/doctor.py | ACME_API_KEY + {base_url}/models 프로브에 대한 상태 확인 |
hermes setup | hermes_cli/config.py | ACME_API_KEY이 OPTIONAL_ENV_VARS에 표시되고 설정 마법사가 나타납니다. |
| URL 역매핑 | agent/model_metadata.py | 호스트 이름 → 자동 감지를 위한 공급자 이름 |
| 보조 모델 | agent/auxiliary_client.py | 압축/요약을 위해 default_aux_model을 사용합니다. |
| 런타임 해상도 | hermes_cli/runtime_provider.py | 올바른 base_url, api_key, api_mode을 반환합니다. |
| 운송 | agent/transports/chat_completions.py | 프로필 경로는 prepare_messages / build_extra_body / build_api_kwargs_extras을 통해 kwargs를 생성합니다. |
공급자 프로필 필드
providers/base.py의 전체 정의입니다. 가장 유용한 것:
| 필드 | 유형 | 목적 |
|---|---|---|
name | str | 정식 ID — --provider 선택 항목 및 HERMES_INFERENCE_PROVIDER과 일치합니다. |
aliases | tuple[str,...] | get_provider_profile()에 의해 확인된 대체 이름(예: grok → xai) |
api_mode | str | chat_completions | codex_responses | anthropic_messages | bedrock_converse |
display_name | str | hermes model 선택기에 표시된 휴먼 라벨 |
description | str | 선택기 자막 |
signup_url | str | 첫 실행 설정 중에 표시됨("여기서 API 키 가져오기") |
env_vars | tuple[str,...] | 우선순위에 따른 API 키 환경 변수. 최종 *_BASE_URL 항목은 사용자 기본 URL 재정의로 사용됩니다. |
base_url | str | 기본 추론 엔드포인트 |
models_url | str | 명시적 카탈로그 URL({base_url}/models으로 대체) |
auth_type | str | api_key | oauth_device_code | oauth_external | copilot | aws_sdk | external_process |
fallback_models | tuple[str,...] | 라이브 카탈로그 가져오기에 실패하면 선별된 목록이 표시됩니다. |
default_headers | dict[str, str] | 모든 요청에 대해 전송됩니다(예: Copilot의 Editor-Version) |
fixed_temperature | 모두 | None = 호출자의 값을 사용합니다. OMIT_TEMPERATURE sentinel = 체온을 전혀 보내지 않습니다 (키미) |
default_max_tokens | int | 없음 | 공급자 수준 max_tokens 한도(Nvidia: 16384) |
default_aux_model | str | 보조 작업(압축, 비전, 요약)을 위한 저렴한 모델 |
재정의 가능한 후크
사소한 문제에 대한 하위 클래스 ProviderProfile:
from typing import Any
from providers.base import ProviderProfile
class AcmeProfile(ProviderProfile):
def prepare_messages(self, messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Provider-specific message preprocessing. Runs after codex
sanitization, before developer-role swap. Default: pass-through."""
# Example: Qwen normalizes plain-text content to a list-of-parts
# array and injects cache_control; Kimi rewrites tool-call JSON
return messages
def build_extra_body(self, *, session_id=None, **컨텍스트) -> dict:
"""Provider-specific extra_body fields merged into the API call.
컨텍스트 includes: session_id, provider_preferences, model, base_url,
reasoning_config. Default: empty dict."""
# Example: OpenRouter's provider-preferences block,
# Gemini's thinking_config translation.
return {}
def build_api_kwargs_extras(self, *, reasoning_config=None, **컨텍스트):
"""Returns (extra_body_additions, top_level_kwargs). Needed when some
fields go top-level (Kimi's reasoning_effort) and some go in extra_body
(OpenRouter's reasoning dict). Default: ({}, {})."""
return {}, {}
def fetch_models(self, *, api_key=None, timeout=8.0) -> list[str] | None:
"""Live catalog fetch. Default hits {models_url or base_url}/models with
Bearer auth. Override for: custom auth (Anthropic), no REST endpoint
(Bedrock → None), or public/unauthenticated catalogs (OpenRouter)."""
return super().fetch_models(api_key=api_key, timeout=timeout)
후크 참조 예
관용구에 대한 번들 플러그인을 살펴보세요:
| 플러그인 | 왜 봐? |
|---|---|
plugins/model-providers/openrouter/ | 공급자 기본 설정, 공개 모델 카탈로그가 있는 수집기 |
plugins/model-providers/gemini/ | thinking_config 번역(네이티브 + OpenAI 호환 중첩 양식) |
plugins/model-providers/kimi-coding/ | OMIT_TEMPERATURE, extra_body.thinking, 최상위 reasoning_effort |
plugins/model-providers/qwen-oauth/ | 메시지 정규화, cache_control 주입, VL 고해상도 |
plugins/model-providers/nous/ | 속성 태그, "비활성화 시 추론 생략" |
plugins/model-providers/custom/ | 올라마 num_ctx + think: false 단점 |
plugins/model-providers/bedrock/ | api_mode="bedrock_converse", fetch_models은 없음(REST 끝점 없음)을 반환합니다. |
사용자 재정의 - 저장소를 편집하지 않고 내장 교체
테스트를 위해 프라이빗 스테이징 엔드포인트에서 gmi을 가리키고 싶다고 가정해 보겠습니다. ~/.hermes/plugins/model-providers/gmi/__init__.py 생성:
from providers import register_provider
from providers.base import ProviderProfile
register_provider(ProviderProfile(
name="gmi",
aliases=("gmi-cloud", "gmicloud"),
env_vars=("GMI_API_KEY",),
base_url="https://gmi-staging.internal.example.com/v1",
auth_type="api_key",
default_aux_model="google/gemini-3.1-flash-lite-preview",
))
다음 세션에서는 get_provider_profile("gmi").base_url이 스테이징 URL을 반환합니다. 리포지토리 패치도 없고 재구축도 없습니다. 사용자 플러그인은 번들 플러그인 이후에 발견되므로 사용자 register_provider() 호출이 승리합니다.
api_mode 선택
4가지 값이 인식됩니다. Hermes는 다음을 기준으로 하나를 선택합니다.
- 사용자 명시적 재정의(설정 시
config.yamlmodel.api_mode) - OpenCode의 모델별 디스패치(Zen 및 Go의 경우
opencode_model_api_mode) - URL 자동 감지 —
/anthropic접미사 →anthropic_messages,api.openai.com→codex_responses,api.x.ai→codex_responses, Kimi 도메인의/coding→chat_completions - URL 감지에서 아무것도 찾지 못할 경우 대체용으로
api_mode프로필을 작성하세요. - 기본값
chat_completions
공급자가 제공하는 기본값과 일치하도록 profile.api_mode을 설정합니다. 이는 힌트 역할을 합니다. 사용자 URL 재정의가 여전히 유효합니다.
인증 유형
auth_type | 의미 | 누가 그것을 사용합니까? |
|---|---|---|
api_key | 단일 환경 변수는 정적 API 키를 전달합니다. | 대부분의 제공자 |
oauth_device_code | 장치 코드 OAuth 흐름 | — |
oauth_external | 사용자가 다른 곳에 로그인하면 토큰은 auth.json에 저장됩니다. | Anthropic OAuth, MiniMax OAuth, Gemini Cloud Code, Qwen Portal, Nous Portal |
copilot | GitHub Copilot 토큰 새로 고침 주기 | copilot 플러그인 전용 |
aws_sdk | AWS SDK 자격 증명 체인(IAM 역할, 프로필, 환경) | bedrock 플러그인 전용 |
external_process | 에이전트가 생성하는 하위 프로세스에 의해 처리되는 인증 | copilot-acp 플러그인 전용 |
auth_type 게이트는 코드 경로가 공급자를 "간단한 API 키 공급자"로 처리합니다. api_key이 아닌 경우 PluginManager는 여전히 매니페스트를 기록하지만 Hermes의 CLI 수준 자동화(의사 확인, --provider 플래그, 설정 마법사 위임)는 이를 건너뛸 수 있습니다.
발견 시기
공급자 검색은 지연입니다. 프로세스의 첫 번째 get_provider_profile() 또는 list_providers() 호출에 의해 트리거됩니다. 실제로 이는 시작 초기에 발생합니다(auth.py 모듈 로드는 PROVIDER_REGISTRY을 열심히 확장합니다). 플러그인이 로드되었는지 확인해야 하는 경우 다음을 실행하세요.
hermes doctor
— 성공적인 auth_type="api_key" 프로필은 /models 프로브가 있는 공급자 연결 섹션 아래에 나타납니다.
프로그래밍 방식 검사의 경우:
from providers import list_providers
for p in list_providers():
print(p.name, p.base_url, p.api_mode)
플러그인 테스트
실제 구성을 오염시키지 않도록 임시 디렉토리에서 HERMES_HOME을 지정하세요.
export HERMES_HOME=/tmp/hermes-plugin-test
mkdir -p $HERMES_HOME/plugins/model-providers/my-provider
cat > $HERMES_HOME/plugins/model-providers/my-provider/__init__.py <<'EOF'
from providers import register_provider
from providers.base import ProviderProfile
register_provider(ProviderProfile(
name="my-provider",
env_vars=("MY_API_KEY",),
base_url="https://api.my-provider.example.com/v1",
auth_type="api_key",
))
EOF
export MY_API_KEY=your-test-key
hermes -z "hello" --provider my-provider -m some-model
일반 PluginManager 통합
일반 PluginManager(hermes plugins이 작동하는 것)은 모델 제공자 플러그인을 보지만 가져오지는 않습니다. — providers/__init__.py이 수명 주기를 소유합니다. 관리자는 점검을 위해 매니페스트를 기록하고 kind: model-provider별로 분류합니다. ProviderProfile을 사용하여 register_provider을 호출하는 레이블이 없는 사용자 플러그인을 $HERMES_HOME/plugins/에 놓으면 관리자는 소스 텍스트 경험적 방법을 통해 해당 플러그인을 kind: model-provider로 자동 강제 변환합니다. plugin.yaml.
pip를 통해 배포
Hermes 플러그인과 마찬가지로 모델 공급자는 pip 패키지로 제공될 수 있습니다. pyproject.toml에 진입점을 추가하세요.
[project.entry-points."hermes.plugins"]
acme-inference = "acme_hermes_plugin:register"
전체 진입점 설정은 Hermes 플러그인 빌드를 참조하세요.
관련 페이지
- Provider Runtime — 해결 우선순위 + 각 레이어가 프로필을 읽는 위치
- 공급자 추가 — 새로운 추론 백엔드를 위한 엔드투엔드 체크리스트(빠른 플러그인 경로와 전체 CLI/인증 통합을 모두 포함)
- 메모리 공급자 플러그인
- 컨텍스트 엔진 플러그인
- Hermes 플러그인 빌드 — 일반 플러그인 제작