General API와 Coding Plan이 서로 다른 엔드포인트와 사용량 경계를 유지하도록 프로필, credential route, provider mapping, full-cycle 검증을 함께 반영한다.
222 lines
21 KiB
Markdown
222 lines
21 KiB
Markdown
---
|
|
spec_doc_type: spec
|
|
spec_id: runtime/provider-pool-config-refresh
|
|
status: 부분
|
|
source_evidence:
|
|
- type: contract
|
|
path: agent-contract/inner/edge-config-runtime-refresh.md
|
|
notes: Edge config, provider pool, config refresh, Node payload 연결 계약
|
|
- type: code
|
|
path: packages/go/config/provider_types.go
|
|
notes: provider/model catalog 설정 타입
|
|
- type: code
|
|
path: packages/go/config/edge_types.go
|
|
notes: Edge root provider_pool canonical queue policy 타입과 기본값
|
|
- type: code
|
|
path: packages/go/config/load.go
|
|
notes: Edge config load와 default 적용
|
|
- type: code
|
|
path: packages/go/config/validate.go
|
|
notes: provider/model references, managed credential exclusivity, TLS/key requirements, and numeric bounds
|
|
- type: code
|
|
path: configs/edge.yaml
|
|
notes: provider-pool 권장 설정 예시와 live refresh 설정 예시
|
|
- type: code
|
|
path: apps/edge/internal/service/provider_resolution.go
|
|
notes: provider 후보 해석과 capacity/priority policy 구성
|
|
- type: code
|
|
path: apps/edge/internal/service/model_queue_admission.go
|
|
notes: provider 전역 lease, 공통 pending 상한, global enqueue 순서와 long-context admission
|
|
- type: code
|
|
path: apps/edge/internal/service/model_queue_release.go
|
|
notes: lease 반환, disconnect/reconnect candidate 재구성과 global queue pump
|
|
- type: code
|
|
path: apps/edge/internal/service/status_provider.go
|
|
notes: lease state와 candidate pressure 기반 online/offline provider snapshot
|
|
- type: code
|
|
path: packages/go/config/protocol_profile.go
|
|
notes: ConcreteProtocolProfile, ProtocolOperation, ProtocolDriver, overlay validation, alias normalization, capability admission, model mapping
|
|
- type: code
|
|
path: apps/edge/internal/configrefresh/classify.go
|
|
notes: dry-run/apply classification과 changed path report 생성
|
|
- type: code
|
|
path: apps/edge/internal/bootstrap/runtime.go
|
|
notes: mutable config apply, runtime snapshot 교체, Node refresh push
|
|
- type: code
|
|
path: apps/edge/internal/node/mapper.go
|
|
notes: provider-first NodeConfigPayload compile과 OpenAI-compatible provider label 보존
|
|
- type: test
|
|
path: packages/go/config/provider_catalog_config_test.go
|
|
notes: provider/model catalog load와 queue 설정 검증
|
|
- type: test
|
|
path: packages/go/config/provider_catalog_validation_config_test.go
|
|
notes: provider/model 참조와 validation 검증
|
|
- type: test
|
|
path: packages/go/config/usage_attribution_config_test.go
|
|
notes: attribution policy 기본값·enum과 direct provider binding 검증
|
|
- type: test
|
|
path: packages/go/config/stream_evidence_gate_config_test.go
|
|
notes: Stream Evidence Gate 기본값, recovery cap과 ingress 상한 검증
|
|
- type: test
|
|
path: apps/edge/internal/node/mapper_test.go
|
|
notes: provider-first adapter payload, Seulgivibe alias/provider label 검증
|
|
- type: test
|
|
path: apps/edge/internal/configrefresh/node_runtime_classify_test.go
|
|
notes: node runtime과 model catalog refresh classification 검증
|
|
- type: test
|
|
path: apps/edge/internal/configrefresh/provider_classify_test.go
|
|
notes: provider field refresh classification 검증
|
|
- type: test
|
|
path: apps/edge/internal/service/model_queue_admission_test.go
|
|
notes: cross-model capacity, terminal unavailable, disconnect/event-drop 회귀 검증
|
|
- type: test
|
|
path: apps/edge/internal/service/status_provider_test.go
|
|
notes: cross-model candidate pressure와 offline/reconnect snapshot 검증
|
|
- type: test
|
|
path: apps/edge/internal/bootstrap/reconnect_readiness_integration_test.go
|
|
notes: dispatch-ready reconnect가 기존 queued waiter를 실제 Node terminal까지 수렴시키는 검증
|
|
- type: test
|
|
path: scripts/e2e-provider-capacity-smoke.sh
|
|
notes: loopback OpenAI-compatible provider에서 two-alias capacity-1 queue와 final counter 회복 검증
|
|
---
|
|
|
|
# 스펙: Provider Pool과 Config Refresh
|
|
|
|
## 목적
|
|
|
|
Edge 설정에서 provider-pool이 어떻게 모델 실행 후보를 고르고, 어떤 설정 변경이 재시작 없이 반영되는지 설명한다.
|
|
|
|
## 기능 목록
|
|
|
|
| 기능 | 설명 |
|
|
|------|------|
|
|
| model catalog | `models[].id`는 외부 OpenAI-compatible `model` key이자 provider-pool `ModelGroupKey`다. |
|
|
| usage attribution policy | `models[].usage_attribution`은 `provider|model_group`만 허용하고 생략 시 provider 귀속으로 해석한다. model-group 귀속은 운영자의 명시적 opt-in이다. |
|
|
| provider mapping | `models[].providers`는 provider id를 실제 served model name으로 매핑한다. |
|
|
| node provider catalog | `nodes[].providers[]`는 Node 아래 resource/provider catalog이며 provider id는 Edge config에서 전역 유일해야 한다. |
|
|
| config validation | config load가 provider id 참조, served model membership, numeric bounds, long-context budget을 검증한다. |
|
|
| provider 후보 필터링 | dispatch는 dispatch-ready connection을 가진 Node의 provider 후보 중 catalog match, enabled, healthy/available, capacity 조건을 만족하는 후보만 사용한다. protocol profile capability(`messages`, `chat`, `responses`, `streaming`, `tool_calling`, `count_tokens`, `models`)는 operation별 admission에 사용된다. |
|
|
| provider 전역 capacity/priority dispatch | `node_id + provider_id` lease가 여러 model group의 일반·long in-flight를 합산한다. available 후보 중 낮은 in-flight를 고르고 동률이면 낮은 `priority`와 round-robin을 적용한다. |
|
|
| provider-pool 공통 queue policy | Edge root `provider_pool.max_queue`가 모든 model group의 전체 pending 상한을, `queue_timeout_ms`가 각 pending request timeout을 소유한다. |
|
|
| global queue 재평가 | lease 반환, capacity/priority/enabled refresh, disconnect/reconnect 뒤 global enqueue 순서에서 현재 dispatch 가능한 가장 이른 waiter부터 candidate를 다시 구성한다. |
|
|
| provider snapshot | 일반·long in-flight는 provider lease state, queued 값은 Edge queue에서 해당 provider를 후보로 포함하는 고유 pending request pressure에서 계산한다. offline provider는 catalog identity를 유지하고 effective 수치를 0으로 보고한다. |
|
|
| mixed provider execution path | 같은 model group의 OpenAI-compatible provider와 Ollama/CLI/native provider를 같은 후보군으로 두며, 선택된 provider capability로 passthrough 또는 normalized 실행 경로를 결정한다. OpenAI-compatible provider는 `openai_chat`, `anthropic_messages`, 또는 `openai_responses` driver로 해석된다. |
|
|
| long-context admission | estimated input token이 threshold 이상이면 `context_class=long`으로 분류하고, provider long slot이 있으면 일반 capacity slot과 함께 점유한다. |
|
|
| config refresh dry-run/apply | loopback admin HTTP `POST /refresh`가 candidate config를 dry-run 또는 apply한다. |
|
|
| refresh classification | listener, Edge identity, bootstrap path, adapter structural 변경 등은 restart-required로 분류한다. |
|
|
| Stream Evidence Gate config | `openai.stream_evidence_gate` provides runtime activation, request-total/strategy fault recovery caps, ingress snapshot bounds, and per-filter capability/enforcement/Unicode hold policy; it is currently restart-required. |
|
|
| mutable apply | 적용 가능한 변경은 Edge `Cfg`, `NodeStore`, service/input model catalog, OpenAI long-context threshold를 copy-on-write로 교체한다. |
|
|
| Node config refresh push | 변경이 있으면 Edge가 dispatch-ready Node에 node-specific `NodeConfigRefreshRequest`를 push한다. accepted지만 pending인 Node는 register response config를 적용한 뒤 ready가 될 때까지 push 대상이 아니다. |
|
|
| Node registry swap | Node는 refresh payload로 새 adapter registry를 만들고 router registry를 swap한다. old registry stop은 active run이 있으면 drain 이후로 지연한다. |
|
|
| principal token mapping config | `openai.principal_tokens[]`는 raw token 없이 `token_ref`, `token_hash_sha256`, `principal_ref`, optional alias를 관리하고 OpenAI usage metering의 principal/token label 후보를 제공한다. 같은 principal에 여러 token entry를 둘 수 있다. |
|
|
| credential mode switch | `credential_plane.enabled` selects managed projection/lease ownership and requires every credential-bearing hop to use TLS plus externally mounted issuer/recipient/keyring material. |
|
|
| managed/legacy exclusivity | Managed mode rejects static principal mappings, legacy bearer/provider-auth forwarding, configured provider credential headers/env/args, and endpoint user-info. Legacy mode retains request-time `openai.provider_auth` compatibility. |
|
|
| managed route binding | An authenticated projection route binds slot, profile, upstream model, selector, credential revision, route revision, and generation before provider-pool admission and lease acquisition. |
|
|
| provider auth forwarding config | In legacy mode only, `openai.provider_auth` forwards a caller-supplied raw provider token to the selected compatible provider tunnel header. |
|
|
| Seulgivibe provider aliases | `seulgivibe_claude`, `seulgivibe_openai` provider type은 runtime adapter type을 `openai_compat`로 정규화하고, 명시 provider label이 없으면 canonical Seulgivibe alias를 Node payload provider label로 보존한다. |
|
|
|
|
## 범위
|
|
|
|
- 포함: Edge config load/default/validation, Stream Evidence Gate 설정, provider-pool dispatch, queue admission, long-context admission, config refresh classification/apply, Node config refresh payload.
|
|
- 제외: 개별 adapter의 provider API 호출 세부, OpenAI HTTP request/response shape, Control Plane credential lifecycle API details, private credential contents.
|
|
|
|
## 주요 흐름
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Caller
|
|
participant Service as Edge service
|
|
participant Queue as Provider queue
|
|
participant Node
|
|
|
|
Caller->>Service: SubmitRun(model)
|
|
Service->>Queue: dispatch-ready provider 후보 선택(capacity + priority)
|
|
Queue-->>Service: selected provider + served target
|
|
alt selected provider supports OpenAI-compatible call
|
|
Service->>Node: ProviderTunnelRequest(adapter, served target)
|
|
else selected provider is Ollama/CLI/native
|
|
Service->>Node: RunRequest(adapter, served target)
|
|
end
|
|
|
|
participant Operator
|
|
participant Refresh as Config refresh
|
|
Operator->>Refresh: POST /refresh(apply)
|
|
Refresh->>Refresh: load, validate, classify
|
|
Refresh->>Node: NodeConfigRefreshRequest
|
|
```
|
|
|
|
## 계약
|
|
|
|
- `iop.edge-config-runtime-refresh`: `agent-contract/inner/edge-config-runtime-refresh.md`
|
|
- `iop.edge-node-runtime-wire`: `agent-contract/inner/edge-node-runtime-wire.md`
|
|
- proto 원문: `proto/iop/runtime.proto`
|
|
|
|
## 설정/데이터/이벤트
|
|
|
|
- `long_context_threshold_tokens` 기본 예시는 `100000`이고 0 이하 값은 config load에서 거부된다.
|
|
- `credential_plane.enabled=true` requires Edge-Node server TLS, an enabled TLS Control Plane connector, and OpenAI ingress TLS when enabled. The Edge cannot combine managed mode with `openai.bearer_token`, `openai.principal_tokens[]`, `openai.provider_auth`, or static provider credential sources.
|
|
- Node managed mode requires Edge transport TLS, `recipient_key_id`/recipient private-key path, issuer key id/public-key path, and a bounded replay cache. All cert/key/keyring values are external file references and credential-plane changes are restart-required.
|
|
- `protocol_profiles` is the top-level catalog of custom overlays. A `ProtocolProfileConf` supplies `base`, `driver`, `base_url`, operation paths, `auth`, `capabilities`, `model_mapping`, and `extensions`; `base` inheritance is separate from legacy provider-type normalization.
|
|
- `nodes[].providers[].profile` selects a catalog entry. Config normalization resolves that selection (or a legacy type alias) into the runtime-only `RuntimeProfile` snapshot; the source YAML remains a selector plus catalog, not a per-model overlay.
|
|
- Profile catalog and provider-selector changes are restart-required. Snapshot immutability describes loaded runtime state and does not make those changes live-applicable.
|
|
- `ConcreteProtocolProfile.MapModel(model)`은 provider의 model alias 정규화를 수행한다. provider가 model mapping을 정의하면 IOP external `model` key를 provider served target으로 변환한다.
|
|
- `ConcreteProtocolProfile.ResolveOperationURL(op)` returns the complete resolved upstream URL. Absolute operation URLs are returned unchanged, while relative operation paths are joined once to the normalized base URL; the listed `/v1/...` values are operation-path inputs, not return values.
|
|
- Built-in GLM profiles: `glm` (General API, `https://api.z.ai/api/paas/v4`) and `glm_coding` (Coding Plan, `https://api.z.ai/api/coding/paas/v4`) are independent identities sharing the `openai_chat` driver, Bearer auth, and `models`/`chat`/`streaming`/`tool_calling` capabilities. Neither declares the `responses` operation or capability. Endpoint selection is driven by external model IDs mapped to distinct provider IDs, never by an extra request field. No automatic fallback exists between the two profiles; selecting one profile routes to one endpoint exclusively. Both are comment-only in the example config and disabled by default. Coding Plan usage is subject to current Z.AI subscription terms.
|
|
- `validOperationsByDriver`는 driver별 허용 operation의 closed set이다. `openai_chat`은 `models`, `chat_completions`, `responses`, `count_tokens`를 허용한다. `anthropic_messages`는 `models`, `messages`, `count_tokens`를 허용한다. `openai_responses`는 `models`, `responses`, `count_tokens`를 허용한다.
|
|
- `openai.stream_evidence_gate.enabled` 기본값은 `false`다. request fault recovery는 0..3, strategy cap은 request-total 이하, ingress snapshot은 1..16777216 bytes이며 설정 변경은 restart-required다.
|
|
- `filters[].hold_evidence_runes` is bounded `1..65536` and defaults to 500. For `repeat_guard` it controls the Unicode pending/look-behind evidence window, not a time-based release or a cross-request retention period.
|
|
- Blocking repeat capability admission is re-resolved for the actual provider/path while the request-start filter policy, history snapshot, recovery ordinals, and temperature candidate order remain generation-stable across provider switches.
|
|
- `provider_pool.max_queue`는 0/생략 시 기본값 `16`, `queue_timeout_ms`는 생략 시 `30000`이고 명시적 0은 timeout 없음이다. canonical root key가 없을 때만 서로 같은 legacy provider queue pair를 승격하며 값이 다르면 load를 거부한다.
|
|
- `nodes[].providers[].capacity`와 `long_context_capacity`는 provider resource 속성이고 같은 provider를 공유하는 model alias가 합산 점유한다. `total_context_tokens`는 runtime ledger가 아니라 `context_window_tokens * long_context_capacity` 정적 validation 값이다.
|
|
- `models[].usage_attribution`은 생략 시 `provider`, 명시값은 `provider|model_group`만 허용한다. 변경은 model catalog policy 변경으로 live apply되며 `models["<id>"].usage_attribution` 경로로 보고한다.
|
|
- provider `enabled=false`는 dispatch pool에서 제외하지만 adapter process lifecycle 변경을 의미하지 않는다.
|
|
- accepted registration은 provider candidate를 바로 복구하지 않는다. Node가 config 적용과 handler 설치 뒤 ready ack를 받아야 해당 generation이 candidate, connected snapshot, refresh push 대상이 되며 이 transition이 stranded provider-pool waiter를 재평가한다.
|
|
- provider capacity, long-context capacity, priority, enabled toggle, root queue policy와 model generation policy는 live apply 대상으로 분류된다. apply는 기존 lease를 보존하고 이후 admission 및 모든 관련 waiter의 live candidate/deadline을 새 값으로 재평가한다.
|
|
- Edge listener, control plane, openai/a2a listener, bootstrap artifact path, node 추가/삭제, node token/alias, adapter 설정 변경은 restart-required 대상이다.
|
|
- `openai.principal_tokens[]`는 `token_ref`와 `token_hash_sha256` 중복을 거부하고, raw token 원문은 tracked config에 저장하지 않는다.
|
|
- 여러 `openai.principal_tokens[]` entry가 같은 `principal_ref`를 공유할 수 있으며, 이때 `token_ref`가 앱/통합/용도별 사용량 분해 기준이다.
|
|
- `openai.principal_tokens[]` 변경은 credential/hash 변경으로 보고 restart-required로 분류된다.
|
|
- In legacy mode, `openai.provider_auth.enabled=true` resolves omitted fields to `from_header=X-IOP-Provider-Authorization`, `target_header=Authorization`, `scheme=Bearer`, and `required=true`. Managed mode rejects the setting and caller-supplied legacy provider credentials.
|
|
- Seulgivibe provider catalog는 top-level `models[]`의 정적 provider mapping을 source of truth로 사용한다. provider `/models` endpoint는 IOP catalog source가 아니다.
|
|
- `models[]`는 mixed provider group과 Ollama-only group을 모두 표현할 수 있다. Ollama는 후보에서 제외하지 않고 `capacity`/`priority`로 운영자가 낮은 동시성과 낮은 선호도를 표현한다.
|
|
- refresh result는 changed nodes/providers/models와 restart-required paths를 stable non-nil slice로 보고한다.
|
|
|
|
## 검증
|
|
|
|
- `go test ./packages/go/config`
|
|
- `go test ./apps/edge/internal/configrefresh`
|
|
- `go test ./apps/edge/internal/service`
|
|
- `go test ./apps/edge/internal/node`
|
|
- `go test ./apps/node/internal/adapters ./apps/node/internal/node`
|
|
- `go test ./apps/edge/internal/bootstrap -run '^TestActualNodeReconnectReadyPumpsQueuedWaiterExactlyOnce$'`
|
|
- `./scripts/e2e-provider-capacity-smoke.sh`
|
|
|
|
## 한계와 주의사항
|
|
|
|
- provider health는 현재 config/provider snapshot 기반이다. 모든 runtime에 대한 active health probe가 완성된 것은 아니다.
|
|
- refresh admin API는 operator-local 표면이다. 접근 제어 없이 public interface에 노출하지 않는다.
|
|
- Stream Evidence Gate의 request-local lifecycle과 지원 OpenAI 경로는 `agent-spec/runtime/stream-evidence-gate.md`에서 관리한다.
|
|
- adapter structural 변경은 contract상 restart-required로 분류된다. Node handler가 registry swap을 지원하더라도 Edge refresh classifier가 허용한 변경만 apply해야 한다.
|
|
- no-change apply는 runtime snapshot을 교체하지만 Node push는 생략한다.
|
|
- `NodeRuntimeConfig.concurrency`는 legacy metadata이며 provider-pool admission의 node-wide capacity로 쓰지 않는다.
|
|
- provider가 full인 상태는 일시적인 queue block이지만, disconnect/disable로 live candidate가 모두 사라지면 waiter는 기존 queue timeout까지 남지 않고 unavailable로 종료된다.
|
|
- credential, private endpoint, bearer token 원문은 tracked config/docs/spec에 남기지 않는다.
|
|
- TLS private keys, the at-rest keyring, issuer private key, Node recipient private key, lease envelopes, and slot plaintext must remain external/runtime-only and must not appear in refresh reports, logs, metrics, or task artifacts.
|
|
- principal token mapping은 완성된 사용자/테넌트 source of truth가 아니라 외부 `principal_ref` 또는 내부 alias에 대한 얇은 운영 매핑이다.
|
|
- Seulgivibe provider endpoint와 raw user token은 환경별 private config 또는 request-time header로 주입해야 하며 tracked 예시에 실제 값을 남기지 않는다.
|
|
|
|
## 변경 기록
|
|
|
|
- 2026-07-07: 현재 코드, 계약, config 예시 기준으로 bootstrap spec 작성.
|
|
- 2026-07-07: 기능 목록 중심으로 축소하고 주요 흐름을 Mermaid sequence diagram으로 정리.
|
|
- 2026-07-10: OpenAI usage metering용 principal token hash mapping config와 restart-required 기준을 반영.
|
|
- 2026-07-11: Seulgivibe OpenAI-compatible provider aliases, provider auth forwarding config, static catalog 기준을 현재 코드/계약/테스트 기준으로 반영.
|
|
- 2026-07-12: Model Group Mixed Provider Dispatch 종료 검토 기준으로 mixed provider group과 Ollama-only group, capacity/priority 가중 예시를 반영.
|
|
- 2026-07-18: 저장소 구조 분해 뒤 config, provider resolution/admission, split test의 `source_evidence`를 현재 경로로 동기화.
|
|
- 2026-07-22: pending accepted connection과 dispatch-ready connection을 구분하고, ready ack 뒤에만 provider candidate 복구·refresh push·queued waiter pump가 일어나는 현재 동작을 반영.
|
|
- 2026-07-22: root provider-pool queue policy, cross-model provider lease, global queue 재평가, refresh lease 보존과 connectivity 기반 snapshot 의미를 현재 구현·계약·테스트 기준으로 동기화.
|
|
- 2026-07-28: Stream Evidence Gate 설정 기본값·상한·restart-required 분류와 runtime spec 포인터를 반영.
|
|
- 2026-07-31: model별 provider-default/model-group opt-in attribution policy와 live-apply refresh 분류를 반영했다.
|
|
- 2026-08-01: protocol profile catalog/selector ownership, runtime-only profile resolution, and restart-required refresh semantics were synchronized with config source.
|
|
- 2026-08-02: Synchronized the managed credential mode switch, TLS/key prerequisites, legacy-auth exclusion, projected route binding, and restart-required credential-plane classification with current validation/runtime source.
|
|
- 2026-08-02: Added the `glm_coding` built-in profile alongside `glm` (General API), both exposing only `models` + `chat_completions` with Bearer auth and no Responses. Endpoint selection is driven by external model IDs mapped to distinct provider IDs. No automatic fallback between General API and Coding Plan. Both are comment-only in the example config and disabled by default. Coding Plan usage is subject to current Z.AI subscription terms.
|