GEODE는 Anthropic, OpenAI(+ChatGPT 구독), OpenRouter, GLM 네 프로바이더 경로를 명시적으로 라우팅합니다. 이 페이지는 모델이 어떻게 결정되고, 호출이 어느 어댑터로 가며, 실패했을 때 무엇이 일어나는지 정리합니다.
구성 요소
| 구성 | 코드 |
|---|---|
| 프로바이더 유틸리티 | core/llm/providers/anthropic.py, codex.py, glm.py, openrouter.py. quota·identity·request shaping을 어댑터에 제공 |
| 비동기 호출 어댑터 | core/llm/adapters/. SDK client와 acomplete() 호출 표면 소유 |
| 프로바이더 composition | core/llm/registry.py. 모델 identity, credential route, transport/API shape를 분리해 선언 |
| 어댑터 레지스트리 | core/llm/adapters/registry.py의 bootstrap_builtins(). 내장 factory와 운영자 정책이 승인한 geode.llm_adapters 진입점을 불변 generation snapshot으로 검색 |
| 라우팅 매니페스트 | core/config/routing.toml (+ ~/.geode/routing.toml 사용자 오버라이드). 모델 id prefix로 프로바이더 결정 |
서브프로세스 워커는 부모의 wiring 컨테이너를 거치지 않으므로 bootstrap_builtins()를 명시적으로 호출해야 합니다. 빈 레지스트리는 AdapterNotFoundError로 끝납니다. 각 AgenticLoop 세션은 생성 시 현재 generation을 캡처하므로, reload는 새 세션에만 보이고 실행 중 세션의 라우팅은 바뀌지 않습니다.
OpenRouter 경계
OpenRouter는 openrouter provider identity와 Chat Completions transport를 조합합니다. GEODE model id는 openrouter/<publisher>/<model>이며 어댑터가 외부 namespace 하나만 제거합니다. direct Anthropic/OpenAI와 equivalence group을 만들지 않으므로 자격이나 비용 경계가 조용히 바뀌지 않습니다. 응답이 제공한 실제 charge와 최종 serving route는 공통 usage/event 경로로 들어가고, 없을 때만 기존 정적 가격 추정을 사용합니다.
모델 해석 우선순위
강한 쪽이 이깁니다.
CLI 인자
> env (os.environ + .env)
> 프로젝트 .geode/config.toml
> 글로벌 ~/.geode/config.toml
> routing.toml 기본값어느 레이어가 이기는지는 geode config explain model이 레이어별 후보와 함께 보여줍니다. 실효 모델 확인은 항상 geode about입니다. config.toml만 보고 판단하면 상위 env 레이어에 가려진 값을 놓칩니다.
폴백 체인은 비어서 출하됩니다
[model.fallbacks]는 기본값이 전부 빈 목록입니다. primary 모델이 실패하면 GEODE는 다른 모델로 몰래 갈아타지 않습니다. 쿼터 소진이면 BillingError를, 일시 오류면 마지막 예외를 그대로 올리고, 다음 모델은 사용자가 /model로 직접 고릅니다. 자동 폴백을 원하면 ~/.geode/routing.toml의 체인을 채워 옵트인합니다. 체인 실행기는 core/llm/router/calls/_failover.py의 call_with_failover입니다.
조용한 cross-provider 자동 전환은 의도적으로 삭제된 기능입니다. 관측 불가능한 폴백은 어느 모델이 답했는지에 대한 신뢰를 무너뜨립니다.
재시도와 fast-fail
분류와 지연은 core/llm/fallback.py의 RetryPolicy, classify_retry_error, retry_delay_for가 공유합니다. 메인 루프는 동일 모델 안에서, 보조 호출은 run_with_retry_policy로 명시된 모델 체인 안에서만 실행합니다. SDK 재시도는 0입니다. 상태를 가진 CircuitBreaker 클래스는 없습니다.
| 판정 | 대상 | 효과 |
|---|---|---|
is_billing_fatal | 결제, 쿼터 소진 | 재시도 없이 즉시 실패 |
is_request_fatal | 400류 요청 오류 | 같은 요청을 다시 보내봤자 같은 결과이므로 즉시 실패 |
classify_retry_error | 연결, timeout, 408/409/429/5xx | 설정된 총 시도 횟수 안에서 jitter backoff |
| stream/effect guard | 이미 보인 출력, 불확실한 부작용 | 동일 호출을 재실행하지 않고 중단 또는 reconcile |
모델별 동작 차이
모델 패밀리별 능력 앵커는 core/llm/model_capabilities.py에 있습니다. 한 가지가 운영에서 특히 중요합니다. Fable 5는 안전 거절을 HTTP 200의 stop_reason: "refusal"로 보내며, Anthropic 프로바이더의 normalize_anthropic이 stop_details를 보존하고 루프가 model_refusal 종료로 매핑합니다. 자세한 동작은 안쪽 agentic 루프의 종료 경로 절을 참고합니다.
실패 모드
| 증상 | 원인 | 해법 |
|---|---|---|
| 모델을 바꿨는데 효과가 없음 | 상위 레이어(env)가 가리는 중 | geode config explain model로 이기는 레이어를 찾고 geode about으로 실효값을 확인합니다 |
| primary 실패 시 다른 모델로 안 넘어감 | 폴백 체인이 기본값(빈 목록) | 의도된 동작입니다. /model로 전환하거나 ~/.geode/routing.toml에서 옵트인합니다 |
서브프로세스에서 AdapterNotFoundError | bootstrap_builtins() 미호출 | 워커 진입점에서 명시적으로 호출합니다 |
| 400 오류가 재시도 없이 바로 실패 | is_request_fatal fast-fail | 의도된 동작입니다. 요청 자체(스키마, 크기)를 고칩니다 |
다음
- 프로바이더 설정 가이드. 자격과 경로 선택.
- 도구 호출. ToolSpec, 선택 모드, 실행과 결과 replay.
- 구조화 출력. JSON Schema 배선과 검증 경계.
- 인증. OAuth, API 키, credential 경로.
- 어댑터 추가 가이드. 새 모델, 새 레인 붙이기.