# SDD: SGLang provider 서빙 경로 추가

## 위치

- Milestone: `agent-roadmap/archive/phase/inference-provider-extension/milestones/sglang-provider-serving-validation.md`
- Phase: `agent-roadmap/phase/inference-provider-extension/PHASE.md`

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
  - 없음

## 문제 / 비목표

- 문제: SGLang은 OpenAI-compatible `/v1/models`와 `/v1/chat/completions` 표면을 제공할 수 있고, IOP에는 이미 vLLM/Lemonade를 위해 `openai_compat` 공통 adapter, `openai.model_routes[]`, `nodes[].adapters.openai_compat_instances[]` 설정 계약이 있다. 사용자는 vLLM에서 이미 정한 같은 부류의 결정은 SGLang에도 동일한 기준으로 따르도록 승인했다. 따라서 남은 값은 사용자 결정이 아니라 field smoke와 구현 plan에서 관측/기록할 provider별 evidence다.
- 비목표:
  - SGLang 전용 process/container lifecycle, 모델 설치/삭제/load/unload 제품화
  - provider/device/model qualification report 저장/조회/비교 제품화
  - cloud fallback, cross-Edge 자동 부하 라우팅, 품질 평가 feedback 구현
  - Responses API 세부 호환 확장
  - Ollama, Lemonade, vLLM 완료 기록 재작성

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | `agent-roadmap/archive/phase/inference-provider-extension/milestones/sglang-provider-serving-validation.md` | Milestone 목표, 범위, 기능 Task, 구현 잠금 반영 기준 |
| SDD | `agent-roadmap/archive/sdd/inference-provider-extension/sglang-provider-serving-validation/SDD.md` | SGLang provider 경계, acceptance scenario, evidence 기준 |
| Code | `apps/node/internal/adapters/openai_compat/openai_compat.go`, `packages/go/config/config.go`, `apps/edge/internal/node/mapper.go`, `apps/edge/internal/service/run_dispatch.go`, `apps/edge/internal/service/model_queue.go`, `apps/edge/internal/openai` | 구현 source of truth. SGLang은 기본적으로 `openai_compat` adapter의 `provider: "sglang"` instance로 연결한다. |
| Contract | `agent-contract/provided/openai-compatible-api.md` | Edge OpenAI-compatible HTTP 입력 표면과 `model` route 계약 원문 |
| External Provider | SGLang OpenAI-compatible endpoint | field 기준 endpoint는 vLLM처럼 `http://<SGLANG_HOST>:<PORT>/v1` 패턴으로 두고, 실제 private host 값은 tracked 문서에 고정하지 않는다. |
| User Decision | 없음 | vLLM에서 정한 같은 부류의 결정은 SGLang에도 동일하게 적용한다. |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| sdd-approved | vLLM과 같은 부류의 결정 상속이 승인되고 SDD 잠금이 해제된다. | implementation-plan | `SDD 잠금: 해제`, 사용자 리뷰 없음 |
| implementation-plan | `agent-task/m-sglang-provider-serving-validation/PLAN-*.md`가 SDD Acceptance Scenario를 Spec Targets로 고정한다. | implementation-review | plan/code changes |
| implementation-review | SGLang config, routing, provider probe, chat, streaming 검증이 수행된다. | milestone-review | `CODE_REVIEW-*.md`, `complete.log` |
| milestone-review | 모든 기능 Task와 Spec Completion evidence가 충족된다. | milestone-reviewing | Milestone 완료 리뷰 |
| provider-unavailable | SGLang `/v1/models` 또는 Node 접근 endpoint가 실패한다. | user-review 또는 follow-up-plan | field smoke 로그, D01 재확인 또는 follow-up Task |
| provider-incompatible | SGLang streaming chunk, `[DONE]`, reasoning/content field가 `openai_compat` 기대와 다르다. | follow-up-plan | `follow-up-scope` evidence |

## Interface Contract

