chore: sync OpenAI compatible API contract, routing policy, and dev-corp test updates
This commit is contained in:
parent
10ede9381b
commit
9f5d15d91c
16 changed files with 482 additions and 69 deletions
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
| id | 읽는 조건 | 원본 경로 | path |
|
| id | 읽는 조건 | 원본 경로 | path |
|
||||||
|----|-----------|-----------|------|
|
|----|-----------|-----------|------|
|
||||||
| `iop.openai-compatible-api` | OpenAI-compatible API, Responses API, Chat Completions, legacy Completions, `model` route, Codex/CLI workspace, generic authoring metadata, `metadata.workspace`, `metadata.task_id`, `metadata.iop_response_mode`, provider raw passthrough/sideband/transformed response modes | `apps/edge/internal/openai/*`, `packages/go/config/config.go`, `configs/edge.yaml` | `agent-contract/outer/openai-compatible-api.md` |
|
| `iop.openai-compatible-api` | OpenAI-compatible API, Responses API, Chat Completions, legacy Completions, `model` route, model-driven passthrough/normalized routing, Codex/CLI workspace, generic authoring metadata, `metadata.workspace`, `metadata.task_id`, provider-native OpenAI-compatible extension fields such as `chat_template_kwargs` | `apps/edge/internal/openai/*`, `packages/go/config/config.go`, `configs/edge.yaml` | `agent-contract/outer/openai-compatible-api.md` |
|
||||||
| `iop.a2a-json-rpc-api` | A2A JSON-RPC API, `message/send`, `tasks/get`, `tasks/cancel`, A2A task state, agent card, `a2a.bearer_token`, Edge A2A input surface | `apps/edge/internal/input/a2a/*`, `packages/go/config/config.go`, `configs/edge.yaml` | `agent-contract/outer/a2a-json-rpc-api.md` |
|
| `iop.a2a-json-rpc-api` | A2A JSON-RPC API, `message/send`, `tasks/get`, `tasks/cancel`, A2A task state, agent card, `a2a.bearer_token`, Edge A2A input surface | `apps/edge/internal/input/a2a/*`, `packages/go/config/config.go`, `configs/edge.yaml` | `agent-contract/outer/a2a-json-rpc-api.md` |
|
||||||
|
|
||||||
## Inner Contracts
|
## Inner Contracts
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ Edge는 Node 연결을 수락하고, Node는 연결 직후 등록 요청을 보
|
||||||
|
|
||||||
- register: Node가 `RegisterRequest`를 보내고 Edge가 `RegisterResponse`로 수락 여부와 `NodeConfigPayload`를 돌려준다.
|
- register: Node가 `RegisterRequest`를 보내고 Edge가 `RegisterResponse`로 수락 여부와 `NodeConfigPayload`를 돌려준다.
|
||||||
- execution: Edge가 `RunRequest`를 보내고 Node가 `RunEvent` stream으로 실행 상태를 보낸다.
|
- execution: Edge가 `RunRequest`를 보내고 Node가 `RunEvent` stream으로 실행 상태를 보낸다.
|
||||||
- provider raw tunnel: Edge가 기존 Edge-Node socket으로 `ProviderTunnelRequest`를 보내고 Node가 provider HTTP/SSE 요청을 연 뒤 `ProviderTunnelFrame` stream으로 provider status/header/body/end/error/usage 후보를 sequence와 함께 돌려준다. 이 경로는 OpenAI-compatible provider `passthrough`와 `passthrough+sideband`용이며 `RunEvent` 실행 stream과 분리된다.
|
- provider raw tunnel: Edge가 기존 Edge-Node socket으로 `ProviderTunnelRequest`를 보내고 Node가 provider HTTP/SSE 요청을 연 뒤 `ProviderTunnelFrame` stream으로 provider status/header/body/end/error/usage 후보를 sequence와 함께 돌려준다. 이 경로는 OpenAI-compatible provider passthrough용이며 `RunEvent` 실행 stream과 분리된다.
|
||||||
- provider-pool mixed dispatch: Edge service는 model group provider candidate를 선택한 뒤, 같은 selected provider/queue lease로 OpenAI-compatible provider에는 `ProviderTunnelRequest`, Ollama/CLI/native provider에는 normalized `RunRequest`를 보낸다. Edge-Node wire는 client-provided response path selector를 받지 않고, provider type만으로 후보를 제외하지 않는다.
|
- provider-pool mixed dispatch: Edge service는 model group provider candidate를 선택한 뒤, 같은 selected provider/queue lease로 OpenAI-compatible provider에는 `ProviderTunnelRequest`, Ollama/CLI/native provider에는 normalized `RunRequest`를 보낸다. Edge-Node wire는 client-provided response path selector를 받지 않고, provider type만으로 후보를 제외하지 않는다.
|
||||||
- cancel: Edge가 `CancelRequest`를 보내며 `CANCEL_RUN`과 `TERMINATE_SESSION`을 구분한다.
|
- cancel: Edge가 `CancelRequest`를 보내며 `CANCEL_RUN`과 `TERMINATE_SESSION`을 구분한다.
|
||||||
- command: Edge가 `NodeCommandRequest`를 보내고 Node가 `NodeCommandResponse`로 usage/capabilities/session/transport/provider 상태를 응답한다.
|
- command: Edge가 `NodeCommandRequest`를 보내고 Node가 `NodeCommandResponse`로 usage/capabilities/session/transport/provider 상태를 응답한다.
|
||||||
|
|
@ -46,8 +46,8 @@ Edge는 Node 연결을 수락하고, Node는 연결 직후 등록 요청을 보
|
||||||
- `RunRequest.input`: adapter가 해석할 실행 입력이다. CLI 실행에서는 prompt 계열 입력으로 변환된다.
|
- `RunRequest.input`: adapter가 해석할 실행 입력이다. CLI 실행에서는 prompt 계열 입력으로 변환된다.
|
||||||
- `RunRequest.metadata`: caller-defined 실행 metadata다. workspace 자체는 별도 `workspace` 필드로 전달한다.
|
- `RunRequest.metadata`: caller-defined 실행 metadata다. workspace 자체는 별도 `workspace` 필드로 전달한다.
|
||||||
- `RunEvent.type`: `start`, `delta`, `complete`, `error`, `cancelled` 같은 실행 이벤트 종류다.
|
- `RunEvent.type`: `start`, `delta`, `complete`, `error`, `cancelled` 같은 실행 이벤트 종류다.
|
||||||
- `ProviderTunnelRequest`: 기존 Edge-Node socket 위에서 provider HTTP request를 열기 위한 요청이다. `adapter`, `target`, `method`, `path`, `headers`, `body`, `stream`, `timeout_sec`, `metadata`, `session_id`를 싣되 normalized adapter execution인 `RunRequest`와 분리된다. Chat Completions response mode는 `metadata["iop_response_mode"]`에 `passthrough` 또는 `passthrough+sideband`로 전달될 수 있다.
|
- `ProviderTunnelRequest`: 기존 Edge-Node socket 위에서 provider HTTP request를 열기 위한 요청이다. `adapter`, `target`, `method`, `path`, `headers`, `body`, `stream`, `timeout_sec`, `metadata`, `session_id`를 싣되 normalized adapter execution인 `RunRequest`와 분리된다. 외부 caller의 response selector를 전달하지 않으며, 경로는 Edge가 `model`로 선택한 provider capability에 의해 결정된다.
|
||||||
- `ProviderTunnelFrame`: Node가 provider response를 Edge로 돌려주는 ordered frame이다. `kind`, `sequence`, `status_code`, `headers`, `body`, `end`, `error`, `usage`, `metadata`를 싣는다. `body`는 passthrough source of truth이며 `RunEvent.delta`나 Edge `events.Bus` fanout payload로 보내지 않는다. `usage`와 `metadata`는 sideband observation 후보이고 pure passthrough body에 합쳐지지 않는다.
|
- `ProviderTunnelFrame`: Node가 provider response를 Edge로 돌려주는 ordered frame이다. `kind`, `sequence`, `status_code`, `headers`, `body`, `end`, `error`, `usage`, `metadata`를 싣는다. `body`는 passthrough source of truth이며 `RunEvent.delta`나 Edge `events.Bus` fanout payload로 보내지 않는다. `usage`와 `metadata`는 Edge의 metric/log/known-key 관측 후보이고 provider passthrough body에 합쳐지지 않는다.
|
||||||
- tunnel cancellation: HTTP caller disconnect, response wait timeout, 또는 Edge write failure가 발생하면 Edge는 같은 run id에 대한 `CancelRequest(CANCEL_RUN)`을 보내 upstream provider request 중단을 요청한다. Node adapter는 provider request context cancellation을 관측하고 ordered error/end semantics를 유지해야 한다.
|
- tunnel cancellation: HTTP caller disconnect, response wait timeout, 또는 Edge write failure가 발생하면 Edge는 같은 run id에 대한 `CancelRequest(CANCEL_RUN)`을 보내 upstream provider request 중단을 요청한다. Node adapter는 provider request context cancellation을 관측하고 ordered error/end semantics를 유지해야 한다.
|
||||||
- `RunEvent.metadata["openai_tool_calls"]`: OpenAI-compatible provider adapter가 native `tool_calls`를 반환했을 때 완료 이벤트에 싣는 JSON 배열이다. Edge OpenAI-compatible 표면은 이 값을 `message.tool_calls` 또는 stream `delta.tool_calls`로 복원한다. provider assistant content 텍스트를 이 값으로 파싱/합성하지 않는다.
|
- `RunEvent.metadata["openai_tool_calls"]`: OpenAI-compatible provider adapter가 native `tool_calls`를 반환했을 때 완료 이벤트에 싣는 JSON 배열이다. Edge OpenAI-compatible 표면은 이 값을 `message.tool_calls` 또는 stream `delta.tool_calls`로 복원한다. provider assistant content 텍스트를 이 값으로 파싱/합성하지 않는다.
|
||||||
- `RunEvent.metadata["openai_text_tool_fallback"]`: OpenAI-compatible provider adapter가 backend native tool API 거부 후 `tools`/`tool_choice`를 제거하고 text tool-call instruction으로 재시도했을 때 `"true"`를 싣는다. 이 instruction은 backend가 system role 위치를 거부하지 않도록 leading system message에 병합한다. Edge는 이 표시가 있는 실행에서만 assistant content의 text tool-call을 OpenAI-compatible `tool_calls`로 복원할 수 있다.
|
- `RunEvent.metadata["openai_text_tool_fallback"]`: OpenAI-compatible provider adapter가 backend native tool API 거부 후 `tools`/`tool_choice`를 제거하고 text tool-call instruction으로 재시도했을 때 `"true"`를 싣는다. 이 instruction은 backend가 system role 위치를 거부하지 않도록 leading system message에 병합한다. Edge는 이 표시가 있는 실행에서만 assistant content의 text tool-call을 OpenAI-compatible `tool_calls`로 복원할 수 있다.
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
이 문서는 외부 프로젝트가 IOP Edge의 OpenAI-compatible HTTP 표면을 호출할 때 확인할 계약 원문이다.
|
이 문서는 외부 프로젝트가 IOP Edge의 OpenAI-compatible HTTP 표면을 호출할 때 확인할 계약 원문이다.
|
||||||
IOP 내부 실행은 `adapter + target` 기준이며, OpenAI-compatible 경계에서는 호환성을 위해 `model`을 사용한다.
|
IOP 내부 실행은 `adapter + target` 기준이며, OpenAI-compatible 경계에서는 호환성을 위해 `model`을 사용한다.
|
||||||
IOP 고유 실행 문맥은 별도 `iop` wrapper field를 만들지 않고 OpenAI request의 `metadata`에 둔다.
|
IOP 고유 실행 문맥은 별도 `iop` wrapper field를 만들지 않고 OpenAI request의 `metadata`에 둔다.
|
||||||
|
기본 설계 기준은 OpenAI-compatible request/response surface 보존이다. OpenAI-compatible provider로 raw passthrough 되는 경로는 선택된 provider가 지원하는 표준 field와 provider extension field를 IOP allowlist로 제한하지 않는다. IOP 고유 field나 추상화 field는 OpenAI-compatible 기본 surface 위에 더하는 확장으로만 사용하며, provider-native OpenAI-compatible field를 대체하거나 금지하지 않는다.
|
||||||
|
라우팅의 1차 기준은 request `model`이 가리키는 route/provider capability다. 선택된 provider가 OpenAI-compatible provider이면 Edge는 provider tunnel passthrough를 사용하고, 그 외 CLI/Ollama/native 실행은 normalized path를 사용한다. 라우팅과 응답 형태를 caller metadata selector로 고르지 않는다. 2차 처리는 OpenAI `metadata` container에서 IOP가 아는 key만 발췌해 workspace, task, principal, usage/observability 같은 내부 문맥으로 쓰는 방식이다.
|
||||||
|
|
||||||
## Auth
|
## Auth
|
||||||
|
|
||||||
|
|
@ -39,6 +41,7 @@ Edge 설정에 `openai.principal_tokens[]`가 설정된 경우, caller는 기존
|
||||||
|
|
||||||
- caller-provided `metadata.user`는 identity source가 아니며 사용되지 않는다.
|
- caller-provided `metadata.user`는 identity source가 아니며 사용되지 않는다.
|
||||||
- caller가 `metadata.iop_principal_*`를 보내도 authenticated context 값이 overwrite한다.
|
- caller가 `metadata.iop_principal_*`를 보내도 authenticated context 값이 overwrite한다.
|
||||||
|
- `metadata`는 route/response mode selector가 아니다. Edge는 model 기반 route 선택 뒤 IOP가 아는 metadata key만 실행 문맥과 관측용으로 발췌한다.
|
||||||
|
|
||||||
### Legacy fallback
|
### Legacy fallback
|
||||||
|
|
||||||
|
|
@ -102,9 +105,9 @@ CLI agent 실행으로 라우팅되는 요청의 최소 형태:
|
||||||
|
|
||||||
- `model`: Edge가 내부 `adapter + target`으로 해석할 외부 route 이름이다. IOP Edge에서는 라우팅을 위해 필수다.
|
- `model`: Edge가 내부 `adapter + target`으로 해석할 외부 route 이름이다. IOP Edge에서는 라우팅을 위해 필수다.
|
||||||
- `instructions`: OpenAI Responses API의 top-level instruction field다. 있으면 `input` 앞에 배치해 agent 실행 prompt를 만든다.
|
- `instructions`: OpenAI Responses API의 top-level instruction field다. 있으면 `input` 앞에 배치해 agent 실행 prompt를 만든다.
|
||||||
- `input`: agent에게 전달할 사용자 요청이다. 현재 구현은 string input만 지원한다.
|
- `input`: agent에게 전달할 사용자 요청이다. normalized(non-provider) route는 현재 string input만 지원한다.
|
||||||
- `stream`: 현재 구현은 `false` 또는 생략만 지원한다.
|
- `stream`: normalized(non-provider) route는 현재 `false` 또는 생략만 지원한다. Provider-pool passthrough는 provider가 지원하는 stream 값을 보존한다.
|
||||||
- `background`: 현재 구현은 `false` 또는 생략만 지원한다.
|
- `background`: normalized(non-provider) route는 현재 `false` 또는 생략만 지원한다. Provider-pool passthrough는 provider가 지원하는 값을 보존한다.
|
||||||
- `metadata.workspace`: CLI process를 실행할 작업 디렉터리다. CLI agent route에서는 필수 실행 문맥이다.
|
- `metadata.workspace`: CLI process를 실행할 작업 디렉터리다. CLI agent route에서는 필수 실행 문맥이다.
|
||||||
- `metadata`: OpenAI 표준 metadata container다. string key/value를 허용하고, IOP는 `workspace`만 실행 문맥으로 해석한다. 나머지 key는 caller-defined metadata로 보존하되 `source`는 지원하지 않는다.
|
- `metadata`: OpenAI 표준 metadata container다. string key/value를 허용하고, IOP는 `workspace`만 실행 문맥으로 해석한다. 나머지 key는 caller-defined metadata로 보존하되 `source`는 지원하지 않는다.
|
||||||
- `metadata.request_id`, `metadata.task_id`: caller-defined metadata 예시다. 특별한 wrapper나 제품 전용 field가 아니다.
|
- `metadata.request_id`, `metadata.task_id`: caller-defined metadata 예시다. 특별한 wrapper나 제품 전용 field가 아니다.
|
||||||
|
|
@ -112,24 +115,24 @@ CLI agent 실행으로 라우팅되는 요청의 최소 형태:
|
||||||
- `temperature`: 생성 다양성 option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다.
|
- `temperature`: 생성 다양성 option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다.
|
||||||
- `top_p`: nucleus sampling option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다.
|
- `top_p`: nucleus sampling option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다.
|
||||||
|
|
||||||
금지:
|
Normalized route 금지:
|
||||||
|
|
||||||
- `metadata.cli` 같은 CLI 전용 wrapper를 추가하지 않는다.
|
- `metadata.cli` 같은 CLI 전용 wrapper를 추가하지 않는다.
|
||||||
- `metadata.inference`처럼 `model` route와 겹치는 target wrapper를 추가하지 않는다.
|
- `metadata.inference`처럼 `model` route와 겹치는 target wrapper를 추가하지 않는다.
|
||||||
- `metadata.nomadcode`처럼 특정 소비자 제품명에 묶인 wrapper를 추가하지 않는다.
|
- `metadata.nomadcode`처럼 특정 소비자 제품명에 묶인 wrapper를 추가하지 않는다.
|
||||||
- `metadata.source`처럼 의미가 불명확한 호출 출처 field를 추가하지 않는다.
|
- `metadata.source`처럼 의미가 불명확한 호출 출처 field를 추가하지 않는다.
|
||||||
- root-level `iop` 같은 별도 wrapper field를 추가하지 않는다.
|
- root-level `iop` 같은 별도 wrapper field를 추가하지 않는다.
|
||||||
- `/v1/responses`에 `options` wrapper를 추가하지 않는다. Responses API option은 OpenAI 표준 top-level field를 따른다.
|
- normalized(non-provider) `/v1/responses`에 `options` wrapper를 추가하지 않는다. Responses API option은 OpenAI 표준 top-level field를 따른다.
|
||||||
- `session_id`, `timeout_sec` 같은 IOP 실행 제어 field를 request body 계약에 추가하지 않는다. logical session과 timeout은 route/config 기본값을 따른다.
|
- `session_id`, `timeout_sec` 같은 IOP 실행 제어 field를 request body 계약에 추가하지 않는다. logical session과 timeout은 route/config 기본값을 따른다.
|
||||||
- workspace를 prompt 본문에 섞어 전달하지 않는다.
|
- workspace를 prompt 본문에 섞어 전달하지 않는다.
|
||||||
|
|
||||||
현재 구현 메모:
|
현재 구현 메모:
|
||||||
|
|
||||||
- normalized(non-provider) `/v1/responses` route는 strict field validation을 유지하며 non-streaming string input만 지원한다.
|
- normalized(non-provider) `/v1/responses` route는 strict field validation을 유지하며 non-streaming string input만 지원한다.
|
||||||
- provider-pool model group route(`models[]`)의 `/v1/responses` 호출은 `metadata.iop_response_mode`를 명시하면 값과 무관하게 `400 invalid_request_error`로 거부한다. 생략 시 raw passthrough로 provider `POST /v1/responses`에 전달한다. caller body는 `model` field만 served target으로 rewrite하고, unknown/Codex field(`max_output_tokens`, `tools`, `store`, ...)는 보존하며, `stream:true`는 provider raw SSE로 relay한다. provider auth forwarding이 적용되고, response model echo rewrite는 적용하지 않는다. 이 경로는 normalized `SubmitRun`으로 fallback하지 않는다.
|
- provider-pool model group route(`models[]`)의 `/v1/responses` 호출은 selected provider가 OpenAI-compatible provider이면 raw passthrough로 provider `POST /v1/responses`에 전달한다. caller body는 `model` field만 served target으로 rewrite하고, selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field(`max_output_tokens`, `tools`, `store`, provider-specific knobs 등)는 보존한다. `stream:true`는 provider raw SSE로 relay한다. provider auth forwarding이 적용되고, response model echo rewrite는 적용하지 않는다. 이 경로는 normalized `SubmitRun`으로 fallback하지 않는다.
|
||||||
- provider-pool model group route는 provider candidate를 먼저 선택한다. 선택된 provider가 OpenAI-compatible 호출 방식을 지원하면 `ProviderTunnelRequest` passthrough를 사용하고, Ollama/CLI/native provider이면 normalized `RunRequest`를 사용한다. provider type만으로 Ollama를 candidate set에서 제거하지 않으며, OpenAI-compatible provider의 tunnel 구현이 없으면 normalized fallback이 아니라 unsupported/implementation error다.
|
- provider-pool model group route는 provider candidate를 먼저 선택한다. 선택된 provider가 OpenAI-compatible 호출 방식을 지원하면 `ProviderTunnelRequest` passthrough를 사용하고, Ollama/CLI/native provider이면 normalized `RunRequest`를 사용한다. provider type만으로 Ollama를 candidate set에서 제거하지 않으며, OpenAI-compatible provider의 tunnel 구현이 없으면 normalized fallback이 아니라 unsupported/implementation error다.
|
||||||
- direct legacy provider route(`openai.model_routes[]`의 `openai_compat`/`vllm` adapter)에서 명시적 `metadata.iop_response_mode="passthrough+sideband"`는 opt-in extension surface다. non-streaming provider JSON object 응답은 top-level `metadata` object를 만들거나 병합해 IOP sideband metadata를 삽입한다. streaming 응답은 provider SSE event stream 사이에 `event: iop.sideband`를 삽입한다. sideband 내용은 `metadata` object 아래 확장 가능하며, 현재 최소 marker는 `iop_response_mode="passthrough+sideband"`다. `"transformed"`는 provider tunnel route에서 `400 invalid_request_error`로 거부한다.
|
- direct legacy provider route(`openai.model_routes[]`의 `openai_compat`/`vllm` adapter)도 OpenAI-compatible provider이면 raw provider tunnel을 사용한다. Non-provider normalized route는 raw tunnel을 쓰지 않고 normalized IOP output path를 사용한다.
|
||||||
- Responses provider passthrough success usage metric label은 endpoint=`responses`, response_mode=`passthrough` 또는 direct sideband route의 `passthrough+sideband`, model_group=request alias를 사용한다.
|
- Responses provider passthrough success usage metric label은 endpoint와 model_group=request alias를 기준으로 집계한다. 관측/usage 정보는 provider body에 섞지 않는다.
|
||||||
- `metadata`는 최대 16개 string key/value를 허용한다. key는 64자 이하, value는 512자 이하를 기준으로 한다.
|
- `metadata`는 최대 16개 string key/value를 허용한다. key는 64자 이하, value는 512자 이하를 기준으로 한다.
|
||||||
- CLI route의 `metadata.workspace`는 이 문서의 계약 기준이다. 구현은 이 값을 Edge service의 run workspace와 Node CLI adapter의 process working directory로 전달해야 한다.
|
- CLI route의 `metadata.workspace`는 이 문서의 계약 기준이다. 구현은 이 값을 Edge service의 run workspace와 Node CLI adapter의 process working directory로 전달해야 한다.
|
||||||
- `metadata.workspace`는 `RunRequest.Workspace`로 전달하고 generic run metadata에는 복사하지 않는다.
|
- `metadata.workspace`는 `RunRequest.Workspace`로 전달하고 generic run metadata에는 복사하지 않는다.
|
||||||
|
|
@ -158,7 +161,7 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa
|
||||||
|
|
||||||
## Chat Completions
|
## Chat Completions
|
||||||
|
|
||||||
`/v1/chat/completions`도 같은 metadata 원칙을 따른다. CLI route의 workspace는 `metadata.workspace`에 둔다. Chat Completions의 provider sampling option은 해당 endpoint의 OpenAI-compatible top-level request field를 따르며, `/v1/responses`와 마찬가지로 별도 `options` wrapper를 두지 않는다.
|
`/v1/chat/completions`도 같은 metadata 원칙을 따른다. CLI route의 workspace는 `metadata.workspace`에 둔다. Normalized route에서 Chat Completions의 sampling option은 해당 endpoint의 OpenAI-compatible top-level request field를 따르며, `/v1/responses`와 마찬가지로 별도 `options` wrapper를 두지 않는다. Provider-pool passthrough route에서는 selected provider가 지원하는 OpenAI-compatible field와 provider extension field를 보존한다.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
@ -175,7 +178,7 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
현재 지원하는 Chat Completions request field:
|
Normalized(non-provider) Chat Completions route가 해석하는 request field:
|
||||||
|
|
||||||
- `model`
|
- `model`
|
||||||
- `messages`
|
- `messages`
|
||||||
|
|
@ -200,27 +203,29 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa
|
||||||
- `thinking_token_budget`
|
- `thinking_token_budget`
|
||||||
- `include_reasoning`
|
- `include_reasoning`
|
||||||
|
|
||||||
### Chat Completions response mode
|
Provider-pool raw passthrough route는 위 목록을 provider request allowlist로 사용하지 않는다. 이 경로의 기본은 selected OpenAI-compatible provider가 지원하는 요청 surface 보존이며, `chat_template_kwargs`, provider별 `extra_body`/template option, 새 OpenAI-compatible field처럼 IOP가 아직 해석하지 않는 top-level field도 model rewrite 후 provider로 전달되어야 한다. 해당 field의 성공/실패 의미는 provider가 결정하고, IOP는 provider HTTP status/header/body를 relay한다.
|
||||||
|
|
||||||
Provider route의 응답 경로는 route 종류에 따라 다르다.
|
### Chat Completions routing and response
|
||||||
|
|
||||||
- provider-pool model group route(`models[]`)는 `metadata.iop_response_mode`를 caller 선택자로 받지 않는다. 생략 시 provider HTTP status/header/body를 Node가 열어 기존 Edge-Node tunnel로 relay하고, Edge가 caller에게 쓴다. Chat Completions 성공 응답의 top-level `model` echo가 provider-served model이면 caller가 요청한 IOP model alias로 정규화한다. reasoning/content/tool_calls 같은 provider payload field는 보존한다. pure passthrough 응답 body에는 IOP sideband field/event를 섞지 않고, `X-IOP-Response-Mode` header도 붙이지 않는다.
|
Chat Completions의 실행 경로는 caller가 보낸 `model`의 route/provider capability로 결정한다.
|
||||||
- provider-pool model group route에서 `metadata.iop_response_mode`를 명시하면 `passthrough`, `passthrough+sideband`, `transformed`, 알 수 없는 값 모두 `400 invalid_request_error`로 거부한다. 이 제한은 provider model group이 client response-mode selector나 normalized `SubmitRun` path로 회귀하지 않게 하기 위한 것이다.
|
|
||||||
- direct legacy provider route(`openai.model_routes[]`의 `openai_compat`/`vllm` adapter)는 `metadata.iop_response_mode` 생략 또는 `passthrough`를 raw provider tunnel로 처리한다. Chat Completions의 `metadata.iop_response_mode="passthrough+sideband"`는 provider body와 IOP route/usage/assembled observation을 명시적 IOP extension surface로 함께 노출한다. streaming 응답은 provider SSE event 경계 사이에 `event: iop.sideband`를 추가하고, non-streaming 응답은 `iop.chat.passthrough_sideband` envelope로 provider body와 `iop_sideband`를 함께 반환한다. `/v1/responses`의 같은 모드는 위 Responses API 섹션처럼 응답 `metadata` 또는 SSE `event: iop.sideband`를 사용한다. 이 모드는 provider-original byte-identical response로 표시하지 않는다.
|
|
||||||
- direct legacy provider route에서 `metadata.iop_response_mode="transformed"`는 지원하지 않으며 `400 invalid_request_error`로 거부한다. non-provider normalized route에서는 raw tunnel을 쓰지 않고 normalized IOP output path를 사용하며 `X-IOP-Response-Mode: transformed`로 라벨링한다.
|
|
||||||
|
|
||||||
알 수 없는 `metadata.iop_response_mode` 값은 silent fallback 없이 `400 invalid_request_error`로 거부한다. provider-pool model group route는 값 검증 전에 explicit selector 자체를 거부한다.
|
- provider-pool model group route(`models[]`)는 candidate를 선택한 뒤 selected provider가 OpenAI-compatible 호출 방식을 지원하면 provider HTTP status/header/body를 Node가 열어 기존 Edge-Node tunnel로 relay하고, Edge가 caller에게 쓴다. 요청 body는 라우팅에 필요한 envelope만 읽고 `model` alias를 selected provider의 served target으로 rewrite하는 것을 기본으로 하며, provider가 지원하는 OpenAI-compatible field와 provider extension field를 보존한다.
|
||||||
|
- selected provider가 Ollama/CLI/native provider처럼 normalized execution을 요구하면 Edge는 normalized `RunRequest` path를 사용한다. 이 경로는 OpenAI-compatible 표면을 입력/출력 compatibility layer로 제공하되, backend 호출은 normalized adapter 계약을 따른다.
|
||||||
|
- `metadata`는 경로 선택자가 아니다. Edge는 route 결정 뒤 `workspace`, `task_id`, 인증 principal, usage/observability 등 IOP가 아는 metadata key만 발췌한다. 이 발췌 정보는 provider body를 바꾸는 selector가 아니며, passthrough 응답 body에 IOP marker/event/envelope를 섞지 않는다.
|
||||||
|
- Chat Completions 성공 응답의 top-level `model` echo가 provider-served model이면 caller가 요청한 IOP model alias로 정규화할 수 있다. reasoning/content/tool_calls 같은 provider payload field는 보존한다.
|
||||||
|
|
||||||
Think 제어 field:
|
IOP 확장 think 제어 field:
|
||||||
|
|
||||||
- `think` (bool, optional): thinking/reasoning 생성 활성화 여부. 생략하면 provider 기본값을 유지한다. `false`는 thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다.
|
- `think` (bool, optional): thinking/reasoning 생성 활성화 여부를 표현하는 IOP 확장 field다. 생략하면 provider 기본값을 유지한다. `false`는 thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다.
|
||||||
- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high` 중 하나. `none`은 `think=false`와 같은 disable 의미로 처리한다. `low`/`medium`/`high`는 provider가 지원하는 경우에만 전달한다.
|
- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high` 중 하나인 IOP 확장 field다. `none`은 `think=false`와 같은 disable 의미로 처리한다. `low`/`medium`/`high`는 provider 또는 normalized backend가 지원하는 경우에만 전달한다.
|
||||||
- `thinking_token_budget` (int, optional): thinking token budget. 0 이상이어야 한다.
|
- `thinking_token_budget` (int, optional): IOP 확장 thinking token budget. 0 이상이어야 한다.
|
||||||
- `include_reasoning` (bool, optional): OpenAI-compatible 응답에서 `reasoning_content` 노출 여부. non-provider normalized route에서는 생략하거나 `true`이면 provider reasoning delta/message를 노출할 수 있고, `false`이면 provider가 reasoning을 생성해도 response의 `reasoning_content`를 제거한다. provider-pool pure `passthrough`는 provider body 보존이 우선이며, 현재 IOP가 이 field만으로 reasoning field를 제거한다고 보장하지 않는다.
|
- `include_reasoning` (bool, optional): OpenAI-compatible 응답에서 `reasoning_content` 노출 여부. non-provider normalized route에서는 생략하거나 `true`이면 provider reasoning delta/message를 노출할 수 있고, `false`이면 provider가 reasoning을 생성해도 response의 `reasoning_content`를 제거한다. provider-pool pure `passthrough`는 provider body 보존이 우선이며, 현재 IOP가 이 field만으로 reasoning field를 제거한다고 보장하지 않는다.
|
||||||
|
|
||||||
|
이 field들은 provider-native field의 대체물이 아니다. Provider-pool passthrough caller는 선택된 provider가 지원하는 native field(예: vLLM/Qwen 계열의 `chat_template_kwargs.enable_thinking=false`)를 그대로 보낼 수 있어야 하며, IOP 확장 field는 provider-native field가 없거나 normalized backend를 호출할 때의 추가 호환 표면이다.
|
||||||
|
|
||||||
### dev-corp `gemma4:26b` provider-pool passthrough 파라미터 범위
|
### dev-corp `gemma4:26b` provider-pool passthrough 파라미터 범위
|
||||||
|
|
||||||
이 표는 dev-corp의 `gemma4:26b` provider-pool route에서, 현재 IOP 설정과 provider/vLLM 최적화 값을 변경하지 않고 standard OpenAI-compatible caller가 요청 파라미터만 바꿔 측정할 때의 현재 계약 범위다. Pi TUI의 고정 호출 방식이나 다른 model/provider route의 동작으로 일반화하지 않는다.
|
이 표는 dev-corp의 `gemma4:26b` provider-pool route에서, 현재 provider/vLLM 최적화 값을 변경하지 않고 standard OpenAI-compatible caller가 요청 파라미터만 바꿔 측정할 때의 계약상 기대 범위다. Provider-pool passthrough는 이 표에 없는 provider-native OpenAI-compatible field도 금지하지 않는다. Pi TUI의 고정 호출 방식이나 다른 model/provider route의 동작으로 일반화하지 않는다.
|
||||||
|
|
||||||
| 요청 파라미터 | 현재 기대 동작 | 권장 판정 |
|
| 요청 파라미터 | 현재 기대 동작 | 권장 판정 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|
@ -230,9 +235,8 @@ Think 제어 field:
|
||||||
| `think=false` | Edge validation conflict가 없으면 요청은 통과하고 model catalog의 default thinking budget 주입은 억제된다. 이후 provider body에 `think:false`가 반영될 수 있으나, 현재 `gemma4:26b` provider 최적화 값과 충돌하거나 backend별로 무시/실패/품질 저하가 날 수 있다. | “숨기기만” 하는 옵션이 아니다. dev-corp `gemma4:26b` 기본 안정 호출에서는 생략한다. |
|
| `think=false` | Edge validation conflict가 없으면 요청은 통과하고 model catalog의 default thinking budget 주입은 억제된다. 이후 provider body에 `think:false`가 반영될 수 있으나, 현재 `gemma4:26b` provider 최적화 값과 충돌하거나 backend별로 무시/실패/품질 저하가 날 수 있다. | “숨기기만” 하는 옵션이 아니다. dev-corp `gemma4:26b` 기본 안정 호출에서는 생략한다. |
|
||||||
| `reasoning_effort="none"` | `think=false`와 같은 disable 의도로 해석되어 default thinking budget 주입을 억제한다. provider tunnel에서는 runtime/provider 지원 여부에 의존한다. | `think=false`와 같은 이유로 기본 안정 호출에서는 생략한다. |
|
| `reasoning_effort="none"` | `think=false`와 같은 disable 의도로 해석되어 default thinking budget 주입을 억제한다. provider tunnel에서는 runtime/provider 지원 여부에 의존한다. | `think=false`와 같은 이유로 기본 안정 호출에서는 생략한다. |
|
||||||
| 명시적 `thinking_token_budget` | 0 이상이면 conflict validation 후 provider tunnel body에 반영될 수 있다. catalog 기본 budget 대신 caller 값으로 provider thinking budget을 바꾸는 요청이다. | 최적화된 `gemma4:26b` 기본값을 바꾸는 측정으로만 사용한다. 일반 표준 안정 호출에서는 생략한다. |
|
| 명시적 `thinking_token_budget` | 0 이상이면 conflict validation 후 provider tunnel body에 반영될 수 있다. catalog 기본 budget 대신 caller 값으로 provider thinking budget을 바꾸는 요청이다. | 최적화된 `gemma4:26b` 기본값을 바꾸는 측정으로만 사용한다. 일반 표준 안정 호출에서는 생략한다. |
|
||||||
| `metadata.iop_response_mode="passthrough+sideband"` | provider-pool model group route에서는 `400 invalid_request_error`로 거부한다. | dev-corp `gemma4:26b` provider-pool에서는 request selector로 사용하지 않는다. route/usage 관측은 metric/log surface를 기준으로 둔다. |
|
| provider-native field 예: `chat_template_kwargs` | selected provider가 해당 OpenAI-compatible extension을 지원하면 IOP provider-pool passthrough는 이를 보존하고 provider로 전달해야 한다. | 이 field를 IOP 추상 field로 치환하지 않는다. provider가 거부하면 provider error를 relay한다. |
|
||||||
| `metadata.iop_response_mode="transformed"` | provider model group route에서는 `400 invalid_request_error`로 거부한다. | dev-corp `gemma4:26b` provider-pool에서는 사용하지 않는다. |
|
| `/v1/responses` 호출 | provider-pool model group route에서 selected provider가 OpenAI-compatible provider이면 raw `passthrough`로 provider `POST /v1/responses`에 전달한다. `model`만 rewrite하고 selected provider가 지원하는 field는 보존하며 `stream:true`는 raw SSE로 relay한다. usage metric은 endpoint=`responses`로 측정한다. | Provider가 `/v1/responses`를 지원하면 그대로 측정할 수 있다. Provider가 지원하지 않으면 provider error를 relay한다. Chat 기반 호출은 `/v1/chat/completions`를 쓴다. |
|
||||||
| `/v1/responses` 호출 | provider-pool model group route에서 raw `passthrough`로 provider `POST /v1/responses`에 전달한다. `model`만 rewrite하고 unknown/Codex field는 보존하며 `stream:true`는 raw SSE로 relay한다. 명시적 `metadata.iop_response_mode`는 값과 무관하게 `400 invalid_request_error`로 거부한다. usage metric은 endpoint=`responses`로 측정한다. | Codex 스타일 `/v1/responses` 호출을 provider-pool로 그대로 넘겨 측정할 수 있다. Chat 기반 호출은 `/v1/chat/completions`를 쓴다. |
|
|
||||||
|
|
||||||
현재 구현에서 `think=false`를 “provider에는 기본 think를 유지하되 IOP가 응답에서 reasoning만 감추는 hide-only 모드”로 해석하지 않는다. 그런 동작이 필요하면 provider/vLLM 설정 변경이 아니라 Edge provider-pool passthrough 응답 filtering 정책을 별도 구현/계약 갱신해야 한다.
|
현재 구현에서 `think=false`를 “provider에는 기본 think를 유지하되 IOP가 응답에서 reasoning만 감추는 hide-only 모드”로 해석하지 않는다. 그런 동작이 필요하면 provider/vLLM 설정 변경이 아니라 Edge provider-pool passthrough 응답 filtering 정책을 별도 구현/계약 갱신해야 한다.
|
||||||
|
|
||||||
|
|
@ -246,7 +250,7 @@ Reasoning-only 완료 처리 (non-provider normalized route):
|
||||||
|
|
||||||
Provider별 think-control 정책:
|
Provider별 think-control 정책:
|
||||||
|
|
||||||
아래 정책은 normalized adapter execution path 기준이다. Chat Completions provider-pool pure `passthrough`는 Node adapter의 normalized `Execute`/request-body builder를 거치지 않고 provider HTTP body를 tunnel로 전달하므로, dev-corp `gemma4:26b` 측정 범위는 위 표를 우선한다.
|
아래 정책은 normalized adapter execution path 또는 IOP 확장 field를 provider-specific request로 변환해야 하는 경로의 기준이다. Chat Completions provider-pool pure `passthrough`는 provider-native OpenAI-compatible field를 우선 보존한다. 따라서 caller가 이미 `chat_template_kwargs` 같은 provider-native field를 보냈다면 IOP 확장 field 변환은 이를 대체하거나 삭제하지 않는다.
|
||||||
|
|
||||||
- `vLLM`:
|
- `vLLM`:
|
||||||
- `think=false` 또는 `reasoning_effort=none` -> 내부 `chat_template_kwargs.enable_thinking=false`
|
- `think=false` 또는 `reasoning_effort=none` -> 내부 `chat_template_kwargs.enable_thinking=false`
|
||||||
|
|
@ -265,11 +269,11 @@ Provider별 think-control 정책:
|
||||||
- `reasoning_effort=low|medium|high` -> `unsupported think control` 오류 반환
|
- `reasoning_effort=low|medium|high` -> `unsupported think control` 오류 반환
|
||||||
- Unknown / 기타 provider: 요청 field를 그대로 전달하되, provider가 지원하지 않는 값은 backend 또는 adapter error가 될 수 있다.
|
- Unknown / 기타 provider: 요청 field를 그대로 전달하되, provider가 지원하지 않는 값은 backend 또는 adapter error가 될 수 있다.
|
||||||
|
|
||||||
Provider pool model catalog의 `models[]` entry가 generation policy를 제공하면 Edge는 요청을 내부 실행 또는 Chat Completions provider tunnel로 넘기기 전에 다음 값을 보정한다.
|
Provider pool model catalog의 `models[]` entry가 generation policy를 제공하면 Edge는 요청을 내부 실행 또는 Chat Completions provider tunnel로 넘기기 전에 다음 값을 보정할 수 있다. 단, provider-pool raw passthrough에서는 caller가 명시한 provider-native OpenAI-compatible field를 삭제하거나 IOP 추상 field로 대체하지 않는다.
|
||||||
|
|
||||||
- `default_max_tokens`: caller가 출력 token limit을 생략했을 때 `max_tokens` 또는 `max_output_tokens`로 주입한다.
|
- `default_max_tokens`: caller가 출력 token limit을 생략했을 때 `max_tokens` 또는 `max_output_tokens`로 주입한다.
|
||||||
- `min_max_tokens`: caller가 너무 작은 출력 token limit을 보냈을 때 해당 값까지 올린다. caller 값이 더 크면 보존한다.
|
- `min_max_tokens`: caller가 너무 작은 출력 token limit을 보냈을 때 해당 값까지 올린다. caller 값이 더 크면 보존한다.
|
||||||
- `default_thinking_token_budget`: caller가 `thinking_token_budget`을 생략했을 때 내부 실행 입력 또는 Chat Completions provider tunnel body에 주입한다. strict output가 함께 활성화된 provider-pool Chat Completions 요청에서는 Edge가 `think=true`도 함께 주입해 vLLM/vLLM-MLX 계열 adapter가 `chat_template_kwargs.enable_thinking=true`로 전달하도록 한다.
|
- `default_thinking_token_budget`: caller가 `thinking_token_budget`과 provider-native thinking budget field를 모두 생략했을 때 내부 실행 입력 또는 Chat Completions provider tunnel body에 주입할 수 있다. strict output가 함께 활성화된 normalized 경로에서는 Edge가 `think=true`도 함께 주입해 provider adapter가 지원하는 request shape로 전달할 수 있다. Provider-pool passthrough에서는 provider-native field 보존이 우선이다.
|
||||||
|
|
||||||
Conflict 정책:
|
Conflict 정책:
|
||||||
|
|
||||||
|
|
@ -282,8 +286,8 @@ Conflict 정책:
|
||||||
Strict output 모드:
|
Strict output 모드:
|
||||||
|
|
||||||
- strict output가 활성화되면 non-provider normalized route에서 `think=true`가 명시되지 않은 요청은 내부 실행 입력에서 `think=false`로 낮춘다.
|
- strict output가 활성화되면 non-provider normalized route에서 `think=true`가 명시되지 않은 요청은 내부 실행 입력에서 `think=false`로 낮춘다.
|
||||||
- strict output만으로 OpenAI-compatible provider model group route를 `transformed` normalized path로 전환하지 않는다. provider model group omitted mode는 계속 raw `passthrough`다.
|
- strict output만으로 OpenAI-compatible provider model group route를 normalized path로 전환하지 않는다. provider model group에서 selected provider가 OpenAI-compatible provider이면 계속 raw `passthrough`다.
|
||||||
- provider-pool `models[]` entry의 `default_thinking_token_budget`가 적용되는 모델은 catalog의 thinking policy가 우선한다. 이 경우 요청이 `think=false` 또는 `reasoning_effort=none`을 명시하지 않았다면 strict output에서도 `think=true`와 `thinking_token_budget`을 내부 실행 입력 또는 Chat Completions provider tunnel body에 넣는다.
|
- provider-pool `models[]` entry의 `default_thinking_token_budget`가 적용되는 모델은 catalog의 thinking policy를 기본값으로 사용할 수 있다. 이 경우에도 caller가 provider-native thinking field를 명시했다면 해당 field가 우선하며, IOP가 `think=true`나 `thinking_token_budget`으로 대체하지 않는다.
|
||||||
|
|
||||||
`tools`가 있는 Chat Completions 요청에서 provider route(`openai_compat`, `vllm`, `ollama`, provider pool)는 forced tool 선택 객체와 `"none"` 같은 명시적 `tool_choice`를 backend에 전달한다. 단, `"auto"`는 OpenAI-compatible 기본값과 같으므로 provider request에서는 생략한다. 일부 vLLM 계열 backend는 explicit/default `"auto"`를 `--enable-auto-tool-choice`/`--tool-call-parser` 없이 400으로 거부한다. 이 400이 발생하고 요청 tool이 정확히 1개이면 Node adapter는 해당 tool에 대한 forced `tool_choice`로 1회 재시도한다. forced tool도 `--tool-call-parser` 요구로 거부되거나 여러 tool이라 forced를 고를 수 없으면, Node adapter는 `tools`/`tool_choice`를 제거하고 text tool-call system instruction을 leading system message에 병합해 1회 재시도하며 완료 metadata에 `openai_text_tool_fallback: "true"`를 싣는다.
|
`tools`가 있는 Chat Completions 요청에서 provider route(`openai_compat`, `vllm`, `ollama`, provider pool)는 forced tool 선택 객체와 `"none"` 같은 명시적 `tool_choice`를 backend에 전달한다. 단, `"auto"`는 OpenAI-compatible 기본값과 같으므로 provider request에서는 생략한다. 일부 vLLM 계열 backend는 explicit/default `"auto"`를 `--enable-auto-tool-choice`/`--tool-call-parser` 없이 400으로 거부한다. 이 400이 발생하고 요청 tool이 정확히 1개이면 Node adapter는 해당 tool에 대한 forced `tool_choice`로 1회 재시도한다. forced tool도 `--tool-call-parser` 요구로 거부되거나 여러 tool이라 forced를 고를 수 없으면, Node adapter는 `tools`/`tool_choice`를 제거하고 text tool-call system instruction을 leading system message에 병합해 1회 재시도하며 완료 metadata에 `openai_text_tool_fallback: "true"`를 싣는다.
|
||||||
provider가 native OpenAI-compatible `tool_calls`를 반환하면 Node는 내부 `RunEvent.metadata["openai_tool_calls"]` JSON으로 보존하고, Edge는 이를 OpenAI-compatible `message.tool_calls` 또는 stream `delta.tool_calls`로 반환하며 `finish_reason: "tool_calls"`를 사용한다.
|
provider가 native OpenAI-compatible `tool_calls`를 반환하면 Node는 내부 `RunEvent.metadata["openai_tool_calls"]` JSON으로 보존하고, Edge는 이를 OpenAI-compatible `message.tool_calls` 또는 stream `delta.tool_calls`로 반환하며 `finish_reason: "tool_calls"`를 사용한다.
|
||||||
|
|
@ -298,9 +302,7 @@ text tool-call을 구조화할 때 Edge는 route와 무관하게 요청의 `tool
|
||||||
금지:
|
금지:
|
||||||
|
|
||||||
- `metadata.source`, `metadata.cli`, `metadata.inference`, `metadata.nomadcode`
|
- `metadata.source`, `metadata.cli`, `metadata.inference`, `metadata.nomadcode`
|
||||||
- provider-pool model group route에서 `metadata.iop_response_mode`를 명시하는 방식
|
- normalized(non-provider) route에서 `options`, `format`, `keep_alive` 같은 backend/provider 전용 request wrapper를 OpenAI-compatible 표준 field처럼 요구하는 방식. 이 금지는 provider-pool raw passthrough에서 selected provider가 지원하는 OpenAI-compatible extension field 보존에는 적용하지 않는다.
|
||||||
- `metadata.iop_response_mode`에 `passthrough`, `passthrough+sideband`, `transformed` 외 값을 넣는 방식
|
|
||||||
- `options`, `chat_template_kwargs`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field
|
|
||||||
- `session_id`, `timeout_sec` 같은 IOP 실행 제어 field
|
- `session_id`, `timeout_sec` 같은 IOP 실행 제어 field
|
||||||
|
|
||||||
## Legacy Completions
|
## Legacy Completions
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ dev-corp provider pool을 현재 dev-runtime과 같은 구조로 배포한다.
|
||||||
- 2026-07-13 기준 Spark Gemma4 `8002`/`8004` runtime은 내린 상태로 보고, Spark 01/02는 Ornith `8003`/`8005`를 IOP provider로 둔다. Gemma4는 Mac Studio `192.168.2.3:8004` provider만 기본 대상으로 둔다.
|
- 2026-07-13 기준 Spark Gemma4 `8002`/`8004` runtime은 내린 상태로 보고, Spark 01/02는 Ornith `8003`/`8005`를 IOP provider로 둔다. Gemma4는 Mac Studio `192.168.2.3:8004` provider만 기본 대상으로 둔다.
|
||||||
- 2026-07-13 재확인 기준 DGX Spark 01 Ornith는 Docker `iop-vllm-ornith35b-fp8`, host `8003`, `--gpu-memory-utilization 0.47`로 동작하며 startup log에서 GPU KV cache `783,347` tokens, full-context concurrency `2.99x`를 확인했다. DGX Spark 02 Ornith는 host `8005`, `--gpu-memory-utilization 0.50`으로 동작하며 GPU KV cache `1,074,276` tokens, full-context concurrency `4.10x`를 확인했다.
|
- 2026-07-13 재확인 기준 DGX Spark 01 Ornith는 Docker `iop-vllm-ornith35b-fp8`, host `8003`, `--gpu-memory-utilization 0.47`로 동작하며 startup log에서 GPU KV cache `783,347` tokens, full-context concurrency `2.99x`를 확인했다. DGX Spark 02 Ornith는 host `8005`, `--gpu-memory-utilization 0.50`으로 동작하며 GPU KV cache `1,074,276` tokens, full-context concurrency `4.10x`를 확인했다.
|
||||||
- 2026-07-02 mac-mini native Control Plane `18002/19004/19005` 관측값은 historical evidence다. 2026-07-09 현재 dev-corp Edge source of truth는 public `iop.ai.kr`이고, current runner에서 public host SSH와 public Control Plane status 접근은 확인되지 않았다.
|
- 2026-07-02 mac-mini native Control Plane `18002/19004/19005` 관측값은 historical evidence다. 2026-07-09 현재 dev-corp Edge source of truth는 public `iop.ai.kr`이고, current runner에서 public host SSH와 public Control Plane status 접근은 확인되지 않았다.
|
||||||
- 2026-07-13 기준 dev-corp provider-pool Edge OpenAI-compatible capacity smoke 표준은 public `https://digitalplatform.iop.ai.kr/v1/chat/completions`에서 `ornith:35b` 9/6, `gemma4:26b` 9/6 동시 요청이다. Direct Edge listener `http://digitalplatform.iop.ai.kr:18086/v1`도 같은 backend로 동작하지만 사용자-facing 기본값으로 쓰지 않는다. `/v1/responses`는 OpenAI-compatible provider model group raw passthrough parity 전까지 미지원이므로 성공 기준에서 제외한다.
|
- 2026-07-13 기준 dev-corp provider-pool Edge OpenAI-compatible capacity smoke 표준은 public `https://digitalplatform.iop.ai.kr/v1/chat/completions`에서 `ornith:35b` 9/6, `gemma4:26b` 9/6 동시 요청이다. Direct Edge listener `http://digitalplatform.iop.ai.kr:18086/v1`도 같은 backend로 동작하지만 사용자-facing 기본값으로 쓰지 않는다. `/v1/responses`는 capacity 성공 기준이 아니라 selected provider 지원 여부와 IOP raw passthrough/relay 동작을 분리해 보고한다.
|
||||||
- 2026-07-13 live status 기준 DGX Spark 01/02와 Mac Studio node는 `iop.ai.kr:18087`로 연결되어 있으며, provider snapshot은 Spark01 Ornith capacity `4`, Spark02 Ornith capacity `4`, Mac Studio Gemma4 capacity `5`이다. 같은 날 model-specific smoke에서 `ornith:35b` 9/6, `gemma4:26b` 9/6 요청이 모두 성공했고 완료 후 `in_flight=0`, `queued=0`, `healthy`로 회복했다.
|
- 2026-07-13 live status 기준 DGX Spark 01/02와 Mac Studio node는 `iop.ai.kr:18087`로 연결되어 있으며, provider snapshot은 Spark01 Ornith capacity `4`, Spark02 Ornith capacity `4`, Mac Studio Gemma4 capacity `5`이다. 같은 날 model-specific smoke에서 `ornith:35b` 9/6, `gemma4:26b` 9/6 요청이 모두 성공했고 완료 후 `in_flight=0`, `queued=0`, `healthy`로 회복했다.
|
||||||
|
|
||||||
## 실행 절차
|
## 실행 절차
|
||||||
|
|
@ -118,7 +118,7 @@ dev-corp provider pool을 현재 dev-runtime과 같은 구조로 배포한다.
|
||||||
- `/v1/models`가 대상 model alias를 노출하는지 확인한다.
|
- `/v1/models`가 대상 model alias를 노출하는지 확인한다.
|
||||||
|
|
||||||
9. **OpenAI-compatible capacity smoke**
|
9. **OpenAI-compatible capacity smoke**
|
||||||
- `/v1/chat/completions`를 검증한다. `/v1/responses`는 OpenAI-compatible provider model group raw passthrough parity 전까지 미지원이므로 provider-pool capacity 성공 기준에서 제외한다. legacy `/v1/completions`는 route가 구현되어 있지 않으면 실패로 보지 않는다.
|
- `/v1/chat/completions`를 검증한다. Provider-pool passthrough 경계를 바꾼 배포라면 selected provider가 지원하는 OpenAI-compatible extension field 예: `chat_template_kwargs`가 IOP에서 거부되지 않고 provider로 전달되는지도 확인한다. `/v1/responses`는 capacity 성공 기준이 아니라 provider-dependent passthrough/relay 검증으로 분리한다. legacy `/v1/completions`는 route가 구현되어 있지 않으면 실패로 보지 않는다.
|
||||||
- 표준 부하 프롬프트는 700~1200 token 수준의 구조화된 답변을 유도해 요청이 동시에 관측될 시간을 만든다.
|
- 표준 부하 프롬프트는 700~1200 token 수준의 구조화된 답변을 유도해 요청이 동시에 관측될 시간을 만든다.
|
||||||
- model-specific smoke를 실행한다. `ornith:35b`는 Spark01/02 합산 capacity `8` 기준 9개와 6개 동시 요청을 보내고, `gemma4:26b`는 Mac Studio capacity `5` 기준 9개와 6개 동시 요청을 보낸다.
|
- model-specific smoke를 실행한다. `ornith:35b`는 Spark01/02 합산 capacity `8` 기준 9개와 6개 동시 요청을 보내고, `gemma4:26b`는 Mac Studio capacity `5` 기준 9개와 6개 동시 요청을 보낸다.
|
||||||
- Control Plane status가 접근 가능하면 요청 실행 중 반복 polling하여 대상 provider들의 `in_flight`가 각 model capacity에 도달하고 초과 요청이 queue에 들어가는지 증거로 남긴다. 짧은 요청에서는 polling이 queued 순간을 놓칠 수 있으므로 HTTP 성공, max in_flight, 최종 회복을 함께 판정한다.
|
- Control Plane status가 접근 가능하면 요청 실행 중 반복 polling하여 대상 provider들의 `in_flight`가 각 model capacity에 도달하고 초과 요청이 queue에 들어가는지 증거로 남긴다. 짧은 요청에서는 polling이 queued 순간을 놓칠 수 있으므로 HTTP 성공, max in_flight, 최종 회복을 함께 판정한다.
|
||||||
|
|
@ -157,7 +157,7 @@ dev-corp runtime 배포 결과
|
||||||
- Ports: <port summary>
|
- Ports: <port summary>
|
||||||
- Nodes: <node_id connected summary>
|
- Nodes: <node_id connected summary>
|
||||||
- Providers: <provider_id capacity/in_flight/queued/health summary>
|
- Providers: <provider_id capacity/in_flight/queued/health summary>
|
||||||
- OpenAI-compatible: models=<pass|fail>, chat-completions-capacity=<pass|fail>, responses-provider-pool=<unsupported|pass|fail>
|
- OpenAI-compatible: models=<pass|fail>, chat-completions-capacity=<pass|fail>, provider-native-field-passthrough=<pass|fail|not-run>, responses-provider-pool=<pass|provider-unsupported|fail|not-run>
|
||||||
- Capacity evidence: <endpoint별 max in_flight/queued snapshot>
|
- Capacity evidence: <endpoint별 max in_flight/queued snapshot>
|
||||||
- Blockers/Risk: <없음 또는 내용>
|
- Blockers/Risk: <없음 또는 내용>
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ OpenAI-compatible Chat Completions provider 경로에서 모델 출력 이상을
|
||||||
- 출력 검증 filter별 enable/disable 정책을 environment(`dev`, `dev-corp`), model group/model/provider, 기능 단위로 평가하는 config/registry 계층
|
- 출력 검증 filter별 enable/disable 정책을 environment(`dev`, `dev-corp`), model group/model/provider, 기능 단위로 평가하는 config/registry 계층
|
||||||
- 반복 출력 루프 감지용 rolling stream inspector, upstream abort, continuation repair, 1회 repair 제한
|
- 반복 출력 루프 감지용 rolling stream inspector, upstream abort, continuation repair, 1회 repair 제한
|
||||||
- `metadata.scheme` JSON schema 계약 수신, 마지막 user message prompt append, buffered validation, schema 위반 시 bounded retry
|
- `metadata.scheme` JSON schema 계약 수신, 마지막 user message prompt append, buffered validation, schema 위반 시 bounded retry
|
||||||
- `passthrough`, `passthrough_guarded`, `contract_schema` 내부 response path 구분과 실행 로그/side observation. 이 이름들은 caller가 임의로 넣는 `metadata.iop_response_mode` 값이 아니라 IOP 내부 경로/로그 기준이다.
|
- `passthrough`, `passthrough_guarded`, `contract_schema` 내부 response path 구분과 실행 로그/관측 기준. 이 이름들은 caller가 지정하는 공개 request field가 아니라 IOP 내부 경로/로그 기준이다.
|
||||||
- normalized 실행 경로와 CLI adapter 경로를 OpenAI-compatible provider 출력 검증 경로와 분리하는 책임 경계
|
- normalized 실행 경로와 CLI adapter 경로를 OpenAI-compatible provider 출력 검증 경로와 분리하는 책임 경계
|
||||||
|
|
||||||
## 기능
|
## 기능
|
||||||
|
|
@ -84,6 +84,6 @@ OpenAI-compatible provider 응답을 사용자에게 노출하기 전에 필터
|
||||||
- 표준선(선택): optional online filter가 비활성화된 모델은 pure passthrough로 처리할 수 있지만, caller가 `metadata.scheme`처럼 필수 계약을 요청했는데 해당 filter가 비활성화된 모델은 silent passthrough가 아니라 unsupported/400으로 거부한다.
|
- 표준선(선택): optional online filter가 비활성화된 모델은 pure passthrough로 처리할 수 있지만, caller가 `metadata.scheme`처럼 필수 계약을 요청했는데 해당 filter가 비활성화된 모델은 silent passthrough가 아니라 unsupported/400으로 거부한다.
|
||||||
- 표준선(선택): OpenAI-compatible provider 출력 검증은 normalized 경로로 전환하지 않는다. normalized는 CLI 전용으로 유지한다.
|
- 표준선(선택): OpenAI-compatible provider 출력 검증은 normalized 경로로 전환하지 않는다. normalized는 CLI 전용으로 유지한다.
|
||||||
- 우선순위 순서: 현재 active 1순위다. 이 Milestone을 먼저 구현하고, 그 다음 [Seulgivibe OpenAI-compatible Provider 연동](../../routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)을 진행한다.
|
- 우선순위 순서: 현재 active 1순위다. 이 Milestone을 먼저 구현하고, 그 다음 [Seulgivibe OpenAI-compatible Provider 연동](../../routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)을 진행한다.
|
||||||
- 선행 작업: [OpenAI-compatible Tool Call Boundary Hardening](../../../archive/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-tool-call-boundary-hardening.md), [OpenAI-compatible Raw Tunnel과 Sideband Passthrough](../../../archive/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md)
|
- 선행 작업: [OpenAI-compatible Tool Call Boundary Hardening](../../../archive/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-tool-call-boundary-hardening.md), [OpenAI-compatible Raw Tunnel 기반](../../../archive/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md)
|
||||||
- 후속 작업: 단계 호출과 검증 최적화 MVP, Tool Call 판정 모델 Gate 리뷰
|
- 후속 작업: 단계 호출과 검증 최적화 MVP, Tool Call 판정 모델 Gate 리뷰
|
||||||
- 확인 필요: 없음
|
- 확인 필요: 없음
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,22 @@ IOP의 OpenAI-compatible, A2A, IOP native 입력 표면에서 들어온 요청
|
||||||
완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다.
|
완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다.
|
||||||
스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다.
|
스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다.
|
||||||
|
|
||||||
- [완료] OpenAI-compatible Raw Tunnel과 Sideband Passthrough
|
- [완료] OpenAI-compatible Raw Tunnel 기반
|
||||||
- 경로: [openai-compatible-raw-tunnel-sideband-passthrough](../../archive/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md)
|
- 경로: [openai-compatible-raw-tunnel-sideband-passthrough](../../archive/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md)
|
||||||
- 요약: OpenAI-compatible provider 응답을 기존 Edge-Node proto-socket 위 lossless raw tunnel로 전달하고, 기본값은 provider-original `passthrough`로 두며, 요청이 명시한 경우에만 `passthrough+sideband` 또는 `transformed`를 사용한다.
|
- 요약: OpenAI-compatible provider 응답을 기존 Edge-Node proto-socket 위 lossless raw tunnel로 전달하는 기반을 만들었다. 현재 계약 동기화 작업에서는 이 기반 위에서 public request selector를 제거하고 model/provider capability 기반 passthrough로 정렬한다.
|
||||||
|
|
||||||
- [완료] Seulgivibe OpenAI-compatible Provider 연동
|
- [완료] Seulgivibe OpenAI-compatible Provider 연동
|
||||||
- 경로: [seulgivibe-openai-compatible-provider](../../archive/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
|
- 경로: [seulgivibe-openai-compatible-provider](../../archive/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
|
||||||
- 요약: Seulgivibe Claude/OpenAI 프록시를 OpenAI-compatible provider family로 관리하고, model group에서는 passthrough(+sideband) 계열 provider로 분류한다. 정적 catalog, 요청 시점 provider token forwarding, Codex Responses passthrough는 코드 감사와 spec sync까지 통과해 archive했다.
|
- 요약: Seulgivibe Claude/OpenAI 프록시를 OpenAI-compatible provider family로 관리하고, model group에서는 OpenAI-compatible passthrough provider로 분류한다. 정적 catalog, 요청 시점 provider token forwarding, Codex Responses passthrough는 코드 감사와 spec sync까지 통과해 archive했다.
|
||||||
|
|
||||||
- [완료] Model Group Mixed Provider Dispatch
|
- [완료] Model Group Mixed Provider Dispatch
|
||||||
- 경로: [model-group-mixed-provider-dispatch](../../archive/phase/routing-policy-model-orchestration/milestones/model-group-mixed-provider-dispatch.md)
|
- 경로: [model-group-mixed-provider-dispatch](../../archive/phase/routing-policy-model-orchestration/milestones/model-group-mixed-provider-dispatch.md)
|
||||||
- 요약: model group provider pool에서 OpenAI-compatible provider와 Ollama/CLI 같은 normalized provider를 같은 후보군으로 두고, 기존 capacity+priority 선택 뒤 OpenAI-compatible 지원 provider는 모두 passthrough, native provider는 normalized 실행 경로로 자동 결정한다.
|
- 요약: model group provider pool에서 OpenAI-compatible provider와 Ollama/CLI 같은 normalized provider를 같은 후보군으로 두고, 기존 capacity+priority 선택 뒤 OpenAI-compatible 지원 provider는 모두 passthrough, native provider는 normalized 실행 경로로 자동 결정한다.
|
||||||
|
|
||||||
|
- [계획] OpenAI-compatible Provider Passthrough 계약 동기화
|
||||||
|
- 경로: [openai-compatible-provider-passthrough-contract-sync](milestones/openai-compatible-provider-passthrough-contract-sync.md)
|
||||||
|
- 요약: 사용자 설계 의도에 맞춰 `model` 기반 provider capability routing을 source of truth로 두고, OpenAI-compatible provider의 provider-native payload를 Edge allowlist 없이 raw tunnel로 보존하며, metadata는 IOP known-key를 read-only로 발췌하는 container로만 정리한다.
|
||||||
|
|
||||||
- [스케치] OpenAI-compatible 하이브리드 라우팅과 컨텍스트 최적화
|
- [스케치] OpenAI-compatible 하이브리드 라우팅과 컨텍스트 최적화
|
||||||
- 경로: [openai-compatible-hybrid-routing-context-optimization](milestones/openai-compatible-hybrid-routing-context-optimization.md)
|
- 경로: [openai-compatible-hybrid-routing-context-optimization](milestones/openai-compatible-hybrid-routing-context-optimization.md)
|
||||||
- 요약: skill/예약어 기반 lane/grade 라우팅을 고신뢰 경로로 유지하고, DiffusionGemma 같은 로컬 모델을 자동 triage, negative guard, cloud context 최적화 보조, 주기적 scoring policy 학습 루프로 선택적으로 사용하는 hybrid local/cloud routing 방향을 스케치한다.
|
- 요약: skill/예약어 기반 lane/grade 라우팅을 고신뢰 경로로 유지하고, DiffusionGemma 같은 로컬 모델을 자동 triage, negative guard, cloud context 최적화 보조, 주기적 scoring policy 학습 루프로 선택적으로 사용하는 hybrid local/cloud routing 방향을 스케치한다.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Milestone: OpenAI-compatible Provider Passthrough 계약 동기화
|
||||||
|
|
||||||
|
## 위치
|
||||||
|
|
||||||
|
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
|
||||||
|
- Phase: [PHASE.md](../PHASE.md)
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
|
||||||
|
OpenAI-compatible provider 경로를 request `model`이 가리키는 provider capability 기준으로 결정하고, provider가 지원하는 OpenAI-compatible 표준 payload와 provider-native extension payload를 Edge allowlist 없이 raw tunnel로 전달한다.
|
||||||
|
`metadata`는 route/response selector가 아니라 IOP가 아는 key를 read-only로 발췌하는 실행/관측 문맥으로 정리하고, caller-facing response mode selector와 provider body sideband 주입 경로를 제거한다.
|
||||||
|
|
||||||
|
## 상태
|
||||||
|
|
||||||
|
[계획]
|
||||||
|
|
||||||
|
## 승격 조건
|
||||||
|
|
||||||
|
- 없음
|
||||||
|
|
||||||
|
## 구현 잠금
|
||||||
|
|
||||||
|
- 상태: 해제
|
||||||
|
- SDD: 필요
|
||||||
|
- SDD 문서: [SDD.md](../../../sdd/routing-policy-model-orchestration/openai-compatible-provider-passthrough-contract-sync/SDD.md)
|
||||||
|
- SDD 사유: OpenAI-compatible API 계약, Edge routing, provider tunnel body, dev-corp field smoke에 영향을 주는 Milestone이다.
|
||||||
|
- 잠금 해제 조건:
|
||||||
|
- [x] SDD 잠금이 해제되어 있다.
|
||||||
|
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다.
|
||||||
|
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다.
|
||||||
|
- [x] Evidence Map이 완료 시 Roadmap Completion과 최종 검증 evidence로 검증 가능하게 연결되어 있다.
|
||||||
|
- 결정 필요: 없음
|
||||||
|
|
||||||
|
## 범위
|
||||||
|
|
||||||
|
- Edge OpenAI-compatible Chat Completions와 Responses provider-pool passthrough request/response 경계
|
||||||
|
- direct legacy OpenAI-compatible provider route의 public response selector 제거와 model/provider capability 기준 정렬
|
||||||
|
- `metadata` known-key 발췌 책임 정리: workspace, task, principal, usage/log/observability
|
||||||
|
- provider-native payload preservation: `chat_template_kwargs`, 새 OpenAI-compatible field, provider-specific extension field와 중첩 값
|
||||||
|
- Node provider tunnel request body와 Edge-Node tunnel metadata의 selector 제거
|
||||||
|
- dev-corp Spark Ornith/vLLM 계열 field passthrough smoke
|
||||||
|
|
||||||
|
## 기능
|
||||||
|
|
||||||
|
### Epic: [route-contract] Model-driven Routing Contract
|
||||||
|
|
||||||
|
OpenAI-compatible 요청의 경로 선택을 caller metadata가 아니라 `model`이 가리키는 provider capability로만 결정하도록 계약과 코드를 동기화한다.
|
||||||
|
|
||||||
|
- [ ] [selector-remove] caller-facing response mode selector 파싱, 분기, header/envelope, 테스트 fixture를 제거한다. 내부 metric/log label이 필요하면 caller 입력과 연결되지 않는 관측명으로만 남긴다. 검증: archive를 제외한 코드/계약/문서 검색에서 public selector field와 selector mode 설명이 남지 않는다.
|
||||||
|
- [ ] [model-route] Chat Completions와 Responses에서 `model` -> route/provider capability -> passthrough 또는 normalized path가 결정된다. 검증: OpenAI-compatible provider는 raw tunnel, CLI/Ollama/native provider는 normalized path를 타는 Edge unit test가 통과한다.
|
||||||
|
- [ ] [metadata-known] `metadata`는 route selector가 아니라 IOP known-key를 read-only로 발췌하는 container로만 처리된다. 검증: workspace/task/principal/usage/log key는 내부 문맥으로 복사되고, 원본 `metadata` payload와 임의 metadata key는 provider passthrough body에서 제거/변형되지 않으며 route/response path를 바꾸지 않는 테스트가 통과한다.
|
||||||
|
|
||||||
|
### Epic: [provider-pass] Provider-native Field Passthrough
|
||||||
|
|
||||||
|
vLLM, vLLM-MLX, Lemonade, SGLang, Seulgivibe 같은 OpenAI-compatible provider가 지원하는 request surface를 Edge가 자체 allowlist로 제한하지 않게 한다.
|
||||||
|
|
||||||
|
- [ ] [field-preserve] provider-pool과 direct OpenAI-compatible provider tunnel body는 `model` served target rewrite와 auth/header 처리 외에 provider-native request payload를 보존한다. 검증: `chat_template_kwargs`, 새 임의 provider field, tools/stream_options/store와 중첩 값 fixture가 provider request body에 그대로 남는 Edge/Node 테스트가 통과한다.
|
||||||
|
- [ ] [provider-error] provider가 모르는 field는 Edge가 선판단하지 않고 provider HTTP status/body로 relay한다. 검증: fake provider가 extension field를 거부하는 fixture에서 Edge가 provider error를 변환 없이 전달한다.
|
||||||
|
- [ ] [policy-priority] catalog generation policy와 IOP 내부 기본값 처리는 caller가 명시한 provider-native thinking field를 삭제하거나 대체하지 않는다. 검증: `chat_template_kwargs.enable_thinking=false`가 있는 요청에서 `think`/budget 주입 또는 rewrite가 provider-native 값을 덮지 않는다.
|
||||||
|
|
||||||
|
### Epic: [verification] Verification and Deployment Evidence
|
||||||
|
|
||||||
|
계약, 테스트, dev-corp smoke가 같은 설계 의도를 증명하도록 완료 evidence를 남긴다.
|
||||||
|
|
||||||
|
- [ ] [contract-sync] agent-contract, inner wire contract, README/docs, roadmap/SDD 포인터가 model-driven passthrough와 metadata known-key 발췌 기준으로 정리된다. 검증: `rg`로 public selector 필드/모드 설명이 archive를 제외한 최신 문서에 남지 않는다.
|
||||||
|
- [ ] [go-tests] Edge OpenAI handler, service provider-pool tunnel, Node OpenAI-compatible adapter 관련 테스트가 새 계약 기준으로 통과한다. 검증: `go test ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/node/internal/adapters/openai_compat` 또는 동등 범위가 통과한다.
|
||||||
|
- [ ] [devcorp-smoke] dev-corp IOP 경유 Ornith Spark 요청에서 `chat_template_kwargs.enable_thinking=false`가 provider까지 전달된다. 검증: direct Spark와 IOP 경유 단일 호출 모두 첫 content가 빠르게 오고 reasoning 생성이 없거나 provider-native think-off 결과와 일치한다.
|
||||||
|
|
||||||
|
## 완료 리뷰
|
||||||
|
|
||||||
|
- 상태: 없음
|
||||||
|
- 요청일: 없음
|
||||||
|
- 완료 근거: 기능 Task와 검증이 아직 충족되지 않았다.
|
||||||
|
- 검토 항목:
|
||||||
|
- [ ] 모든 기능 Task와 Task별 검증 evidence가 `Roadmap Completion`에 남아 있다.
|
||||||
|
- [ ] SDD Evidence Map이 최종 검증 evidence와 일치한다.
|
||||||
|
- [ ] dev-corp smoke 결과가 문서화되어 있다.
|
||||||
|
- agent-ui 상태 반영: 해당 없음
|
||||||
|
- 리뷰 코멘트: 없음
|
||||||
|
|
||||||
|
## 범위 제외
|
||||||
|
|
||||||
|
- 새 route scorer, hybrid local/cloud routing policy, score learning loop 구현
|
||||||
|
- provider runtime launch/restart option 변경
|
||||||
|
- output validation filter의 schema/repair policy 구현
|
||||||
|
- archive 문서의 과거 결정 기록 재작성
|
||||||
|
- billing/chargeback, 조직 IAM, 장기 retention 정책
|
||||||
|
|
||||||
|
## 작업 컨텍스트
|
||||||
|
|
||||||
|
- 관련 경로: `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-node-runtime-wire.md`, `agent-test/dev-corp`
|
||||||
|
- 표준선(선택): OpenAI-compatible provider 경로는 provider-compatible proxy처럼 동작하고, Edge는 provider field를 자체 구현/해석하지 않고 provider가 판단하도록 전달한다.
|
||||||
|
- 표준선(선택): `model`이 1차 route source of truth이며, selected provider capability가 passthrough와 normalized path를 결정한다.
|
||||||
|
- 표준선(선택): `metadata`는 workspace/task/principal/usage/log 같은 IOP known-key를 read-only로 발췌하는 container이며 response mode selector가 아니다.
|
||||||
|
- 표준선(선택): IOP 확장 field는 OpenAI-compatible 기본 surface 위의 보완 surface이고 provider-native field를 대체하지 않는다.
|
||||||
|
- 선행 작업: 기존 raw tunnel과 model group mixed dispatch 구현
|
||||||
|
- 후속 작업: OpenAI-compatible hybrid routing/context optimization, output validation filters
|
||||||
|
- 확인 필요: 없음
|
||||||
|
|
@ -72,8 +72,8 @@
|
||||||
- 반복루프 guard 같은 optional online filter가 비활성화되면 해당 filter만 skip하고 pure passthrough 또는 남은 filter path로 진행한다.
|
- 반복루프 guard 같은 optional online filter가 비활성화되면 해당 filter만 skip하고 pure passthrough 또는 남은 filter path로 진행한다.
|
||||||
- `metadata.scheme`처럼 caller가 필수 출력 계약을 요청한 filter가 비활성화되면 silent passthrough로 낮추지 않고 `policy_rejected`로 종료한다.
|
- `metadata.scheme`처럼 caller가 필수 출력 계약을 요청한 filter가 비활성화되면 silent passthrough로 낮추지 않고 `policy_rejected`로 종료한다.
|
||||||
- 내부 path 주의:
|
- 내부 path 주의:
|
||||||
- `passthrough_guarded`와 `contract_schema`는 caller가 임의로 지정하는 `metadata.iop_response_mode` 값이 아니라 IOP 내부 실행/로그 path 이름이다.
|
- `passthrough_guarded`와 `contract_schema`는 caller가 지정하는 공개 request field가 아니라 IOP 내부 실행/로그 path 이름이다.
|
||||||
- caller가 지정할 수 있는 공개 response mode 값은 [계약 원문](../../../../agent-contract/outer/openai-compatible-api.md)에서 별도로 명시한 값만 허용한다.
|
- 공개 OpenAI-compatible 경로 선택은 [계약 원문](../../../../agent-contract/outer/openai-compatible-api.md)에 따라 request `model`이 가리키는 provider capability로 결정한다.
|
||||||
- 금지:
|
- 금지:
|
||||||
- `metadata.scheme`을 `iop_output_contract` 같은 wrapper로 감싸지 않는다.
|
- `metadata.scheme`을 `iop_output_contract` 같은 wrapper로 감싸지 않는다.
|
||||||
- schema 계약 요청을 normalized path로 보내지 않는다.
|
- schema 계약 요청을 normalized path로 보내지 않는다.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
# SDD: OpenAI-compatible Provider Passthrough 계약 동기화
|
||||||
|
|
||||||
|
## 위치
|
||||||
|
|
||||||
|
- Milestone: [Milestone 문서](../../../phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md)
|
||||||
|
- Phase: [PHASE.md](../../../phase/routing-policy-model-orchestration/PHASE.md)
|
||||||
|
|
||||||
|
## 상태
|
||||||
|
|
||||||
|
[승인됨]
|
||||||
|
|
||||||
|
## SDD 잠금
|
||||||
|
|
||||||
|
- 상태: 해제
|
||||||
|
- 사용자 리뷰: 없음
|
||||||
|
- 잠금 항목:
|
||||||
|
- 없음
|
||||||
|
|
||||||
|
## 문제 / 비목표
|
||||||
|
|
||||||
|
- 문제: 현재 코드에는 caller metadata로 응답 경로를 선택하는 과거 설계와 provider-native OpenAI-compatible payload를 Edge가 제한하거나 IOP 확장 field로 치환하는 흐름이 남아 있다. 사용자 설계 의도는 `model`이 가리키는 provider capability가 passthrough/normalized 경로를 결정하고, OpenAI-compatible provider에서는 provider request surface를 그대로 보존하는 것이다.
|
||||||
|
- 비목표:
|
||||||
|
- 새 route scorer 또는 hybrid local/cloud routing policy 구현
|
||||||
|
- provider runtime launch/restart option 변경
|
||||||
|
- output validation filter의 schema/repair policy 구현
|
||||||
|
- archive 문서의 과거 결정 기록 재작성
|
||||||
|
|
||||||
|
## Source of Truth
|
||||||
|
|
||||||
|
| 영역 | 기준 | 메모 |
|
||||||
|
|------|------|------|
|
||||||
|
| Roadmap | [Milestone 문서](../../../phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md) | 목표, 범위, 기능 Task, 완료 evidence 기준 |
|
||||||
|
| Outer Contract | [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md) | OpenAI-compatible public request/response 계약 |
|
||||||
|
| Inner Wire Contract | [edge-node-runtime-wire.md](../../../../agent-contract/inner/edge-node-runtime-wire.md) | Edge-Node provider tunnel body/frame 책임 |
|
||||||
|
| Code | `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat` | 구현 source of truth |
|
||||||
|
| External Provider | dev-corp Spark Ornith/vLLM provider | provider-native field passthrough smoke 대상 |
|
||||||
|
| User Decision | 현재 사용자 설계 의도 | `model` 기반 route, metadata known-key read-only 발췌, provider-native payload 보존 |
|
||||||
|
|
||||||
|
## State Machine
|
||||||
|
|
||||||
|
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|
||||||
|
|------|-----------|-----------|------|
|
||||||
|
| request-received | Edge OpenAI-compatible endpoint가 JSON body를 수신한다 | route-resolved 또는 invalid-request | `model` envelope parse |
|
||||||
|
| route-resolved | request `model`이 route catalog 또는 provider-pool model group에 매칭된다 | metadata-observed | selected provider capability |
|
||||||
|
| metadata-observed | route 결정 후 metadata가 있으면 known-key를 내부 문맥으로 복사한다 | provider-passthrough 또는 normalized-dispatch | read-only extraction, route/response path 불변 |
|
||||||
|
| provider-passthrough | selected provider가 OpenAI-compatible 호출 방식을 지원한다 | provider-response-relayed 또는 provider-error-relayed | ProviderTunnelRequest/ProviderTunnelFrame |
|
||||||
|
| normalized-dispatch | selected provider/backend가 normalized adapter 실행을 요구한다 | normalized-response-built 또는 run-error | RunRequest/RunEvent |
|
||||||
|
| provider-response-relayed | provider가 HTTP/SSE 성공 응답을 반환한다 | terminal success | provider status/header/body relay |
|
||||||
|
| provider-error-relayed | provider가 HTTP error 또는 tunnel error를 반환한다 | terminal error | provider status/body 또는 tunnel error |
|
||||||
|
|
||||||
|
## Interface Contract
|
||||||
|
|
||||||
|
- 계약 원문: [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md)
|
||||||
|
- 입력:
|
||||||
|
- `model`: route/provider capability 선택의 1차 source of truth
|
||||||
|
- `messages` 또는 `input`: endpoint별 OpenAI-compatible request payload
|
||||||
|
- `metadata`: IOP known-key를 read-only로 발췌하는 container. route/response selector가 아니다.
|
||||||
|
- provider-native payload: selected provider가 지원하는 OpenAI-compatible 표준 field, extension field, 중첩 값. Edge passthrough 경로에서 보존한다.
|
||||||
|
- 출력:
|
||||||
|
- provider-passthrough: provider HTTP status/header/body를 relay한다. Chat Completions의 top-level model echo는 alias 보정을 할 수 있다.
|
||||||
|
- normalized-dispatch: normalized adapter 결과를 OpenAI-compatible response shape로 구성한다.
|
||||||
|
- metrics/log: provider usage 후보와 IOP known-key 관측은 internal metric/log로 남긴다.
|
||||||
|
- 금지:
|
||||||
|
- caller metadata로 passthrough/normalized/response shape를 선택하게 하지 않는다.
|
||||||
|
- OpenAI-compatible provider passthrough body에 IOP marker/event/envelope를 주입하지 않는다.
|
||||||
|
- Edge allowlist로 provider-native OpenAI-compatible extension field를 거부하지 않는다.
|
||||||
|
- IOP 확장 field가 caller의 provider-native field를 삭제하거나 대체하지 않는다.
|
||||||
|
- metadata known-key 발췌 과정에서 원본 provider payload를 strip/mutate하지 않는다.
|
||||||
|
|
||||||
|
## Acceptance Scenarios
|
||||||
|
|
||||||
|
| ID | Milestone Task | Given | When | Then |
|
||||||
|
|----|----------------|-------|------|------|
|
||||||
|
| S01 | `selector-remove` | caller가 metadata 안에 임의 response selector로 보일 수 있는 key를 넣는다 | Edge가 OpenAI-compatible request를 처리한다 | 해당 key는 route selector로 해석되지 않고, provider-passthrough body를 바꾸지 않는다 |
|
||||||
|
| S02 | `model-route` | request `model`이 OpenAI-compatible provider를 가리킨다 | Chat Completions 또는 Responses를 호출한다 | Edge는 provider tunnel passthrough를 사용한다 |
|
||||||
|
| S03 | `model-route` | request `model`이 CLI/Ollama/native normalized backend를 가리킨다 | Chat Completions 또는 Responses를 호출한다 | Edge는 normalized RunRequest path를 사용한다 |
|
||||||
|
| S04 | `metadata-known` | request metadata에 workspace/task/principal 관련 known key와 임의 key가 섞여 있다 | Edge가 route를 결정한 뒤 metadata를 처리한다 | known key는 내부 문맥으로 복사되고 원본 metadata payload는 제거/변형되지 않으며 임의 key는 route/response path를 바꾸지 않는다 |
|
||||||
|
| S05 | `field-preserve` | provider-pool 또는 direct OpenAI-compatible Chat request가 `chat_template_kwargs`와 임의 provider extension field를 포함한다 | Edge가 provider tunnel body를 만든다 | provider request body에는 `model` rewrite 외 field와 중첩 값이 보존된다 |
|
||||||
|
| S06 | `provider-error` | provider가 특정 extension field를 지원하지 않아 HTTP error를 반환한다 | Edge가 provider response를 relay한다 | caller는 provider status/body를 받으며 Edge가 자체 unsupported error로 선변환하지 않는다 |
|
||||||
|
| S07 | `policy-priority` | caller가 provider-native thinking field를 명시한다 | catalog generation policy 또는 IOP think 확장 처리가 실행된다 | caller provider-native field가 삭제되거나 대체되지 않는다 |
|
||||||
|
| S08 | `contract-sync` | 구현이 완료된다 | 계약/문서/로드맵 최신 문서를 검색한다 | archive를 제외한 최신 문서에는 public response selector field/모드 설명이 없다 |
|
||||||
|
| S09 | `go-tests` | 코드 변경이 완료된다 | 관련 Go 테스트를 실행한다 | Edge OpenAI handler, service provider-pool, Node OpenAI adapter 테스트가 통과한다 |
|
||||||
|
| S10 | `devcorp-smoke` | dev-corp IOP가 Spark Ornith provider를 라우팅한다 | `chat_template_kwargs.enable_thinking=false` 요청을 IOP 경유로 1회 호출한다 | provider-native think-off 동작과 일치하는 빠른 content-first 응답을 관찰한다 |
|
||||||
|
|
||||||
|
## Evidence Map
|
||||||
|
|
||||||
|
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|
||||||
|
|----------|-------------------|------------------|---------------------------|
|
||||||
|
| S01 | selector metadata negative test, request body fixture | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `selector-remove`와 test name |
|
||||||
|
| S02 | OpenAI-compatible provider route unit test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `model-route`와 provider tunnel dispatch evidence |
|
||||||
|
| S03 | normalized backend route unit test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `model-route`와 normalized dispatch evidence |
|
||||||
|
| S04 | metadata known-key parsing and body immutability test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `metadata-known`, route-stability, body immutability evidence |
|
||||||
|
| S05 | provider tunnel body preservation test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `field-preserve`와 captured body fixture |
|
||||||
|
| S06 | fake provider error relay test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `provider-error`와 status/body relay fixture |
|
||||||
|
| S07 | generation policy/provider-native priority test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `policy-priority`와 body diff evidence |
|
||||||
|
| S08 | `rg` evidence over non-archive docs/contracts/roadmap | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `contract-sync`와 command output summary |
|
||||||
|
| S09 | Go test command output | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `go-tests`와 pass summary |
|
||||||
|
| S10 | dev-corp single-call smoke output summary | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `devcorp-smoke`, first content latency, reasoning/content observation |
|
||||||
|
|
||||||
|
## Cross-repo Dependencies
|
||||||
|
|
||||||
|
- 없음
|
||||||
|
|
||||||
|
## Drift Check
|
||||||
|
|
||||||
|
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
|
||||||
|
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
|
||||||
|
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
|
||||||
|
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 이력
|
||||||
|
|
||||||
|
- 없음
|
||||||
|
|
||||||
|
## 작업 컨텍스트
|
||||||
|
|
||||||
|
- 표준선: OpenAI-compatible provider 경로는 provider-compatible proxy처럼 동작하고, provider-native field 판단은 provider에 맡긴다.
|
||||||
|
- 표준선: `model`이 route source of truth이며, metadata는 IOP known-key를 read-only로 발췌하는 container다.
|
||||||
|
- 표준선: provider tunnel body는 `model` served target rewrite와 auth/header 처리 외에는 caller/provider request surface와 중첩 값을 보존한다.
|
||||||
|
- 후속 SDD: 없음
|
||||||
|
|
@ -110,11 +110,11 @@ Mac Studio의 `http://192.168.2.3:8005/v1` mlx-vlm DiffusionGemma endpoint는
|
||||||
- Active Edge runtime is `iop.ai.kr` (`115.21.224.82`) with public `18085` bootstrap, `18086` OpenAI-compatible, and `18087` Edge-Node TCP.
|
- Active Edge runtime is `iop.ai.kr` (`115.21.224.82`) with public `18085` bootstrap, `18086` OpenAI-compatible, and `18087` Edge-Node TCP.
|
||||||
- mac-mini checkout/runtime is a runner and must not be used as Edge source of truth unless explicitly doing a local-runner comparison.
|
- mac-mini checkout/runtime is a runner and must not be used as Edge source of truth unless explicitly doing a local-runner comparison.
|
||||||
- On 2026-07-09 the three provider nodes were moved from mac-mini/reverse-tunnel Edge addresses to `iop.ai.kr:18087`; the public `/v1/chat/completions` aggregate 15-concurrency smoke passed 15/15 and is retained as historical routing evidence.
|
- On 2026-07-09 the three provider nodes were moved from mac-mini/reverse-tunnel Edge addresses to `iop.ai.kr:18087`; the public `/v1/chat/completions` aggregate 15-concurrency smoke passed 15/15 and is retained as historical routing evidence.
|
||||||
- On 2026-07-09 after rebuilding the public Edge from the current passthrough source, default streaming `/v1/chat/completions` preserved provider reasoning deltas for 15/15 historical aggregate concurrent requests; transformed `chatcmpl-manual` IDs were 0/15. Evidence: `build/dev-corp-runtime/logs/public_passthrough_chat_15_20260709_180115.json`.
|
- On 2026-07-09 after rebuilding the public Edge from the current passthrough source, default streaming `/v1/chat/completions` preserved provider reasoning deltas for 15/15 historical aggregate concurrent requests; manual IOP-generated `chatcmpl-manual` IDs were 0/15. Evidence: `build/dev-corp-runtime/logs/public_passthrough_chat_15_20260709_180115.json`.
|
||||||
- On 2026-07-09 after deploying latest node binaries to DGX Spark 01/02 and Mac Studio, public streaming `/v1/chat/completions` again passed 15/15 historical aggregate concurrency with provider reasoning deltas preserved and transformed `chatcmpl-manual` IDs 0/15. Evidence: `build/dev-corp-runtime/logs/public_latest_edge_node_chat_15_20260709_181222.json`.
|
- On 2026-07-09 after deploying latest node binaries to DGX Spark 01/02 and Mac Studio, public streaming `/v1/chat/completions` again passed 15/15 historical aggregate concurrency with provider reasoning deltas preserved and manual IOP-generated `chatcmpl-manual` IDs 0/15. Evidence: `build/dev-corp-runtime/logs/public_latest_edge_node_chat_15_20260709_181222.json`.
|
||||||
- Mac Studio provider catalog `type`은 최신 config validator 기준으로 `openai_compat`를 사용한다. 실제 provider runtime은 vLLM-MLX이고 `runtime_type: vllm-mlx`로 추적한다.
|
- Mac Studio provider catalog `type`은 최신 config validator 기준으로 `openai_compat`를 사용한다. 실제 provider runtime은 vLLM-MLX이고 `runtime_type: vllm-mlx`로 추적한다.
|
||||||
- `/v1/chat/completions` capacity smoke 표준: public `https://digitalplatform.iop.ai.kr/v1`에서 `ornith:35b`는 9개/6개 동시 요청, `gemma4:26b`는 9개/6개 동시 요청을 확인한다. Direct Edge listener `http://digitalplatform.iop.ai.kr:18086/v1`도 같은 backend로 동작하지만 사용자-facing 기본값으로 쓰지 않는다. 2026-07-13 live smoke에서는 네 시나리오 모두 성공했고 완료 후 provider `in_flight=0`, `queued=0`, `healthy`로 회복했다.
|
- `/v1/chat/completions` capacity smoke 표준: public `https://digitalplatform.iop.ai.kr/v1`에서 `ornith:35b`는 9개/6개 동시 요청, `gemma4:26b`는 9개/6개 동시 요청을 확인한다. Direct Edge listener `http://digitalplatform.iop.ai.kr:18086/v1`도 같은 backend로 동작하지만 사용자-facing 기본값으로 쓰지 않는다. 2026-07-13 live smoke에서는 네 시나리오 모두 성공했고 완료 후 provider `in_flight=0`, `queued=0`, `healthy`로 회복했다.
|
||||||
- `/v1/responses`는 OpenAI-compatible provider model group raw passthrough parity 전까지 미지원이므로 provider-pool capacity 성공 기준에서 제외한다.
|
- Provider-pool passthrough 계약은 selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field를 보존한다. `/v1/responses`는 capacity 성공 기준이 아니라 provider-dependent passthrough/relay 증거로 분리한다. Provider가 지원하면 raw passthrough 성공을 확인하고, provider가 미지원하면 provider status/body가 IOP 변환 없이 relay되는지 확인한다.
|
||||||
|
|
||||||
## 명령
|
## 명령
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ Mac Studio의 `http://192.168.2.3:8005/v1` mlx-vlm DiffusionGemma endpoint는
|
||||||
|
|
||||||
- 변경한 edge 패키지 또는 `go test ./apps/edge/...`를 실행한다.
|
- 변경한 edge 패키지 또는 `go test ./apps/edge/...`를 실행한다.
|
||||||
- registry, service, transport, console, HTTP/A2A 입력 표면을 바꾼 경우 edge-node 메시지 2회 왕복과 command 응답을 확인한다.
|
- registry, service, transport, console, HTTP/A2A 입력 표면을 바꾼 경우 edge-node 메시지 2회 왕복과 command 응답을 확인한다.
|
||||||
- OpenAI-compatible 경계를 바꾼 경우 dev-corp `18086` 기준 `iop-edge smoke openai` 또는 동등한 `/healthz`, `/v1/models`, `/v1/chat/completions` 확인으로 edge service와 node adapter 경로 수렴을 확인한다. provider-pool `/v1/responses` 미지원은 실패로 보지 않는다.
|
- OpenAI-compatible 경계를 바꾼 경우 dev-corp `18086` 기준 `iop-edge smoke openai` 또는 동등한 `/healthz`, `/v1/models`, `/v1/chat/completions` 확인으로 edge service와 node adapter 경로 수렴을 확인한다. Provider-pool `/v1/responses`는 selected provider 지원 여부와 relay 동작을 분리해 판정한다.
|
||||||
- provider pool config 변경 전 mac-mini에서 세 기본 provider endpoint의 `/health`와 `/v1/models`가 성공하는지 확인한다.
|
- provider pool config 변경 전 mac-mini에서 세 기본 provider endpoint의 `/health`와 `/v1/models`가 성공하는지 확인한다.
|
||||||
- DGX Spark 02가 provider port down과 SSH banner exchange timeout을 동시에 보이면 provider runtime 추가 조작을 보류하고, SSH 회복 후 process/log, `/health`, `/v1/models`, Edge OpenAI-compatible smoke를 순서대로 재검증한다.
|
- DGX Spark 02가 provider port down과 SSH banner exchange timeout을 동시에 보이면 provider runtime 추가 조작을 보류하고, SSH 회복 후 process/log, `/health`, `/v1/models`, Edge OpenAI-compatible smoke를 순서대로 재검증한다.
|
||||||
- bootstrap/artifact 경계를 바꾼 경우 dev-corp artifact/base URL 후보 `18085`가 local/test/dev field baseline을 덮어쓰지 않는지 확인한다.
|
- bootstrap/artifact 경계를 바꾼 경우 dev-corp artifact/base URL 후보 `18085`가 local/test/dev field baseline을 덮어쓰지 않는지 확인한다.
|
||||||
|
|
@ -146,8 +146,8 @@ Mac Studio의 `http://192.168.2.3:8005/v1` mlx-vlm DiffusionGemma endpoint는
|
||||||
- node 등록, `/nodes`, console 메시지 전송, 기대 payload를 포함한 `[node-*-message]` 출력, 같은 run의 complete event가 확인된다.
|
- node 등록, `/nodes`, console 메시지 전송, 기대 payload를 포함한 `[node-*-message]` 출력, 같은 run의 complete event가 확인된다.
|
||||||
- node 로컬 `[node-message]` payload 라인 목록과 edge `[node-*-message]` payload 라인 목록이 run별로 내용/순서까지 동일해야 한다.
|
- node 로컬 `[node-message]` payload 라인 목록과 edge `[node-*-message]` payload 라인 목록이 run별로 내용/순서까지 동일해야 한다.
|
||||||
- edge complete는 같은 run의 마지막 `[node-*-message]` 이후에만 정상이다.
|
- edge complete는 같은 run의 마지막 `[node-*-message]` 이후에만 정상이다.
|
||||||
- OpenAI-compatible smoke에서 `/healthz`, `/v1/models`, `/v1/chat/completions`가 기대 상태로 응답한다. provider-pool `/v1/responses`는 현재 미지원이다.
|
- OpenAI-compatible smoke에서 `/healthz`, `/v1/models`, `/v1/chat/completions`가 기대 상태로 응답한다. Provider-pool passthrough 경계 변경 시 provider-native field 예: `chat_template_kwargs`가 IOP에서 거부되지 않고 selected provider로 전달되는지 확인한다.
|
||||||
- dev-corp provider-pool capacity smoke는 현재 `/v1/chat/completions`에 model-specific 동시 요청을 보낸다. 표준 시나리오는 `ornith:35b` 9/6, `gemma4:26b` 9/6이다. `/v1/responses`는 OpenAI-compatible provider model group raw passthrough parity 전까지 미지원이므로 capacity 성공 기준에서 제외한다.
|
- dev-corp provider-pool capacity smoke는 현재 `/v1/chat/completions`에 model-specific 동시 요청을 보낸다. 표준 시나리오는 `ornith:35b` 9/6, `gemma4:26b` 9/6이다. `/v1/responses`는 capacity 성공 기준이 아니라 provider-dependent passthrough/relay 검증으로 분리한다.
|
||||||
- capacity smoke 완료 후 대상 provider의 `in_flight=0`, `queued=0` 회복을 확인한다.
|
- capacity smoke 완료 후 대상 provider의 `in_flight=0`, `queued=0` 회복을 확인한다.
|
||||||
- Gemma 계열 provider-pool smoke는 thinking enabled 기준이며 reasoning/tool-parser 관련 텍스트가 포함될 수 있다. exact-output match를 기본 판정으로 쓰지 않는다.
|
- Gemma 계열 provider-pool smoke는 thinking enabled 기준이며 reasoning/tool-parser 관련 텍스트가 포함될 수 있다. exact-output match를 기본 판정으로 쓰지 않는다.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ edge:
|
||||||
concurrent_requests: 15
|
concurrent_requests: 15
|
||||||
elapsed_sec: 5.678
|
elapsed_sec: 5.678
|
||||||
passthrough_reasoning_stream_seen: 15
|
passthrough_reasoning_stream_seen: 15
|
||||||
transformed_manual_id_count: 0
|
manual_iop_response_id_count: 0
|
||||||
remote_log: build/dev-corp-runtime/logs/public_latest_edge_node_chat_15_20260709_181222.json
|
remote_log: build/dev-corp-runtime/logs/public_latest_edge_node_chat_15_20260709_181222.json
|
||||||
latest_node_binary_deploy:
|
latest_node_binary_deploy:
|
||||||
observed_at: "2026-07-09T18:12:22+09:00"
|
observed_at: "2026-07-09T18:12:22+09:00"
|
||||||
|
|
@ -138,7 +138,7 @@ edge:
|
||||||
corp-mac-studio-mlx-vllm-node:
|
corp-mac-studio-mlx-vllm-node:
|
||||||
pid_after_restart: 87325
|
pid_after_restart: 87325
|
||||||
binary_sha256: 95c59ee0dacb134dc2c6d46fb34f2dac49c33d7da38abe96157583822364f57a
|
binary_sha256: 95c59ee0dacb134dc2c6d46fb34f2dac49c33d7da38abe96157583822364f57a
|
||||||
note: public /v1/responses remains unsupported for OpenAI-compatible provider model groups until raw passthrough parity is implemented; use /v1/chat/completions for provider-pool capacity smoke.
|
note: provider-pool passthrough contract preserves selected provider OpenAI-compatible fields; use /v1/chat/completions for capacity smoke, and treat /v1/responses as provider-dependent passthrough/relay evidence rather than an IOP-level unsupported route.
|
||||||
latest_bootstrap_domain_fix:
|
latest_bootstrap_domain_fix:
|
||||||
observed_at: "2026-07-11"
|
observed_at: "2026-07-11"
|
||||||
public_host: iop.ai.kr
|
public_host: iop.ai.kr
|
||||||
|
|
@ -327,8 +327,8 @@ model:
|
||||||
capacity_smoke:
|
capacity_smoke:
|
||||||
endpoints:
|
endpoints:
|
||||||
- /v1/chat/completions
|
- /v1/chat/completions
|
||||||
unsupported_endpoints:
|
provider_dependent_endpoints:
|
||||||
/v1/responses: OpenAI-compatible provider model group raw passthrough parity 전까지 미지원
|
/v1/responses: selected provider가 지원하면 raw passthrough로 검증하고, provider가 미지원하면 provider status/body relay 여부를 증거로 남긴다
|
||||||
aggregate_provider_capacity: 13
|
aggregate_provider_capacity: 13
|
||||||
model_scenarios:
|
model_scenarios:
|
||||||
ornith_9:
|
ornith_9:
|
||||||
|
|
@ -445,8 +445,9 @@ model:
|
||||||
max_queued: 1
|
max_queued: 1
|
||||||
final_recovery: in_flight_0_queued_0_healthy
|
final_recovery: in_flight_0_queued_0_healthy
|
||||||
responses:
|
responses:
|
||||||
supported: false
|
iop_passthrough_contract: true
|
||||||
reason: /v1/responses is not supported for OpenAI-compatible provider model groups until raw passthrough parity is implemented
|
provider_support: provider_dependent
|
||||||
|
reason: selected provider가 /v1/responses를 지원하면 raw passthrough로 검증하고, provider가 미지원하면 provider error relay를 확인한다
|
||||||
legacy_capacity_verification_2026_07_08:
|
legacy_capacity_verification_2026_07_08:
|
||||||
date: "2026-07-08"
|
date: "2026-07-08"
|
||||||
source_ref: c2437aaedefbac4312d69dfd10aa017c2739e187
|
source_ref: c2437aaedefbac4312d69dfd10aa017c2739e187
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
# Mac Studio Ornith Think-On Baseline
|
||||||
|
|
||||||
|
측정 목적: Mac Studio Ornith runtime의 `think off` 실험 전 기준 성능을 보존한다.
|
||||||
|
|
||||||
|
## Runtime Identity
|
||||||
|
|
||||||
|
- 측정 시각: 2026-07-13 17:47:59 KST
|
||||||
|
- Host: `dc-devui-MacStudio.local`
|
||||||
|
- Endpoint: `http://127.0.0.1:8007/v1`
|
||||||
|
- Model alias: `ornith:35b`
|
||||||
|
- Current runtime path: `ornith35b_alias_proxy.py` on `0.0.0.0:8007` -> `mlx_lm.server` on `127.0.0.1:8008`
|
||||||
|
- Upstream model path: `/Users/dc_dev/.cache/huggingface/manual/mlx-community/Ornith-1.0-35B-8bit`
|
||||||
|
- Runtime note: the active `vllm_mlx.cli serve` process on this host is currently Gemma4 on `8004`; this Ornith baseline uses the current Mac Studio Ornith alias path above.
|
||||||
|
- Thinking mode: current default. Upstream `mlx_lm.server` was started with `--chat-template-args '{"enable_thinking":true}'`; benchmark requests did not pass `think=false`.
|
||||||
|
- API shape: OpenAI-compatible streaming `/v1/chat/completions`
|
||||||
|
- Token counting: `Qwen2Tokenizer` over concatenated streaming `reasoning`, `reasoning_content`, and `content` deltas.
|
||||||
|
|
||||||
|
## Request Parameters
|
||||||
|
|
||||||
|
- `max_tokens`: `256`
|
||||||
|
- `temperature`: `0.2`
|
||||||
|
- `top_p`: `0.95`
|
||||||
|
- Warmup: one short warmup request, excluded from the table.
|
||||||
|
- Prompt: `Benchmark generation task. Do not use tools. Produce a compact Korean report with exactly 24 numbered lines about reliable agent runtime operations. Keep each line short, but continue until the list is complete.`
|
||||||
|
|
||||||
|
## Metric Definitions
|
||||||
|
|
||||||
|
- `avg_ttft_s`: average seconds from request start to first non-empty streaming delta. This includes `reasoning` deltas and is not the user-visible content latency.
|
||||||
|
- `avg_per_call_tok_s`: average per-call decode throughput, computed as output tokens divided by time from first non-empty delta to completion.
|
||||||
|
- `total_tok_s`: aggregate group throughput, computed as total output tokens divided by wall-clock time from earliest request start to latest completion.
|
||||||
|
- `group_wall_s`: wall-clock duration for the concurrency group.
|
||||||
|
|
||||||
|
## Content-First Baseline
|
||||||
|
|
||||||
|
This corrected measurement records when the first non-empty `content` delta appears. It is the user-visible answer latency requested for the think-off comparison.
|
||||||
|
|
||||||
|
- 측정 시각: 2026-07-13 17:55:18 KST
|
||||||
|
- Endpoint: `http://127.0.0.1:8007/v1`
|
||||||
|
- Model alias: `ornith:35b`
|
||||||
|
- Thinking mode: current default. Upstream `mlx_lm.server` was started with `--chat-template-args '{"enable_thinking":true}'`; benchmark requests did not pass `think=false`.
|
||||||
|
- `max_tokens`: `1024`
|
||||||
|
- `temperature`: `0.2`
|
||||||
|
- `top_p`: `0.95`
|
||||||
|
- Prompt: `Benchmark generation task. Produce exactly 8 short numbered Korean lines about reliable agent runtime operations.`
|
||||||
|
|
||||||
|
| Concurrent calls | Success | Avg first reasoning/any delta (s) | Avg first content delta (s) | Avg reasoning tokens/call | Avg content tokens/call | Avg all tok/s per call | Avg content tok/s per call | Total all tok/s | Total content tok/s | Group wall (s) | Finish reason |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|
|
||||||
|
| 1 | 1/1 | 0.227 | 17.830 | 919.0 | 104.0 | 52.29 | 53.00 | 51.69 | 5.25 | 19.792 | length |
|
||||||
|
| 2 | 2/2 | 0.339 | 22.193 | 978.0 | 45.0 | 44.74 | 44.44 | 88.17 | 3.88 | 23.206 | length |
|
||||||
|
| 3 | 3/3 | 0.462 | 26.260 | 978.0 | 45.0 | 37.93 | 38.26 | 111.85 | 4.92 | 27.439 | length |
|
||||||
|
| 4 | 4/4 | 0.598 | 30.638 | 978.0 | 45.0 | 32.55 | 32.34 | 127.74 | 5.62 | 32.035 | length |
|
||||||
|
| 5 | 5/5 | 0.728 | 38.788 | 978.0 | 45.0 | 25.69 | 25.64 | 126.13 | 5.55 | 40.553 | length |
|
||||||
|
|
||||||
|
### Content-First Per-Call Detail
|
||||||
|
|
||||||
|
| Concurrent calls | Call | First content delta (s) | Reasoning tokens | Content tokens | All tok/s | Content tok/s | Duration (s) |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 1 | 1 | 17.830 | 919 | 104 | 52.29 | 53.00 | 19.792 |
|
||||||
|
| 2 | 1 | 22.195 | 978 | 45 | 44.74 | 44.49 | 23.206 |
|
||||||
|
| 2 | 2 | 22.191 | 978 | 45 | 44.73 | 44.38 | 23.205 |
|
||||||
|
| 3 | 1 | 26.258 | 978 | 45 | 37.93 | 38.33 | 27.432 |
|
||||||
|
| 3 | 2 | 26.263 | 978 | 45 | 37.93 | 38.29 | 27.439 |
|
||||||
|
| 3 | 3 | 26.258 | 978 | 45 | 37.92 | 38.15 | 27.437 |
|
||||||
|
| 4 | 1 | 30.637 | 978 | 45 | 32.55 | 32.47 | 32.023 |
|
||||||
|
| 4 | 2 | 30.637 | 978 | 45 | 32.54 | 32.22 | 32.034 |
|
||||||
|
| 4 | 3 | 30.642 | 978 | 45 | 32.55 | 32.31 | 32.035 |
|
||||||
|
| 4 | 4 | 30.637 | 978 | 45 | 32.55 | 32.34 | 32.028 |
|
||||||
|
| 5 | 1 | 38.787 | 978 | 45 | 25.69 | 25.50 | 40.551 |
|
||||||
|
| 5 | 2 | 38.787 | 978 | 45 | 25.70 | 25.70 | 40.538 |
|
||||||
|
| 5 | 3 | 38.787 | 978 | 45 | 25.69 | 25.61 | 40.544 |
|
||||||
|
| 5 | 4 | 38.793 | 978 | 45 | 25.69 | 25.58 | 40.552 |
|
||||||
|
| 5 | 5 | 38.788 | 978 | 45 | 25.70 | 25.81 | 40.531 |
|
||||||
|
|
||||||
|
### Content-First Observations
|
||||||
|
|
||||||
|
- The user-visible first content delta was much later than the first reasoning delta.
|
||||||
|
- At concurrency 1, first reasoning/any delta arrived at `0.227s`, but first content arrived at `17.830s`.
|
||||||
|
- At concurrency 5, first reasoning/any delta arrived at `0.728s`, but first content arrived at `38.788s`.
|
||||||
|
- The run consistently spent most of the `1024` token budget on reasoning before content. Most calls produced about `978` reasoning tokens and `45` content tokens before `finish_reason=length`.
|
||||||
|
- For think-off comparison, `Avg first content delta (s)` is the latency column to compare first.
|
||||||
|
|
||||||
|
## Think-Off Result
|
||||||
|
|
||||||
|
### Compatibility Note
|
||||||
|
|
||||||
|
The alias proxy currently maps request-level `think:false` to upstream `chat_template_args.enable_thinking=false`. The installed `mlx_lm.server` request parser reads `chat_template_kwargs`, not request-level `chat_template_args` (`mlx_lm/server.py` request parse path stores `self.chat_template_kwargs = self.body.get("chat_template_kwargs")`). Because of that mismatch, plain `think:false` did not produce a real think-off result in this runtime path.
|
||||||
|
|
||||||
|
Observed `think:false` compatibility probe:
|
||||||
|
|
||||||
|
| Mode | First content delta (s) | Reasoning chars | Content chars | Finish reason | Interpretation |
|
||||||
|
|---|---:|---:|---:|---|---|
|
||||||
|
| `think:false` via alias proxy | 17.975 | 3300 | 203 | length | Not effective; still generated reasoning first |
|
||||||
|
|
||||||
|
The effective think-off measurement below used request `chat_template_kwargs: {"enable_thinking": false}`.
|
||||||
|
|
||||||
|
### Effective Think-Off Content-First Table
|
||||||
|
|
||||||
|
- 측정 시각: 2026-07-13 18:05:09 KST
|
||||||
|
- Endpoint: `http://127.0.0.1:8007/v1`
|
||||||
|
- Model alias: `ornith:35b`
|
||||||
|
- Thinking mode: effective request-level think-off via `chat_template_kwargs.enable_thinking=false`
|
||||||
|
- `max_tokens`: `1024`
|
||||||
|
- `temperature`: `0.2`
|
||||||
|
- `top_p`: `0.95`
|
||||||
|
- Prompt: same as the Content-First Baseline.
|
||||||
|
|
||||||
|
| Concurrent calls | Success | Avg first content delta (s) | Avg reasoning tokens/call | Avg content tokens/call | Avg content tok/s per call | Total content tok/s | Group wall (s) | Finish reason |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|---:|---|
|
||||||
|
| 1 | 1/1 | 0.208 | 0.0 | 104.0 | 52.77 | 47.74 | 2.179 | stop |
|
||||||
|
| 2 | 2/2 | 0.333 | 0.0 | 113.0 | 44.81 | 79.15 | 2.855 | stop |
|
||||||
|
| 3 | 3/3 | 0.457 | 0.0 | 113.0 | 37.79 | 98.27 | 3.450 | stop |
|
||||||
|
| 4 | 4/4 | 0.580 | 0.0 | 113.0 | 32.47 | 111.20 | 4.065 | stop |
|
||||||
|
| 5 | 5/5 | 0.703 | 0.0 | 113.0 | 25.82 | 111.03 | 5.089 | stop |
|
||||||
|
|
||||||
|
### Think-On vs Effective Think-Off
|
||||||
|
|
||||||
|
| Concurrent calls | Think-on first content (s) | Effective think-off first content (s) | Delta (s) | Speedup |
|
||||||
|
|---:|---:|---:|---:|---:|
|
||||||
|
| 1 | 17.830 | 0.208 | 17.622 | 85.9x |
|
||||||
|
| 2 | 22.193 | 0.333 | 21.860 | 66.7x |
|
||||||
|
| 3 | 26.260 | 0.457 | 25.803 | 57.5x |
|
||||||
|
| 4 | 30.638 | 0.580 | 30.058 | 52.9x |
|
||||||
|
| 5 | 38.788 | 0.703 | 38.085 | 55.2x |
|
||||||
|
|
||||||
|
### Effective Think-Off Observations
|
||||||
|
|
||||||
|
- Effective think-off eliminated reasoning output in this benchmark (`0` reasoning tokens for all calls).
|
||||||
|
- First user-visible content became the first non-empty stream delta.
|
||||||
|
- Total content throughput peaked around concurrency 4-5 at about `111 tok/s`.
|
||||||
|
- The largest improvement is first content latency, not single-stream decode tok/s.
|
||||||
|
- To make `think:false` work through the alias endpoint, update the proxy to map `think` to `chat_template_kwargs.enable_thinking`, not only `chat_template_args.enable_thinking`.
|
||||||
|
|
||||||
|
## First-Content Optimization Candidates
|
||||||
|
|
||||||
|
| Priority | Candidate | Expected effect | Risk / note |
|
||||||
|
|---:|---|---|---|
|
||||||
|
| 1 | Fix alias proxy `think` mapping to upstream `chat_template_kwargs.enable_thinking` | Makes simple `think:false` clients get the measured `0.2-0.7s` first-content path | Very small code change, but verify `/v1/chat/completions` streaming and existing `chat_template_kwargs` passthrough |
|
||||||
|
| 2 | Start a separate always-non-thinking Ornith endpoint with `mlx_lm.server --chat-template-args '{"enable_thinking":false}'` | Makes non-thinking the default without per-request kwargs | Keep on a separate port first; quality/tool-call behavior may differ |
|
||||||
|
| 3 | Use `vllm_mlx` Ornith path if stable, with `--default-chat-template-kwargs '{"enable_thinking": false}'` or request `enable_thinking=false` | Better OpenAI-compatible semantics, continuous batching, prefix cache, warm prompts, and request-level thinking controls | This changes runtime stack from current `mlx_lm.server`; test as a parallel endpoint before replacing `8007` |
|
||||||
|
| 4 | If reasoning must stay on, enforce a small thinking budget where supported | Bounds reasoning delay before content instead of fully disabling reasoning | Current `mlx_lm.server` path has no observed thinking budget control; `vllm_mlx`/vLLM-style serving has this concept |
|
||||||
|
| 5 | Warm repeated prompt prefixes / prompt cache | Reduces prefill/TTFT for repeated system prompts and agent templates | Helps prefill, not the long reasoning-before-content delay. Think-off gives the bigger win here |
|
||||||
|
| 6 | Tune `decode-concurrency`, `prompt-concurrency`, and `prefill-step-size` on `mlx_lm.server` | May improve batch scheduling and prefill behavior | Current content-first bottleneck was reasoning generation. Tune only after think-off route is correct |
|
||||||
|
|
||||||
|
References checked:
|
||||||
|
|
||||||
|
- Local `mlx_lm.server --help`: supports `--chat-template-args`, `--decode-concurrency`, `--prompt-concurrency`, `--prefill-step-size`, `--prompt-cache-size`, `--prompt-cache-bytes`, and draft-model options.
|
||||||
|
- Local installed `mlx_lm/server.py`: request body uses `chat_template_kwargs`; startup CLI uses `chat_template_args`.
|
||||||
|
- Qwen docs: previous Qwen3-style hybrid thinking can be controlled by `enable_thinking=False`; newer Qwen3 Instruct/Thinking variants may be mode-specific.
|
||||||
|
- vLLM docs: request-level `chat_template_kwargs` can override server defaults, and thinking budgets can bound reasoning tokens.
|
||||||
|
- `vllm-mlx` docs: continuous batching, prefix cache, SSD-tiered cache, and warm prompts are intended Apple Silicon serving optimizations.
|
||||||
|
|
||||||
|
## Reasoning-Inclusive First-Delta Table
|
||||||
|
|
||||||
|
| Concurrent calls | Success | Avg output tokens/call | Total output tokens | Avg TTFT (s) | Avg duration (s) | Avg decode duration (s) | Avg tok/s per call | Total tok/s | Group wall (s) | Finish reason |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|
|
||||||
|
| 1 | 1/1 | 256.0 | 256 | 0.221 | 5.085 | 4.864 | 52.63 | 50.34 | 5.085 | length |
|
||||||
|
| 2 | 2/2 | 256.0 | 512 | 0.340 | 6.072 | 5.732 | 44.66 | 84.31 | 6.073 | length |
|
||||||
|
| 3 | 3/3 | 256.0 | 768 | 0.462 | 7.213 | 6.751 | 37.92 | 106.42 | 7.217 | length |
|
||||||
|
| 4 | 4/4 | 256.0 | 1024 | 0.570 | 8.445 | 7.875 | 32.51 | 121.17 | 8.451 | length |
|
||||||
|
| 5 | 5/5 | 256.0 | 1280 | 0.698 | 10.639 | 9.940 | 25.75 | 120.20 | 10.649 | length |
|
||||||
|
|
||||||
|
## Per-Call Detail
|
||||||
|
|
||||||
|
| Concurrent calls | Call | Output tokens | TTFT (s) | Duration (s) | Decode duration (s) | Tok/s |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 1 | 1 | 256 | 0.221 | 5.085 | 4.864 | 52.63 |
|
||||||
|
| 2 | 1 | 256 | 0.339 | 6.072 | 5.734 | 44.65 |
|
||||||
|
| 2 | 2 | 256 | 0.342 | 6.072 | 5.730 | 44.68 |
|
||||||
|
| 3 | 1 | 256 | 0.465 | 7.216 | 6.751 | 37.92 |
|
||||||
|
| 3 | 2 | 256 | 0.461 | 7.216 | 6.755 | 37.90 |
|
||||||
|
| 3 | 3 | 256 | 0.460 | 7.208 | 6.748 | 37.94 |
|
||||||
|
| 4 | 1 | 256 | 0.569 | 8.443 | 7.874 | 32.51 |
|
||||||
|
| 4 | 2 | 256 | 0.569 | 8.438 | 7.869 | 32.53 |
|
||||||
|
| 4 | 3 | 256 | 0.569 | 8.450 | 7.881 | 32.48 |
|
||||||
|
| 4 | 4 | 256 | 0.574 | 8.451 | 7.877 | 32.50 |
|
||||||
|
| 5 | 1 | 256 | 0.697 | 10.647 | 9.950 | 25.73 |
|
||||||
|
| 5 | 2 | 256 | 0.697 | 10.632 | 9.935 | 25.77 |
|
||||||
|
| 5 | 3 | 256 | 0.696 | 10.639 | 9.943 | 25.75 |
|
||||||
|
| 5 | 4 | 256 | 0.704 | 10.648 | 9.944 | 25.74 |
|
||||||
|
| 5 | 5 | 256 | 0.698 | 10.626 | 9.929 | 25.78 |
|
||||||
|
|
||||||
|
## Observations
|
||||||
|
|
||||||
|
- Every measured request completed successfully and stopped at `finish_reason=length`.
|
||||||
|
- Aggregate throughput increased from concurrency 1 to 4, then flattened at concurrency 5.
|
||||||
|
- In this baseline, total throughput was `121.17 tok/s` at concurrency 4 and `120.20 tok/s` at concurrency 5.
|
||||||
|
- Average TTFT rose with concurrency, from `0.221s` at concurrency 1 to `0.698s` at concurrency 5.
|
||||||
|
|
@ -109,7 +109,7 @@ Mac Studio의 secondary `http://192.168.2.3:8005/v1` endpoint는 기본 Node/pro
|
||||||
|
|
||||||
- DGX Spark 01, DGX Spark 02, Mac Studio Node는 각 host의 `~/iop-dev-corp-field/node.yaml`에서 `edge_addr: "iop.ai.kr:18087"`로 실행된다.
|
- DGX Spark 01, DGX Spark 02, Mac Studio Node는 각 host의 `~/iop-dev-corp-field/node.yaml`에서 `edge_addr: "iop.ai.kr:18087"`로 실행된다.
|
||||||
- 2026-07-13 live status 기준 세 Node 모두 connected이며 provider snapshot은 Spark01 Ornith capacity `4`, Spark02 Ornith capacity `4`, Mac Studio Gemma4 capacity `5`이다. Public OpenAI-compatible route에서 `ornith:35b` 9/6, `gemma4:26b` 9/6 동시 요청이 모두 성공했고 완료 후 `in_flight=0`, `queued=0`, `healthy`로 회복했다. 사용자-facing 기본 base URL은 `https://digitalplatform.iop.ai.kr/v1`이다.
|
- 2026-07-13 live status 기준 세 Node 모두 connected이며 provider snapshot은 Spark01 Ornith capacity `4`, Spark02 Ornith capacity `4`, Mac Studio Gemma4 capacity `5`이다. Public OpenAI-compatible route에서 `ornith:35b` 9/6, `gemma4:26b` 9/6 동시 요청이 모두 성공했고 완료 후 `in_flight=0`, `queued=0`, `healthy`로 회복했다. 사용자-facing 기본 base URL은 `https://digitalplatform.iop.ai.kr/v1`이다.
|
||||||
- `/v1/responses`는 OpenAI-compatible provider model group raw passthrough parity 전까지 미지원이므로 provider-pool capacity 성공 기준에서 제외한다.
|
- Provider-pool passthrough 계약은 selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field를 보존한다. `/v1/responses`는 capacity 성공 기준이 아니라 provider-dependent passthrough/relay 검증으로 분리한다. Provider가 지원하면 raw passthrough 성공을 확인하고, provider가 미지원하면 provider status/body가 IOP 변환 없이 relay되는지 확인한다.
|
||||||
|
|
||||||
## 명령
|
## 명령
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ openai:
|
||||||
target: "llama3:8b"
|
target: "llama3:8b"
|
||||||
```
|
```
|
||||||
|
|
||||||
`/v1/chat/completions`는 기본 non-streaming과 streaming SSE 응답을 모두 지원한다. `/v1/responses`는 현재 non-streaming 요청만 지원한다. 두 endpoint 모두 `metadata.workspace`를 run workspace로 전달한다. `metadata`는 OpenAI 표준의 caller-defined string metadata container로 보고, IOP가 특별히 해석하는 key는 `workspace`뿐이다. `/v1/responses`의 `max_output_tokens`, `temperature`, `top_p`, `instructions`, `background`와 `/v1/chat/completions`의 `max_tokens`, `max_completion_tokens`, `temperature`, `top_p`, `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 등은 OpenAI API처럼 top-level field에 둔다. `metadata.source`, `metadata.cli`, `metadata.inference`, 소비자 전용 metadata wrapper, `options`, `chat_template_kwargs`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field는 지원하지 않는다.
|
`/v1/chat/completions`는 기본 non-streaming과 streaming SSE 응답을 모두 지원한다. Normalized `/v1/responses`는 현재 non-streaming 요청을 지원하고, provider-pool `/v1/responses`는 selected OpenAI-compatible provider로 raw passthrough 된다. 두 endpoint 모두 `metadata.workspace`를 run workspace로 전달한다. `metadata`는 OpenAI 표준의 caller-defined string metadata container로 보고, IOP가 특별히 해석하는 key는 `workspace`뿐이다. `/v1/responses`의 `max_output_tokens`, `temperature`, `top_p`, `instructions`, `background`와 `/v1/chat/completions`의 `max_tokens`, `max_completion_tokens`, `temperature`, `top_p` 등은 OpenAI API처럼 top-level field에 둔다. Provider-pool passthrough에서는 `chat_template_kwargs` 같은 provider-native OpenAI-compatible extension field도 보존한다. `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`은 OpenAI-compatible 기본 surface 위의 IOP 확장 field다. Normalized route에서는 `metadata.source`, `metadata.cli`, `metadata.inference`, 소비자 전용 metadata wrapper, `options`, `format`, `keep_alive` 같은 backend/provider 전용 wrapper를 OpenAI-compatible 표준 field처럼 요구하지 않는다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s http://127.0.0.1:18081/v1/chat/completions \
|
curl -s http://127.0.0.1:18081/v1/chat/completions \
|
||||||
|
|
|
||||||
|
|
@ -133,15 +133,15 @@ http://<edge-host>:18081/v1
|
||||||
|
|
||||||
## 7. Thinking/Reasoning 제어 smoke
|
## 7. Thinking/Reasoning 제어 smoke
|
||||||
|
|
||||||
`/v1/chat/completions` 요청은 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`으로 thinking/reasoning 동작과 응답 노출을 제어할 수 있다. 단, provider-pool pure `passthrough`는 provider body 보존이 우선이므로 `include_reasoning=false`만으로 IOP-side reasoning 제거를 보장하지 않는다.
|
`/v1/chat/completions` 요청은 OpenAI-compatible field를 기본으로 사용한다. Provider-pool pure `passthrough`는 selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field를 보존해야 하며, `chat_template_kwargs` 같은 provider-native option을 IOP allowlist로 막지 않는다. `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`은 normalized backend 또는 provider별 차이를 보완하기 위한 IOP 확장 field다.
|
||||||
|
|
||||||
- 현재 dev-corp provider-pool device mapping은 `gemma4:26b` -> Mac Studio provider capacity `5`, `ornith:35b` -> DGX Spark 01/02 provider 합산 capacity `8`이다. 세부 endpoint와 runtime args는 `agent-test/dev-corp/inventory.yaml`을 기준으로 한다.
|
- 현재 dev-corp provider-pool device mapping은 `gemma4:26b` -> Mac Studio provider capacity `5`, `ornith:35b` -> DGX Spark 01/02 provider 합산 capacity `8`이다. 세부 endpoint와 runtime args는 `agent-test/dev-corp/inventory.yaml`을 기준으로 한다.
|
||||||
- dev-corp provider-pool 안정 smoke: `think`, `reasoning_effort`, `thinking_token_budget`을 생략하고 현재 provider 기본값을 유지한다.
|
- dev-corp provider-pool 안정 smoke: 기본 smoke에서는 `think`, `reasoning_effort`, `thinking_token_budget`을 생략하고 현재 provider 기본값을 유지한다. Provider-native passthrough를 검증할 때는 selected provider가 직접 지원하는 field를 그대로 보낸다.
|
||||||
- 현재 dev-corp public capacity smoke 표준은 public OpenAI-compatible base `https://digitalplatform.iop.ai.kr/v1`의 `/chat/completions`에서 `ornith:35b` 9/6 동시 요청과 `gemma4:26b` 9/6 동시 요청을 각각 확인하는 방식이다.
|
- 현재 dev-corp public capacity smoke 표준은 public OpenAI-compatible base `https://digitalplatform.iop.ai.kr/v1`의 `/chat/completions`에서 `ornith:35b` 9/6 동시 요청과 `gemma4:26b` 9/6 동시 요청을 각각 확인하는 방식이다.
|
||||||
- 일반 표준 caller의 `stream=false` 측정은 `/v1/chat/completions`에서 요청 파라미터만으로 확인한다.
|
- 일반 표준 caller의 `stream=false` 측정은 `/v1/chat/completions`에서 요청 파라미터만으로 확인한다.
|
||||||
- `include_reasoning=false`는 non-provider normalized route의 hide 동작 기준이다. dev-corp provider-pool pure `passthrough`에서는 client가 reasoning field를 선택적으로 무시/제거한다.
|
- `include_reasoning=false`는 non-provider normalized route의 hide 동작 기준이다. dev-corp provider-pool pure `passthrough`에서는 client가 reasoning field를 선택적으로 무시/제거한다.
|
||||||
- `think=false` 또는 `reasoning_effort=none`은 hide-only 옵션이 아니라 thinking disable 요청이다. dev-corp 기본 안정 smoke에서는 쓰지 않는다.
|
- `think=false` 또는 `reasoning_effort=none`은 hide-only 옵션이 아니라 thinking disable 요청이다. Provider-native field가 있는 경우 해당 field를 우선 사용해 passthrough 보존을 검증한다.
|
||||||
- Gemma4 passthrough 파라미터의 세부 표와 금지/허용 범위는 `agent-contract/outer/openai-compatible-api.md`의 dev-corp `gemma4:26b` provider-pool passthrough 파라미터 범위를 기준으로 한다.
|
- Provider-pool passthrough 파라미터의 세부 계약과 금지/허용 범위는 `agent-contract/outer/openai-compatible-api.md`를 기준으로 한다.
|
||||||
|
|
||||||
예시 (dev-corp `gemma4:26b` provider-pool non-stream 측정, think 생략):
|
예시 (dev-corp `gemma4:26b` provider-pool non-stream 측정, think 생략):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
|
|
||||||
주요 현재 동작:
|
주요 현재 동작:
|
||||||
|
|
||||||
- Chat Completions provider route의 기본 응답 mode는 provider-original `passthrough`다.
|
- IOP OpenAI-compatible 표면의 기본 베이스는 OpenAI-compatible request/response surface 보존이다. Provider-pool passthrough는 selected provider가 지원하는 표준 field와 provider extension field를 IOP allowlist로 제한하지 않는다.
|
||||||
- provider-pool model group route는 caller가 `metadata.iop_response_mode`를 명시하면 값과 무관하게 거부하고, 생략 시 provider-original `passthrough`로 동작한다.
|
- `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 같은 IOP field는 OpenAI-compatible 기본 surface 위의 확장이다. Provider-native field를 대체하거나 금지하는 수단으로 해석하지 않는다.
|
||||||
- direct legacy provider route는 `passthrough+sideband` extension selector를 지원한다. `passthrough+sideband`와 `transformed`는 IOP 확장/변환 응답이며 provider-original byte identity로 취급하지 않는다.
|
- 라우팅의 1차 기준은 request `model`이 가리키는 provider capability다. OpenAI-compatible provider이면 provider-original `passthrough`, 그 외 CLI/Ollama/native 실행이면 normalized path를 사용한다.
|
||||||
- dev-corp `gemma4:26b` provider-pool에서 `think`/`stream`/`include_reasoning` 요청 파라미터만 바꿔 측정하는 범위는 계약 원문의 `dev-corp gemma4:26b provider-pool passthrough 파라미터 범위` 표를 기준으로 한다.
|
- `metadata`는 route/response selector가 아니다. Edge는 모델 기반 route 선택 뒤 IOP가 아는 metadata key만 workspace, task, principal, usage/observability 문맥으로 발췌한다.
|
||||||
|
- dev-corp provider-pool에서 기본 smoke 파라미터와 provider-native field passthrough 기대 동작은 계약 원문의 provider-pool passthrough 섹션을 기준으로 한다.
|
||||||
- dev-corp Ollama `gemma4:26b` 이미지 입력 호출 가이드는 [dev-corp-ollama-gemma4-image-call-guide.md](./dev-corp-ollama-gemma4-image-call-guide.md)를 기준으로 한다.
|
- dev-corp Ollama `gemma4:26b` 이미지 입력 호출 가이드는 [dev-corp-ollama-gemma4-image-call-guide.md](./dev-corp-ollama-gemma4-image-call-guide.md)를 기준으로 한다.
|
||||||
- dev-corp Pi coding agent 설정 가이드는 [dev-corp-pi-settings-guide.md](./dev-corp-pi-settings-guide.md)를 기준으로 한다.
|
- dev-corp Pi coding agent 설정 가이드는 [dev-corp-pi-settings-guide.md](./dev-corp-pi-settings-guide.md)를 기준으로 한다.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue