# Milestone: Model Group Mixed Provider Dispatch

## 위치

- Roadmap: [ROADMAP.md](../../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)

## 목표

Model group provider pool이 OpenAI-compatible provider와 native/normalized provider를 같은 후보군으로 다룰 수 있게 한다.
Edge는 기존 `capacity + priority` 기준으로 provider를 먼저 선택하고, 선택된 provider가 OpenAI-compatible 호출 방식을 지원하면 모두 passthrough 방식으로 dispatch한다.
OpenAI-compatible 호출 방식을 지원하지 않는 Ollama/CLI 같은 native provider만 normalized 실행 경로로 dispatch한다.
Client 요청은 provider 실행 경로를 고르는 필드를 넣지 않으며, provider-specific/custom request field는 provider-pool route에서 먼저 보존한 뒤 선택된 실행 경로의 계약에 맞게 처리한다.

## 상태

[완료]

## 승격 조건

- 없음

## 구현 잠금

- 상태: 해제
- SDD: 필요
- SDD 문서: [SDD.md](../../../sdd/routing-policy-model-orchestration/model-group-mixed-provider-dispatch/SDD.md)
- SDD 사유: Model group provider-pool dispatch, OpenAI-compatible request surface, Edge-Node runtime path, provider 실행 경로 계약이 함께 바뀌는 Milestone이다.
- 잠금 해제 조건:
  - [x] SDD 잠금이 해제되어 있다
  - [x] SDD 사용자 리뷰가 없거나 승인/해결되었다
  - [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다
  - [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다
- 결정 필요: 없음

## 범위

- `models[]` provider-pool/model group에서 OpenAI-compatible provider와 normalized provider를 동일 후보로 평가하는 selection-first dispatch
- `capacity + priority` 기준의 기존 provider 선택 정책 유지
- OpenAI-compatible 호출 방식 지원 여부 기반 실행 경로 분기
  - OpenAI-compatible 호출 방식을 지원하는 모든 provider: passthrough 방식
  - provider type/label/선언된 지원 방식이 `openai_compat`, `openai_api`, `vllm`, `vllm-mlx`, `lemonade`, `sglang`, `seulgivibe_*`, openweight cloud request model 계열인 provider: OpenAI-compatible provider로 보고 passthrough 방식
  - provider type/선언된 지원 방식이 `ollama`, `cli` 및 OpenAI-compatible 호출 방식을 지원하지 않는 provider: normalized
- Seulgivibe Claude/OpenAI provider는 현재 구현 중인 Seulgivibe Milestone의 auth/catalog/Responses passthrough 범위를 유지하되, 이 Milestone의 classifier에서는 `seulgivibe_claude`, `seulgivibe_openai` 모두 OpenAI-compatible passthrough provider에 포함한다.
- OpenAI-compatible provider의 tunnel 구현이 누락된 경우 normalized fallback으로 낮추지 않고 구현 결함 또는 unsupported 상태로 처리하는 기준
- Ollama provider를 model group에서 제외하지 않고, 작은 capacity/priority 설정으로 운영자가 후보 가중치를 조절하는 방식
- model group 요청에서 client-controlled `metadata.iop_response_mode` 또는 동등한 passthrough/normalized selector를 제거/거부하는 계약
- provider-pool Chat/Responses route의 raw request 보존과 provider-specific/custom field 처리
- 선택된 provider id/type/adapter/target/execution path를 sideband/usage/log observation에 남기는 기준
- 관련 `agent-contract`, `agent-spec`, config 예시, Edge/Node 테스트 갱신

## 기능

### Epic: [mixed-dispatch] Mixed Provider Dispatch

Model group이 provider 종류에 따라 normalized-only로 후퇴하지 않고, 선택된 provider의 OpenAI-compatible 지원 여부에 맞는 실행 경로로 dispatch되는 기능을 묶는다.

- [x] [selection-first-path] Provider-pool route가 provider 후보를 먼저 선택하고 같은 queue slot/lease로 `ProviderTunnelRequest` 또는 normalized `RunRequest` 중 하나를 실행한다. 검증: `go test ./apps/edge/internal/service -count=1`에서 double scheduling 없이 selected provider path가 고정됨을 확인한다.
- [x] [provider-path-classifier] Provider classifier가 OpenAI-compatible 호출 방식을 지원하는 모든 provider를 passthrough 방식으로, Ollama/CLI/native provider를 normalized로 분류한다. Seulgivibe aliases는 passthrough provider에 포함한다. 검증: `go test ./packages/go/config -count=1`, `go test ./apps/edge/internal/node -count=1`, `go test ./apps/node/internal/adapters -count=1`이 provider alias와 adapter implementation case를 포함해 통과한다.
- [x] [mixed-model-group] 같은 model group 안의 vLLM/vLLM-MLX/Lemonade/openweight cloud provider와 Ollama provider가 모두 후보로 남고, 선택된 provider별로 tunnel 또는 normalized path가 실행된다. 검증: `go test ./apps/edge/internal/openai -count=1`, `go test ./apps/edge/internal/service -count=1`이 mixed group, Ollama-only group, tunnel-only group fixture를 포함해 통과한다.

### Epic: [model-group-surface] Model Group Client Surface

Client가 OpenAI-compatible 표면으로 호출하되 model group 내부 실행 방식을 직접 고르지 않도록 계약과 handler를 정리한다.

- [x] [no-client-response-mode] Model group route에서 `metadata.iop_response_mode` 또는 동등한 passthrough/normalized selector를 지원하지 않도록 계약, 구현, 테스트를 정리한다. 검증: [openai-compatible-api.md](../../../../../agent-contract/outer/openai-compatible-api.md), [openai-compatible-surface.md](../../../../../agent-spec/input/openai-compatible-surface.md), handler tests가 model group selector 거부/제거 기준과 일치한다.
- [x] [custom-field-preservation] Provider-pool Chat route가 Responses route처럼 strict decode 전에 raw body와 routing envelope를 분리하고, OpenAI-compatible provider 선택 시 unknown/Codex/provider-specific fields를 model rewrite 외에는 보존한다. 검증: `go test ./apps/edge/internal/openai -count=1`이 Chat unknown field preservation과 normalized-provider 선택 시 supported field mapping/unsupported policy를 확인한다.
- [x] [sideband-observation] Provider-pool 실행 결과가 selected provider, adapter, served target, execution path, queue decision, usage 후보를 sideband/log/metric에 남기며 표준 client 응답에는 불필요한 custom field를 섞지 않는다. 검증: `go test ./apps/edge/internal/openai -count=1`, `go test ./apps/edge/internal/service -count=1`이 standard-client response와 IOP-aware observation fixture를 함께 확인한다.

### Epic: [contract-spec] Contract and Spec Sync

Model group mixed dispatch의 공개/내부 계약을 문서와 구현 타입에 맞춘다.

- [x] [contract-sync] `agent-contract`와 `agent-spec`가 model group의 provider-derived execution path, custom field 보존, client selector 금지, normalized/passthrough 경계를 같은 용어로 설명한다. 검증: contract/spec diff와 관련 Go tests가 SDD Evidence Map에 연결된다.
- [x] [config-examples] provider-first config 예시가 mixed model group과 Ollama-only model group을 보여주되, Ollama는 capacity/priority로만 가중치를 조절한다. 검증: `go test ./packages/go/config -count=1`과 config fixture validation이 통과한다.

## 완료 리뷰

- 상태: 통과
- 요청일: 2026-07-12
- 완료 근거:
  - `contract-sync`: [openai-compatible-api.md](../../../../../agent-contract/outer/openai-compatible-api.md), [edge-node-runtime-wire.md](../../../../../agent-contract/inner/edge-node-runtime-wire.md), [edge-config-runtime-refresh.md](../../../../../agent-contract/inner/edge-config-runtime-refresh.md), [openai-compatible-surface.md](../../../../../agent-spec/input/openai-compatible-surface.md), [provider-pool-config-refresh.md](../../../../../agent-spec/runtime/provider-pool-config-refresh.md), [edge-node-execution.md](../../../../../agent-spec/runtime/edge-node-execution.md)에 provider-first mixed dispatch 용어를 동기화했다.
  - `config-examples`: [edge.yaml](../../../../../configs/edge.yaml)에 mixed model group과 Ollama-only group을 추가하고 [config_test.go](../../../../../packages/go/config/config_test.go) fixture validation으로 보호했다.
  - 검증: `GOCACHE=/tmp/iop-go-build-cache go test ./packages/go/config -count=1`, `GOCACHE=/tmp/iop-go-build-cache go test ./packages/go/... -count=1`, `GOCACHE=/tmp/iop-go-build-cache go test ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/edge/internal/node ./apps/node/internal/adapters -count=1`, `git diff --check` 통과.
  - Spec sync: 완료. [openai-compatible-surface.md](../../../../../agent-spec/input/openai-compatible-surface.md), [provider-pool-config-refresh.md](../../../../../agent-spec/runtime/provider-pool-config-refresh.md), [edge-node-execution.md](../../../../../agent-spec/runtime/edge-node-execution.md)의 변경 기록까지 반영했다.
- 남은 차단 항목: 없음
- 검토 항목:
  - [x] 직접 완료 evidence가 각 기능 Task id와 연결된다.
  - [x] 최종 검증 출력이 SDD Evidence Map과 일치한다.
  - [x] model group 요청에서 client-controlled response path selector가 남아 있지 않다.
  - [x] mixed group에서 Ollama가 후보에서 제외되지 않고 normalized path로만 실행된다.
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 종료 감사에서 SDD gate, 기능 Task, complete.log evidence, 관련 Go test, spec sync가 모두 충족됨을 확인했다.

## 범위 제외

- provider 선택 기준을 `capacity + priority` 밖의 score policy로 확장하는 작업
- Ollama를 고동시성 기본 provider로 취급하거나 Ollama capacity를 자동 상향하는 작업
- client 요청이 model group 실행 경로를 직접 고르는 새 field/header/query parameter
- provider-specific endpoint credential, token source, auth forwarding 정책 추가
- provider response payload를 모든 provider에 대해 byte-identical하게 강제하는 작업
- cloud fallback, route scorer, policy learning loop 구현

## 작업 컨텍스트

- 관련 경로: `packages/go/config`, `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/edge/internal/node`, `apps/node/internal/adapters/openai_compat`, `apps/node/internal/adapters/ollama`, `apps/node/internal/runtime`, `proto/iop/runtime.proto`, [openai-compatible-api.md](../../../../../agent-contract/outer/openai-compatible-api.md), [edge-node-runtime-wire.md](../../../../../agent-contract/inner/edge-node-runtime-wire.md), [edge-config-runtime-refresh.md](../../../../../agent-contract/inner/edge-config-runtime-refresh.md)
- 표준선(선택): Model group은 normalized provider만 묶는 추상화가 아니다. OpenAI-compatible provider와 native provider를 같은 candidate set에 두고, 선택된 provider가 실행 경로를 결정한다.
- 표준선(선택): OpenAI-compatible 호출 방식을 지원하는 모든 provider는 passthrough 방식에 속한다. 여기에는 openweight cloud provider, Seulgivibe Claude/OpenAI provider, 로컬 vLLM/vLLM-MLX/Lemonade/SGLang 계열이 포함된다. sideband는 내부 observation 또는 문서화된 extension-safe 지점으로만 다루며, client 요청 selector가 아니다.
- 표준선(선택): OpenAI-compatible provider에서 tunnel/passthrough 구현이 빠져 있으면 normalized fallback으로 처리하지 않는다. 이는 구현 결함 또는 unsupported 상태다.
- 표준선(선택): Ollama와 CLI처럼 OpenAI-compatible 호출 방식을 지원하지 않는 provider는 model group 안에서도 normalized로 실행한다. Ollama는 제외하지 않으며 운영자는 capacity/priority로 낮은 동시성을 표현한다.
- 표준선(선택): Model group client request에는 `passthrough`, `passthrough+sideband`, `normalized`, `transformed` 같은 실행 경로 selector를 넣지 않는다.
- 표준선(선택): 표준 OpenAI-compatible client 요청은 표준-compatible 응답을 받고, IOP-aware/custom 요청은 문서화된 extension-safe 지점에서만 sideband/custom observation을 볼 수 있다.
- 우선순위/정합성: 현재 active 흐름에서는 [OpenAI-compatible 출력 검증 필터](../../../../phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)와 [Seulgivibe OpenAI-compatible Provider 연동](seulgivibe-openai-compatible-provider.md)을 함께 고려한다. Seulgivibe 구현은 OpenAI-compatible provider는 passthrough 방식이라는 기준과 충돌하지 않아야 하며, mixed provider dispatch 자체는 본 Milestone이 소유한다.
- 선행 작업: [OpenAI-compatible Raw Tunnel과 Sideband Passthrough](openai-compatible-raw-tunnel-sideband-passthrough.md), [Model Alias Provider Pool과 Provider Catalog](../../../phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md)
- 후속 작업: OpenAI-compatible 하이브리드 라우팅과 컨텍스트 최적화, route scorer, policy learning loop
- 확인 필요: 없음
