GEODE . 문서
GitHub
가이드
How-to

LLM 어댑터 추가

라우터와 어댑터 레이어에 프로바이더를 추가하고 폴백 항목을 거는 방법입니다.

어댑터는 하나의 (provider, source) 조합을 실제 호출로 바꾸는 계층입니다. PAYG API 키 호출이든, OAuth 구독 호출이든, 로컬 CLI 서브프로세스든 전부 같은 프로토콜을 따릅니다. 새 백엔드를 붙이는 작업은 어댑터 작성, 레지스트리 등록, 라우팅 연결, 호출 계약 문서화의 네 단계입니다.

1. 어댑터를 작성합니다

어댑터는 core/llm/adapters/base.py LLMAdapter 프로토콜을 만족하면 됩니다. 최소 요건은 네 정체성 속성(name, provider, source, billing_type)과 비동기 호출 메서드 acomplete()입니다. source CONCRETE_SOURCES(payg / subscription / adapter) 중 하나여야 하고, "auto"는 picker 전용 sentinel이라 어댑터에 박을 수 없습니다. 요청·응답 셰이핑은 프로토콜이 정의한 provider-agnostic 타입(AdapterCallRequest, AdapterCallResult)을 어댑터 내부에서 SDK 페이로드로 번역하는 일입니다. AnthropicPaygAdapter(core/llm/adapters/anthropic_payg.py)가 PAYG 경로의 참조 구현입니다.

# core/llm/adapters/acme_payg.py
from dataclasses import dataclass, field
from typing import Any
from core.llm.adapters.base import (
    SOURCE_PAYG, AdapterBillingType,
    AdapterCallRequest, AdapterCallResult,
    EnvironmentReport, UsageSummary,
)

@dataclass
class AcmePaygAdapter:
    name: str = "acme-payg"
    provider: str = "acme"
    source: str = SOURCE_PAYG
    billing_type: AdapterBillingType = AdapterBillingType.API
    _client: Any = field(default=None, init=False, repr=False)

    async def acomplete(self, req: AdapterCallRequest) -> AdapterCallResult:
        client = self._get_client()
        raw = await client.create(...)  # translate req -> SDK payload
        return AdapterCallResult(
            text=raw.text,
            usage=UsageSummary(input_tokens=..., output_tokens=...),
            stop_reason=raw.stop_reason,
        )

    def test_environment(self) -> EnvironmentReport:
        from core.config import settings
        if not settings.acme_api_key:
            return EnvironmentReport(ok=False, hints=("set ACME_API_KEY",))
        return EnvironmentReport(ok=True)

스트리밍·introspection 메서드(astream, list_models, get_quota_windows, detect_credential)는 프로토콜이 요구하지만, 해당 표면을 지원하지 않으면 빈 값이나 None을 돌려줘도 됩니다. 단, test_environment는 항상 정직해야 합니다.

2. 레지스트리에 등록합니다

어댑터는 프로세스 전역 core/llm/adapters/registry.py 레지스트리로 조회됩니다. 내장 어댑터는 bootstrap_builtins()에서 등록되므로, 새 내장 어댑터라면 그 함수의 클래스 튜플에 추가합니다. 외부 플러그인이면 진입점에서 register_adapter(AcmePaygAdapter())를 직접 호출합니다. resolve_for(provider, source) (provider, source) 쌍이 정확히 하나의 어댑터에 매칭되도록 강제하므로, 같은 쌍을 둘 등록하면 invariant 위반으로 곧바로 실패합니다.

# core/llm/adapters/registry.py — bootstrap_builtins()
from core.llm.adapters.acme_payg import AcmePaygAdapter

for adapter_cls in (..., AcmePaygAdapter):
    instance = adapter_cls()
    if instance.name in _REGISTRY:
        continue
    register_adapter(instance)

서브프로세스(워커·audit)는 부모의 wiring 컨테이너를 거치지 않으므로 bootstrap_builtins()를 명시 호출해야 합니다. 안 그러면 레지스트리가 비어 AdapterNotFoundError가 납니다.

3. 라우팅과 폴백 체인을 연결합니다

core/llm/router/calls/_route.py _route_provider(model)이 모델 이름을 프로바이더로 해석합니다. 등록된 Plan이 있으면 그것을 따르고, 없으면 core.config의 정적 _resolve_provider로 떨어집니다. 모델 접두사와 프로바이더의 매핑은 core/config/routing.toml [routing.prefixes]가 SoT이고, 사용자 override는 ~/.geode/routing.toml입니다. 새 프로바이더의 모델 접두사를 여기에 추가합니다. 다중 모델 폴백은 core/llm/router/calls/_failover.py call_with_failover(models, call_fn)이 처리합니다. 모델 체인을 순서대로 시도하며, 재시도 가능한 오류(rate-limit, timeout, connection, server)는 백오프 후 다음 모델로 넘어가고, 인증 오류 같은 비재시도 오류는 즉시 전파됩니다. 단, 폴백 체인은 기본 출하값이 전부 빈 리스트입니다([model.fallbacks]). 조용한 모델 교체 대신 실패를 드러내는 설계이므로, 새 어댑터의 모델을 폴백 후보로 쓰려면 ~/.geode/routing.toml에서 직접 체인을 켜야 합니다.

4. 호출 계약을 문서화합니다

adapter가 등록됐다는 사실과 agentic 기능이 보장된다는 주장은 다릅니다. 새 경로의 실제 request builder를 확인한 뒤 도구 호출 구조화 출력 표에 provider/source/adapter 경계를 추가합니다.

항목기록할 내용
도구 호출ToolSpec encoding, tool_choice 변환, 복수 호출, call id와 result replay
구조화 출력response_schema wire field, strict 판정, local validation과 retry 범위
미지원 경계필드를 무시하는 경로와 모델별 확인이 필요한 부분을 지원으로 뭉개지 않고 명시
근거공식 provider 문서 또는 source, local request builder, request-shape test, 남은 live test

SDK type에 필드가 있다는 사실만으로 지원을 선언하지 않습니다. adapter가 값을 실제 wire payload에 싣는지와, GEODE가 결과를 어떻게 정규화·검증하는지를 함께 적습니다.

5. 확인합니다

(provider, source) 쌍이 정확히 어댑터로 해석되는지 확인합니다.

uv run python -c "
from core.llm.adapters.registry import bootstrap_builtins, resolve_for
bootstrap_builtins()
a = resolve_for('acme', 'payg')
print(a.name, a.provider, a.source)
print(a.test_environment().ok)
"

어댑터 이름이 출력되면 라우팅이 그 쌍을 찾을 수 있습니다. test_environment().ok는 자격증명 상태를 정직하게 보고합니다.

참조: Providers, Tool calling, Structured output, Pick a path.