diff --git a/agent-contract/outer/openai-compatible-api.md b/agent-contract/outer/openai-compatible-api.md index 88b1d5c..9ce7d0a 100644 --- a/agent-contract/outer/openai-compatible-api.md +++ b/agent-contract/outer/openai-compatible-api.md @@ -179,16 +179,21 @@ Think 제어 field: Provider별 think-control 정책: -- `vLLM` / `vLLM-MLX`: +- `vLLM`: - `think=false` 또는 `reasoning_effort=none` -> 내부 `chat_template_kwargs.enable_thinking=false` - `think=true` 또는 budget-only -> 내부 `chat_template_kwargs.enable_thinking=true` - `thinking_token_budget` -> 내부 `chat_template_kwargs.thinking_token_budget` - `reasoning_effort=low|medium|high` -> `unsupported think control` 오류 반환 +- `vLLM-MLX`: + - `think=true` 또는 budget-only -> 내부 `chat_template_kwargs.enable_thinking=true` + - `thinking_token_budget` -> 내부 `chat_template_kwargs.thinking_token_budget` + - `think=false` 또는 `reasoning_effort=none` -> `unsupported think control` 오류 반환. vLLM-MLX 런타임은 스트리밍 응답 전체를 `reasoning_content`로 표기하고 `enable_thinking=false`로도 reasoning 생성을 멈추지 않으므로, think disable을 조용한 성공(200 reasoning stream)으로 처리하지 않고 명시 오류를 반환한다. + - `reasoning_effort=low|medium|high` -> `unsupported think control` 오류 반환 - `Lemonade`: - - `think` -> top-level `think` - - `reasoning_effort=low|medium|high` -> top-level `reasoning_effort` - - `thinking_token_budget` -> top-level `thinking_token_budget` - - `reasoning_effort=none` -> `think=false`, `reasoning_effort`는 전달하지 않음 + - `think=false` 또는 `reasoning_effort=none` -> 내부 `chat_template_kwargs.enable_thinking=false`. 이 런타임은 top-level `think` field를 무시하므로 top-level `think`를 사용하지 않는다. + - `think=true` 또는 budget-only -> 내부 `chat_template_kwargs.enable_thinking=true` + - `thinking_token_budget` -> 내부 `chat_template_kwargs.thinking_token_budget` + - `reasoning_effort=low|medium|high` -> `unsupported think control` 오류 반환 - Unknown / 기타 provider: 요청 field를 그대로 전달하되, provider가 지원하지 않는 값은 backend 또는 adapter error가 될 수 있다. Conflict 정책: diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_0.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_0.log new file mode 100644 index 0000000..53da4ff --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_0.log @@ -0,0 +1,256 @@ + + +# Code Review Reference - TEST + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> Start only after predecessors `01`, `02`, `03`, and `04` have `complete.log`. +> Fill implementation-owned sections and verification output, then stop with active files in place. Finalization is review-agent-only. + +## 개요 + +date=2026-07-03 +task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke, plan=0, tag=TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` +- Task ids: + - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다. + +이 review는 외부 dev-runtime 증거의 신뢰성을 확인한다. source ref와 binary freshness, full test 결과, provider pool 연결 상태, capacity observation, 그리고 think-control raw trace/SSE 비교가 모두 있어야 PASS다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [TEST-1] Remote Runner Preflight | [x] | +| [TEST-2] Clean Sync, Full Test, Rebuild, Restart | [x] | +| [TEST-3] Think-Control SSE And Provider Trace | [x] | +| [TEST-4] Capacity Health Evidence | [x] | + +## 구현 체크리스트 + +- [x] Predecessor `complete.log` for `01`, `02`, `03`, and `04`를 확인한 뒤 시작한다. (모두 archive에서 PASS 확인) +- [x] Remote runner inventory를 재확인하고 source ref, dirty state, branch, HEAD를 기록한다. +- [x] Clean sync 기준 ref를 확정한다. 원격은 `8838e0a`에 머물러 있었고 local/origin `main`은 이미 `eea8dd0`(milestone 01~04 구현 포함)를 갖고 있어 stale `origin/main` 문제는 없었다. `git fetch origin main && git reset --hard origin/main && git clean -fd`로 clean sync했다. +- [x] Remote runner에서 `go test ./...`를 빌드 전 실행한다. 전체 PASS. +- [x] dev-runtime Edge/mac/Linux ARM64/Windows AMD64 Node binary를 같은 source ref(`eea8dd0`)에서 rebuild한다. +- [x] 빌드 후 `config check`, `config refresh --help`, `config refresh --mode dry-run`, `go test ./...`를 실행한다. 전체 PASS/OK. +- [x] Edge, mac-codex-node, gx10-vllm-node, onexplayer-lemonade-node를 dev-runtime 절차대로 재시작하고 port/process/status를 확인한다. 3개 node 모두 `connected:true`. +- [x] `/v1/models`가 `qwen3.6:35b`를 노출하는지 확인한다. +- [x] `/v1/responses`와 `/v1/chat/completions` capacity smoke를 capacity+1(11개) 기준으로 실행하고 provider `in_flight/queued` snapshot을 남겼다. 둘 다 총 `in_flight=10`, 합산 `queued=3` 관측, 완료 후 전부 회복. +- [x] `/v1/chat/completions`에서 omitted default, `think=false`, `include_reasoning=false` 세 케이스를 stream으로 실행하고 Edge SSE와 provider raw trace를 비교했다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. + +## 코드리뷰 전용 체크리스트 + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] active plan/review 파일을 `.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. +- [x] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다. + +## 계획 대비 변경 사항 + +- 계획은 GX10/OneXPlayer가 Linux/Windows에서 각각 `bin/edge`류 build 산출물을 그대로 배치하는 것을 전제로 서술했지만, 실제 두 host의 기존 배치 관례는 (a) GX10: `/home/toki/iop-gx10-vllm/iop-node`에 직접 이진 파일, `iop-node.prev-` 백업 관례, (b) OneXPlayer: `C:/Users/r0bin/iop-field/iop-node.exe`에 직접 이진 파일, `iop-node.exe.prev-` 백업 관례였다. 기존 host 관례를 그대로 따라 배치했다 (계획의 경로 표현과 실제 배치 대상 파일명이 다를 뿐 절차상 차이는 없음). +- OneXPlayer에서 최초 Win32_Process.Create 시도는 CommandLine에 중첩 큰따옴표를 직접 넣어 PowerShell 파싱 오류(`ParserError`)가 발생했다. 이를 우회하기 위해 로컬에서 `.bat` 스크립트(환경변수 설정 + `iop-node.exe` 실행)를 만들어 scp로 전달한 뒤 `Win32_Process.Create`의 CommandLine을 `cmd.exe /c ` 형태로 단순화해서 세션 독립 실행을 유지했다. `Start-Process`는 SSH 세션 내부에서 사용하지 않았다. +- TEST-3 provider raw trace 확보를 위해 계획에 명시되지 않았던 임시 조치로 `apps/node/internal/adapters/openai_compat`의 기존 trace 기능(`IOP_OPENAI_COMPAT_TRACE_STREAM=true` 환경변수, 코드 변경 아님, 기존 구현에 이미 존재)을 3개 node 프로세스에 켜서 재시작했다. TEST-3 증거 수집 후 다시 trace 없이 재시작해 원래 상태로 되돌렸다(최종 검증 시점 3-node 재연결 재확인 완료). +- provider raw trace 소스는 계획이 가정한 "provider 서버 자체 로그"가 아니라 Node의 `openai_compat` adapter가 남기는 "provider stream raw chunk" 로그(`reasoning_len`, `raw_has_open_think` 등)였다. GX10 vLLM 서버 프로세스는 root 권한으로 떠 있어 toki 계정에서 서버 자체 stdout/stderr에 접근할 수 없었고, mac-mlx-vllm/onexplayer-lemonade도 자체 request-body 로그를 남기지 않는다. Node adapter의 raw response chunk trace는 provider가 실제로 reasoning을 생성했는지(`reasoning_len`, `raw_has_open_think`)를 직접 보여주므로 S05 검증 목적(생성 여부 vs 노출 여부 구분)에는 충분하다고 판단했다. Request body 자체(예: `chat_template_kwargs.enable_thinking`)의 직접 관측은 이 smoke에서 하지 않았다 — 해당 매핑은 predecessor `02+01_node_provider_mapping`의 단위 테스트로 이미 검증됨. +- 세 케이스 모두 실제로는 `gx10-vllm` provider(priority 0)가 처리했다. 요청이 idle 상태에서 단일 요청으로 들어갈 때 priority 0 provider가 우선 선택되는 것으로 관측되었으며, 계획에서 provider를 명시적으로 고정하는 절차는 없었으므로 실제 라우팅 결과를 그대로 채택했다. + +## 주요 설계 결정 + +- Source ref: `eea8dd0` (local/origin/main과 동일, milestone 01~04 구현 전부 포함). 원격 checkout은 이 ref로 clean sync 후 재빌드했다. +- Masking policy: bearer token(`sk-local` 포함), node 등록 token, private key 경로는 이 문서에 원문으로 남기지 않았다. curl 호출에는 사용했으나 결과 로그에서 값은 마스킹 처리했다. +- Capacity smoke 프롬프트는 depth-first/breadth-first 비교 900단어 에세이 요청으로, capacity+1(11개) 동시 요청을 `/v1/responses`, `/v1/chat/completions` 각각에 실행했다. +- Skipped: `git clean -fdx`는 사용하지 않았고 `git clean -fd`만 사용했다(스킬 금지 사항 준수). Windows onexplayer 접속은 계획대로 현재 host에서 직접 `ssh r0bin@192.168.0.59`로 수행했고 `toki` 경유 접속은 하지 않았다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Source ref가 predecessor 구현을 포함하고, remote binary가 같은 ref에서 rebuild되었는지 확인한다. +- Full `go test ./...` pre/post 결과가 있거나, 누락 사유와 위험이 명확한지 확인한다. +- Raw trace/SSE evidence가 세 케이스 모두에 대해 있고, token/API key/private path가 마스킹되었는지 확인한다. +- `include_reasoning=false`는 generation disable이 아니라 exposure disable로 판정했는지 확인한다. +- Capacity smoke는 request success만이 아니라 provider snapshot의 `in_flight/queued` 관측을 포함하는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다. Secrets and tokens must be masked._ + +### TEST-1 중간 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1 && test -f build/dev-runtime/edge.yaml && test -x build/dev-runtime/bin/edge && test -x build/dev-runtime/bin/iop-node' +## main...origin/main +8838e0a fix(openai): reasoning alias를 처리한다 +edge.yaml OK +edge bin OK +iop-node bin OK +``` +사전 확인 결과 원격은 `8838e0a`로 stale했다 (local/origin `main`은 `eea8dd0`). 로컬 `git log origin/main -1`도 `eea8dd0`이므로 stale `origin/main` 문제는 아니고, 원격 checkout만 뒤처져 있었다. TEST-2에서 clean sync로 해소했다. + +### TEST-2 중간 검증 +Clean sync: +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git fetch origin main && git reset --hard origin/main && git clean -fd && git status --short --branch && git log --oneline -1' +From https://git.toki-labs.com/toki/iop + * branch main -> FETCH_HEAD + 8838e0a..eea8dd0 main -> origin/main +HEAD is now at eea8dd0 feat: add think-control API fields and dev smoke tests +## main...origin/main +eea8dd0 feat: add think-control API fields and dev smoke tests +``` + +빌드 전 전체 테스트: 전 패키지 `ok` (실패 없음, `iop/apps/edge/...`, `iop/apps/node/...`, `iop/packages/go/...` 등 전부 포함). + +Rebuild (source ref `eea8dd0`): +```bash +$ go build -trimpath -o build/dev-runtime/bin/edge ./apps/edge/cmd/edge +$ go build -trimpath -o build/dev-runtime/bin/iop-node ./apps/node/cmd/node +$ GOOS=linux GOARCH=arm64 go build -trimpath -o build/dev-runtime/bin/iop-node-linux-arm64 ./apps/node/cmd/node +$ GOOS=windows GOARCH=amd64 go build -trimpath -o build/dev-runtime/bin/iop-node-windows-amd64.exe ./apps/node/cmd/node +-rwxr-xr-x edge 21993234 bytes +-rwxr-xr-x iop-node 27168258 bytes +-rwxr-xr-x iop-node-linux-arm64 26253618 bytes +-rwxr-xr-x iop-node-windows-amd64.exe 28501504 bytes +``` + +빌드 후 동작 확인: +```bash +$ build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check +OK build/dev-runtime/edge.yaml + +$ build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config refresh --help +(--mode, --addr, --config-path 플래그 정상 노출) + +$ build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config refresh --mode dry-run --addr 127.0.0.1:19093 +{"status":"applied","mode":"dry_run","changes":[],"summary":"no changes detected","changed_nodes":[],"changed_providers":[],"changed_models":[],"restart_required_paths":[]} + +$ go test ./... # 재실행, 전체 ok +``` + +재시작 후 연결 확인: +```bash +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}]}"' +{ + "edge_id": "edge-toki-labs-dev", + "nodes": [ + {"node_id": "mac-codex-node", "connected": true}, + {"node_id": "gx10-vllm-node", "connected": true}, + {"node_id": "onexplayer-lemonade-node", "connected": true} + ] +} +``` + +```bash +$ ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer " http://127.0.0.1:18083/v1/models | jq ".data[].id"' +"qwen3.6:35b" +``` + +재빌드/재시작 대상: Edge(`toki-labs.com`), mac-codex-node(같은 host, `iop-node --config node-codex.yaml`), gx10-vllm-node(`192.168.0.91`, linux-arm64 바이너리 교체 + `iop-node.prev-` 백업), onexplayer-lemonade-node(`192.168.0.59`, windows-amd64 바이너리 교체 + `iop-node.exe.prev-` 백업, 현재 host에서 직접 SSH, `Win32_Process.Create`로 세션 독립 실행). + +### TEST-3 중간 검증 + +공통: `/v1/chat/completions`, `stream=true`, `model=qwen3.6:35b`, prompt="Solve a two-step logic puzzle and explain the answer briefly.". 세 요청 모두 실제로는 `gx10-vllm` provider(priority 0)가 처리했다(node-side `openai_compat` trace `run_id`로 상호 확인). provider raw trace는 Node `openai_compat` adapter의 기존 stream trace 로그(`IOP_OPENAI_COMPAT_TRACE_STREAM=true`)에서 얻었다. + +**Case 1 — omitted default** +```bash +$ curl -sS -N -H "Authorization: Bearer " -d '{"model":"qwen3.6:35b","stream":true,"messages":[...]}' http://127.0.0.1:18083/v1/chat/completions +``` +- Edge SSE: `reasoning_content` 청크 1381개 관측, `content` 청크 188개, 정상 `finish_reason":"stop"` + `[DONE]`으로 종료. +- Provider raw trace(gx10-vllm, 동일 run_id): `reasoning_len` 값 다수 non-zero(1~4자 단위 청크 다수), `raw_has_open_think:false`(think 태그 자체는 없고 델타 단위 reasoning 필드로 옴). → provider가 실제 reasoning을 생성했고 Edge가 기본 노출 정책대로 그대로 노출. + +**Case 2 — `think=false`** +```bash +$ curl -sS -N -H "Authorization: Bearer " -d '{"model":"qwen3.6:35b","stream":true,"think":false,"messages":[...]}' http://127.0.0.1:18083/v1/chat/completions +``` +- Edge SSE: `reasoning_content` 0건. 정상 `finish_reason":"stop"` + `[DONE]`. +- Provider raw trace(gx10-vllm, 동일 run_id, 235개 청크): `reasoning_len:0` 전 구간, `raw_has_open_think:false` 전 구간. → provider 레벨에서 reasoning 생성 자체가 비활성화됨(단순 노출 억제가 아니라 생성 비활성화로 확인). + +**Case 3 — `include_reasoning=false`** +```bash +$ curl -sS -N -H "Authorization: Bearer " -d '{"model":"qwen3.6:35b","stream":true,"include_reasoning":false,"messages":[...]}' http://127.0.0.1:18083/v1/chat/completions +``` +- Edge SSE: `reasoning_content` 0건, `content` 236건 관측. 정상 `finish_reason":"stop"` + `[DONE]`. +- Provider raw trace(gx10-vllm, 동일 run_id): `reasoning_len` non-zero 다수(1~4자 단위 청크 다수, case 1과 유사 분포). → provider는 reasoning을 그대로 생성했지만 Edge가 노출만 억제함(생성 비활성화가 아니라 노출 비활성화로 확인, 계획의 판정 기준과 일치). + +### TEST-4 중간 검증 + +`/v1/chat/completions` capacity+1(11개 동시 요청, 900단어 에세이 프롬프트, non-stream): +```bash +$ (11개 동시 curl 실행) → chat_1..chat_11 전부 HTTP 200 +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq -c "[.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued}]"' +[{"id":"mac-mlx-vllm","capacity":3,"in_flight":3,"queued":1},{"id":"gx10-vllm","capacity":4,"in_flight":4,"queued":1},{"id":"onexplayer-lemonade","capacity":3,"in_flight":3,"queued":1}] +``` +peak 관측: 총 `in_flight=10`(=capacity 합), 합산 `queued=3`(요구치 `>=1` 충족). 완료 후 재조회 시 세 provider 전부 `in_flight=0, queued=0` 회복. + +`/v1/responses` capacity+1(11개 동시 요청, 동일 900단어 프롬프트): +```bash +$ (11개 동시 curl 실행) → resp_1..resp_11 전부 HTTP 200 +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq -c "[.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued}]"' +[{"id":"mac-mlx-vllm","capacity":3,"in_flight":3,"queued":1},{"id":"gx10-vllm","capacity":4,"in_flight":4,"queued":1},{"id":"onexplayer-lemonade","capacity":3,"in_flight":3,"queued":1}] +``` +peak 관측: 총 `in_flight=10`, 합산 `queued=3`. 완료 후 세 provider 전부 `in_flight=0, queued=0` 회복 확인. + +### 최종 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +## main...origin/main +eea8dd0 feat: add think-control API fields and dev smoke tests +``` + +```bash +$ ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...' +(전 패키지 ok, 실패 없음) +``` + +```bash +$ ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer " http://127.0.0.1:18083/v1/models | jq ".data[].id"' +"qwen3.6:35b" +``` + +```bash +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}]}"' +{"edge_id":"edge-toki-labs-dev","nodes":[{"node_id":"mac-codex-node","connected":true},{"node_id":"gx10-vllm-node","connected":true},{"node_id":"onexplayer-lemonade-node","connected":true}]} +``` +TEST-3 증거 수집을 위해 켰던 node trace(`IOP_OPENAI_COMPAT_TRACE_STREAM`)는 TEST-4 이후 3개 node를 trace 없이 재시작하여 원복했고, 위 최종 상태는 원복 이후 재확인한 결과다. + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - Correctness: Pass + - Completeness: Fail + - Test coverage: Fail + - API contract: Pass + - Code quality: Pass + - Implementation deviation: Fail + - Verification trust: Fail + - Spec conformance: Fail +- 발견된 문제: + - Required: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md:66`에 따르면 `omitted default`, `think=false`, `include_reasoning=false` 세 TEST-3 케이스가 모두 `gx10-vllm` provider에서만 처리됐다. 하지만 Milestone `dev-smoke`는 dev-runtime 세 provider에서 smoke가 통과해야 한다고 요구하고(`agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md:54`), SDD Evidence Map도 `dev-smoke` 완료 evidence에 provider별 관측 기록을 요구한다(`agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md:85`). Capacity smoke는 세 provider가 connected/healthy이고 capacity queue가 동작한다는 증거일 뿐, mac vLLM-MLX와 OneXPlayer Lemonade에서 think-control raw trace/SSE 동작이 실제로 맞는지는 증명하지 못한다. Follow-up에서는 provider별로 `omitted default`, `think=false`, `include_reasoning=false`를 라우팅/관측하고, 각 provider id와 run id, Edge SSE reasoning/content 관측, node/provider raw trace 관측을 마스킹된 실제 출력으로 기록해야 한다. +- 다음 단계: FAIL follow-up plan/review를 작성해 provider별 S05 smoke evidence를 보강한다. diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log new file mode 100644 index 0000000..e5de29f --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log @@ -0,0 +1,313 @@ + + +# Code Review Reference - REVIEW_TEST + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service setup, generic scope conflicts, loop exhaustion, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only the linked Milestone lock decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-07-03 +task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke, plan=1, tag=REVIEW_TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` +- Task ids: + - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다. +- Completion mode: check-on-pass + +## Archive Evidence Snapshot + +- Archived plan: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_0.log` +- Archived review: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_0.log` +- Verdict: FAIL +- Required summary: + - `code_review_cloud_G07_0.log` records that all TEST-3 `omitted_default`, `think=false`, and `include_reasoning=false` stream/trace cases were handled by `gx10-vllm` only. + - Milestone `dev-smoke` requires smoke across dev-runtime three providers: mac vLLM-MLX, GX10 vLLM, and OneXPlayer Lemonade. + - SDD S05 Evidence Map requires `dev-smoke` completion evidence with provider별 observations. +- Affected files: + - No source files should change. + - Active evidence artifact to fill: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md` +- Verification evidence already trusted from prior loop and review rerun: + - Remote source ref `eea8dd0`; remote checkout clean and aligned with `origin/main`. + - Remote `go test ./...` passed on `2026-07-03` during review rerun. + - Local targeted `go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/adapters/openai_compat` passed during review rerun. + - Remote `build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check` returned OK. + - Remote `/v1/models` exposed `qwen3.6:35b`. + - Remote Control Plane status showed `mac-codex-node`, `gx10-vllm-node`, and `onexplayer-lemonade-node` connected with providers healthy and recovered to `in_flight=0`, `queued=0`. +- Allowed archive reread: + - Read only the two archived files listed above if exact previous-loop context is needed. Do not scan `agent-task/archive/**`. +- Roadmap carryover: + - `dev-smoke` is still incomplete until provider별 S05 stream/trace observations are recorded. + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 source-free evidence와 대조하고, `검증 결과` 섹션의 출력이 plan의 provider별 S05 요구를 충족하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-openai-compatible-think-control`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_TEST-1] Remote State And Route Strategy | [x] | +| [REVIEW_TEST-2] Provider별 Think-Control Stream/Trace Matrix | [x] | +| [REVIEW_TEST-3] Restore And Final Verification | [x] | + +## 구현 체크리스트 + +- [x] Remote runner source ref, clean state, config check, `/v1/models`, and 3-provider health/recovery를 재확인한다. +- [x] Provider별 deterministic routing 또는 traced batch strategy를 확정하고, `mac-mlx-vllm`, `gx10-vllm`, `onexplayer-lemonade` 각각의 routing proof를 남긴다. +- [x] `mac-mlx-vllm`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다. +- [x] `gx10-vllm`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다. +- [x] `onexplayer-lemonade`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다. +- [x] 임시 config/trace 변경을 원복하고, 최종 `go test ./...`, `/v1/models`, 3-node connected/provider healthy/recovered 상태를 기록한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +- 계획은 provider-isolated temp config 또는 traced capacity batch 중 하나를 "라이브 config refresh 거부 시" fallback으로 제시했다. 실제로는 `models["qwen3.6:35b"].providers`를 provider 1개만 남긴 후보 config에 대해 `config refresh --mode dry-run`이 매번 `"summary":"all changes can be applied without restart"`를 반환했으므로, 계획이 우선한 provider-isolated temp config 경로를 그대로 사용했고 traced capacity batch fallback은 필요하지 않았다. +- Provider trace 수집 방식은 predecessor loop(`code_review_cloud_G07_0.log`)가 사용한 것과 동일하게 `IOP_OPENAI_COMPAT_TRACE_STREAM=true` 환경변수(기존 구현에 이미 존재, 코드 변경 아님)를 대상 provider의 node 프로세스에만 순차적으로 켜고, 증거 수집 직후 다시 trace-off로 재시작해 원복했다. 세 node를 동시에 trace-on 상태로 두지 않고 provider 1개씩 순차 처리해, 각 시점에 정확히 1개 provider만 routing 대상이면서 trace-on 상태가 되도록 했다(routing 격리와 trace 대상이 항상 일치). +- `onexplayer-lemonade-node`(Windows)는 `node.yaml`의 `logging.path`가 파일(`iop-node.log`)로 설정되어 있어 stdout/stderr 리다이렉션이 아니라 이 로그 파일에 trace가 기록된다. 이 로그 파일은 여러 세션에 걸쳐 append되는 대용량(수십 MB) 파일이라, `Select-String`으로 대상 `run_id`의 첫/마지막 라인 번호를 찾고 해당 구간만 추출해 로컬로 가져와 분석했다. 이 구간의 `raw_preview` 필드 중 일부(멀티바이트 문자 인코딩이 `??`로 깨진 지점)에서 JSON이 깨져 `jq` 전체 파싱이 중단되는 문제가 있어, `jq` 대신 `"reasoning_len": N` 라인 단위 `grep`으로 집계했다. 이 라인 단위 집계 결과는 Edge SSE의 `reasoning_content` 비어있지 않은 청크 수와 3케이스 모두 정확히 일치했다(2481/2067/913 vs 2481/2067/913 대응 없음 청크 0). 원인은 Windows 콘솔/로그 인코딩 이슈로 추정되며 이번 smoke의 범위(소스 변경 없음)를 벗어나 별도 조치를 하지 않았다. +- OneXPlayer 세션 독립 재시작은 predecessor 관례(`.bat` + `Win32_Process.Create`)를 그대로 따랐다. Trace on/off 버전 각각에 별도 `.bat`(`start-node-trace.bat`, `start-node-normal.bat`)을 만들어 사용했고, 증거 수집 후 두 파일과 로그 구간 추출 임시 파일을 모두 삭제했다. +- **주요 발견(소스 변경 아님, provider 환경 관측 결과)**: `think=false` 요청이 `mac-mlx-vllm`과 `onexplayer-lemonade`에서는 provider가 reasoning을 계속 생성하는 것으로 관측됐다(gx10-vllm만 생성을 완전히 억제함). Node의 `openai_compat.buildRequestBody`는 세 provider 모두에 대해 계획/코드 기준대로 올바른 필드를 보냈다(`vllm`/`vllm-mlx`는 `chat_template_kwargs.enable_thinking=false`, `lemonade`는 top-level `think:false`) — 이는 predecessor 코드 리뷰(`02+01_node_provider_mapping`)에서 이미 단위 테스트로 검증된 매핑이다. 하지만 `mac-mlx-vllm`은 `omitted_default` 케이스에서도 이미 reasoning을 생성하고 있어(인벤토리 `default_chat_template_kwargs.enable_thinking:false`가 서버 자체에서 지켜지지 않는 것으로 보임), 요청 단위 override 여부와 무관하게 이 vllm-mlx 서버 빌드/채팅 템플릿이 `enable_thinking` kwarg를 실질적으로 반영하지 않는 것으로 판단된다. `onexplayer-lemonade`는 `omitted_default`에서 provider 기본값대로 생성하고, top-level `think:false`를 보내도 동일하게 생성을 계속해 이 Lemonade 빌드가 request-level `think` 필드로 생성을 끄는 기능을 지원하지 않는 것으로 보인다. `include_reasoning=false`(노출 억제)는 세 provider 모두 Edge SSE에서 정상적으로 동작했다. 이 발견은 이번 split의 범위(no source files)를 벗어나므로 코드를 수정하지 않았고, 아래 검증 결과 매트릭스에 있는 그대로 기록한다. + +## 주요 설계 결정 + +- 라우팅 전략: 계획이 우선한 "provider-isolated temp config + `config refresh --mode apply`" 경로를 그대로 채택했다. `edge.yaml`을 복사해 `models["qwen3.6:35b"].providers` map에서 대상 provider 1개만 남기고 나머지 2개 라인을 제거한 후보 config를 provider별로 만들고, dry-run으로 `restart_required_paths: []`를 먼저 확인한 뒤 apply했다. 이 변경은 `nodes[]`/`providers[]` 자체를 건드리지 않으므로 3-node 연결은 전 구간에서 끊기지 않았고, 오직 해당 모델에 대한 provider 선택지만 좁혔다. +- Provider 전환 순서: mac-mlx-vllm(3케이스) → gx10-vllm(3케이스) → onexplayer-lemonade(3케이스) → 원본 3-provider config 복원 순으로 진행했다. Provider 간 전환은 중간에 원본으로 되돌리지 않고 isolated-A -> isolated-B로 직접 전환했다(둘 다 `models[].providers` 값만 바뀌는 `StatusApplied` 변경이므로 안전). 최종 복원만 원본 `edge.yaml` 전체를 다시 apply했다. +- Trace 활성화는 provider 순서와 맞춰 해당 provider를 서빙하는 node 프로세스 1개만 재시작해서 켰고(`IOP_OPENAI_COMPAT_TRACE_STREAM=true`), 그 provider의 3케이스 수집이 끝나면 즉시 trace 없이 재시작해 원복한 뒤 다음 provider로 넘어갔다. 이렇게 하면 항상 "라우팅 대상 provider == trace-on node"가 유지되어 run_id 혼선 없이 매칭할 수 있었다. +- run_id 상관관계는 Edge SSE 응답의 `chatcmpl-manual-` id에서 `manual-` 부분을 그대로 node trace의 `run_id` 필드와 매칭하는 방식을 사용했다(모든 케이스에서 유일하게 매칭됨, 동시 요청이 없었으므로 충돌 없음). +- Masking: bearer token은 매 curl 호출마다 원격 셸에서 `edge.yaml`을 직접 읽어 로컬 변수로만 사용했고, 이 문서와 커맨드 로그에는 값을 출력하지 않았다. node registration token도 동일하게 원문을 출력하지 않았다(단, `node.yaml` 조회 과정에서 한 차례 도구 호출 출력에 노출된 적이 있었음 — 최종 산출물인 이 문서와 최종 보고에는 포함하지 않았다). + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Provider matrix가 `mac-mlx-vllm`, `gx10-vllm`, `onexplayer-lemonade` 세 provider를 모두 포함하는지 확인한다. +- 각 provider가 `omitted_default`, `think=false`, `include_reasoning=false` 세 required cases를 모두 가지고 있는지 확인한다. +- Routing proof가 provider id/run id 또는 equivalent trace correlation으로 증명되는지 확인한다. +- `think=false`는 provider raw trace와 Edge SSE 모두 reasoning 없음으로 판정했는지 확인한다. +- `include_reasoning=false`는 Edge SSE reasoning exposure가 없고 provider raw generation 상태가 관측값으로 기록됐는지 확인한다. +- 임시 trace/config 변경이 원복되고 최종 3-node/provider 상태가 healthy/recovered인지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_TEST-1 중간 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +## main...origin/main +eea8dd0 feat: add think-control API fields and dev smoke tests + +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check' +OK build/dev-runtime/edge.yaml + +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"' +{ + "edge_id": "edge-toki-labs-dev", + "nodes": [ + {"node_id": "mac-codex-node", "connected": true}, + {"node_id": "gx10-vllm-node", "connected": true}, + {"node_id": "onexplayer-lemonade-node", "connected": true} + ], + "providers": [ + {"id": "mac-mlx-vllm", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["mlx-community/Qwen3.6-35B-A3B-4bit"]}, + {"id": "gx10-vllm", "capacity": 4, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["nvidia/Qwen3.6-35B-A3B-NVFP4"]}, + {"id": "onexplayer-lemonade", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["Qwen3.6-35B-A3B-MTP-GGUF"]} + ] +} + +$ ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer " http://127.0.0.1:18083/v1/models' +{"object":"list","data":[{"id":"qwen3.6:35b","object":"model","created":1783088162,"owned_by":"iop"}]} +``` + +라우팅 전략 결정: provider-isolated temp config(`models["qwen3.6:35b"].providers`를 provider 1개로 축소) + `config refresh --mode apply --config-path `. Dry-run 결과 `"summary":"all changes can be applied without restart"`, `"restart_required_paths":[]`를 확인했으므로 계획이 우선한 경로를 그대로 채택했고 traced capacity batch fallback은 사용하지 않았다. +```bash +$ ssh toki@toki-labs.com 'cd .../build/dev-runtime && ./bin/edge --config edge.yaml config refresh --mode dry-run --config-path edge.route-mac-mlx-vllm.yaml --addr 127.0.0.1:19093' +{"status":"applied","mode":"dry_run","changes":[{"path":"models[\"qwen3.6:35b\"].providers","class":"applied","previous":"map[gx10-vllm:... mac-mlx-vllm:... onexplayer-lemonade:...]","next":"map[mac-mlx-vllm:mlx-community/Qwen3.6-35B-A3B-4bit]"}],"summary":"all changes can be applied without restart","restart_required_paths":[]} +``` + +### REVIEW_TEST-2 중간 검증 + +공통: `POST http://127.0.0.1:18083/v1/chat/completions`, `stream:true`, `model:"qwen3.6:35b"`, prompt=`"Solve a two-step logic puzzle and explain the answer briefly."`. 각 provider는 `models["qwen3.6:35b"].providers` 후보 config를 해당 provider 1개만 남겨 `config refresh --mode apply`로 적용한 뒤 요청을 보냈다(라우팅 격리를 곧 routing proof로 사용). Provider raw trace는 대상 node의 `openai_compat` adapter stream trace(`IOP_OPENAI_COMPAT_TRACE_STREAM=true`)에서 동일 `run_id`로 수집했다. + +```bash +# 예시 1건(mac-mlx-vllm omitted_default) 실제 호출 +$ curl -sS -N -H "Authorization: Bearer " -H "Content-Type: application/json" \ + -d '{"model":"qwen3.6:35b","stream":true,"messages":[{"role":"user","content":"Solve a two-step logic puzzle and explain the answer briefly."}]}' \ + http://127.0.0.1:18083/v1/chat/completions +id="chatcmpl-manual-1783088347584527000", reasoning_content nonempty chunks=2038, finish_reason=stop, [DONE] 정상 종료 + +# node trace 대응 라인 (마스킹, run_id로 매칭) +{"msg":"openai_compat provider stream parsed chunk","run_id":"manual-1783088347584527000","reasoning_len":,"raw_has_open_think":false} +``` + +동일한 방식으로 나머지 8개 조합을 실행했다(각 케이스의 정확한 `run_id`와 카운트는 아래 매트릭스 참조). think-control 필드: `think=false` 요청은 `"think":false`를, `include_reasoning=false` 요청은 `"include_reasoning":false`를 body에 추가했다. + +Provider matrix: + +| Provider | Case | Routing proof | Edge SSE observation | Provider raw trace observation | Judgment | +|----------|------|---------------|----------------------|--------------------------------|----------| +| mac-mlx-vllm | omitted_default | `models["qwen3.6:35b"].providers` 후보 config가 `mac-mlx-vllm`만 남긴 상태로 apply됨(`restart_required_paths:[]`); run_id `manual-1783088347584527000` | `reasoning_content` nonempty 2038청크, `finish_reason:"stop"` + `[DONE]` 정상 종료 | node trace(mac-codex-node, `IOP_OPENAI_COMPAT_TRACE_STREAM=true`): `reasoning_len>0` 2038/2038 parsed chunk, `raw_has_open_think:false`(델타 필드로 생성) | PASS — provider가 reasoning을 생성했고 기본 노출 정책대로 그대로 노출됨 | +| mac-mlx-vllm | think=false | 동일 isolated config, run_id `manual-1783088414683221000` | `reasoning_content` nonempty **302청크**(0이 아님), `[DONE]` 정상 종료 | node trace: `reasoning_len>0` 302/332 parsed chunk, `raw_has_open_think:false` | **FAIL(관측 불일치)** — Node는 `chat_template_kwargs.enable_thinking:false`를 올바르게 전송했으나(코드 기준 정상), 이 vllm-mlx 서버가 요청 단위 override를 반영하지 않아 provider가 reasoning을 계속 생성함. `omitted_default`에서도 이미 생성 중이었으므로(인벤토리의 `default_chat_template_kwargs.enable_thinking:false` 서버 기본값도 실질적으로 미반영) 서버/템플릿 레벨 한계로 판단. 소스 변경 범위 밖 | +| mac-mlx-vllm | include_reasoning=false | 동일 isolated config, run_id `manual-1783088479225551000` | `reasoning_content` nonempty **0**, `content` nonempty 178청크, `[DONE]` 정상 종료 | node trace: `reasoning_len>0` 1993개 parsed chunk(생성은 계속됨) | PASS — provider는 reasoning을 생성했지만 Edge가 노출만 정확히 억제(생성 비활성화가 아니라 노출 비활성화로 확인, 기대 기준과 일치) | +| gx10-vllm | omitted_default | 후보 config가 `gx10-vllm`만 남긴 상태로 apply됨; run_id `manual-1783088746625773000` | `reasoning_content` nonempty 4559청크, `content` nonempty 262청크, `[DONE]` 정상 종료 | node trace(gx10-vllm-node, pretty json이라 `grep '"reasoning_len":'` 라인 단위 집계): 4559/4559 청크 `reasoning_len>0`, `raw_has_open_think:false` | PASS — provider가 reasoning을 생성했고 기본 노출 정책대로 노출됨 | +| gx10-vllm | think=false | 동일 isolated config, run_id `manual-1783088903794225000` | `reasoning_content` nonempty **0**, `[DONE]` 정상 종료 | node trace: 3106개 parsed chunk 전부 `reasoning_len:0` | PASS — provider 레벨에서 reasoning 생성 자체가 비활성화됨(생성 비활성화로 확인, 세 provider 중 유일하게 기대대로 동작) | +| gx10-vllm | include_reasoning=false | 동일 isolated config, run_id `manual-1783088958460890000` | `reasoning_content` nonempty **0**, `content` nonempty 148청크, `[DONE]` 정상 종료 | node trace: 1690/1840 parsed chunk `reasoning_len>0`(생성은 계속됨) | PASS — provider는 생성했지만 Edge가 노출만 억제 | +| onexplayer-lemonade | omitted_default | 후보 config가 `onexplayer-lemonade`만 남긴 상태로 apply됨; run_id `manual-1783089076606197000` | `reasoning_content` nonempty 2481청크, `content` nonempty 152청크, `[DONE]` 정상 종료 | node trace(onexplayer-lemonade-node, `iop-node.log`에서 `Select-String`으로 run_id 구간 추출 후 `"reasoning_len":` 라인 단위 집계, 일부 `raw_preview` 멀티바이트 인코딩 손상으로 `jq` 전체 파싱은 불가): 2481/2481 라인 `reasoning_len>0`, Edge SSE 카운트와 정확히 일치 | PASS — provider가 reasoning을 생성했고 기본 노출 정책대로 노출됨 | +| onexplayer-lemonade | think=false | 동일 isolated config, run_id `manual-1783089248458043000` | `reasoning_content` nonempty **2067청크**(0이 아님), `[DONE]` 정상 종료 | node trace: 2067/2283 라인 `reasoning_len>0` | **FAIL(관측 불일치)** — Node는 `lemonade` provider 매핑대로 top-level `"think":false`를 올바르게 전송했으나(코드 기준 정상), 이 Lemonade 빌드가 request-level `think` 필드로 생성을 끄는 기능을 지원하지 않는 것으로 판단됨. 소스 변경 범위 밖 | +| onexplayer-lemonade | include_reasoning=false | 동일 isolated config, run_id `manual-1783089330300900000` | `reasoning_content` nonempty **0**, `content` nonempty 161청크, `[DONE]` 정상 종료 | node trace: 913/1076 라인 `reasoning_len>0`(생성은 계속됨) | PASS — provider는 생성했지만 Edge가 노출만 억제 | + +### REVIEW_TEST-3 중간 검증 + +원복 절차: 세 node를 trace-off로 재시작(mac-codex-node, gx10-vllm-node, onexplayer-lemonade-node 순서로 provider 처리 직후 각각 원복) → 마지막으로 `edge.yaml`(원본, 3-provider 전체)을 `config refresh --mode dry-run` 확인 후 `--mode apply`로 재적용 → 임시 candidate config 파일(`edge.route-*.yaml`)과 OneXPlayer 임시 `.bat`/로그 추출 파일 삭제. + +```bash +$ ssh toki@toki-labs.com 'cd .../build/dev-runtime && ./bin/edge --config edge.yaml config refresh --mode apply --config-path edge.yaml --addr 127.0.0.1:19093' +{"status":"applied","mode":"apply","changes":[{"path":"models[\"qwen3.6:35b\"].providers","class":"applied","previous":"map[onexplayer-lemonade:...]","next":"map[gx10-vllm:... mac-mlx-vllm:... onexplayer-lemonade:...]"}],"summary":"all changes can be applied without restart","restart_required_paths":[],"node_results":[{"node_id":"mac-codex-node","status":"applied"},{"node_id":"gx10-vllm-node","status":"applied"},{"node_id":"onexplayer-lemonade-node","status":"applied"}]} + +$ ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...' +ok iop/apps/control-plane/... (전 패키지) +ok iop/apps/edge/... (전 패키지, openai 포함) +ok iop/apps/node/... (전 패키지, adapters/openai_compat 포함) +ok iop/packages/go/... (전 패키지) +(실패 없음, 전 패키지 ok 또는 no test files) + +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health}]}"' +{ + "edge_id": "edge-toki-labs-dev", + "nodes": [ + {"node_id": "mac-codex-node", "connected": true}, + {"node_id": "gx10-vllm-node", "connected": true}, + {"node_id": "onexplayer-lemonade-node", "connected": true} + ], + "providers": [ + {"id": "mac-mlx-vllm", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy"}, + {"id": "gx10-vllm", "capacity": 4, "in_flight": 0, "queued": 0, "health": "healthy"}, + {"id": "onexplayer-lemonade", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy"} + ] +} +``` + +### 최종 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +## main...origin/main +eea8dd0 feat: add think-control API fields and dev smoke tests + +$ ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...' +(전 패키지 ok, 실패 없음 — REVIEW_TEST-3 원복 직후 재실행한 결과와 동일) + +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"' +{ + "edge_id": "edge-toki-labs-dev", + "nodes": [ + {"node_id": "mac-codex-node", "connected": true}, + {"node_id": "gx10-vllm-node", "connected": true}, + {"node_id": "onexplayer-lemonade-node", "connected": true} + ], + "providers": [ + {"id": "mac-mlx-vllm", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["mlx-community/Qwen3.6-35B-A3B-4bit"]}, + {"id": "gx10-vllm", "capacity": 4, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["nvidia/Qwen3.6-35B-A3B-NVFP4"]}, + {"id": "onexplayer-lemonade", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["Qwen3.6-35B-A3B-MTP-GGUF"]} + ] +} + +$ ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer " http://127.0.0.1:18083/v1/models' +{"object":"list","data":[{"id":"qwen3.6:35b","object":"model","created":...,"owned_by":"iop"}]} +``` + +원본 3-provider config 완전 복원 확인(요청 전 provider 스냅샷과 served_models까지 동일), 3-node 모두 connected, 세 provider 모두 healthy/`in_flight=0`/`queued=0`으로 회복. Trace용으로 만든 `edge.route-mac-mlx-vllm.yaml`, `edge.route-gx10-vllm.yaml`, `edge.route-onexplayer-lemonade.yaml`, OneXPlayer의 `start-node-trace.bat`/`start-node-normal.bat`/로그 추출 임시 파일은 모두 삭제했다. + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +Sections and their ownership: + +| 섹션 | 소유자 | 설명 | +|------|--------|------| +| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 | +| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 | +| Archive Evidence Snapshot | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트의 기본 이전 루프 컨텍스트; 추가 확인이 필요할 때 여기에 명시된 archive 파일만 좁게 읽음 | +| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 | +| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 | +| 코드리뷰 전용 체크리스트 | Review agent only | 구현 에이전트는 수정하거나 체크하지 않음 | +| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 | +| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채움 | +| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 | +| 검증 결과 | 구현 에이전트가 채움 | 실행 출력만 구현 에이전트가 채움 | +| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 | + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - Correctness: Fail + - Completeness: Fail + - Test coverage: Pass + - API contract: Fail + - Code quality: Pass + - Implementation deviation: Warn + - Verification trust: Pass + - Spec conformance: Fail +- 발견된 문제: + - Required: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log:206`의 `mac-mlx-vllm` `think=false` smoke가 Edge SSE `reasoning_content` 302청크와 provider raw `reasoning_len>0` 302/332청크를 기록했습니다. 이는 archived plan의 `think=false` 기대 기준(`agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_1.log:63-65`)과 SDD S05(`agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md:74-85`)를 충족하지 못합니다. 다음 루프에서 mac vLLM-MLX가 실제로 지원하는 disable field를 직접 프로브하고, 지원 field가 없으면 공개 `think=false` 요청을 조용히 성공시키지 않도록 compatibility error 또는 명시 fallback 정책을 구현/문서화한 뒤 재-smoke하세요. + - Required: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log:212`의 `onexplayer-lemonade` `think=false` smoke가 Edge SSE `reasoning_content` 2067청크와 provider raw `reasoning_len>0` 2067/2283라인을 기록했습니다. 이는 `think=false`가 생성 비활성화 요청이라는 계약(`agent-contract/outer/openai-compatible-api.md:175-192`)과 SDD의 "unsupported 조합을 조용히 무시하지 않는다" 기준(`agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md:60-64`)을 만족하지 못합니다. 다음 루프에서 Lemonade의 실제 request-level disable 지원 여부를 직접 프로브하고, 미지원이면 명확한 error/fallback으로 수습한 뒤 provider별 S05 evidence를 다시 기록하세요. +- 다음 단계: FAIL 후속 `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성한다. diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_2.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_2.log new file mode 100644 index 0000000..128c211 --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_2.log @@ -0,0 +1,304 @@ + + +# Code Review Reference - REVIEW_REVIEW_TEST + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service setup, generic scope conflicts, loop exhaustion, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only the linked Milestone lock decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-07-03 +task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke, plan=2, tag=REVIEW_REVIEW_TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` +- Task ids: + - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다. +- Completion mode: check-on-pass + +## Archive Evidence Snapshot + +- Archived plan: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_1.log` +- Archived review: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log` +- Verdict: FAIL +- Required summary: + - `code_review_cloud_G07_1.log` records that `mac-mlx-vllm` `think=false` returned a normal stream with Edge SSE `reasoning_content` nonempty 302 chunks and provider raw `reasoning_len>0` for 302/332 parsed chunks. + - `code_review_cloud_G07_1.log` records that `onexplayer-lemonade` `think=false` returned a normal stream with Edge SSE `reasoning_content` nonempty 2067 chunks and provider raw `reasoning_len>0` for 2067/2283 trace lines. + - Active plan expectation required `think=false` to produce no provider reasoning and no Edge `reasoning_content`; SDD S05 requires provider raw trace and Edge SSE generation/exposure state to match the defined expectation. + - SDD and contract allow unsupported provider combinations only when they become a clear compatibility error or defined fallback, not a silent successful stream with reasoning. +- Affected files: + - Likely source: `apps/node/internal/adapters/openai_compat/openai_compat.go` + - Likely tests: `apps/node/internal/adapters/openai_compat/openai_compat_test.go`, `apps/edge/internal/openai/server_test.go` + - Likely contract/docs if unsupported/fallback policy changes: `agent-contract/outer/openai-compatible-api.md`, `docs/openai-compatible-api-contract.md` + - If a provider capability/config field is added: `packages/go/config/config.go`, `configs/edge.yaml`, `agent-contract/inner/edge-config-runtime-refresh.md` + - Active evidence artifact to fill: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md` +- Verification evidence already trusted from prior loop: + - Remote source ref `eea8dd0`; remote checkout clean and aligned with `origin/main`. + - Remote `go test ./...` passed after restore in the previous loop. + - Provider-isolated routing via `models["qwen3.6:35b"].providers` apply worked without restart-required paths. + - `gx10-vllm` `think=false` produced no provider reasoning and no Edge `reasoning_content`. + - `include_reasoning=false` suppressed Edge SSE reasoning exposure on all three providers while preserving content and `[DONE]`. + - Final previous-loop restore returned all three nodes/providers to connected/healthy/recovered state. +- Allowed archive reread: + - Read only the two archived files listed above if exact previous-loop context is needed. Do not scan `agent-task/archive/**`. +- Roadmap carryover: + - `dev-smoke` remains incomplete until provider별 `think=false` behavior is either raw no-reasoning or an explicit unsupported/fallback outcome, and the provider matrix is re-recorded after any code/docs/config change. + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 source/evidence와 대조하고, `검증 결과` 섹션의 출력이 plan의 S05 요구와 unsupported/fallback 정책을 충족하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-openai-compatible-think-control`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_REVIEW_TEST-1] Provider Disable Capability Probe | [x] | +| [REVIEW_REVIEW_TEST-2] Compatibility Policy Implementation | [x] | +| [REVIEW_REVIEW_TEST-3] Dev-Runtime Rebuild And S05 Re-Smoke | [x] | + +## 구현 체크리스트 + +- [x] mac vLLM-MLX와 OneXPlayer Lemonade의 request-level thinking disable 지원 여부를 direct provider probe 또는 equivalent trace로 확인하고, 지원 field 또는 미지원 결론을 기록한다. +- [x] probe 결과에 따라 Node provider mapping을 수정하거나, 미지원 provider에서 `think=false`가 조용히 성공하지 않도록 compatibility error/명시 fallback 정책을 구현하고 계약/문서를 갱신한다. +- [x] `gx10-vllm`의 기존 `think=false` 성공 경로와 `include_reasoning=false` 노출 억제를 단위/통합 테스트로 보존한다. +- [x] 변경한 source ref로 dev-runtime binary를 rebuild/redeploy한 뒤 provider-isolated S05 matrix를 재실행하고, `mac-mlx-vllm`, `gx10-vllm`, `onexplayer-lemonade` 각각의 final judgment를 기록한다. +- [x] 임시 config/trace 변경을 원복하고, 최종 `go test ./...`, `/v1/models`, 3-node connected/provider healthy/recovered 상태를 기록한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +- **Probe 방식**: 계획은 provider-isolated IOP routing + raw trace 또는 direct provider probe를 허용했다. 실제로는 각 provider endpoint를 direct로 probe했다(mac-mlx-vllm `http://127.0.0.1:8002/v1`은 `edge.yaml`에서 읽은 `Authorization` 헤더를 원격 셸 변수로만 주입하고 값을 출력하지 않음, gx10/lemonade는 auth 없음). Non-stream + streaming 두 형태를 모두 측정해 provider별 disable 지원 여부를 판정했다. Node stream trace(`IOP_OPENAI_COMPAT_TRACE_STREAM`)는 이번에는 사용하지 않았는데, 이번 루프의 판정 대상은 "provider가 실제로 어떤 request field에 반응하는가"이고 이는 IOP를 우회한 direct probe로 더 깨끗하게 확인되기 때문이다. provider raw generation 근거는 direct probe 값으로 대체 기록한다. +- **provider raw trace 재수집 생략 사유**: 이전 루프(`code_review_cloud_G07_1.log`)는 provider가 계속 reasoning을 "생성"하는지 증명하려고 node 로그를 추출했다. 이번 fix 이후 `think=false`의 결과가 (a) mac은 Edge SSE에서 명시 error, (b) lemonade/gx10은 Edge SSE `reasoning_content` 0이라는 관측 자체가 판정 근거이므로, Windows 대용량 로그 구간 추출 같은 heavy trace는 불필요했다. provider 레벨 생성 억제 근거는 direct provider probe(아래 검증 결과)로 별도 확보했다. +- **Redeploy 범위 축소**: dev-runtime-deploy 스킬은 4개 binary 전량 rebuild/redeploy를 기준으로 한다. 이번 소스 변경은 `apps/node/internal/adapters/openai_compat/openai_compat.go` 한 파일뿐이고 `vllm`(gx10) 매핑 동작은 바뀌지 않으므로, functional 변경이 있는 `mac-codex-node`(mac native binary)와 `onexplayer-lemonade-node`(windows amd64 binary)만 rebuild/redeploy했다. `gx10-vllm-node`(linux arm64) binary는 소스 동작 불변이라 재배포하지 않고 재-smoke로 무회귀만 확인했다. Edge binary도 변경 없음. +- **원격 checkout dirty 유지**: `/Users/toki/agent-work/iop-dev` 원격 checkout에 변경 소스 2파일(`openai_compat.go`, `openai_compat_test.go`)을 반영해 dirty 상태로 두고 그 기준으로 binary를 빌드/배포했다. commit/push는 리뷰-종결 단계 책임이므로 이 루프에서 수행하지 않았다. plan의 "source changed" 재-smoke 기대와 일치한다. 원본 mac binary는 `bin/iop-node.eea8dd0.bak`, onexplayer 원본은 `iop-node.exe.prev-fix-`로 롤백 가능하게 보존했다. +- **Secret masking**: mac-mlx-vllm provider `Authorization` 헤더, edge `bearer_token`, onexplayer node registration 값은 원격 셸 변수로만 사용하고 이 문서/명령 로그에 원문을 남기지 않았다. + +## 주요 설계 결정 + +- **Probe 결론(REVIEW_REVIEW_TEST-1)**: + - **Lemonade = 지원(via `chat_template_kwargs.enable_thinking=false`)**: 현재 매핑인 top-level `think:false`는 이 Lemonade 빌드가 무시한다(streaming reasoning 400 deltas 유지). `chat_template_kwargs.enable_thinking=false`를 보내면 reasoning 0 + content 정상 생성으로 깨끗하게 disable된다. Lemonade는 Qwen3 GGUF를 서빙하며 chat template kwargs를 반영한다. + - **vLLM-MLX = 미지원(current runtime)**: `enable_thinking=false`가 생성 토큰 수는 줄이지만(streaming 397→220 tok), 이 mlx 런타임의 streaming reasoning parser가 응답 전체를 `reasoning_content`로 라벨링하고 content를 0으로 만든다. 따라서 어떤 request field로도 streaming에서 reasoning-free 응답을 만들 수 없다. `/no_think` soft switch, `reasoning_effort=none` 역시 효과 없음. +- **매핑 통합(REVIEW_REVIEW_TEST-2)**: `vllm`/`vllm-mlx`/`lemonade` 세 provider의 think-control을 모두 `chat_template_kwargs.enable_thinking`(+ `thinking_token_budget`) 경로로 통합했다. Lemonade의 top-level `think`/`reasoning_effort`/`thinking_token_budget` 전달을 제거했고, Lemonade의 `reasoning_effort=low|medium|high`도 vLLM과 동일하게 `unsupported think control` error로 처리한다(Qwen3 template에 effort level kwarg가 없고 top-level은 무시됨). +- **vLLM-MLX disable 시 명시 error**: `vllm-mlx` provider에서 `think=false` 또는 `reasoning_effort=none`(생성 disable 요청)이 오면 `buildRequestBody`가 `unsupported think control: provider "vllm-mlx" cannot disable reasoning generation for streaming responses (think=false / reasoning_effort=none)`를 반환한다. 이는 기존 vLLM `reasoning_effort=low` 미지원 error와 동일한 경로(Node adapter error → Edge SSE `{"error":{"type":"run_error",...}}`)를 재사용하므로 Edge 측 신규 처리가 필요 없다. `think=true`/budget-only는 error 없이 `enable_thinking=true`로 유지해 정상 reasoning 요청을 깨지 않는다. +- **fallback 대신 error 선택 근거**: vLLM-MLX에 대해 "documented fallback"(예: enable_thinking=false + 노출 억제)은 이 런타임이 답변까지 `reasoning_content`로 라벨링하기 때문에 답변 자체가 사라져 성립하지 않는다. 따라서 SDD State Machine의 `unsupported -> error` 경로를 택해, 공개 `think=false` 요청을 조용한 200 reasoning stream으로 성공시키지 않고 명시 compatibility error로 반환한다. +- **계약/문서**: `agent-contract/outer/openai-compatible-api.md`의 "Provider별 think-control 정책"을 vLLM/vLLM-MLX/Lemonade로 분리해 갱신했다(vLLM-MLX disable → error, Lemonade → chat_template_kwargs). `docs/openai-compatible-api-contract.md`는 계약 원문 포인터라 별도 수정 없이 반영된다. provider capability config flag는 도입하지 않았으므로 `packages/go/config`, `configs/edge.yaml`, `agent-contract/inner/edge-config-runtime-refresh.md`는 건드리지 않았다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- mac vLLM-MLX와 OneXPlayer Lemonade probe가 secret 없이 실제 request-level generation disable 지원 여부를 판정했는지 확인한다. +- provider가 unsupported로 결론났다면 `think=false`가 정상 200 reasoning stream으로 남지 않고 명확한 error 또는 문서화된 fallback으로 처리되는지 확인한다. +- `gx10-vllm`의 기존 `think=false` raw no-reasoning 경로가 회귀하지 않았는지 확인한다. +- `include_reasoning=false`는 세 provider에서 Edge SSE reasoning exposure를 계속 억제하는지 확인한다. +- S05 matrix가 provider별 omitted default, `think=false`, `include_reasoning=false`를 모두 포함하고, final judgment가 SDD/contract 기대와 일치하는지 확인한다. +- 임시 trace/config 변경이 원복되고 최종 3-node/provider 상태가 healthy/recovered인지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_REVIEW_TEST-1 중간 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +## main...origin/main +eea8dd0 feat: add think-control API fields and dev smoke tests +# (probe 시작 시점, 소스 변경 전 clean) + +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{...}"' +nodes: mac-codex-node/gx10-vllm-node/onexplayer-lemonade-node 모두 connected=true +providers: mac-mlx-vllm/gx10-vllm/onexplayer-lemonade 모두 health=healthy, in_flight=0, queued=0 +``` + +Direct provider probe (secret masking: mac Authorization 헤더는 `edge.yaml`에서 원격 셸 변수로만 주입, 값 미출력). 판정용 non-stream + streaming 측정: + +```text +# NON-STREAM (max_tokens=300), reasoning_content_len / content_len / finish +mac baseline : reasoning=0 content=768 finish=length # mlx는 non-stream에서 답변을 content에 담음 +mac chat_template_kwargs.enable_thinking=false : reasoning=0 content=199 finish=stop # 생성은 짧아짐 +mac /no_think : reasoning=0 content=764 finish=length # 무효 +mac reasoning_effort=none : reasoning=0 content=806 finish=length # 무효 +lem baseline : reasoning=753 content=0 +lem think=false (현행 매핑) : reasoning=717 content=0 finish=length # 무시됨 +lem chat_template_kwargs.enable_thinking=false : reasoning=0 content=202 finish=stop # 지원! 깨끗한 disable +lem /no_think : reasoning=812 무효 +lem reasoning_effort=none : reasoning=747 무효 + +# STREAMING (reasoning/content delta 및 chars) +mac baseline stream : reasoning_deltas=344 +mac enable_thinking=false stream : reasoning_deltas=89(원시), 전량 reasoning_content 라벨링 +mac enable_thinking=false 완주(max_tokens 1200): reasoning_chars=512 content_chars=0 completion_tokens=220 +mac baseline 완주 : reasoning_chars=942 content_chars=23 completion_tokens=397 +lem baseline stream : reasoning_deltas=400 +lem think=false stream : reasoning_deltas=400 # 무시됨 +lem enable_thinking=false stream : reasoning_deltas=0 content_deltas=88 # 지원 + +결론: Lemonade는 chat_template_kwargs.enable_thinking로 disable 지원. vLLM-MLX는 streaming에서 전체 출력을 +reasoning_content로 라벨링하고 enable_thinking=false로도 reasoning-free 응답을 못 만드는 미지원 런타임. +``` + +### REVIEW_REVIEW_TEST-2 중간 검증 +```bash +$ go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai +ok iop/apps/node/internal/adapters/openai_compat 0.016s +ok iop/apps/edge/internal/openai 1.567s + +# 원격 checkout(변경 소스 반영)에서도 동일 통과: +$ ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd .../iop-dev && go test -count=1 ./apps/node/internal/adapters/openai_compat' +ok iop/apps/node/internal/adapters/openai_compat 0.487s +``` + +### REVIEW_REVIEW_TEST-3 중간 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check' +OK build/dev-runtime/edge.yaml + +$ ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer " http://127.0.0.1:18083/v1/models' +{"object":"list","data":[{"id":"qwen3.6:35b","object":"model","created":1783110783,"owned_by":"iop"}]} + +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{...}"' +nodes: 3개 모두 connected=true +providers: mac-mlx-vllm(cap3)/gx10-vllm(cap4)/onexplayer-lemonade(cap3) 모두 healthy, in_flight=0, queued=0 +``` + +Rebuild/redeploy: 변경 소스 2파일을 원격 checkout에 반영 → `go build`로 mac native `iop-node`, cross-build `iop-node-windows-amd64.exe` 생성 → `mac-codex-node`(mac, pid 52757→24030)와 `onexplayer-lemonade-node`(windows, WMI `Win32_Process.Create`, pid 31584→51776) 재시작, 세 node 모두 재연결/healthy 확인. `gx10-vllm-node`는 소스 동작 불변이라 재배포하지 않음. + +Provider-isolated S05 re-smoke (`models["qwen3.6:35b"].providers`를 provider 1개로 축소한 candidate config를 `config refresh --mode apply`로 적용, `restart_required_paths:[]`). `/v1/chat/completions` stream, prompt `"What is 17 times 23? Reason briefly, then give the final number."`: + +| Provider | Case | Edge SSE 관측 | Provider raw (direct probe) | Judgment | +|----------|------|---------------|------------------------------|----------| +| mac-mlx-vllm | omitted_default | reasoning_deltas=351, [DONE] 정상 | 생성함(baseline reasoning 942ch) | PASS — 기본 동작 유지 | +| mac-mlx-vllm | think=false | reasoning_deltas=0, content_deltas=0, **`{"error":{"type":"run_error","message":"...unsupported think control: provider \"vllm-mlx\" cannot disable reasoning generation for streaming responses (think=false / reasoning_effort=none)"}}`** | (요청이 provider에 도달 전 error) | PASS — 조용한 200 reasoning stream이 아니라 명시 compatibility error | +| mac-mlx-vllm | include_reasoning=false | reasoning_deltas=0(노출 억제), [DONE] 정상 | 생성은 계속(direct probe 기준) | PASS — Edge 노출만 억제 | +| gx10-vllm | omitted_default | reasoning_deltas=400, [DONE] 정상 | 생성함 | PASS | +| gx10-vllm | think=false | reasoning_deltas=0, **content_deltas=96**, [DONE] 정상 | enable_thinking=false로 생성 자체 비활성 | PASS — 무회귀(기대대로 clean disable) | +| gx10-vllm | include_reasoning=false | reasoning_deltas=0, content_deltas=20, [DONE] 정상 | 생성은 계속 | PASS — 노출만 억제 | +| onexplayer-lemonade | omitted_default | reasoning_deltas=400, [DONE] 정상 | 생성함(baseline reasoning 753ch) | PASS | +| onexplayer-lemonade | think=false | reasoning_deltas=0, **content_deltas=113**, [DONE] 정상 | enable_thinking=false → direct probe reasoning 0, content 202 | PASS — **fix 적용됨**(이전 루프 2067 reasoning → 0) | +| onexplayer-lemonade | include_reasoning=false | reasoning_deltas=0(노출 억제), [DONE] 정상 | 생성은 계속 | PASS — 노출만 억제 | + +Restore: candidate config 3개 적용 후 원본 `edge.yaml`(3-provider 전체)을 `config refresh --mode apply`로 재적용(`node_results: applied/applied/applied`). 임시 `edge.route-active.yaml`, mac 빌드 staging `bin/iop-node-windows-amd64.exe.new`, onexplayer `iop-node.new.exe` 삭제. 원본 binary는 `bin/iop-node.eea8dd0.bak`, onexplayer `iop-node.exe.prev-fix-`로 보존. + +### 최종 검증 +```bash +$ go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai +ok iop/apps/node/internal/adapters/openai_compat 0.016s +ok iop/apps/edge/internal/openai 1.567s + +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +## main...origin/main + M apps/node/internal/adapters/openai_compat/openai_compat.go + M apps/node/internal/adapters/openai_compat/openai_compat_test.go +eea8dd0 feat: add think-control API fields and dev smoke tests +# (source changed 재-smoke 기준: 변경 소스가 원격 checkout에 반영된 dirty 상태. commit/push는 리뷰-종결 책임) + +$ ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...' +GO_TEST_EXIT=0 +# 전 패키지 통과(ok 33 packages, FAIL/panic 없음) + +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{...}"' +{ + "nodes": [ + {"node_id": "mac-codex-node", "connected": true}, + {"node_id": "gx10-vllm-node", "connected": true}, + {"node_id": "onexplayer-lemonade-node", "connected": true} + ], + "providers": [ + {"id": "mac-mlx-vllm", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["mlx-community/Qwen3.6-35B-A3B-4bit"]}, + {"id": "gx10-vllm", "capacity": 4, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["nvidia/Qwen3.6-35B-A3B-NVFP4"]}, + {"id": "onexplayer-lemonade", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["Qwen3.6-35B-A3B-MTP-GGUF"]} + ] +} +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +Sections and their ownership: + +| 섹션 | 소유자 | 설명 | +|------|--------|------| +| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 | +| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 | +| Archive Evidence Snapshot | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트의 기본 이전 루프 컨텍스트; 추가 확인이 필요할 때 여기에 명시된 archive 파일만 좁게 읽음 | +| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 | +| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 | +| 코드리뷰 전용 체크리스트 | Review agent only | 구현 에이전트는 수정하거나 체크하지 않음 | +| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 | +| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채움 | +| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 | +| 검증 결과 | 구현 에이전트가 채움 | 실행 출력만 구현 에이전트가 채움 | +| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 | + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - Correctness: Pass + - Completeness: Pass + - Test coverage: Pass + - API contract: Pass + - Code quality: Pass + - Implementation deviation: Pass + - Verification trust: Pass + - Spec conformance: Pass +- 발견된 문제: + - Nit(직접 수정됨): `docs/edge-local-dev-guide.md:240`의 think-control smoke 안내가 vLLM-MLX `think=false` 명시 error 및 Lemonade `reasoning_effort=low|medium|high` unsupported 정책과 맞지 않았다. 리뷰 중 provider-isolated 기대 결과와 unsupported 판정을 반영하도록 문구를 갱신했다. +- 다음 단계: PASS 종결. active plan/review를 `.log`로 아카이브하고 `complete.log` 작성 후 task directory를 `agent-task/archive/2026/07/` 아래로 이동한다. diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/complete.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/complete.log new file mode 100644 index 0000000..e245265 --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/complete.log @@ -0,0 +1,48 @@ +# Complete - m-openai-compatible-think-control/05+01,02,03,04_dev_smoke + +## 완료 일시 + +2026-07-03T20:47:11Z + +## 요약 + +OpenAI-compatible Think 제어 MVP의 dev-runtime provider smoke split을 3회 리뷰 루프로 종결했다. 최종 판정은 PASS이며, `dev-smoke` evidence는 provider별 `think=false`, 기본값, `include_reasoning=false` 관측과 vLLM-MLX unsupported 정책 구현으로 충족되었다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | 최초 smoke가 `gx10-vllm` 중심 evidence만 기록해 세 provider S05 관측이 부족했다. | +| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | FAIL | `mac-mlx-vllm`과 `onexplayer-lemonade`의 `think=false`가 정상 200 reasoning stream으로 남아 계약/SDD 기대를 충족하지 못했다. | +| `plan_cloud_G07_2.log` | `code_review_cloud_G07_2.log` | PASS | Lemonade disable mapping과 vLLM-MLX unsupported error 정책을 구현하고 provider-isolated S05 matrix를 재기록했다. | + +## 구현/정리 내용 + +- `apps/node/internal/adapters/openai_compat/openai_compat.go`에서 vLLM, vLLM-MLX, Lemonade의 think-control 매핑을 `chat_template_kwargs.enable_thinking` / `thinking_token_budget` 중심으로 통합했다. +- vLLM-MLX의 `think=false` 또는 `reasoning_effort=none` 요청은 조용한 reasoning stream 성공 대신 `unsupported think control` adapter error로 반환하도록 했다. +- Lemonade의 top-level `think`/`thinking_token_budget` 전달을 중단하고 `chat_template_kwargs` 매핑으로 clean disable을 보장했다. +- `agent-contract/outer/openai-compatible-api.md`에 provider별 unsupported/mapping 정책을 반영했다. +- 리뷰 중 `docs/edge-local-dev-guide.md`의 think-control smoke 안내를 현재 provider 정책에 맞게 정리했다. + +## 최종 검증 + +- `go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai` - PASS; local rerun 결과 두 패키지 모두 통과했다. +- `go test ./...` - PASS; local checkout 전체 Go 패키지 테스트가 통과했다. +- `ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1'` - PASS with note; 원격 checkout은 `eea8dd0` 기준이며 구현 소스 2파일 dirty 상태를 확인했다. 추가로 `apps/node/internal/bootstrap/iop.db` untracked 런타임/테스트 산물이 관측되었지만 local 변경 산출물은 아니다. +- `ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq ...'` - PASS; `mac-codex-node`, `gx10-vllm-node`, `onexplayer-lemonade-node`가 모두 connected이고 세 provider가 `healthy`, `in_flight=0`, `queued=0` 상태임을 재확인했다. +- 구현 기록의 provider-isolated S05 matrix - PASS; `mac-mlx-vllm` `think=false`는 명시 `unsupported think control` error, `gx10-vllm`과 `onexplayer-lemonade` `think=false`는 reasoning 0/content stream, 세 provider `include_reasoning=false`는 Edge SSE reasoning 노출 0으로 기록되었다. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` +- Completed task ids: + - `dev-smoke`: PASS; evidence=`agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_2.log`, `agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_2.log`; verification=`go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai`, `go test ./...`, remote provider status check, provider-isolated S05 matrix in `code_review_cloud_G07_2.log` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/PLAN-cloud-G07.md b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_0.log similarity index 100% rename from agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/PLAN-cloud-G07.md rename to agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_0.log diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_1.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_1.log new file mode 100644 index 0000000..45cd8b3 --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_1.log @@ -0,0 +1,201 @@ + + +# Plan - Review Follow-up: Provider별 Think-Control Dev Smoke + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan은 이전 TEST loop의 FAIL Required를 닫기 위한 좁은 후속 작업이다. 새 코드나 문서 변경을 만들지 말고, dev-runtime provider pool에서 `dev-smoke` S05 evidence만 보강한다. 사용자에게 직접 질문하지 말고, Milestone lock 결정이 아니라면 `사용자 리뷰 요청`을 채우지 않는다. token, bearer value, private key path, node registration token 원문은 어떤 출력에도 남기지 않는다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` +- Task ids: + - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다. +- Completion mode: check-on-pass + +## Archive Evidence Snapshot + +- Archived plan: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_0.log` +- Archived review: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_0.log` +- Verdict: FAIL +- Required summary: + - `code_review_cloud_G07_0.log` records that all TEST-3 `omitted_default`, `think=false`, and `include_reasoning=false` stream/trace cases were handled by `gx10-vllm` only. + - Milestone `dev-smoke` requires smoke across dev-runtime three providers: mac vLLM-MLX, GX10 vLLM, and OneXPlayer Lemonade. + - SDD S05 Evidence Map requires `dev-smoke` completion evidence with provider별 observations. +- Affected files: + - No source files should change. + - Active evidence artifact to fill: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md` +- Verification evidence already trusted from prior loop and review rerun: + - Remote source ref `eea8dd0`; remote checkout clean and aligned with `origin/main`. + - Remote `go test ./...` passed on `2026-07-03` during review rerun. + - Local targeted `go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/adapters/openai_compat` passed during review rerun. + - Remote `build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check` returned OK. + - Remote `/v1/models` exposed `qwen3.6:35b`. + - Remote Control Plane status showed `mac-codex-node`, `gx10-vllm-node`, and `onexplayer-lemonade-node` connected with providers healthy and recovered to `in_flight=0`, `queued=0`. +- Allowed archive reread: + - Read only the two archived files listed above if exact previous-loop context is needed. Do not scan `agent-task/archive/**`. +- Roadmap carryover: + - `dev-smoke` is still incomplete until provider별 S05 stream/trace observations are recorded. + +## 분석 결과 + +### 문제 + +The previous smoke proved deployment freshness, overall capacity health, and `gx10-vllm` behavior. It did not prove live think-control behavior on `mac-mlx-vllm` or `onexplayer-lemonade`, so it cannot close the provider별 S05 Evidence Map. + +### 해결 방법 + +- Reconfirm the remote runner and provider pool are still on the intended source ref and healthy. +- Collect deterministic routing evidence for each provider before claiming a case result. +- Preferred route control: use a temporary provider-isolated Edge config and `config refresh --mode apply --config-path ` so exactly one provider is mapped to `qwen3.6:35b` at a time. Restore the original config after each provider or at the end. +- If live config refresh rejects the temporary provider-isolated mapping, do not claim PASS from a single-provider run. Instead, run a traced capacity batch large enough to hit all providers for each case and record run ids/provider ids proving all three providers handled that case. +- Enable existing node/provider raw stream trace only as long as needed. After evidence capture, restart or refresh back to trace-off state and confirm all three nodes reconnect. +- For each provider and each case, record: + - provider id and node id + - request case name + - routing proof, run id, or equivalent trace correlation + - Edge SSE `reasoning_content` and content observation + - node/provider raw trace observation, with masked snippets or exact counts + - pass/fail judgment + +### 기대 기준 + +- `omitted_default`: record the provider default as observed. Do not require exact text or guaranteed reasoning; verify no unexpected error and SSE completes with valid content/DONE behavior. +- `think=false`: provider raw trace should show no generated reasoning for that request, and Edge SSE should expose no `reasoning_content`. +- `include_reasoning=false`: Edge SSE should expose no `reasoning_content`. If a provider default does not generate reasoning for a reasoning-friendly prompt, record that explicitly; when supported, add a diagnostic `think=true` or `thinking_token_budget` plus `include_reasoning=false` run to prove exposure filtering without replacing the required original case. +- Secrets stay masked. Do not print bearer tokens or private paths. + +## 구현 체크리스트 + +- [ ] Remote runner source ref, clean state, config check, `/v1/models`, and 3-provider health/recovery를 재확인한다. +- [ ] Provider별 deterministic routing 또는 traced batch strategy를 확정하고, `mac-mlx-vllm`, `gx10-vllm`, `onexplayer-lemonade` 각각의 routing proof를 남긴다. +- [ ] `mac-mlx-vllm`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다. +- [ ] `gx10-vllm`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다. +- [ ] `onexplayer-lemonade`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다. +- [ ] 임시 config/trace 변경을 원복하고, 최종 `go test ./...`, `/v1/models`, 3-node connected/provider healthy/recovered 상태를 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +This follow-up stays in `05+01,02,03,04_dev_smoke` and reuses the already archived predecessor completions. Do not re-open `01`, `02`, `03`, or `04`. + +### [REVIEW_TEST-1] Remote State And Route Strategy + +#### 문제 + +The previous TEST-3 matrix had no provider별 coverage. A follow-up run can still be misleading if the runner is stale, the pool is unhealthy, or routing proof is missing. + +#### 해결 방법 + +- Confirm remote source ref and clean state. +- Confirm Edge config check, model exposure, connected nodes, provider health, and recovered `in_flight=0`, `queued=0`. +- Decide and record the routing strategy before smoke: + - provider-isolated temp config via Edge admin refresh; or + - traced capacity batch that proves every provider handled each case. + +#### 수정 파일 및 체크리스트 + +- [ ] No source files. +- [ ] `CODE_REVIEW-*-G??.md`: record remote state and routing strategy output. + +#### 테스트 작성 + +No committed test file. Use remote shell verification. + +#### 중간 검증 + +```bash +ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check' +ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"' +``` + +Expected: `eea8dd0`, clean branch, config OK, all three nodes connected, all three providers healthy and recovered. + +### [REVIEW_TEST-2] Provider별 Think-Control Stream/Trace Matrix + +#### 문제 + +S05 cannot PASS until the live dev-runtime evidence includes `mac-mlx-vllm`, `gx10-vllm`, and `onexplayer-lemonade`. + +#### 해결 방법 + +- Use masked `curl -N` requests against `http://127.0.0.1:18083/v1/chat/completions`. +- For every provider, run the required three cases: + - omitted default + - `think=false` + - `include_reasoning=false` +- Capture Edge SSE outputs under `/tmp/iop-think-smoke//...` on the runner or host. +- Capture node/provider raw trace snippets for matching run ids. +- Include actual masked snippets or exact parser output in `CODE_REVIEW-*-G??.md`. + +#### 수정 파일 및 체크리스트 + +- [ ] No source files. +- [ ] `CODE_REVIEW-*-G??.md`: record a 3 providers x 3 cases matrix with routing proof, Edge SSE observation, provider raw trace observation, and judgment. + +#### 테스트 작성 + +No committed test file. Temporary runner scripts are allowed under `/tmp` only. + +#### 중간 검증 + +```bash +ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && BASE=http://127.0.0.1:18083/v1 MODEL=qwen3.6:35b bash -lc '\''printf "provider matrix smoke start\n"'\''' +``` + +Replace the placeholder with the actual masked provider-routing and curl script. Do not print bearer token values. + +Expected: + +- Matrix contains all three providers and all three required cases. +- `think=false` has no provider reasoning and no Edge `reasoning_content`. +- `include_reasoning=false` has no Edge `reasoning_content`; provider raw generation is recorded as observed. +- All streams complete normally with `[DONE]` or equivalent valid finish evidence. + +### [REVIEW_TEST-3] Restore And Final Verification + +#### 문제 + +Trace env and temporary route isolation are acceptable for evidence capture only if the dev-runtime pool is restored afterward. + +#### 해결 방법 + +- Restore original Edge config/provider mapping. +- Disable temporary trace env and restart affected nodes if needed. +- Confirm all three nodes reconnect and providers recover. +- Run full Go tests on the remote runner after restoration. + +#### 수정 파일 및 체크리스트 + +- [ ] No source files. +- [ ] `CODE_REVIEW-*-G??.md`: record restore commands and final verification output. + +#### 테스트 작성 + +No committed test file. + +#### 중간 검증 + +```bash +ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...' +ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health}]}"' +``` + +Expected: full tests pass, all three nodes connected, all providers healthy with `in_flight=0`, `queued=0`. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| none | external dev-runtime smoke evidence only | + +## 최종 검증 + +```bash +ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...' +ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"' +``` + +Expected: intended source ref, full tests pass, original provider pool restored, all three provider-specific think-control smoke rows recorded with masked Edge SSE and provider raw trace evidence. diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_2.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_2.log new file mode 100644 index 0000000..5c8121f --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_2.log @@ -0,0 +1,202 @@ + + +# Plan - Review Follow-up: Think=False Runtime Compatibility And Re-Smoke + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan은 이전 REVIEW_TEST loop의 FAIL Required를 닫기 위한 후속 작업이다. `mac-mlx-vllm`과 `onexplayer-lemonade`에서 `think=false`가 200 stream으로 성공하면서 reasoning을 생성/노출한 상태를 그대로 PASS 처리하지 않는다. 사용자에게 직접 질문하지 말고, Milestone lock 결정이 아니라면 `사용자 리뷰 요청`을 채우지 않는다. token, bearer value, private key path, node registration token 원문은 어떤 출력에도 남기지 않는다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` +- Task ids: + - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다. +- Completion mode: check-on-pass + +## Archive Evidence Snapshot + +- Archived plan: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_1.log` +- Archived review: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log` +- Verdict: FAIL +- Required summary: + - `code_review_cloud_G07_1.log` records that `mac-mlx-vllm` `think=false` returned a normal stream with Edge SSE `reasoning_content` nonempty 302 chunks and provider raw `reasoning_len>0` for 302/332 parsed chunks. + - `code_review_cloud_G07_1.log` records that `onexplayer-lemonade` `think=false` returned a normal stream with Edge SSE `reasoning_content` nonempty 2067 chunks and provider raw `reasoning_len>0` for 2067/2283 trace lines. + - Active plan expectation required `think=false` to produce no provider reasoning and no Edge `reasoning_content`; SDD S05 requires provider raw trace and Edge SSE generation/exposure state to match the defined expectation. + - SDD and contract allow unsupported provider combinations only when they become a clear compatibility error or defined fallback, not a silent successful stream with reasoning. +- Affected files: + - Likely source: `apps/node/internal/adapters/openai_compat/openai_compat.go` + - Likely tests: `apps/node/internal/adapters/openai_compat/openai_compat_test.go`, `apps/edge/internal/openai/server_test.go` + - Likely contract/docs if unsupported/fallback policy changes: `agent-contract/outer/openai-compatible-api.md`, `docs/openai-compatible-api-contract.md` + - If a provider capability/config field is added: `packages/go/config/config.go`, `configs/edge.yaml`, `agent-contract/inner/edge-config-runtime-refresh.md` + - Active evidence artifact to fill: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md` +- Verification evidence already trusted from prior loop: + - Remote source ref `eea8dd0`; remote checkout clean and aligned with `origin/main`. + - Remote `go test ./...` passed after restore in the previous loop. + - Provider-isolated routing via `models["qwen3.6:35b"].providers` apply worked without restart-required paths. + - `gx10-vllm` `think=false` produced no provider reasoning and no Edge `reasoning_content`. + - `include_reasoning=false` suppressed Edge SSE reasoning exposure on all three providers while preserving content and `[DONE]`. + - Final previous-loop restore returned all three nodes/providers to connected/healthy/recovered state. +- Allowed archive reread: + - Read only the two archived files listed above if exact previous-loop context is needed. Do not scan `agent-task/archive/**`. +- Roadmap carryover: + - `dev-smoke` remains incomplete until provider별 `think=false` behavior is either raw no-reasoning or an explicit unsupported/fallback outcome, and the provider matrix is re-recorded after any code/docs/config change. + +## 분석 결과 + +### 문제 + +The previous follow-up gathered the missing provider별 matrix, but the matrix itself proves two required `think=false` rows do not satisfy the plan, SDD, or OpenAI-compatible contract. Treating those rows as "source 변경 범위 밖" would leave a public `think=false` request silently succeeding while reasoning is still generated and exposed. + +### 해결 방법 + +- First prove whether mac vLLM-MLX and OneXPlayer Lemonade have an alternate request-level disable field that actually prevents reasoning generation. +- If an alternate supported field exists, update the Node provider mapping and tests to use that field. +- If no supported field exists for the current runtime, implement a compatibility error or explicitly documented fallback for that provider path so `think=false` is not silently accepted as a successful reasoning stream. +- Preserve the known-good `gx10-vllm` path and `include_reasoning=false` exposure filtering. +- Rebuild/redeploy the dev-runtime source ref if code/docs/config changed, then rerun the provider-isolated S05 matrix and record the final state. + +### 기대 기준 + +- `omitted_default`: record provider default as observed and require normal stream completion with content or valid finish evidence. +- `think=false`: + - Supported provider path: provider raw trace has no generated reasoning and Edge SSE has no `reasoning_content`. + - Unsupported provider path: request fails with a clear OpenAI-compatible/adapter compatibility error, or follows a documented fallback that does not present as silent generation-disable success. Do not PASS a 200 stream that contains reasoning. +- `include_reasoning=false`: Edge SSE must not expose `reasoning_content`; provider raw generation may be observed and recorded. +- Secrets stay masked. + +## 구현 체크리스트 + +- [ ] mac vLLM-MLX와 OneXPlayer Lemonade의 request-level thinking disable 지원 여부를 direct provider probe 또는 equivalent trace로 확인하고, 지원 field 또는 미지원 결론을 기록한다. +- [ ] probe 결과에 따라 Node provider mapping을 수정하거나, 미지원 provider에서 `think=false`가 조용히 성공하지 않도록 compatibility error/명시 fallback 정책을 구현하고 계약/문서를 갱신한다. +- [ ] `gx10-vllm`의 기존 `think=false` 성공 경로와 `include_reasoning=false` 노출 억제를 단위/통합 테스트로 보존한다. +- [ ] 변경한 source ref로 dev-runtime binary를 rebuild/redeploy한 뒤 provider-isolated S05 matrix를 재실행하고, `mac-mlx-vllm`, `gx10-vllm`, `onexplayer-lemonade` 각각의 final judgment를 기록한다. +- [ ] 임시 config/trace 변경을 원복하고, 최종 `go test ./...`, `/v1/models`, 3-node connected/provider healthy/recovered 상태를 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +This follow-up stays in `05+01,02,03,04_dev_smoke` and reuses the already archived predecessor completions. Do not re-open `01`, `02`, `03`, or `04`. Because source or contract changes may be needed, do not claim PASS from evidence-only notes. + +### [REVIEW_REVIEW_TEST-1] Provider Disable Capability Probe + +#### 문제 + +The previous matrix proves IOP's current mapped fields do not disable reasoning on mac vLLM-MLX or OneXPlayer Lemonade, but it does not prove whether another supported provider field exists. + +#### 해결 방법 + +- Use the dev-runtime inventory and avoid printing bearer/API keys. +- Probe mac vLLM-MLX and OneXPlayer Lemonade directly, or through provider-isolated IOP routing with raw trace, using a short deterministic script under `/tmp`. +- For each provider, compare current mapped field plus plausible provider-native alternatives only when supported by provider docs/help/runtime behavior. +- Record the conclusion as either `supported via ` or `unsupported in current runtime`. + +#### 수정 파일 및 체크리스트 + +- [ ] No committed source files unless probe discovers a mapping fix. +- [ ] `CODE_REVIEW-*-G??.md`: record probe command summary, masked request-body summary, raw reasoning count, Edge/provider response status, and conclusion. + +#### 테스트 작성 + +No committed test file for probes. Temporary scripts are allowed under `/tmp` only. + +#### 중간 검증 + +```bash +ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, health, in_flight, queued, served_models}]}"' +``` + +Expected: intended source/ref is known, three providers are connected/healthy before probe, and no secret value is printed. + +### [REVIEW_REVIEW_TEST-2] Compatibility Policy Implementation + +#### 문제 + +The public contract says `think=false` requests generation disable, while the SDD forbids silently ignoring unsupported think/budget combinations. Current behavior can return a normal reasoning stream on providers that do not honor the disable field. + +#### 해결 방법 + +- If REVIEW_REVIEW_TEST-1 finds a working provider field, update `apps/node/internal/adapters/openai_compat/openai_compat.go` mapping and tests for that provider. +- If a provider is unsupported, return `unsupported think control` or another OpenAI-compatible compatibility error before presenting the request as a successful generation-disabled stream. If a fallback is chosen, document the fallback and test that it is explicit. +- Update `agent-contract/outer/openai-compatible-api.md` and `docs/openai-compatible-api-contract.md` if provider policy changes from the current text. +- If implementation requires a provider capability/config flag, update `packages/go/config/config.go`, `configs/edge.yaml`, and `agent-contract/inner/edge-config-runtime-refresh.md` consistently. +- Keep `include_reasoning=false` as response exposure filtering, not provider trace suppression. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/node/internal/adapters/openai_compat/openai_compat.go`: provider mapping or unsupported/fallback policy. +- [ ] `apps/node/internal/adapters/openai_compat/openai_compat_test.go`: request body or unsupported/fallback tests for vLLM/vLLM-MLX/Lemonade. +- [ ] `apps/edge/internal/openai/server_test.go`: OpenAI-compatible error/response behavior if the Node adapter error reaches the HTTP surface. +- [ ] Contract/docs/config files only if the implementation changes their documented policy or schema. + +#### 테스트 작성 + +Add or update tests before claiming implementation complete. Tests must prove: + +- supported disable path emits the expected provider request body; +- unsupported provider path does not silently accept `think=false`; +- `include_reasoning=false` still suppresses Edge response exposure; +- `gx10-vllm` behavior remains supported. + +#### 중간 검증 + +```bash +go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai +``` + +Expected: targeted tests pass and include meaningful assertions for the changed provider policy. + +### [REVIEW_REVIEW_TEST-3] Dev-Runtime Rebuild And S05 Re-Smoke + +#### 문제 + +The Roadmap `dev-smoke` task cannot close from local tests alone; SDD S05 requires provider raw trace and Edge SSE evidence from the live dev-runtime provider pool. + +#### 해결 방법 + +- Sync/rebuild/redeploy the dev-runtime binaries from the source ref containing REVIEW_REVIEW_TEST-2 if source changed. +- Reconfirm `config check`, `/v1/models`, connected nodes, and provider health. +- Rerun provider-isolated matrix for `omitted_default`, `think=false`, and `include_reasoning=false`. +- For `think=false`, record either raw no-reasoning success or explicit unsupported/fallback result. A normal 200 reasoning stream is a failure. +- Restore original provider config and trace settings, then confirm recovery. + +#### 수정 파일 및 체크리스트 + +- [ ] No tracked smoke artifact other than `CODE_REVIEW-*-G??.md`. +- [ ] `CODE_REVIEW-*-G??.md`: record source ref, rebuild/deploy evidence if applicable, final matrix, restore, and final verification. + +#### 테스트 작성 + +No committed test file. Use remote shell verification and temporary `/tmp` scripts only. + +#### 중간 검증 + +```bash +ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check' +ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer " http://127.0.0.1:18083/v1/models' +ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"' +``` + +Expected: config OK, model exposed, original provider pool restored, all three providers healthy/recovered after matrix execution. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/node/internal/adapters/openai_compat/openai_compat.go` | provider disable mapping or unsupported/fallback policy | +| `apps/node/internal/adapters/openai_compat/openai_compat_test.go` | provider mapping/unsupported tests | +| `apps/edge/internal/openai/server_test.go` | HTTP error/response behavior if needed | +| `agent-contract/outer/openai-compatible-api.md` | provider unsupported/fallback policy if changed | +| `docs/openai-compatible-api-contract.md` | human docs mirror if contract policy changed | +| `packages/go/config/config.go`, `configs/edge.yaml`, `agent-contract/inner/edge-config-runtime-refresh.md` | only if provider capability config is introduced | + +## 최종 검증 + +```bash +go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai +ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...' +ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"' +``` + +Expected: targeted tests pass, remote full tests pass after rebuild/redeploy when source changed, final S05 matrix does not contain any normal 200 `think=false` stream with provider/Edge reasoning for an unsupported provider, and the dev-runtime pool is restored. diff --git a/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md b/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md deleted file mode 100644 index 4e81a86..0000000 --- a/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md +++ /dev/null @@ -1,160 +0,0 @@ - - -# Code Review Reference - TEST - -> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** -> Start only after predecessors `01`, `02`, `03`, and `04` have `complete.log`. -> Fill implementation-owned sections and verification output, then stop with active files in place. Finalization is review-agent-only. - -## 개요 - -date=2026-07-03 -task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke, plan=0, tag=TEST - -## Roadmap Targets - -- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` -- Task ids: - - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다. -- Completion mode: check-on-pass - -## 이 파일을 읽는 리뷰 에이전트에게 - -> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다. - -이 review는 외부 dev-runtime 증거의 신뢰성을 확인한다. source ref와 binary freshness, full test 결과, provider pool 연결 상태, capacity observation, 그리고 think-control raw trace/SSE 비교가 모두 있어야 PASS다. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [TEST-1] Remote Runner Preflight | [ ] | -| [TEST-2] Clean Sync, Full Test, Rebuild, Restart | [ ] | -| [TEST-3] Think-Control SSE And Provider Trace | [ ] | -| [TEST-4] Capacity Health Evidence | [ ] | - -## 구현 체크리스트 - -- [ ] Predecessor `complete.log` for `01`, `02`, `03`, and `04`를 확인한 뒤 시작한다. -- [ ] Remote runner inventory를 재확인하고 source ref, dirty state, branch, HEAD를 기록한다. -- [ ] Clean sync 기준 ref를 확정한다. Milestone 구현 commit이 `origin/main`에 없으면 같은 source ref를 원격에 명시적으로 반영한 뒤 진행하고, 임의로 stale `origin/main`을 검증하지 않는다. -- [ ] Remote runner에서 `go test ./...`를 빌드 전 실행한다. 실패하면 배포를 중단한다. -- [ ] dev-runtime Edge/mac/Linux ARM64/Windows AMD64 Node binary를 같은 source ref에서 rebuild한다. -- [ ] 빌드 후 `config check`, `config refresh --help`, `config refresh --mode dry-run`, `go test ./...`를 실행한다. -- [ ] Edge, mac-codex-node, gx10-vllm-node, onexplayer-lemonade-node를 dev-runtime 절차대로 재시작하고 port/process/status를 확인한다. -- [ ] `/v1/models`가 `qwen3.6:35b`를 노출하는지 확인한다. -- [ ] `/v1/responses`와 `/v1/chat/completions` capacity smoke를 capacity+1 기준으로 실행하고 provider `in_flight/queued` snapshot을 남긴다. -- [ ] `/v1/chat/completions`에서 omitted default, `think=false`, `include_reasoning=false` 세 케이스를 stream으로 실행하고 Edge SSE와 provider raw trace를 비교한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -## 코드리뷰 전용 체크리스트 - -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] active plan/review 파일을 `.log`로 아카이브한다. -- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. -- [ ] PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. -- [ ] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다. - -## 계획 대비 변경 사항 - -_구현 에이전트가 계획과 다르게 실행한 부분을 이유와 함께 기록한다._ - -## 주요 설계 결정 - -_구현 에이전트가 주요 실행/검증 결정 사항을 기록한다. Source ref, provider trace source, masking policy, and any skipped deployment step must be explicit._ - -## 사용자 리뷰 요청 - -_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ - -- 상태: 없음 -- 사유 유형: 없음 -- 연결 대상: 없음 -- 결정 필요: 없음 -- 차단 근거: 없음 -- 실행한 검증/명령: 없음 -- 자동 후속 불가 이유: 없음 -- 재개 조건: 없음 - -## 리뷰어를 위한 체크포인트 - -- Source ref가 predecessor 구현을 포함하고, remote binary가 같은 ref에서 rebuild되었는지 확인한다. -- Full `go test ./...` pre/post 결과가 있거나, 누락 사유와 위험이 명확한지 확인한다. -- Raw trace/SSE evidence가 세 케이스 모두에 대해 있고, token/API key/private path가 마스킹되었는지 확인한다. -- `include_reasoning=false`는 generation disable이 아니라 exposure disable로 판정했는지 확인한다. -- Capacity smoke는 request success만이 아니라 provider snapshot의 `in_flight/queued` 관측을 포함하는지 확인한다. - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다. Secrets and tokens must be masked._ - -### TEST-1 중간 검증 -```bash -$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1 && test -f build/dev-runtime/edge.yaml && test -x build/dev-runtime/bin/edge && test -x build/dev-runtime/bin/iop-node' -(output) -``` - -### TEST-2 중간 검증 -```bash -$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && go test ./...' -(output) -``` - -```bash -$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq ".edge_id, .nodes"' -(output) -``` - -```bash -$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18083/v1/models | jq ".data[].id"' -(output) -``` - -### TEST-3 중간 검증 -```bash -$ # omitted default SSE and raw trace summary -(output) -``` - -```bash -$ # think=false SSE and raw trace summary -(output) -``` - -```bash -$ # include_reasoning=false SSE and raw trace summary -(output) -``` - -### TEST-4 중간 검증 -```bash -$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq ".. | objects | select(has(\"capacity\") and has(\"in_flight\") and has(\"queued\")) | {id, capacity, in_flight, queued, health, served_models}"' -(output) -``` - -### 최종 검증 -```bash -$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' -(output) -``` - -```bash -$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && go test ./...' -(output) -``` - -```bash -$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18083/v1/models | jq ".data[].id"' -(output) -``` - -```bash -$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "."' -(output) -``` - ---- - -> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/apps/node/internal/adapters/openai_compat/openai_compat.go b/apps/node/internal/adapters/openai_compat/openai_compat.go index 43082cb..374f8fd 100644 --- a/apps/node/internal/adapters/openai_compat/openai_compat.go +++ b/apps/node/internal/adapters/openai_compat/openai_compat.go @@ -486,8 +486,23 @@ func (a *Adapter) buildRequestBody(model string, messages []chatMessage, input m budgetVal = v } - isVllm := a.provider == "vllm" || a.provider == "vllm-mlx" - if isVllm { + // vLLM, vLLM-MLX and Lemonade all serve Qwen3-family templates that honor + // chat_template_kwargs.enable_thinking / thinking_token_budget. The Lemonade + // build ignores the top-level `think` field, so all three providers map + // think-control into chat_template_kwargs rather than top-level fields. + switch a.provider { + case "vllm", "vllm-mlx", "lemonade": + thinkDisableRequested := (hasThink && !thinkVal) || (hasReasoningEffort && reasoningEffortVal == "none") + + // The vLLM-MLX runtime labels its entire streamed output as + // reasoning_content and keeps generating reasoning even with + // enable_thinking=false, so a think-disable request can never yield a + // reasoning-free stream. Fail with a clear compatibility error instead + // of silently returning a reasoning stream for think=false. + if a.provider == "vllm-mlx" && thinkDisableRequested { + return nil, fmt.Errorf("unsupported think control: provider %q cannot disable reasoning generation for streaming responses (think=false / reasoning_effort=none)", a.provider) + } + if hasReasoningEffort && reasoningEffortVal != "" && reasoningEffortVal != "none" { return nil, fmt.Errorf("unsupported think control: reasoning_effort %q is not supported by %s", reasoningEffortVal, a.provider) } @@ -536,21 +551,8 @@ func (a *Adapter) buildRequestBody(model string, messages []chatMessage, input m body["chat_template_kwargs"] = chatTemplateKwargs } - } else if a.provider == "lemonade" { - if (hasThink && !thinkVal) || (hasReasoningEffort && reasoningEffortVal == "none") { - body["think"] = false - } else if hasThink && thinkVal { - body["think"] = true - } - - if hasReasoningEffort && reasoningEffortVal != "none" { - body["reasoning_effort"] = reasoningEffortVal - } - if hasBudget { - body["thinking_token_budget"] = budgetVal - } - } else { - // empty/unknown provider + default: + // empty/unknown provider: pass requested fields through as-is. if hasThink { body["think"] = thinkVal } diff --git a/apps/node/internal/adapters/openai_compat/openai_compat_test.go b/apps/node/internal/adapters/openai_compat/openai_compat_test.go index faeff2c..4bae1db 100644 --- a/apps/node/internal/adapters/openai_compat/openai_compat_test.go +++ b/apps/node/internal/adapters/openai_compat/openai_compat_test.go @@ -655,46 +655,35 @@ func TestOpenAICompatExecuteMapsThinkFalseForVLLMChatTemplateKwargs(t *testing.T } } -func TestOpenAICompatExecuteMapsThinkFalseForVLLMMLXChatTemplateKwargs(t *testing.T) { - var body map[string]any - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - t.Fatalf("decode: %v", err) +func TestOpenAICompatExecuteVLLMMLXUnsupportedThinkDisable(t *testing.T) { + // The vLLM-MLX runtime cannot produce a reasoning-free streaming response, + // so think=false / reasoning_effort=none must fail loudly instead of being + // silently accepted as a reasoning stream. + for _, disable := range []map[string]any{ + {"prompt": "hello", "think": false}, + {"prompt": "hello", "reasoning_effort": "none"}, + } { + adapter := New(config.OpenAICompatConf{ + Provider: "vllm-mlx", + Endpoint: "http://localhost:8000", + }, zap.NewNop()) + + sink := &fakeSink{} + err := adapter.Execute(context.Background(), runtime.ExecutionSpec{ + RunID: "run-vllm-mlx-disable", + Target: "qwen-model", + Input: disable, + }, sink) + if err == nil { + t.Fatalf("expected error for vllm-mlx think disable %v, got nil", disable) + } + if !strings.Contains(err.Error(), "unsupported think control") { + t.Fatalf("expected error message to contain 'unsupported think control' for %v, got: %v", disable, err) } - w.Header().Set("Content-Type", "text/event-stream") - _, _ = fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{"content":"ok"}}]}`) - _, _ = fmt.Fprintf(w, "data: [DONE]\n\n") - })) - defer server.Close() - - adapter := New(config.OpenAICompatConf{ - Provider: "vllm-mlx", - Endpoint: server.URL, - }, zap.NewNop()) - - sink := &fakeSink{} - err := adapter.Execute(context.Background(), runtime.ExecutionSpec{ - RunID: "run-vllm-mlx", - Target: "qwen-model", - Input: map[string]any{ - "prompt": "hello", - "think": false, - }, - }, sink) - if err != nil { - t.Fatalf("Execute failed: %v", err) - } - - ctk, ok := body["chat_template_kwargs"].(map[string]any) - if !ok { - t.Fatalf("expected chat_template_kwargs to be a map, got %T", body["chat_template_kwargs"]) - } - if ctk["enable_thinking"] != false { - t.Fatalf("expected enable_thinking to be false, got %v", ctk["enable_thinking"]) } } -func TestOpenAICompatExecuteMapsThinkFalseForLemonadeTopLevel(t *testing.T) { +func TestOpenAICompatExecuteMapsThinkFalseForLemonadeChatTemplateKwargs(t *testing.T) { var body map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { @@ -724,8 +713,15 @@ func TestOpenAICompatExecuteMapsThinkFalseForLemonadeTopLevel(t *testing.T) { t.Fatalf("Execute failed: %v", err) } - if body["think"] != false { - t.Fatalf("expected think to be false, got %v", body["think"]) + if _, ok := body["think"]; ok { + t.Fatalf("expected top-level think to be omitted for lemonade, got %v", body["think"]) + } + ctk, ok := body["chat_template_kwargs"].(map[string]any) + if !ok { + t.Fatalf("expected chat_template_kwargs to be a map, got %T", body["chat_template_kwargs"]) + } + if ctk["enable_thinking"] != false { + t.Fatalf("expected enable_thinking to be false, got %v", ctk["enable_thinking"]) } } @@ -885,12 +881,16 @@ func TestOpenAICompatExecuteMapsReasoningEffortNone(t *testing.T) { t.Fatalf("Execute failed: %v", err) } - if body["think"] != false { - t.Fatalf("expected think to be false, got %v", body["think"]) - } if _, ok := body["reasoning_effort"]; ok { t.Fatalf("expected reasoning_effort to be omitted, got %v", body["reasoning_effort"]) } + ctk, ok := body["chat_template_kwargs"].(map[string]any) + if !ok { + t.Fatalf("expected chat_template_kwargs to be a map, got %T", body["chat_template_kwargs"]) + } + if ctk["enable_thinking"] != false { + t.Fatalf("expected enable_thinking to be false, got %v", ctk["enable_thinking"]) + } }) } @@ -972,7 +972,7 @@ func TestOpenAICompatExecuteMapsThinkTrueForVLLMMLXChatTemplateKwargs(t *testing } } -func TestOpenAICompatExecuteMapsThinkTrueForLemonadeTopLevel(t *testing.T) { +func TestOpenAICompatExecuteMapsThinkTrueForLemonadeChatTemplateKwargs(t *testing.T) { var body map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { @@ -1003,11 +1003,21 @@ func TestOpenAICompatExecuteMapsThinkTrueForLemonadeTopLevel(t *testing.T) { t.Fatalf("Execute failed: %v", err) } - if body["think"] != true { - t.Fatalf("expected think to be true, got %v", body["think"]) + if _, ok := body["think"]; ok { + t.Fatalf("expected top-level think to be omitted for lemonade, got %v", body["think"]) } - if body["thinking_token_budget"] != float64(1024) { - t.Fatalf("expected thinking_token_budget to be float64 1024, got %v", body["thinking_token_budget"]) + if _, ok := body["thinking_token_budget"]; ok { + t.Fatalf("expected top-level thinking_token_budget to be omitted for lemonade, got %v", body["thinking_token_budget"]) + } + ctk, ok := body["chat_template_kwargs"].(map[string]any) + if !ok { + t.Fatalf("expected chat_template_kwargs to be a map, got %T", body["chat_template_kwargs"]) + } + if ctk["enable_thinking"] != true { + t.Fatalf("expected enable_thinking to be true, got %v", ctk["enable_thinking"]) + } + if ctk["thinking_token_budget"] != float64(1024) { + t.Fatalf("expected thinking_token_budget to be float64 1024, got %v", ctk["thinking_token_budget"]) } } diff --git a/docs/edge-local-dev-guide.md b/docs/edge-local-dev-guide.md index e719667..71dbad4 100644 --- a/docs/edge-local-dev-guide.md +++ b/docs/edge-local-dev-guide.md @@ -237,13 +237,14 @@ Qwen 계열 모델은 thinking/reasoning 텍스트를 포함해 응답할 수 ### Request-level think-control smoke -`/v1/chat/completions` 요청은 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`으로 thinking/reasoning 동작과 응답 노출을 제어할 수 있다. dev-runtime provider pool에서는 vLLM, vLLM-MLX, Lemonade 지원 범위가 다르므로 smoke 예시는 provider 공통으로 통과하는 조합을 우선한다. +`/v1/chat/completions` 요청은 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`으로 thinking/reasoning 동작과 응답 노출을 제어할 수 있다. dev-runtime provider pool에서는 vLLM, vLLM-MLX, Lemonade 지원 범위가 다르므로 provider-isolated smoke로 provider별 기대 결과를 분리해 판정한다. -- 안정 smoke: `think=false`, `think` 생략, `include_reasoning=false`, 또는 budget-only `thinking_token_budget` -- Lemonade-only/provider-specific smoke: `reasoning_effort=low|medium|high` -- vLLM/vLLM-MLX는 `reasoning_effort=none`만 disable alias로 처리하고, `low|medium|high`는 `unsupported think control` 오류를 반환한다. +- 공통 안정 smoke: `think` 생략, `include_reasoning=false`, 또는 budget-only `thinking_token_budget` +- vLLM / Lemonade `think=false`: `chat_template_kwargs.enable_thinking=false`로 reasoning 생성을 끄고 content stream이 유지되어야 한다. +- vLLM-MLX `think=false` 또는 `reasoning_effort=none`: 현재 런타임은 streaming reasoning disable을 지원하지 않으므로 `unsupported think control` 오류를 기대한다. +- vLLM / vLLM-MLX / Lemonade `reasoning_effort=low|medium|high`: 현재 provider 매핑에서는 `unsupported think control` 오류를 반환한다. -예시 (thinking 비활성화): +예시 (thinking 비활성화, provider-isolated vLLM 또는 Lemonade 기대): ```bash curl -fsS http://toki-labs.com:18083/v1/chat/completions \ @@ -270,7 +271,7 @@ curl -fsS http://toki-labs.com:18083/v1/chat/completions \ -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"thinking_token_budget":512}' ``` -`reasoning_effort=low` 같은 effort smoke는 Lemonade-only 검증이거나 provider route를 고정할 수 있을 때만 사용한다. provider pool에 vLLM/vLLM-MLX가 섞여 있으면 해당 provider로 라우팅될 때 실패할 수 있다. +`reasoning_effort=low` 같은 effort smoke는 현재 dev-runtime provider 매핑에서 unsupported error를 기대한다. provider pool에 vLLM/vLLM-MLX/Lemonade가 섞여 있으면 어떤 provider로 라우팅되더라도 성공 stream으로 판정하지 않는다. 현재 공개 API는 개별 request의 최종 `node_id`를 응답에 노출하지 않는다. 요청별 배정을 확정해야 할 때는 Edge dispatch trace/log를 추가한 뒤 판정한다.