- 계약 원문: `agent-contract/provided/openai-compatible-api.md`
- 입력:
  - `openai.model_routes[].model`: 외부 OpenAI-compatible caller가 사용하는 Edge route alias다. vLLM과 같은 모델 계열 field smoke라면 alias 기준은 `qwen3.6:35b`를 따른다.
  - `openai.model_routes[].adapter`: SGLang route는 기본값으로 `openai_compat`을 사용한다.
  - `openai.model_routes[].target`: SGLang `/v1/models`가 반환하는 served model id다. vLLM과 같은 모델 계열 field smoke라면 target 기준은 `nvidia/Qwen3.6-35B-A3B-NVFP4`를 우선하되, 실제 SGLang `/v1/models` 응답이 다르면 관측값을 구현 evidence로 기록한다.
  - `openai.model_routes[].node`: SGLang provider instance가 설정된 Node id 또는 alias다.
  - `nodes[].adapters.openai_compat_instances[].name`: Node 안의 안정적인 adapter instance identity다. 예: `sglang`.
  - `nodes[].adapters.openai_compat_instances[].provider`: `sglang`으로 둔다.
  - `nodes[].adapters.openai_compat_instances[].endpoint`: Node 프로세스가 접근 가능한 SGLang OpenAI-compatible base URL이다. field 기준은 vLLM과 같이 `http://<SGLANG_HOST>:<PORT>/v1` 패턴으로 두며, 실제 private host 값은 tracked 문서에 고정하지 않는다. `/v1` 포함 여부는 adapter가 중복 `/v1`을 피하도록 처리한다.
  - `nodes[].adapters.openai_compat_instances[].headers`: vLLM field smoke와 같이 인증/추가 header 없음으로 시작한다. 추후 인증이 필요한 환경은 config contract 확장에서 별도 옵션으로 다룬다. secret 원문은 tracked 문서에 기록하지 않는다.
  - `nodes[].adapters.openai_compat_instances[].capacity`, `max_queue`, `queue_timeout_ms`, `request_timeout_ms`: provider instance capacity와 timeout 기준이다.
  - OpenAI request `model`: Edge route alias와 매칭되며 내부 `adapter + target`으로 변환된다.
  - Chat request `messages` 또는 `prompt`: Node `openai_compat` adapter가 `/v1/chat/completions`의 `messages`로 변환한다.
  - Chat request `options`: `temperature`, `max_tokens` 같은 OpenAI-compatible top-level option passthrough다. adapter-owned `model`, `messages`, `stream`이 우선한다.
  - Optional request fields `tools`, `format`, `think`, `keep_alive`: 현재 `openai_compat` passthrough 후보로 유지한다.
- 출력:
  - `/v1/models`: Edge catalog는 route alias를 노출하고, provider capability/probe는 SGLang `/v1/models`의 served model id 존재 여부를 확인한다.
  - Non-streaming `/v1/chat/completions`: Edge는 runtime delta를 집계해 OpenAI-compatible 응답을 반환한다.
  - Streaming `/v1/chat/completions`: vLLM field smoke와 같이 지원 전제로 검증하며 Edge는 SSE chunk를 caller에게 전달한다.
  - Runtime delta: SGLang `delta.content`는 `RuntimeEvent` delta로 전달한다.
  - Runtime reasoning delta: SGLang `delta.reasoning_content`가 있으면 `RuntimeEvent` reasoning_delta로 전달한다.
  - Completion: provider `[DONE]` 또는 finish event 이후 complete event가 생성되며 `finish_reason`과 usage가 있으면 보존한다.
- 금지:
  - SGLang을 붙이기 위해 Edge/OpenAI surface가 Node adapter나 provider HTTP endpoint를 직접 우회하지 않는다.
  - 내부 실행 계약을 `model` 중심으로 되돌리지 않는다. 외부 API 경계에서만 `model`을 사용하고 내부는 `adapter + target`을 유지한다.
  - secret, bearer token, private endpoint 원문을 tracked roadmap/docs에 기록하지 않는다.
  - 실제 SGLang divergence가 확인되기 전에는 독립 `sglang` adapter를 새로 만들지 않는다.
  - SGLang 전용 lifecycle, model management, qualification report를 이 Milestone에 끌어오지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `provider-boundary` | 기존 `openai_compat` adapter가 provider label, headers, option passthrough, `/v1/models`, streaming `/v1/chat/completions`를 지원한다. | SGLang provider 경계를 설계한다. | SGLang은 `openai_compat` adapter와 `provider: "sglang"` instance를 기본 경로로 사용하고, 독립 `sglang` adapter는 field incompatibility가 확인된 follow-up에서만 검토한다. |
| S02 | `config-contract` | vLLM에서 정한 같은 부류 결정이 SGLang에도 동일하게 적용되어 있다. | Edge config와 Node payload 계약을 정리한다. | `openai.model_routes[]`와 `nodes[].adapters.openai_compat_instances[]` 기준 설정이 문서화되고, config/proto/mapper 변경이 필요하면 plan의 Spec Targets에 고정된다. |
| S03 | `models-chat` | SGLang endpoint가 Node host에서 접근 가능하고 `/v1/models`가 served model id를 반환한다. | Edge `/v1/models`와 non-streaming `/v1/chat/completions`를 route alias로 호출한다. | Edge `/v1/models`는 route alias를 노출하고, non-streaming chat은 SGLang served model target으로 수렴해 비어 있지 않은 content를 반환한다. |
| S04 | `streaming` | SGLang endpoint가 SSE streaming과 `[DONE]` 종료를 지원한다. | Edge streaming `/v1/chat/completions`를 호출한다. | SSE chunk 순서가 유지되고 `delta.content`, optional `delta.reasoning_content`, finish signal이 runtime event와 Edge response로 안정 전달된다. |
| S05 | `follow-up-scope` | SGLang 실테스트 중 `openai_compat` 기대와 다른 응답 shape, auth, timeout, streaming 종료 문제가 발견된다. | plan/code-review 또는 field smoke evidence를 정리한다. | 이번 Milestone 안에서 바로 수정할 항목과 후속 Milestone으로 넘길 항목이 `follow-up-scope` Task 또는 같은 task group의 후속 plan에 기록된다. |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | `Spec Completion` 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | `apps/node/internal/adapters/openai_compat/openai_compat.go`, `apps/node/README.md`, `apps/edge/README.md`, SDD provider-boundary 근거 | `agent-task/m-sglang-provider-serving-validation/...` | `provider-boundary`: `openai_compat` + `provider="sglang"` 기본 경계가 plan과 구현에 반영되었고 독립 adapter 미도입 사유가 기록됨 |
| S02 | config/proto/mapper diff 또는 변경 불필요 근거, `configs/edge.yaml` 예시 검토, vLLM 기준 상속 근거 | `agent-task/m-sglang-provider-serving-validation/...` | `config-contract`: route alias, served model target, endpoint, headers, queue/timeout 설정 계약이 검증됨 |
| S03 | Node host에서 SGLang `/v1/models` 확인, Edge `/v1/models` 결과, non-streaming `/v1/chat/completions` 호출 로그 | `agent-task/m-sglang-provider-serving-validation/...` | `models-chat`: Edge model catalog와 non-streaming chat이 SGLang provider target으로 수렴하고 content가 비어 있지 않음 |
| S04 | streaming curl 또는 `iop-edge smoke openai` 동등 증거, SSE chunk 로그, `[DONE]` 또는 finish signal 확인, reasoning/content 분리 여부 | `agent-task/m-sglang-provider-serving-validation/...` | `streaming`: chunk 순서, content/reasoning delta, complete event가 기대대로 관찰됨 |
| S05 | field smoke 실패/차이 로그, code-review finding 또는 follow-up plan 링크 | `agent-task/m-sglang-provider-serving-validation/...` | `follow-up-scope`: 즉시 수정 항목과 후속 범위가 분리되어 기록됨 |

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 plan/code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.

