From 9f5d15d91c8c4ab2366c7b44fa2fc2e194ac66b4 Mon Sep 17 00:00:00 2001 From: leedongmyun Date: Mon, 13 Jul 2026 19:18:07 +0900 Subject: [PATCH] chore: sync OpenAI compatible API contract, routing policy, and dev-corp test updates --- agent-contract/index.md | 2 +- .../inner/edge-node-runtime-wire.md | 6 +- agent-contract/outer/openai-compatible-api.md | 68 +++---- .../project/dev-corp-runtime-deploy/SKILL.md | 6 +- ...ai-compatible-output-validation-filters.md | 4 +- .../PHASE.md | 10 +- ...ible-provider-passthrough-contract-sync.md | 98 +++++++++ .../SDD.md | 4 +- .../SDD.md | 120 +++++++++++ agent-test/dev-corp/edge-smoke.md | 12 +- agent-test/dev-corp/inventory.yaml | 13 +- ...tudio-ornith-think-on-baseline-20260713.md | 187 ++++++++++++++++++ agent-test/dev-corp/node-smoke.md | 2 +- apps/edge/README.md | 2 +- docs/edge-local-dev-guide.md | 8 +- docs/openai-compatible-api-contract.md | 9 +- 16 files changed, 482 insertions(+), 69 deletions(-) create mode 100644 agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md create mode 100644 agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-provider-passthrough-contract-sync/SDD.md create mode 100644 agent-test/dev-corp/mac-studio-ornith-think-on-baseline-20260713.md diff --git a/agent-contract/index.md b/agent-contract/index.md index a4f6392..daf130d 100644 --- a/agent-contract/index.md +++ b/agent-contract/index.md @@ -12,7 +12,7 @@ | 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` | ## Inner Contracts diff --git a/agent-contract/inner/edge-node-runtime-wire.md b/agent-contract/inner/edge-node-runtime-wire.md index 4a1b2a9..0a91013 100644 --- a/agent-contract/inner/edge-node-runtime-wire.md +++ b/agent-contract/inner/edge-node-runtime-wire.md @@ -33,7 +33,7 @@ Edge는 Node 연결을 수락하고, Node는 연결 직후 등록 요청을 보 - register: Node가 `RegisterRequest`를 보내고 Edge가 `RegisterResponse`로 수락 여부와 `NodeConfigPayload`를 돌려준다. - 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만으로 후보를 제외하지 않는다. - cancel: Edge가 `CancelRequest`를 보내며 `CANCEL_RUN`과 `TERMINATE_SESSION`을 구분한다. - command: Edge가 `NodeCommandRequest`를 보내고 Node가 `NodeCommandResponse`로 usage/capabilities/session/transport/provider 상태를 응답한다. @@ -46,8 +46,8 @@ Edge는 Node 연결을 수락하고, Node는 연결 직후 등록 요청을 보 - `RunRequest.input`: adapter가 해석할 실행 입력이다. CLI 실행에서는 prompt 계열 입력으로 변환된다. - `RunRequest.metadata`: caller-defined 실행 metadata다. workspace 자체는 별도 `workspace` 필드로 전달한다. - `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`로 전달될 수 있다. -- `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에 합쳐지지 않는다. +- `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`는 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를 유지해야 한다. - `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`로 복원할 수 있다. diff --git a/agent-contract/outer/openai-compatible-api.md b/agent-contract/outer/openai-compatible-api.md index 3b5c38c..d1d07b4 100644 --- a/agent-contract/outer/openai-compatible-api.md +++ b/agent-contract/outer/openai-compatible-api.md @@ -20,6 +20,8 @@ 이 문서는 외부 프로젝트가 IOP Edge의 OpenAI-compatible HTTP 표면을 호출할 때 확인할 계약 원문이다. IOP 내부 실행은 `adapter + target` 기준이며, OpenAI-compatible 경계에서는 호환성을 위해 `model`을 사용한다. 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 @@ -39,6 +41,7 @@ Edge 설정에 `openai.principal_tokens[]`가 설정된 경우, caller는 기존 - caller-provided `metadata.user`는 identity source가 아니며 사용되지 않는다. - caller가 `metadata.iop_principal_*`를 보내도 authenticated context 값이 overwrite한다. +- `metadata`는 route/response mode selector가 아니다. Edge는 model 기반 route 선택 뒤 IOP가 아는 metadata key만 실행 문맥과 관측용으로 발췌한다. ### Legacy fallback @@ -102,9 +105,9 @@ CLI agent 실행으로 라우팅되는 요청의 최소 형태: - `model`: Edge가 내부 `adapter + target`으로 해석할 외부 route 이름이다. IOP Edge에서는 라우팅을 위해 필수다. - `instructions`: OpenAI Responses API의 top-level instruction field다. 있으면 `input` 앞에 배치해 agent 실행 prompt를 만든다. -- `input`: agent에게 전달할 사용자 요청이다. 현재 구현은 string input만 지원한다. -- `stream`: 현재 구현은 `false` 또는 생략만 지원한다. -- `background`: 현재 구현은 `false` 또는 생략만 지원한다. +- `input`: agent에게 전달할 사용자 요청이다. normalized(non-provider) route는 현재 string input만 지원한다. +- `stream`: normalized(non-provider) route는 현재 `false` 또는 생략만 지원한다. Provider-pool passthrough는 provider가 지원하는 stream 값을 보존한다. +- `background`: normalized(non-provider) route는 현재 `false` 또는 생략만 지원한다. Provider-pool passthrough는 provider가 지원하는 값을 보존한다. - `metadata.workspace`: CLI process를 실행할 작업 디렉터리다. CLI agent route에서는 필수 실행 문맥이다. - `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가 아니다. @@ -112,24 +115,24 @@ CLI agent 실행으로 라우팅되는 요청의 최소 형태: - `temperature`: 생성 다양성 option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다. - `top_p`: nucleus sampling option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다. -금지: +Normalized route 금지: - `metadata.cli` 같은 CLI 전용 wrapper를 추가하지 않는다. - `metadata.inference`처럼 `model` route와 겹치는 target wrapper를 추가하지 않는다. - `metadata.nomadcode`처럼 특정 소비자 제품명에 묶인 wrapper를 추가하지 않는다. - `metadata.source`처럼 의미가 불명확한 호출 출처 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 기본값을 따른다. - workspace를 prompt 본문에 섞어 전달하지 않는다. 현재 구현 메모: - 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다. -- 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`로 거부한다. -- Responses provider passthrough success usage metric label은 endpoint=`responses`, response_mode=`passthrough` 또는 direct sideband route의 `passthrough+sideband`, model_group=request alias를 사용한다. +- 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와 model_group=request alias를 기준으로 집계한다. 관측/usage 정보는 provider body에 섞지 않는다. - `metadata`는 최대 16개 string key/value를 허용한다. key는 64자 이하, value는 512자 이하를 기준으로 한다. - CLI route의 `metadata.workspace`는 이 문서의 계약 기준이다. 구현은 이 값을 Edge service의 run workspace와 Node CLI adapter의 process working directory로 전달해야 한다. - `metadata.workspace`는 `RunRequest.Workspace`로 전달하고 generic run metadata에는 복사하지 않는다. @@ -158,7 +161,7 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa ## 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 { @@ -175,7 +178,7 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa } ``` -현재 지원하는 Chat Completions request field: +Normalized(non-provider) Chat Completions route가 해석하는 request field: - `model` - `messages` @@ -200,27 +203,29 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa - `thinking_token_budget` - `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도 붙이지 않는다. -- 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`로 라벨링한다. +Chat Completions의 실행 경로는 caller가 보낸 `model`의 route/provider capability로 결정한다. -알 수 없는 `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 생성을 명시 활성화한다. -- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high` 중 하나. `none`은 `think=false`와 같은 disable 의미로 처리한다. `low`/`medium`/`high`는 provider가 지원하는 경우에만 전달한다. -- `thinking_token_budget` (int, optional): thinking token budget. 0 이상이어야 한다. +- `think` (bool, optional): thinking/reasoning 생성 활성화 여부를 표현하는 IOP 확장 field다. 생략하면 provider 기본값을 유지한다. `false`는 thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다. +- `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): 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를 제거한다고 보장하지 않는다. +이 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 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` 기본 안정 호출에서는 생략한다. | | `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` 기본값을 바꾸는 측정으로만 사용한다. 일반 표준 안정 호출에서는 생략한다. | -| `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를 기준으로 둔다. | -| `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에서 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`를 쓴다. | +| provider-native field 예: `chat_template_kwargs` | selected provider가 해당 OpenAI-compatible extension을 지원하면 IOP provider-pool passthrough는 이를 보존하고 provider로 전달해야 한다. | 이 field를 IOP 추상 field로 치환하지 않는다. provider가 거부하면 provider error를 relay한다. | +| `/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`를 쓴다. | 현재 구현에서 `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 정책: -아래 정책은 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`: - `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` 오류 반환 - 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`로 주입한다. - `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 정책: @@ -282,8 +286,8 @@ Conflict 정책: Strict output 모드: - 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`다. -- 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에 넣는다. +- 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를 기본값으로 사용할 수 있다. 이 경우에도 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"`를 싣는다. 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` -- provider-pool model group route에서 `metadata.iop_response_mode`를 명시하는 방식 -- `metadata.iop_response_mode`에 `passthrough`, `passthrough+sideband`, `transformed` 외 값을 넣는 방식 -- `options`, `chat_template_kwargs`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field +- 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 보존에는 적용하지 않는다. - `session_id`, `timeout_sec` 같은 IOP 실행 제어 field ## Legacy Completions diff --git a/agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md b/agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md index ea6be58..e95d46f 100644 --- a/agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md +++ b/agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md @@ -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 재확인 기준 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-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`로 회복했다. ## 실행 절차 @@ -118,7 +118,7 @@ dev-corp provider pool을 현재 dev-runtime과 같은 구조로 배포한다. - `/v1/models`가 대상 model alias를 노출하는지 확인한다. 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 수준의 구조화된 답변을 유도해 요청이 동시에 관측될 시간을 만든다. - 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, 최종 회복을 함께 판정한다. @@ -157,7 +157,7 @@ dev-corp runtime 배포 결과 - Ports: - Nodes: - Providers: -- OpenAI-compatible: models=, chat-completions-capacity=, responses-provider-pool= +- OpenAI-compatible: models=, chat-completions-capacity=, provider-native-field-passthrough=, responses-provider-pool= - Capacity evidence: - Blockers/Risk: <없음 또는 내용> ``` diff --git a/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md index 3f2bd2e..8601963 100644 --- a/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md +++ b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md @@ -37,7 +37,7 @@ OpenAI-compatible Chat Completions provider 경로에서 모델 출력 이상을 - 출력 검증 filter별 enable/disable 정책을 environment(`dev`, `dev-corp`), model group/model/provider, 기능 단위로 평가하는 config/registry 계층 - 반복 출력 루프 감지용 rolling stream inspector, upstream abort, continuation repair, 1회 repair 제한 - `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 출력 검증 경로와 분리하는 책임 경계 ## 기능 @@ -84,6 +84,6 @@ OpenAI-compatible provider 응답을 사용자에게 노출하기 전에 필터 - 표준선(선택): optional online filter가 비활성화된 모델은 pure passthrough로 처리할 수 있지만, caller가 `metadata.scheme`처럼 필수 계약을 요청했는데 해당 filter가 비활성화된 모델은 silent passthrough가 아니라 unsupported/400으로 거부한다. - 표준선(선택): OpenAI-compatible provider 출력 검증은 normalized 경로로 전환하지 않는다. normalized는 CLI 전용으로 유지한다. - 우선순위 순서: 현재 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 리뷰 - 확인 필요: 없음 diff --git a/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md b/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md index a71bd9a..3c47f67 100644 --- a/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md +++ b/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md @@ -16,18 +16,22 @@ IOP의 OpenAI-compatible, A2A, IOP native 입력 표면에서 들어온 요청 완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다. 스케치 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 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](../../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](../../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 실행 경로로 자동 결정한다. +- [계획] 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-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 방향을 스케치한다. diff --git a/agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md b/agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md new file mode 100644 index 0000000..614bbf6 --- /dev/null +++ b/agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md @@ -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 +- 확인 필요: 없음 diff --git a/agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md b/agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md index 08f7d90..c7391aa 100644 --- a/agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md +++ b/agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md @@ -72,8 +72,8 @@ - 반복루프 guard 같은 optional online filter가 비활성화되면 해당 filter만 skip하고 pure passthrough 또는 남은 filter path로 진행한다. - `metadata.scheme`처럼 caller가 필수 출력 계약을 요청한 filter가 비활성화되면 silent passthrough로 낮추지 않고 `policy_rejected`로 종료한다. - 내부 path 주의: - - `passthrough_guarded`와 `contract_schema`는 caller가 임의로 지정하는 `metadata.iop_response_mode` 값이 아니라 IOP 내부 실행/로그 path 이름이다. - - caller가 지정할 수 있는 공개 response mode 값은 [계약 원문](../../../../agent-contract/outer/openai-compatible-api.md)에서 별도로 명시한 값만 허용한다. + - `passthrough_guarded`와 `contract_schema`는 caller가 지정하는 공개 request field가 아니라 IOP 내부 실행/로그 path 이름이다. + - 공개 OpenAI-compatible 경로 선택은 [계약 원문](../../../../agent-contract/outer/openai-compatible-api.md)에 따라 request `model`이 가리키는 provider capability로 결정한다. - 금지: - `metadata.scheme`을 `iop_output_contract` 같은 wrapper로 감싸지 않는다. - schema 계약 요청을 normalized path로 보내지 않는다. diff --git a/agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-provider-passthrough-contract-sync/SDD.md b/agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-provider-passthrough-contract-sync/SDD.md new file mode 100644 index 0000000..45c335b --- /dev/null +++ b/agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-provider-passthrough-contract-sync/SDD.md @@ -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: 없음 diff --git a/agent-test/dev-corp/edge-smoke.md b/agent-test/dev-corp/edge-smoke.md index 8c8a1fe..740f40e 100644 --- a/agent-test/dev-corp/edge-smoke.md +++ b/agent-test/dev-corp/edge-smoke.md @@ -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. - 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 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 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 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 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`로 추적한다. - `/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/...`를 실행한다. - 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`가 성공하는지 확인한다. - 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을 덮어쓰지 않는지 확인한다. @@ -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 로컬 `[node-message]` payload 라인 목록과 edge `[node-*-message]` payload 라인 목록이 run별로 내용/순서까지 동일해야 한다. - edge complete는 같은 run의 마지막 `[node-*-message]` 이후에만 정상이다. -- OpenAI-compatible smoke에서 `/healthz`, `/v1/models`, `/v1/chat/completions`가 기대 상태로 응답한다. provider-pool `/v1/responses`는 현재 미지원이다. -- 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 성공 기준에서 제외한다. +- 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`는 capacity 성공 기준이 아니라 provider-dependent passthrough/relay 검증으로 분리한다. - capacity smoke 완료 후 대상 provider의 `in_flight=0`, `queued=0` 회복을 확인한다. - Gemma 계열 provider-pool smoke는 thinking enabled 기준이며 reasoning/tool-parser 관련 텍스트가 포함될 수 있다. exact-output match를 기본 판정으로 쓰지 않는다. diff --git a/agent-test/dev-corp/inventory.yaml b/agent-test/dev-corp/inventory.yaml index ca2ddd1..d5da82c 100644 --- a/agent-test/dev-corp/inventory.yaml +++ b/agent-test/dev-corp/inventory.yaml @@ -121,7 +121,7 @@ edge: concurrent_requests: 15 elapsed_sec: 5.678 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 latest_node_binary_deploy: observed_at: "2026-07-09T18:12:22+09:00" @@ -138,7 +138,7 @@ edge: corp-mac-studio-mlx-vllm-node: pid_after_restart: 87325 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: observed_at: "2026-07-11" public_host: iop.ai.kr @@ -327,8 +327,8 @@ model: capacity_smoke: endpoints: - /v1/chat/completions - unsupported_endpoints: - /v1/responses: OpenAI-compatible provider model group raw passthrough parity 전까지 미지원 + provider_dependent_endpoints: + /v1/responses: selected provider가 지원하면 raw passthrough로 검증하고, provider가 미지원하면 provider status/body relay 여부를 증거로 남긴다 aggregate_provider_capacity: 13 model_scenarios: ornith_9: @@ -445,8 +445,9 @@ model: max_queued: 1 final_recovery: in_flight_0_queued_0_healthy responses: - supported: false - reason: /v1/responses is not supported for OpenAI-compatible provider model groups until raw passthrough parity is implemented + iop_passthrough_contract: true + provider_support: provider_dependent + reason: selected provider가 /v1/responses를 지원하면 raw passthrough로 검증하고, provider가 미지원하면 provider error relay를 확인한다 legacy_capacity_verification_2026_07_08: date: "2026-07-08" source_ref: c2437aaedefbac4312d69dfd10aa017c2739e187 diff --git a/agent-test/dev-corp/mac-studio-ornith-think-on-baseline-20260713.md b/agent-test/dev-corp/mac-studio-ornith-think-on-baseline-20260713.md new file mode 100644 index 0000000..2588e46 --- /dev/null +++ b/agent-test/dev-corp/mac-studio-ornith-think-on-baseline-20260713.md @@ -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. diff --git a/agent-test/dev-corp/node-smoke.md b/agent-test/dev-corp/node-smoke.md index 0b0235f..d923692 100644 --- a/agent-test/dev-corp/node-smoke.md +++ b/agent-test/dev-corp/node-smoke.md @@ -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"`로 실행된다. - 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되는지 확인한다. ## 명령 diff --git a/apps/edge/README.md b/apps/edge/README.md index 3225017..29c280c 100644 --- a/apps/edge/README.md +++ b/apps/edge/README.md @@ -235,7 +235,7 @@ openai: 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 curl -s http://127.0.0.1:18081/v1/chat/completions \ diff --git a/docs/edge-local-dev-guide.md b/docs/edge-local-dev-guide.md index c8f30e2..89dbac8 100644 --- a/docs/edge-local-dev-guide.md +++ b/docs/edge-local-dev-guide.md @@ -133,15 +133,15 @@ http://:18081/v1 ## 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 안정 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 동시 요청을 각각 확인하는 방식이다. - 일반 표준 caller의 `stream=false` 측정은 `/v1/chat/completions`에서 요청 파라미터만으로 확인한다. - `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에서는 쓰지 않는다. -- Gemma4 passthrough 파라미터의 세부 표와 금지/허용 범위는 `agent-contract/outer/openai-compatible-api.md`의 dev-corp `gemma4:26b` provider-pool passthrough 파라미터 범위를 기준으로 한다. +- `think=false` 또는 `reasoning_effort=none`은 hide-only 옵션이 아니라 thinking disable 요청이다. Provider-native field가 있는 경우 해당 field를 우선 사용해 passthrough 보존을 검증한다. +- Provider-pool passthrough 파라미터의 세부 계약과 금지/허용 범위는 `agent-contract/outer/openai-compatible-api.md`를 기준으로 한다. 예시 (dev-corp `gemma4:26b` provider-pool non-stream 측정, think 생략): diff --git a/docs/openai-compatible-api-contract.md b/docs/openai-compatible-api-contract.md index fc59306..2b31b29 100644 --- a/docs/openai-compatible-api-contract.md +++ b/docs/openai-compatible-api-contract.md @@ -6,10 +6,11 @@ 주요 현재 동작: -- Chat Completions provider route의 기본 응답 mode는 provider-original `passthrough`다. -- provider-pool model group route는 caller가 `metadata.iop_response_mode`를 명시하면 값과 무관하게 거부하고, 생략 시 provider-original `passthrough`로 동작한다. -- direct legacy provider route는 `passthrough+sideband` extension selector를 지원한다. `passthrough+sideband`와 `transformed`는 IOP 확장/변환 응답이며 provider-original byte identity로 취급하지 않는다. -- dev-corp `gemma4:26b` provider-pool에서 `think`/`stream`/`include_reasoning` 요청 파라미터만 바꿔 측정하는 범위는 계약 원문의 `dev-corp gemma4:26b provider-pool passthrough 파라미터 범위` 표를 기준으로 한다. +- IOP OpenAI-compatible 표면의 기본 베이스는 OpenAI-compatible request/response surface 보존이다. Provider-pool passthrough는 selected provider가 지원하는 표준 field와 provider extension field를 IOP allowlist로 제한하지 않는다. +- `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 같은 IOP field는 OpenAI-compatible 기본 surface 위의 확장이다. Provider-native field를 대체하거나 금지하는 수단으로 해석하지 않는다. +- 라우팅의 1차 기준은 request `model`이 가리키는 provider capability다. OpenAI-compatible provider이면 provider-original `passthrough`, 그 외 CLI/Ollama/native 실행이면 normalized path를 사용한다. +- `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 Pi coding agent 설정 가이드는 [dev-corp-pi-settings-guide.md](./dev-corp-pi-settings-guide.md)를 기준으로 한다.