16 KiB
16 KiB
SDD: Model Group Mixed Provider Dispatch
위치
- Milestone: Model Group Mixed Provider Dispatch
- Phase: PHASE.md
상태
[승인됨]
SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
- 없음
문제 / 비목표
- 문제: 현재 provider-pool/model group route는 provider pool이면 raw tunnel을 먼저 가정하는 경향이 있어, 같은 model group 안에 Ollama 같은 normalized-only provider가 들어오면 선택된 provider의 OpenAI-compatible 지원 여부와 실행 경로가 어긋날 수 있다. 반대로 model group 전체를 normalized로 낮추면 vLLM/vLLM-MLX/Lemonade/openweight cloud provider의 provider-original passthrough 장점이 사라진다. Model group은 provider 후보를 동일하게 평가하되, 선택된 provider의 OpenAI-compatible 지원 여부에 따라 실행 경로를 자동 결정해야 한다.
- 비목표:
- provider 선택 기준을
capacity + priority밖의 score policy로 바꾼다. - Ollama를 model group에서 제외한다.
- Ollama를 고동시성 기본 provider로 취급한다.
- client 요청 field로 passthrough/normalized/transformed 경로를 선택하게 한다.
- 이 Milestone에서 cloud fallback, route learning loop, provider auth 정책을 추가한다.
- provider 선택 기준을
Source of Truth
| 영역 | 기준 | 메모 |
|---|---|---|
| Roadmap | Model Group Mixed Provider Dispatch | 목표, 기능 Task, 범위 제외 기준 |
| Contract | openai-compatible-api.md, edge-node-runtime-wire.md, edge-config-runtime-refresh.md | 외부 OpenAI-compatible model group surface, provider tunnel, normalized run dispatch, provider config 계약 |
| Spec | openai-compatible-surface.md, provider-pool-config-refresh.md, edge-node-execution.md | 현재 구현 surface와 runtime behavior 문서 |
| Code | 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 |
config validation, provider selection, OpenAI handler, Edge-Node wire, adapter implementation 기준 |
| User Decision | 현재 사용자 요청 | Ollama는 model group에서 제외하지 않는다. 후보는 동일하게 두고 capacity/priority로 가중한다. model group request에는 response path selector를 두지 않는다. OpenAI-compatible 호출 방식을 지원하는 provider는 모두 passthrough 방식으로 처리하고, Ollama/CLI/native provider는 normalized로 처리한다. Seulgivibe Claude/OpenAI aliases는 passthrough provider에 포함한다. custom request fields는 provider-pool ingress에서 보존한다. |
State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|---|---|---|---|
ingress |
OpenAI-compatible Chat 또는 Responses request가 models[] catalog의 model group id를 사용한다 |
route-envelope-parsed 또는 invalid-request |
Edge OpenAI handler |
route-envelope-parsed |
raw body는 보존하고 routing에 필요한 model, metadata, stream 등 최소 envelope를 읽었다 |
candidate-selection |
provider-pool route |
invalid-request |
model group request가 metadata.iop_response_mode 또는 동등한 execution path selector를 포함하거나 필수 routing field가 없다 |
terminal error | model group client surface contract |
candidate-selection |
model group의 provider mapping과 connected node status가 있다 | provider-selected 또는 no-capacity |
existing capacity/priority/health rules |
no-capacity |
사용 가능한 provider candidate가 없다 | terminal error | queue policy |
provider-selected |
queue slot이 특정 provider id/node/adapter/served target에 할당되었다 | execution-path-resolved |
selected provider의 OpenAI-compatible 지원 여부 |
execution-path-resolved |
selected provider가 OpenAI-compatible 호출 방식을 지원한다 | passthrough-dispatch |
OpenAI-compatible provider |
execution-path-resolved |
selected provider가 normalized-only다 | normalized-dispatch |
Ollama/CLI/native provider |
passthrough-dispatch |
raw request body를 model rewrite와 provider auth/header 정책만 적용해 Node tunnel로 보낸다 | provider-response-relayed 또는 provider-error |
ProviderTunnelRequest |
normalized-dispatch |
standard OpenAI-compatible request subset을 internal RunRequest로 변환해 selected adapter로 보낸다 |
normalized-response-built 또는 provider-error |
RunRequest/adapter Execute |
provider-response-relayed |
provider tunnel frames가 caller response로 relay되고 sideband/log/metric observation이 기록된다 | terminal success | passthrough bridge |
normalized-response-built |
normalized runtime events가 OpenAI-compatible response shape로 변환되고 sideband/log/metric observation이 기록된다 | terminal success | normalized response builder |
provider-error |
provider HTTP/tunnel 또는 adapter execution이 실패한다 | terminal error | OpenAI-compatible error mapping |
Interface Contract
- 계약 원문: openai-compatible-api.md
- 입력:
model: caller-facing model group id다.models[]catalog와 provider mapping이 source of truth다.metadata.workspace,metadata.task_id등 routing/operation metadata는 기존 OpenAI-compatible surface를 따른다.metadata.iop_response_mode또는 같은 의미의 execution path selector는 model group route에서 지원하지 않는다. 포함 시 silent fallback 없이400 invalid_request_error로 거부한다.- unknown/Codex/provider-specific request fields는 provider-pool ingress에서 strict decode로 버리거나 거부하지 않는다.
nodes[].providers[].capacity,priority,enabled, health/status, model membership은 기존 provider selection 기준이다.
- 실행 경로:
- OpenAI-compatible 호출 방식을 지원하는 provider는 모두 passthrough 실행 경로를 사용한다.
- OpenAI-compatible provider는 Node adapter가
ProviderTunnelAdapterinterface를 구현해야 한다. 이 interface가 없으면 normalized fallback이 아니라 구현 결함 또는 unsupported 상태로 처리한다. ollama,cli및 OpenAI-compatible 호출 방식을 지원하지 않는 native provider는 normalizedRunRequest실행 경로를 사용한다.- provider type/label/선언된 지원 방식이
openai_compat,openai_api,vllm,vllm-mlx,lemonade,sglang,seulgivibe_claude,seulgivibe_openai계열이면 OpenAI-compatible provider로 분류한다. - provider type/선언된 지원 방식이
ollama,cli이면 normalized-only provider로 분류한다. - Seulgivibe Claude/OpenAI provider는 현재 Seulgivibe Milestone에서 다루는 auth/catalog/Responses passthrough 구현을 유지하면서도, 이 classifier에서는 OpenAI-compatible passthrough provider에 포함된다.
- selection에서 provider type만을 이유로 Ollama를 제외하지 않는다. 운영자는 작은 capacity/priority로 Ollama 동시성 한계를 표현한다.
- 출력:
- 표준 OpenAI-compatible client 요청에는 표준-compatible response를 반환하고 불필요한 IOP custom field/event를 섞지 않는다.
- OpenAI-compatible provider 선택 시 provider HTTP status/header/body 또는 provider SSE를 최대한 보존하며, model rewrite와 문서화된 IOP extension-safe 지점만 예외다.
- normalized provider 선택 시 runtime event를 OpenAI-compatible Chat/Responses response shape로 변환한다.
- 모든 실행 경로는 내부 observation으로 selected provider id/type, adapter, served target, execution path, queue decision, usage 후보를 남긴다.
- IOP-aware/custom request surface가 문서화된 extension-safe 지점을 사용하면 sideband/custom observation을 받을 수 있다. 표준-only request에는 응답 custom field를 만들지 않는다.
- custom field 처리:
- OpenAI-compatible provider가 선택되면 raw body의 unknown/Codex/provider-specific field는 model rewrite와 명시 변환 외에는 provider로 보존 전달한다.
- normalized-only provider가 선택되면 standard OpenAI-compatible subset과 IOP가 아는 extension만 native adapter request로 변환한다. 변환할 수 없는 provider-specific required field는 dispatch 전 명시적 unsupported error로 끝내고, 단순 unknown field는 observation에 남기되 native provider request에 임의로 invent하지 않는다.
- 금지:
- model group 전체를 normalized로 낮춰 vLLM/vLLM-MLX/Lemonade/openweight cloud provider의 raw passthrough 경로를 잃지 않는다.
- provider pool이라는 이유만으로 Ollama/CLI/native provider에
ProviderTunnelRequest를 보내지 않는다. - client가
metadata.iop_response_mode로 model group의 passthrough/normalized/transformed 경로를 고르게 하지 않는다. - provider selection을 두 번 수행해 queue slot과 실제 실행 provider가 달라지게 하지 않는다.
Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|---|---|---|---|---|
| S01 | selection-first-path |
mixed model group에 vLLM provider와 Ollama provider가 있고 둘 다 healthy/capacity가 있다 | provider-pool route를 실행한다 | 기존 capacity/priority 기준으로 provider를 한 번 선택하고, 같은 selected provider로 tunnel 또는 normalized 실행을 수행한다 |
| S02 | selection-first-path |
selected provider가 Ollama다 | Chat Completions request를 처리한다 | Edge가 ProviderTunnelRequest를 보내지 않고 normalized RunRequest로 Ollama adapter를 실행한다 |
| S03 | provider-path-classifier |
provider type/label/선언된 지원 방식이 vllm, vllm-mlx, lemonade, openai_compat, seulgivibe_claude, seulgivibe_openai 중 하나이거나 OpenAI-compatible 호출 방식을 지원한다 |
execution path를 resolve한다 | passthrough path를 선택한다 |
| S04 | provider-path-classifier |
provider type/선언된 지원 방식이 ollama 또는 cli다 |
execution path를 resolve한다 | normalized-only로 분류하고 model group 후보에서는 제외하지 않는다 |
| S05 | mixed-model-group |
Ollama-only model group config가 있다 | OpenAI-compatible Chat request를 보낸다 | provider-pool route가 정상 후보를 찾고 normalized OpenAI-compatible response를 반환한다 |
| S06 | no-client-response-mode |
model group request가 metadata.iop_response_mode를 포함한다 |
Edge handler가 route envelope를 parse한다 | request는 400 invalid_request_error로 거부되고 provider dispatch가 발생하지 않는다 |
| S07 | custom-field-preservation |
Chat request에 Codex/provider-specific unknown field가 있고 selected provider가 OpenAI-compatible provider다 | provider tunnel request body를 만든다 | unknown field가 model rewrite 외에는 그대로 provider로 전달된다 |
| S08 | custom-field-preservation |
같은 unknown field request에서 selected provider가 Ollama다 | normalized request를 만든다 | standard field는 Ollama request로 변환되고, 변환 불가능한 required field는 explicit unsupported error 또는 observation으로 처리된다 |
| S09 | sideband-observation |
표준 OpenAI-compatible client가 model group을 호출한다 | provider가 성공 응답을 반환한다 | caller response에는 불필요한 IOP custom field가 없고, internal log/metric에는 selected provider와 execution path가 남는다 |
| S10 | contract-sync |
contract/spec 문서를 검토한다 | Milestone 구현 diff가 준비된다 | model group의 provider-derived execution path, selector 금지, custom field 보존 기준이 문서와 구현에 동일하게 반영되어 있다 |
Evidence Map
| Scenario | Required Evidence | agent-task 연결 |
완료 Evidence 기대 |
|---|---|---|---|
| S01 | service queue/selection unit tests | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 selection-first-path와 go test ./apps/edge/internal/service -count=1 결과 |
| S02 | Edge service/openai handler tests proving no tunnel for Ollama | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 selection-first-path와 Ollama normalized dispatch assertion |
| S03 | provider classifier tests for OpenAI-compatible aliases | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 provider-path-classifier, config/node/adapters test 결과 |
| S04 | provider classifier tests for Ollama/CLI | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 provider-path-classifier, normalized-only provider candidate assertion |
| S05 | mixed and Ollama-only model group fixtures | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 mixed-model-group, go test ./apps/edge/internal/openai -count=1, go test ./apps/edge/internal/service -count=1 결과 |
| S06 | handler rejection tests and contract/spec diff | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 no-client-response-mode, 400 invalid_request_error assertion |
| S07 | Chat raw body preservation tunnel test | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 custom-field-preservation, provider request body fixture |
| S08 | normalized custom field policy test | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 custom-field-preservation, unsupported/observation assertion |
| S09 | standard response and observation tests | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 sideband-observation, response body/log/metric fixture |
| S10 | contract/spec sync review | agent-task/m-model-group-mixed-provider-dispatch/... |
Roadmap Completion에 contract-sync, linked contract/spec paths and final test output |
Cross-repo Dependencies
- 없음
Drift Check
- Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- Evidence Map이 code-review/complete.log에서 검증 가능하다.
- agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- 사용자 리뷰가 필요한 항목은
USER_REVIEW.md에만 남겼다.
사용자 리뷰 이력
- 없음
작업 컨텍스트
- 표준선: Model group provider 후보는 provider type 때문에 제외하지 않고 기존 capacity/priority/health/model membership으로 선택한다.
- 표준선: 선택된 provider가 OpenAI-compatible 호출 방식을 지원하면 passthrough path를 사용하고, Ollama/CLI/native provider면 normalized path를 사용한다. Seulgivibe Claude/OpenAI aliases는 passthrough provider에 포함한다.
- 표준선: OpenAI-compatible provider에서 tunnel/passthrough 구현이 누락된 경우 normalized fallback으로 처리하지 않는다.
- 표준선: model group client request는 provider execution path를 지정하지 않는다.
metadata.iop_response_mode는 model group route에서 제거/거부 대상이다. - 표준선: Chat provider-pool ingress는 Responses provider-pool ingress처럼 raw body와 route envelope를 분리해 custom/provider-specific field를 보존할 준비를 한다.
- 후속 SDD: 없음