## 사용자 리뷰 이력

- 2026-06-19: 사용자가 SGLang도 vLLM과 같은 방식, 즉 `openai_compat` adapter의 `provider: "sglang"` instance로 진행하도록 승인했다.
- 2026-06-19: 사용자가 SGLang endpoint도 vLLM과 같이 실제 private host 값을 tracked 문서에 고정하지 않고 `http://<SGLANG_HOST>:<PORT>/v1` 패턴으로 두도록 승인했다.
- 2026-06-19: 사용자가 본인이 정해야 할 항목 중 vLLM에서 이미 정한 같은 부류의 결정은 SGLang에도 vLLM 설정과 동일하게 따르도록 승인했다. 남은 SGLang 값은 사용자 결정이 아니라 field smoke와 구현 plan에서 관측/기록할 provider별 evidence로 본다.
- 2026-06-19: field validation 결과, 현재 GX10 + Qwen3.6 35B NVFP4 조건에서는 SGLang provider 채택 ROI가 낮으므로 Milestone을 미채택 종료로 닫도록 승인했다.

## Field Validation 종료 결론

- 결론: SGLang provider는 현 GX10 + Qwen3.6 35B NVFP4 조건에서 채택하지 않는다.
- SGLang smoke:
  - `lmsysorg/sglang:latest-cu130` image와 `unsloth/Qwen3.6-35B-A3B-NVFP4` served model로 `/v1/models`와 `/v1/chat/completions` smoke는 통과했다.
  - 기존 `nvidia/Qwen3.6-35B-A3B-NVFP4` checkpoint는 현재 SGLang image에서 quantization/block shape 오류로 로딩 실패했다.
  - `--context-length 262144`, `--mem-fraction-static 0.30` 기준 SGLang KV pool은 `270101` tokens로 관측됐다.
- vLLM 비교:
  - vLLM은 `nvidia/Qwen3.6-35B-A3B-NVFP4` checkpoint를 직접 로드했고, KV cache `561737` tokens와 262K 요청 기준 maximum concurrency `2.14x`가 관측됐다.
  - 동일 256 completion-token, thinking-off, concurrency sweep에서 vLLM이 전 구간 우세했다.

| 동시 호출 수 | SGLang 요청당 tok/s | vLLM 요청당 tok/s | vLLM 우위 | SGLang 전체 tok/s | vLLM 전체 tok/s | vLLM 우위 |
|---:|---:|---:|---:|---:|---:|---:|
| 1 | 40.95 | 77.42 | +89.1% | 40.94 | 77.38 | +89.0% |
| 2 | 42.34 | 67.29 | +58.9% | 84.66 | 134.54 | +58.9% |
| 3 | 36.61 | 55.41 | +51.4% | 109.82 | 166.20 | +51.3% |
| 4 | 38.98 | 60.41 | +55.0% | 155.90 | 241.57 | +55.0% |
| 5 | 34.09 | 42.04 | +23.3% | 170.42 | 210.15 | +23.3% |

## 작업 컨텍스트

- 표준선: 내부 실행 계약은 `adapter + target`을 유지하고, OpenAI-compatible 경계에서만 `model` route alias를 사용한다.
- 표준선: SGLang은 vLLM/Lemonade와 같은 OpenAI-compatible inference server 계열이므로 기본 adapter 경계는 `openai_compat`이다.
- 표준선: Edge는 `openai.model_routes[]`를 route catalog로 사용하고, Node는 `openai_compat_instances[]`를 provider instance source로 사용한다.
- 표준선: field smoke는 `agent-test/local/edge-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/platform-common-smoke.md` 기준을 따른다.
- 표준선: endpoint, token, header secret, private host 원문은 tracked 문서에 남기지 않는다.
- 후속 SDD: 없음
