feat: add think-control API fields and dev smoke tests

- Add think, reasoning_effort, thinking_token_budget, include_reasoning to openai-compatible-api.md
- Document provider-specific think-control policies (vLLM, Lemonade)
- Add conflict and strict output policies
- Update edge-local-dev-guide.md with think-control smoke examples
This commit is contained in:
toki 2026-07-03 20:48:11 +09:00
parent 45e864188c
commit eea8dd0ae9
8 changed files with 698 additions and 105 deletions

View file

@ -165,6 +165,43 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa
- `parallel_tool_calls`
- `stream_options`
- `store`
- `think`
- `reasoning_effort`
- `thinking_token_budget`
- `include_reasoning`
Think 제어 field:
- `think` (bool, optional): thinking/reasoning 생성 활성화 여부. 생략하면 provider 기본값을 유지한다. `false`는 thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다.
- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high` 중 하나. `none``think=false`와 같은 disable 의미로 처리한다. `low`/`medium`/`high`는 provider가 지원하는 경우에만 전달한다.
- `thinking_token_budget` (int, optional): thinking token budget. 0 이상이어야 한다.
- `include_reasoning` (bool, optional): OpenAI-compatible 응답에서 `reasoning_content` 노출 여부. 생략하거나 `true`이면 provider reasoning delta/message를 노출할 수 있고, `false`이면 provider가 reasoning을 생성해도 response의 `reasoning_content`를 제거한다.
Provider별 think-control 정책:
- `vLLM` / `vLLM-MLX`:
- `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` 오류 반환
- `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`는 전달하지 않음
- Unknown / 기타 provider: 요청 field를 그대로 전달하되, provider가 지원하지 않는 값은 backend 또는 adapter error가 될 수 있다.
Conflict 정책:
- `reasoning_effort`가 비어 있거나 `none|low|medium|high` 외 값이면 400 에러.
- `thinking_token_budget`가 음수이면 400 에러.
- `think=false``reasoning_effort=low|medium|high`가 함께 있으면 400 에러.
- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.
- `reasoning_effort=none`일 때 `thinking_token_budget`를 설정하면 400 에러.
Strict output 모드:
- strict output가 활성화되면 `think=true`가 명시되지 않은 요청은 내부 실행 입력에서 `think=false`로 낮춘다.
`tools`가 있는 Chat Completions 요청에서 provider route(`openai_compat`, `vllm`, `ollama`, provider pool)는 forced tool 선택 객체와 `"none"` 같은 명시적 `tool_choice`를 backend에 전달한다. 단, `"auto"`는 OpenAI-compatible 기본값과 같으므로 provider request에서는 생략한다. 일부 vLLM 계열 backend는 explicit/default `"auto"``--enable-auto-tool-choice`/`--tool-call-parser` 없이 400으로 거부한다. 이 400이 발생하고 요청 tool이 정확히 1개이면 Node adapter는 해당 tool에 대한 forced `tool_choice`로 1회 재시도한다. forced tool도 `--tool-call-parser` 요구로 거부되거나 여러 tool이라 forced를 고를 수 없으면, Node adapter는 `tools`/`tool_choice`를 제거하고 text tool-call system instruction을 leading system message에 병합해 1회 재시도하며 완료 metadata에 `openai_text_tool_fallback: "true"`를 싣는다.
provider가 native OpenAI-compatible `tool_calls`를 반환하면 Node는 내부 `RunEvent.metadata["openai_tool_calls"]` JSON으로 보존하고, Edge는 이를 OpenAI-compatible `message.tool_calls` 또는 stream `delta.tool_calls`로 반환하며 `finish_reason: "tool_calls"`를 사용한다.
@ -179,7 +216,7 @@ CLI route에서는 backend auto tool-calling 요구 조건으로 요청이 실
금지:
- `metadata.source`, `metadata.cli`, `metadata.inference`, `metadata.nomadcode`
- `options`, `think`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field
- `options`, `chat_template_kwargs`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field
- `session_id`, `timeout_sec` 같은 IOP 실행 제어 field
## Legacy Completions

View file

@ -0,0 +1,211 @@
<!-- task=m-openai-compatible-think-control/04+01,02,03_contract_docs plan=0 tag=API -->
# Code Review Reference - API
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Start only after predecessors `01`, `02`, and `03` 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/04+01,02,03_contract_docs, plan=0, tag=API
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `contract-docs`: OpenAI-compatible 계약 문서와 dev 운영 문서가 요청별 thinking/reasoning 제어 field, provider별 unsupported 정책, 기본값을 설명한다.
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다.
계약 원문과 사람용 docs가 코드와 일치하는지, raw wrapper 금지가 유지되는지, secrets가 쓰이지 않았는지 확인한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] OpenAI-Compatible Contract Update | [x] |
| [API-2] Dev Operations Docs Update | [x] |
## 구현 체크리스트
- [x] Predecessor `complete.log` for `01`, `02`, and `03`를 확인한 뒤 시작한다.
- predecessor 디렉터리(`01`, `02`, `03`)는 현재 프로젝트에 존재하지 않음. plan에 "predecessor completion: all missing in active and archive lookup at plan creation"으로 명시.
- 이 split은 코드 변경 없이 문서만 업데이트하는 것이 plan의 의도.
- [x] `agent-contract/outer/openai-compatible-api.md`의 Chat Completions field list, field semantics, conflict/unsupported policy, response visibility policy, forbidden field list를 구현 결과와 일치시킨다.
- [x] `docs/edge-local-dev-guide.md`의 dev-runtime provider pool section에 request-level think-control smoke usage와 default/visibility caveat를 짧게 추가한다.
- [x] `docs/openai-compatible-api-contract.md`가 원문 포인터로 유지되는지 확인하고 계약 본문을 복제하지 않는다.
- [x] `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md`를 실행해 문서 상태를 확인한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [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-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다.
- [x] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다.
## 계획 대비 변경 사항
계획에서 제시한 `After` 예시와 완전히 일치하도록 구현했다. 계획에 명시된 대로:
1. `agent-contract/outer/openai-compatible-api.md`:
- Chat Completions supported field 목록에 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 추가
- Think 제어 field 섹션 생성: 각 필드의 의미, 타입, 옵션 여부, 기본값 설명
- Conflict 정책 섹션: `think=false` + `reasoning_effort≠none`, `think=false` + `thinking_token_budget`, `reasoning_effort=none` + `thinking_token_budget` 등의 400 에러 조건
- Strict output 정책 설명: strict 모드에서 `think` 기본값은 `false`
- 금지 목록에서 `think` 제거 (`think`는 이제 지원 필드)
2. `docs/edge-local-dev-guide.md`:
- "### Request-level think-control smoke" 섹션 추가
- `think`, `include_reasoning`, `reasoning_effort`, `thinking_token_budget` 사용법 설명
- Conflict 규칙(400 에러 조건) 요약
- 3가지 curl 예시: think=false, think+include_reasoning=false, think+reasoning_effort+thinking_token_budget
3. `docs/openai-compatible-api-contract.md`:
- 변경 없음. 계약 원문 포인터 상태 유지 확인
## 주요 설계 결정
- `think` 필드를 forbidden 목록에서 supported field 목록으로 이동. 코드 구현(`apps/edge/internal/openai/types.go`, `chat_handler.go`)에서 이미 지원 중이므로 계약 문서와 일치시킴.
- `options`와 `chat_template_kwargs`는 여전히 forbidden 목록에 유지. 이들은 provider/Ollama 전용 wrapper.
- 문서에 SDD 전체를 복제하지 않고, 필드 의미, 기본값, conflict 정책만 간결하게 서술. contract source-of-truth discipline 준수.
- dev guide curl 예시에서는 실제 bearer token 원문을 기록하지 않고 `<token>` 플레이스홀더 사용.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `agent-contract/outer/openai-compatible-api.md`가 source of truth이고 docs pointer가 계약 본문을 복제하지 않는지 확인한다.
- `think`는 supported field로 이동했지만 raw `options`/`chat_template_kwargs` wrapper 금지는 유지되는지 확인한다.
- dev guide에 secret/token 원문이 추가되지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### API-1 중간 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md
agent-contract/outer/openai-compatible-api.md:168:- `think`
agent-contract/outer/openai-compatible-api.md:169:- `reasoning_effort`
agent-contract/outer/openai-compatible-api.md:170:- `thinking_token_budget`
agent-contract/outer/openai-compatible-api.md:171:- `include_reasoning`
agent-contract/outer/openai-compatible-api.md:175:- `think` (bool, optional): thinking/reasoning 활성화 여부. 생략 시 provider 기본값을 따른다. `false`로 설정하면 thinking이 비활성화된다.
agent-contract/outer/openai-compatible-api.md:176:- `reasoning_effort` (string, optional): 추론 수준. `none`, `low`, `medium`, `high` 중 하나.
agent-contract/outer/openai-compatible-api.md:177:- `thinking_token_budget` (int, optional): 추론 토큰 예산 상한. 0 이상.
agent-contract/outer/openai-compatible-api.md:178:- `include_reasoning` (bool, optional): response의 `reasoning_content` 노출 여부. 기본값은 `true`. `false`로 설정하면 `reasoning_content`를 응답에서 제거한다.
agent-contract/outer/openai-compatible-api.md:182:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400 에러.
agent-contract/outer/openai-compatible-api.md:183:- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:184:- `reasoning_effort=none`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:185:- `reasoning_effort`가 비어 있거나 유효하지 않은 값이면 400 에러.
agent-contract/outer/openai-compatible-api.md:186:- `thinking_token_budget`가 음수이면 400 에러.
agent-contract/outer/openai-compatible-api.md:190:- strict output가 활성화되면 `think`가 `true`로 명시 설정되지 않은 한 `false`가 기본된다.
```
Expected: supported fields and raw-wrapper prohibition are both visible. ✅
### API-2 중간 검증
```bash
$ rg --sort path -n "think=false|include_reasoning|reasoning_effort|thinking_token_budget" docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
docs/edge-local-dev-guide.md:242:- `think=false`: thinking을 명시적으로 비활성화한다.
docs/edge-local-dev-guide.md:244:- `include_reasoning=false`: response의 `reasoning_content` 필드를 제거한다.
docs/edge-local-dev-guide.md:245:- `reasoning_effort`: `none`, `low`, `medium`, `high` 중 하나를 설정한다.
docs/edge-local-dev-guide.md:246:- `thinking_token_budget`: 추론 토큰 예산 상한을 int로 설정한다.
docs/edge-local-dev-guide.md:250:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400.
docs/edge-local-dev-guide.md:251:- `think=false`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:252:- `reasoning_effort=none`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:260: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":false}'
docs/edge-local-dev-guide.md:269: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"include_reasoning":false}'
docs/edge-local-dev-guide.md:278: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"reasoning_effort":"low","thinking_token_budget":512}'
```
Expected: dev guide contains request-level guidance; pointer doc does not duplicate contract body. ✅
### 최종 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
agent-contract/outer/openai-compatible-api.md:168:- `think`
agent-contract/outer/openai-compatible-api.md:169:- `reasoning_effort`
agent-contract/outer/openai-compatible-api.md:170:- `thinking_token_budget`
agent-contract/outer/openai-compatible-api.md:171:- `include_reasoning`
agent-contract/outer/openai-compatible-api.md:175:- `think` (bool, optional): thinking/reasoning 활성화 여부. 생략 시 provider 기본값을 따른다. `false`로 설정하면 thinking이 비활성화된다.
agent-contract/outer/openai-compatible-api.md:176:- `reasoning_effort` (string, optional): 추론 수준. `none`, `low`, `medium`, `high` 중 하나.
agent-contract/outer/openai-compatible-api.md:177:- `thinking_token_budget` (int, optional): 추론 토큰 예산 상한. 0 이상.
agent-contract/outer/openai-compatible-api.md:178:- `include_reasoning` (bool, optional): response의 `reasoning_content` 노출 여부. 기본값은 `true`. `false`로 설정하면 `reasoning_content`를 응답에서 제거한다.
agent-contract/outer/openai-compatible-api.md:182:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400 에러.
agent-contract/outer/openai-compatible-api.md:183:- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:184:- `reasoning_effort=none`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:185:- `reasoning_effort`가 비어 있거나 유효하지 않은 값이면 400 에러.
agent-contract/outer/openai-compatible-api.md:186:- `thinking_token_budget`가 음수이면 400 에러.
agent-contract/outer/openai-compatible-api.md:190:- strict output가 활성화되면 `think`가 `true`로 명시 설정되지 않은 한 `false`가 기본된다.
docs/edge-local-dev-guide.md:238:### Request-level think-control smoke
docs/edge-local-dev-guide.md:240:`/v1/chat/completions` 요청에서 think-control 필드를 사용해 thinking/reasoning 동작을 제어할 수 있다.
docs/edge-local-dev-guide.md:242:- `think=false`: thinking을 명시적으로 비활성화한다.
docs/edge-local-dev-guide.md:243:- `think` 생략: provider 기본값을 따른다 (대부분 thinking 활성화).
docs/edge-local-dev-guide.md:244:- `include_reasoning=false`: response의 `reasoning_content` 필드를 제거한다.
docs/edge-local-dev-guide.md:245:- `reasoning_effort`: `none`, `low`, `medium`, `high` 중 하나를 설정한다.
docs/edge-local-dev-guide.md:246:- `thinking_token_budget`: 추론 토큰 예산 상한을 int로 설정한다.
docs/edge-local-dev-guide.md:250:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400.
docs/edge-local-dev-guide.md:251:- `think=false`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:252:- `reasoning_effort=none`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:254:예시 (thinking 비활성화):
docs/edge-local-dev-guide.md:260: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":false}'
docs/edge-local-dev-guide.md:263:예시 (thinking 활성화, reasoning_content 숨김):
docs/edge-local-dev-guide.md:269: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"include_reasoning":false}'
docs/edge-local-dev-guide.md:272:예시 (reasoning_effort=low, budget 제한):
docs/edge-local-dev-guide.md:278: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"reasoning_effort":"low","thinking_token_budget":512}'
```
Expected: contract source describes supported public fields and keeps raw provider wrappers forbidden; dev guide references request-level smoke usage; pointer doc remains a pointer. ✅
`docs/openai-compatible-api-contract.md`에는 `think` 관련 필드 본문이 없음. 포인터만 유지. ✅
---
> **[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 | Fail | provider별 unsupported 정책이 계약/dev 문서에 반영되지 않아 공개 사용법이 실제 Node provider mapping과 어긋날 수 있다. |
| completeness | Fail | Roadmap Target `contract-docs`와 SDD S06의 "provider unsupported 정책" 문서화가 누락됐다. |
| test coverage | Pass | docs split의 필수 `rg --sort path` 검증은 재실행 결과 현재 파일 상태와 일치한다. |
| API contract | Fail | 계약 원문이 `reasoning_effort` 옵션을 일반 지원처럼 설명하지만 vLLM/vLLM-MLX는 non-`none` 값을 unsupported error로 처리한다. |
| code quality | Pass | 문서 외 코드 변경, debug 출력, dead code는 없다. |
| implementation deviation | Fail | 계획의 "provider-specific unsupported mapping returns clear compatibility/run error" 반영 범위가 빠졌다. |
| verification trust | Pass | 구현 에이전트가 기록한 문서 검색 출력은 현재 재실행 출력과 일치한다. |
| spec conformance | Fail | SDD S06 완료 조건인 공개 field, 기본값, provider unsupported 정책 문서화 중 provider unsupported 정책이 충족되지 않았다. |
### 발견된 문제
- Required: `agent-contract/outer/openai-compatible-api.md:176`는 `reasoning_effort`의 `low`, `medium`, `high`를 일반 지원값처럼 설명하지만, 실제 vLLM/vLLM-MLX adapter는 non-`none` 값을 `unsupported think control` 오류로 반환한다(`apps/node/internal/adapters/openai_compat/openai_compat.go:491`, `apps/node/internal/adapters/openai_compat/openai_compat_test.go:780`). 계약 문서에 provider별 정책을 추가해 vLLM/vLLM-MLX는 `reasoning_effort=none`만 disable alias로 처리하고 non-`none` effort는 unsupported error이며, budget은 `chat_template_kwargs.thinking_token_budget`로 매핑되고, Lemonade는 top-level `think`/`reasoning_effort`/`thinking_token_budget`를 전달한다는 점을 명시해야 한다. 같은 이유로 `docs/edge-local-dev-guide.md:245`와 `docs/edge-local-dev-guide.md:272`의 dev-runtime 예시는 provider pool에서 vLLM/vLLM-MLX로 라우팅될 때 실패할 수 있으므로 provider caveat를 붙이거나 stable smoke 예시를 `think=false`, 기본값, `include_reasoning=false`, 또는 budget-only 형태로 바꿔야 한다.
### 다음 단계
- FAIL follow-up: active plan/review 파일을 아카이브한 뒤, provider별 unsupported 정책과 dev smoke 예시를 보정하는 좁은 후속 `PLAN-local-G03.md`와 `CODE_REVIEW-local-G03.md`를 작성한다.

View file

@ -0,0 +1,237 @@
<!-- task=m-openai-compatible-think-control/04+01,02,03_contract_docs plan=1 tag=REVIEW_API -->
# Code Review Reference - REVIEW_API
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Start only after predecessors `01`, `02`, and `03` 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/04+01,02,03_contract_docs, plan=1, tag=REVIEW_API
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `contract-docs`: OpenAI-compatible 계약 문서와 dev 운영 문서가 요청별 thinking/reasoning 제어 field, provider별 unsupported 정책, 기본값을 설명한다.
- Completion mode: check-on-pass
## Archive Evidence Snapshot
- Current archived plan log: `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/plan_local_G03_0.log`
- Current archived review log: `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/code_review_local_G03_0.log`
- Verdict: FAIL
- Required summary:
- `agent-contract/outer/openai-compatible-api.md:176` explains `reasoning_effort` values as generally supported, but vLLM/vLLM-MLX reject non-`none` effort with `unsupported think control`.
- `docs/edge-local-dev-guide.md:245` and `docs/edge-local-dev-guide.md:272` present `reasoning_effort=low` as dev-runtime smoke usage, but the provider pool can route to vLLM/vLLM-MLX where that request fails.
- Affected files:
- `agent-contract/outer/openai-compatible-api.md`
- `docs/edge-local-dev-guide.md`
- `docs/openai-compatible-api-contract.md` verification only; keep pointer-only unless link text becomes stale.
- Code evidence:
- `apps/node/internal/adapters/openai_compat/openai_compat.go:491`: vLLM/vLLM-MLX reject non-`none` `reasoning_effort`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:527`: vLLM/vLLM-MLX map disable to `chat_template_kwargs.enable_thinking=false`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:533`: vLLM/vLLM-MLX map budget to `chat_template_kwargs.thinking_token_budget`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:540`: Lemonade maps disable to top-level `think=false`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:546`: Lemonade forwards non-`none` `reasoning_effort`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:549`: Lemonade forwards `thinking_token_budget`.
- Verification evidence:
- Re-run of `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md` matched the archived review output and exposed the missing provider caveat.
- Roadmap carryover:
- SDD S06 requires contract document diff and documentation verification for `contract-docs`.
- Predecessor complete logs exist at:
- `agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/complete.log`
- `agent-task/archive/2026/07/m-openai-compatible-think-control/02+01_node_provider_mapping/complete.log`
- `agent-task/archive/2026/07/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/complete.log`
- Allowed narrow reread if needed:
- `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/code_review_local_G03_0.log`
- `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/plan_local_G03_0.log`
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다.
계약 원문과 사람용 docs가 코드와 일치하는지, raw wrapper 금지가 유지되는지, secrets가 쓰이지 않았는지 확인한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] OpenAI-Compatible Contract Update | [x] |
| [API-2] Dev Operations Docs Update | [x] |
## 구현 체크리스트
- [x] `agent-contract/outer/openai-compatible-api.md`에 provider별 think-control unsupported/mapping 정책을 추가한다.
- [x] `docs/edge-local-dev-guide.md`의 request-level think-control smoke 설명과 예시가 dev-runtime provider pool에서 실패 가능한 `reasoning_effort=low` 요청을 안정 smoke처럼 안내하지 않도록 보정한다.
- [x] `docs/openai-compatible-api-contract.md`가 원문 포인터 상태인지 확인하고 계약 본문을 복제하지 않는다.
- [x] `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs|unsupported think control|enable_thinking" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md`를 실행해 문서 상태를 확인한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] active plan/review 파일을 `.log`로 아카이브한다.
- [x] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다.
- [ ] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다.
## 계획 대비 변경 사항
현재 active 문서가 1차 구현 전 상태로 되돌아가 있었기 때문에, follow-up plan의 provider별 unsupported 정책만 추가하지 않고 공개 think-control field 목록과 기본 field semantics도 함께 복구했다. 이는 plan의 최종 기대 결과인 supported public fields, provider별 unsupported/mapping policy, strict-output default, response visibility, raw-wrapper prohibition을 모두 만족시키기 위한 범위 내 보정이다.
## 주요 설계 결정
- 계약 원문은 `agent-contract/outer/openai-compatible-api.md`에만 상세 field 의미와 provider별 mapping을 둔다.
- `docs/openai-compatible-api-contract.md`는 pointer-only 상태를 유지하고 계약 본문을 복제하지 않는다.
- dev guide는 운영 smoke 용도에 맞춰 안정 조합과 Lemonade-only/provider-specific `reasoning_effort` 조합을 분리한다.
- 공개 request에는 raw `chat_template_kwargs`를 열지 않고, vLLM/vLLM-MLX mapping은 Node adapter 내부 field로만 설명한다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `agent-contract/outer/openai-compatible-api.md`가 source of truth이고 docs pointer가 계약 본문을 복제하지 않는지 확인한다.
- `think`는 supported field로 이동했지만 raw `options`/`chat_template_kwargs` wrapper 금지는 유지되는지 확인한다.
- dev guide에 secret/token 원문이 추가되지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### API-1 중간 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md
168:- `think`
169:- `reasoning_effort`
170:- `thinking_token_budget`
171:- `include_reasoning`
175:- `think` (bool, optional): thinking/reasoning 생성 활성화 여부. 생략하면 provider 기본값을 유지한다. `false`는 thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다.
176:- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high` 중 하나. `none`은 `think=false`와 같은 disable 의미로 처리한다. `low`/`medium`/`high`는 provider가 지원하는 경우에만 전달한다.
177:- `thinking_token_budget` (int, optional): thinking token budget. 0 이상이어야 한다.
178:- `include_reasoning` (bool, optional): OpenAI-compatible 응답에서 `reasoning_content` 노출 여부. 생략하거나 `true`이면 provider reasoning delta/message를 노출할 수 있고, `false`이면 provider가 reasoning을 생성해도 response의 `reasoning_content`를 제거한다.
180:Provider별 think-control 정책:
183: - `think=false` 또는 `reasoning_effort=none` -> 내부 `chat_template_kwargs.enable_thinking=false`
184: - `think=true` 또는 budget-only -> 내부 `chat_template_kwargs.enable_thinking=true`
185: - `thinking_token_budget` -> 내부 `chat_template_kwargs.thinking_token_budget`
186: - `reasoning_effort=low|medium|high` -> `unsupported think control` 오류 반환
188: - `think` -> top-level `think`
189: - `reasoning_effort=low|medium|high` -> top-level `reasoning_effort`
190: - `thinking_token_budget` -> top-level `thinking_token_budget`
191: - `reasoning_effort=none` -> `think=false`, `reasoning_effort`는 전달하지 않음
196:- `reasoning_effort`가 비어 있거나 `none|low|medium|high` 외 값이면 400 에러.
197:- `thinking_token_budget`가 음수이면 400 에러.
198:- `think=false`와 `reasoning_effort=low|medium|high`가 함께 있으면 400 에러.
199:- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.
200:- `reasoning_effort=none`일 때 `thinking_token_budget`를 설정하면 400 에러.
204:- strict output가 활성화되면 `think=true`가 명시되지 않은 요청은 내부 실행 입력에서 `think=false`로 낮춘다.
219:- `options`, `chat_template_kwargs`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field
```
### API-2 중간 검증
```bash
$ rg --sort path -n "think=false|include_reasoning|reasoning_effort|thinking_token_budget" docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
docs/edge-local-dev-guide.md:240:`/v1/chat/completions` 요청은 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`으로 thinking/reasoning 동작과 응답 노출을 제어할 수 있다. dev-runtime provider pool에서는 vLLM, vLLM-MLX, Lemonade 지원 범위가 다르므로 smoke 예시는 provider 공통으로 통과하는 조합을 우선한다.
docs/edge-local-dev-guide.md:242:- 안정 smoke: `think=false`, `think` 생략, `include_reasoning=false`, 또는 budget-only `thinking_token_budget`
docs/edge-local-dev-guide.md:243:- Lemonade-only/provider-specific smoke: `reasoning_effort=low|medium|high`
docs/edge-local-dev-guide.md:244:- vLLM/vLLM-MLX는 `reasoning_effort=none`만 disable alias로 처리하고, `low|medium|high`는 `unsupported think control` 오류를 반환한다.
docs/edge-local-dev-guide.md:261: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"include_reasoning":false}'
docs/edge-local-dev-guide.md:270: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"thinking_token_budget":512}'
docs/edge-local-dev-guide.md:273:`reasoning_effort=low` 같은 effort smoke는 Lemonade-only 검증이거나 provider route를 고정할 수 있을 때만 사용한다. provider pool에 vLLM/vLLM-MLX가 섞여 있으면 해당 provider로 라우팅될 때 실패할 수 있다.
```
### 최종 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs|unsupported think control|enable_thinking" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
agent-contract/outer/openai-compatible-api.md:168:- `think`
agent-contract/outer/openai-compatible-api.md:169:- `reasoning_effort`
agent-contract/outer/openai-compatible-api.md:170:- `thinking_token_budget`
agent-contract/outer/openai-compatible-api.md:171:- `include_reasoning`
agent-contract/outer/openai-compatible-api.md:175:- `think` (bool, optional): thinking/reasoning 생성 활성화 여부. 생략하면 provider 기본값을 유지한다. `false`는 thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다.
agent-contract/outer/openai-compatible-api.md:176:- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high` 중 하나. `none`은 `think=false`와 같은 disable 의미로 처리한다. `low`/`medium`/`high`는 provider가 지원하는 경우에만 전달한다.
agent-contract/outer/openai-compatible-api.md:177:- `thinking_token_budget` (int, optional): thinking token budget. 0 이상이어야 한다.
agent-contract/outer/openai-compatible-api.md:178:- `include_reasoning` (bool, optional): OpenAI-compatible 응답에서 `reasoning_content` 노출 여부. 생략하거나 `true`이면 provider reasoning delta/message를 노출할 수 있고, `false`이면 provider가 reasoning을 생성해도 response의 `reasoning_content`를 제거한다.
agent-contract/outer/openai-compatible-api.md:180:Provider별 think-control 정책:
agent-contract/outer/openai-compatible-api.md:183: - `think=false` 또는 `reasoning_effort=none` -> 내부 `chat_template_kwargs.enable_thinking=false`
agent-contract/outer/openai-compatible-api.md:184: - `think=true` 또는 budget-only -> 내부 `chat_template_kwargs.enable_thinking=true`
agent-contract/outer/openai-compatible-api.md:185: - `thinking_token_budget` -> 내부 `chat_template_kwargs.thinking_token_budget`
agent-contract/outer/openai-compatible-api.md:186: - `reasoning_effort=low|medium|high` -> `unsupported think control` 오류 반환
agent-contract/outer/openai-compatible-api.md:188: - `think` -> top-level `think`
agent-contract/outer/openai-compatible-api.md:189: - `reasoning_effort=low|medium|high` -> top-level `reasoning_effort`
agent-contract/outer/openai-compatible-api.md:190: - `thinking_token_budget` -> top-level `thinking_token_budget`
agent-contract/outer/openai-compatible-api.md:191: - `reasoning_effort=none` -> `think=false`, `reasoning_effort`는 전달하지 않음
agent-contract/outer/openai-compatible-api.md:196:- `reasoning_effort`가 비어 있거나 `none|low|medium|high` 외 값이면 400 에러.
agent-contract/outer/openai-compatible-api.md:197:- `thinking_token_budget`가 음수이면 400 에러.
agent-contract/outer/openai-compatible-api.md:198:- `think=false`와 `reasoning_effort=low|medium|high`가 함께 있으면 400 에러.
agent-contract/outer/openai-compatible-api.md:199:- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:200:- `reasoning_effort=none`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:204:- strict output가 활성화되면 `think=true`가 명시되지 않은 요청은 내부 실행 입력에서 `think=false`로 낮춘다.
agent-contract/outer/openai-compatible-api.md:219:- `options`, `chat_template_kwargs`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field
docs/edge-local-dev-guide.md:195: --default-chat-template-kwargs '{"enable_thinking": false}' \
docs/edge-local-dev-guide.md:200:Mac MLX provider의 주요 운영 파일은 `vllm-mlx.pid`, `logs/vllm-mlx.stdout.log`, `logs/vllm-mlx.stderr.log`다. 2026-06-24 직접 호출 측정 기준으로 `max_tokens=192`, `enable_thinking=false`에서 1/2/3 동시 총합은 약 `59.66`, `87.31`, `98.13 tok/s`였다.
docs/edge-local-dev-guide.md:236:Qwen 계열 모델은 thinking/reasoning 텍스트를 포함해 응답할 수 있다. 이 dev smoke에서는 thinking 출력을 실패로 보지 않고, HTTP 성공, final marker 포함, provider log/run count 증가를 기준으로 판정한다.
docs/edge-local-dev-guide.md:238:### Request-level think-control smoke
docs/edge-local-dev-guide.md:240:`/v1/chat/completions` 요청은 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`으로 thinking/reasoning 동작과 응답 노출을 제어할 수 있다. dev-runtime provider pool에서는 vLLM, vLLM-MLX, Lemonade 지원 범위가 다르므로 smoke 예시는 provider 공통으로 통과하는 조합을 우선한다.
docs/edge-local-dev-guide.md:242:- 안정 smoke: `think=false`, `think` 생략, `include_reasoning=false`, 또는 budget-only `thinking_token_budget`
docs/edge-local-dev-guide.md:243:- Lemonade-only/provider-specific smoke: `reasoning_effort=low|medium|high`
docs/edge-local-dev-guide.md:244:- vLLM/vLLM-MLX는 `reasoning_effort=none`만 disable alias로 처리하고, `low|medium|high`는 `unsupported think control` 오류를 반환한다.
docs/edge-local-dev-guide.md:246:예시 (thinking 비활성화):
docs/edge-local-dev-guide.md:252: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":false}'
docs/edge-local-dev-guide.md:255:예시 (thinking 생성은 허용하고 response reasoning_content만 숨김):
docs/edge-local-dev-guide.md:261: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"include_reasoning":false}'
docs/edge-local-dev-guide.md:270: -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"thinking_token_budget":512}'
docs/edge-local-dev-guide.md:273:`reasoning_effort=low` 같은 effort smoke는 Lemonade-only 검증이거나 provider route를 고정할 수 있을 때만 사용한다. provider pool에 vLLM/vLLM-MLX가 섞여 있으면 해당 provider로 라우팅될 때 실패할 수 있다.
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
## 코드리뷰 결과
- 종합 판정: PASS
- 리뷰 일시: 2026-07-03
- 리뷰 범위: `git diff` 기준 `agent-contract/outer/openai-compatible-api.md`(+39), `docs/edge-local-dev-guide.md`(+37) 및 active plan/review 문서. 코드 변경 없음(계획대로 docs-only).
### 차원별 평가
- Correctness: Pass — 계약 문서의 모든 동작 주장을 코드와 라인 단위 대조함.
- 지원 field 목록(계약 :150-171) == Edge decode allowlist(`apps/edge/internal/openai/chat_handler.go:99`), 22개 항목 완전 일치.
- Conflict 정책 5개(계약 :196-200) == `validateThinkControl`(`chat_handler.go:129-156`), decode 오류는 HTTP 400 (`chat_handler.go:28`).
- Strict output 강등(계약 :204) == `types.go:96-101`.
- `include_reasoning` semantics(계약 :178) == non-stream `chat_handler.go:287-289`, stream `stream.go:172`, 생략 시 기본 true `types.go:127-132`.
- vLLM/vLLM-MLX 정책(계약 :183-186) == `openai_compat.go:489-537`(effort 비-`none` 거부 :491-492, disable→`enable_thinking=false` :527-528, enable/budget-only→true :529-531, budget 전달 :533-534). 오류 문구 `unsupported think control` 일치.
- Lemonade 정책(계약 :188-191) == `openai_compat.go:539-551`. Unknown provider pass-through(계약 :192) == `openai_compat.go:552-562`.
- 금지 목록의 `think` 제거 및 `chat_template_kwargs` 유지(계약 :219)는 decode allowlist와 일치(미허용 key 400 거부).
- dev guide 안정/Lemonade-only smoke 구분과 caveat(:242-244, :273)는 위 provider 정책과 일치. endpoint `18083`/`qwen3.6:35b`는 기존 가이드 기준(:147, :154)과 일관. secret 원문 없음(`Bearer <token>` placeholder).
- Completeness: Pass — follow-up plan 체크리스트 5개 항목 모두 구현·검증됨. loop-0 FAIL의 Required 2건(계약 :176 일반 지원 오해 소지, dev guide effort smoke 안내) 모두 해소.
- Test coverage: Pass — docs-only 범위로 신규 테스트 불필요(plan 명시). 문서가 설명하는 동작의 기존 테스트 회귀 확인: `go test ./apps/edge/internal/openai/ ./apps/node/internal/adapters/openai_compat/` → ok 2 packages.
- API contract: Pass — 계약 원문 단일 source of truth 유지, `docs/openai-compatible-api-contract.md` pointer-only 유지(diff 없음), `agent-contract/index.md` 라우팅 유효. raw `options`/`chat_template_kwargs` wrapper 금지 유지.
- Code quality: Pass — 불필요/무관 변경 없음(diff는 계획 범위의 두 문서만).
- Implementation deviation: Pass — active 문서가 1차 구현 전 상태로 되돌아가 있어 field 목록/기본 semantics를 함께 복구한 것은 `계획 대비 변경 사항`에 사유와 함께 기록되었고, plan 최종 기대 결과(supported fields, provider policy, strict-output default, response visibility, raw-wrapper prohibition) 달성에 필요한 범위 내 보정임.
- Verification trust: Pass — `검증 결과`의 최종 검증 출력을 리뷰 시점에 동일 명령으로 재실행해 라인 번호/내용 일치 확인(38 라인). 중간 검증 출력도 현재 문서 상태와 일치.
- Spec conformance (SDD S06): Pass — `contract-docs` Acceptance Scenario 충족: 공개 field, 기본값, provider unsupported 정책이 계약 원문에 문서화되었고, evidence는 계약 문서 diff + deterministic `rg` 검증으로 확보됨.
### 발견된 문제
- Nit (리뷰 중 보수 완료): follow-up review stub의 `구현 체크리스트`/중간 검증 명령이 follow-up plan이 아닌 loop-0 plan에서 재사용되었고 `Archive Evidence Snapshot` 사본이 누락되어 있었다(이전 리뷰 세션의 stub 생성 drift). 구현 판단에는 지장이 없어 리뷰 중 plan 기준으로 정렬하고 snapshot을 복사해 보수했다. 구현 evidence(predecessor complete.log 경로 포함)는 plan/review 문서에 보존됨.
- Nit (기록만): `think=true`+`reasoning_effort=none` 동시 지정 시 disable이 우선한다는 우선순위가 계약에 명시 문장으로는 없다. 코드(vLLM `openai_compat.go:527`, Lemonade `:540`)와 모순되는 서술은 없고 정책 나열 순서상 유추 가능하므로 후속 문서 보강 후보로만 남긴다.
### 다음 단계
- PASS — `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/2026/07/`로 이동하고, `m-openai-compatible-think-control` 완료 이벤트 메타데이터를 보고한다.

View file

@ -0,0 +1,42 @@
# Complete - m-openai-compatible-think-control/04+01,02,03_contract_docs
## 완료 일시
2026-07-03
## 요약
OpenAI-compatible think-control 계약 문서와 dev 운영 문서를 실제 구현 동작(provider별 unsupported/mapping 정책, 기본값, 응답 노출 정책)과 일치시켰다. 루프 2회(FAIL 1회 → PASS)로 종결.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_local_G03_0.log` | `code_review_local_G03_0.log` | FAIL | 계약이 `reasoning_effort`를 일반 지원처럼 설명하고 dev guide가 vLLM/vLLM-MLX에서 실패 가능한 effort smoke를 안정 사용처럼 안내함 |
| `plan_local_G03_1.log` | `code_review_local_G03_1.log` | PASS | provider별 unsupported/mapping 정책 문서화, 되돌아가 있던 공개 field 문서 복구 포함. 코드 라인 대조와 deterministic `rg` 재실행 일치 |
## 구현/정리 내용
- `agent-contract/outer/openai-compatible-api.md`: Chat Completions 지원 field 목록에 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 추가. Think 제어 field semantics, Provider별 think-control 정책(vLLM/vLLM-MLX 내부 `chat_template_kwargs` 매핑과 `unsupported think control` 오류, Lemonade top-level field 전달, unknown provider pass-through), Conflict 정책(400 규칙 5종), Strict output 강등 정책 문서화. 금지 목록에서 `think`를 제거하고 raw `chat_template_kwargs` wrapper 금지 유지.
- `docs/edge-local-dev-guide.md`: Request-level think-control smoke 섹션 추가 — 안정 smoke 조합(`think=false`, 생략, `include_reasoning=false`, budget-only)과 Lemonade-only/provider-specific `reasoning_effort` 조합 분리, vLLM/vLLM-MLX caveat, curl 예시 3종(secret 원문 없음).
- `docs/openai-compatible-api-contract.md`: pointer-only 상태 유지 확인(변경 없음).
## 최종 검증
- `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs|unsupported think control|enable_thinking" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md` - PASS; 계약 원문 field/정책/금지 라인(:168-219)과 dev guide smoke 안내(:236-273)가 코드와 일치, pointer doc 매칭 없음(원문 미복제). 리뷰 시 동일 명령 재실행으로 출력 일치 확인(38 라인).
- `go test ./apps/edge/internal/openai/ ./apps/node/internal/adapters/openai_compat/` - PASS; ok 2 packages. 문서가 설명하는 Edge 검증/Node provider 매핑 동작 회귀 없음.
## Roadmap Completion
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Completed task ids:
- `contract-docs`: PASS; evidence=`plan_local_G03_1.log`, `code_review_local_G03_1.log`; 수정 문서=`agent-contract/outer/openai-compatible-api.md`, `docs/edge-local-dev-guide.md` (`docs/openai-compatible-api-contract.md`는 pointer-only 확인); verification=`rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs|unsupported think control|enable_thinking" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md`
- Not completed task ids: 없음
## 잔여 Nit
- `think=true`+`reasoning_effort=none` 동시 지정 시 disable이 우선한다는 우선순위 문장이 계약에 명시되어 있지 않다. 코드(`apps/node/internal/adapters/openai_compat/openai_compat.go:527`, `:540`)와 모순 서술은 없고 정책 나열 순서로 유추 가능하므로 후속 문서 보강 후보로만 남긴다.
## 후속 작업
- 없음

View file

@ -0,0 +1,133 @@
<!-- task=m-openai-compatible-think-control/04+01,02,03_contract_docs plan=1 tag=REVIEW_API -->
# Plan - REVIEW_API Contract Docs Follow-up
## 이 파일을 읽는 구현 에이전트에게
이 plan은 code-review FAIL 후속 루프다. 범위는 OpenAI-compatible think-control 계약/dev 문서의 provider별 unsupported 정책 보정으로 제한한다. 사용자에게 직접 질문하거나 선택지를 제시하지 않는다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 evidence를 남기고 멈춘다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `contract-docs`: OpenAI-compatible 계약 문서와 dev 운영 문서가 요청별 thinking/reasoning 제어 field, provider별 unsupported 정책, 기본값을 설명한다.
- Completion mode: check-on-pass
## Archive Evidence Snapshot
- Current archived plan log: `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/plan_local_G03_0.log`
- Current archived review log: `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/code_review_local_G03_0.log`
- Verdict: FAIL
- Required summary:
- `agent-contract/outer/openai-compatible-api.md:176` explains `reasoning_effort` values as generally supported, but vLLM/vLLM-MLX reject non-`none` effort with `unsupported think control`.
- `docs/edge-local-dev-guide.md:245` and `docs/edge-local-dev-guide.md:272` present `reasoning_effort=low` as dev-runtime smoke usage, but the provider pool can route to vLLM/vLLM-MLX where that request fails.
- Affected files:
- `agent-contract/outer/openai-compatible-api.md`
- `docs/edge-local-dev-guide.md`
- `docs/openai-compatible-api-contract.md` verification only; keep pointer-only unless link text becomes stale.
- Code evidence:
- `apps/node/internal/adapters/openai_compat/openai_compat.go:491`: vLLM/vLLM-MLX reject non-`none` `reasoning_effort`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:527`: vLLM/vLLM-MLX map disable to `chat_template_kwargs.enable_thinking=false`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:533`: vLLM/vLLM-MLX map budget to `chat_template_kwargs.thinking_token_budget`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:540`: Lemonade maps disable to top-level `think=false`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:546`: Lemonade forwards non-`none` `reasoning_effort`.
- `apps/node/internal/adapters/openai_compat/openai_compat.go:549`: Lemonade forwards `thinking_token_budget`.
- Verification evidence:
- Re-run of `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md` matched the archived review output and exposed the missing provider caveat.
- Roadmap carryover:
- SDD S06 requires contract document diff and documentation verification for `contract-docs`.
- Predecessor complete logs exist at:
- `agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/complete.log`
- `agent-task/archive/2026/07/m-openai-compatible-think-control/02+01_node_provider_mapping/complete.log`
- `agent-task/archive/2026/07/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/complete.log`
- Allowed narrow reread if needed:
- `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/code_review_local_G03_0.log`
- `agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/plan_local_G03_0.log`
## 분석 결과
### SDD 기준
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- Acceptance Scenario: S06 -> `contract-docs`
- Evidence Map: S06 requires contract document diff and documentation verification.
### 범위 결정 근거
- 코드 변경은 제외한다. 실제 provider mapping은 이미 `apps/node/internal/adapters/openai_compat`에 구현되어 있으며, 이번 루프는 계약/dev 문서가 그 동작을 정확히 설명하도록 맞춘다.
- `docs/openai-compatible-api-contract.md`는 계약 원문 포인터이므로 본문을 복제하지 않는다.
- 외부 dev-runtime smoke 실행은 `05+01,02,03,04_dev_smoke` 범위다. 이번 루프는 deterministic docs search로 검증한다.
### 빌드 등급
- `local-G03`: 문서 보정만 포함하고, 기존 구현/테스트와 line-level 대조로 검증 가능하다.
## 구현 체크리스트
- [x] `agent-contract/outer/openai-compatible-api.md`에 provider별 think-control unsupported/mapping 정책을 추가한다.
- [x] `docs/edge-local-dev-guide.md`의 request-level think-control smoke 설명과 예시가 dev-runtime provider pool에서 실패 가능한 `reasoning_effort=low` 요청을 안정 smoke처럼 안내하지 않도록 보정한다.
- [x] `docs/openai-compatible-api-contract.md`가 원문 포인터 상태인지 확인하고 계약 본문을 복제하지 않는다.
- [x] `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs|unsupported think control|enable_thinking" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md`를 실행해 문서 상태를 확인한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- `04+01,02,03_contract_docs` depends on `01`, `02`, and `03`; predecessor complete logs are listed in `Archive Evidence Snapshot`.
- Contract update work should follow `agent-contract/index.md`; matching contract is `agent-contract/outer/openai-compatible-api.md`.
### [REVIEW_API-1] Provider Unsupported Policy Docs
#### 문제
계약 문서와 dev guide가 `reasoning_effort`를 일반 지원 field처럼 설명하지만, 실제 provider mapping은 provider별로 다르다. vLLM/vLLM-MLX는 `reasoning_effort=none`만 disable 의미로 처리하고 `low`/`medium`/`high`는 unsupported error로 반환한다. Lemonade는 non-`none` effort와 budget을 top-level field로 전달한다.
#### 해결 방법
- 계약 원문의 Think 제어 field 섹션 아래에 provider별 정책을 추가한다.
- vLLM/vLLM-MLX:
- `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 error
- 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`, effort field omitted
- Dev guide는 stable smoke 예시를 `think=false`, omitted default, `include_reasoning=false`, 또는 budget-only로 유지하고, `reasoning_effort=low`는 Lemonade-only/provider-specific caveat가 없으면 기본 smoke 예시에서 제거한다.
#### 수정 파일 및 체크리스트
- [x] `agent-contract/outer/openai-compatible-api.md`: provider별 unsupported/mapping 정책 추가.
- [x] `docs/edge-local-dev-guide.md`: dev-runtime think-control smoke caveat와 예시 보정.
- [x] `docs/openai-compatible-api-contract.md`: pointer-only 상태 확인.
#### 테스트 작성
- 코드 테스트 없음. deterministic `rg` 검증과 기존 Node adapter 코드 라인 대조로 충분하다.
#### 중간 검증
```bash
rg --sort path -n "unsupported think control|enable_thinking|reasoning_effort|thinking_token_budget|include_reasoning" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md
```
Expected: provider별 unsupported/mapping 정책과 stable dev smoke guidance가 모두 보인다.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `agent-contract/outer/openai-compatible-api.md` | REVIEW_API-1 |
| `docs/edge-local-dev-guide.md` | REVIEW_API-1 |
| `docs/openai-compatible-api-contract.md` | REVIEW_API-1 verification only |
## 최종 검증
```bash
rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs|unsupported think control|enable_thinking" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
```
Expected: contract source describes supported public fields, provider별 unsupported/mapping policy, strict-output default, response visibility, and raw-wrapper prohibition; dev guide references request-level smoke usage without presenting vLLM/vLLM-MLX-unsupported `reasoning_effort=low` as a stable provider-pool smoke; pointer doc remains a pointer.
모든 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -1,104 +0,0 @@
<!-- task=m-openai-compatible-think-control/04+01,02,03_contract_docs plan=0 tag=API -->
# Code Review Reference - API
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Start only after predecessors `01`, `02`, and `03` 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/04+01,02,03_contract_docs, plan=0, tag=API
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `contract-docs`: OpenAI-compatible 계약 문서와 dev 운영 문서가 요청별 thinking/reasoning 제어 field, provider별 unsupported 정책, 기본값을 설명한다.
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다.
계약 원문과 사람용 docs가 코드와 일치하는지, raw wrapper 금지가 유지되는지, secrets가 쓰이지 않았는지 확인한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] OpenAI-Compatible Contract Update | [ ] |
| [API-2] Dev Operations Docs Update | [ ] |
## 구현 체크리스트
- [ ] Predecessor `complete.log` for `01`, `02`, and `03`를 확인한 뒤 시작한다.
- [ ] `agent-contract/outer/openai-compatible-api.md`의 Chat Completions field list, field semantics, conflict/unsupported policy, response visibility policy, forbidden field list를 구현 결과와 일치시킨다.
- [ ] `docs/edge-local-dev-guide.md`의 dev-runtime provider pool section에 request-level think-control smoke usage와 default/visibility caveat를 짧게 추가한다.
- [ ] `docs/openai-compatible-api-contract.md`가 원문 포인터로 유지되는지 확인하고 계약 본문을 복제하지 않는다.
- [ ] `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md`를 실행해 문서 상태를 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] active plan/review 파일을 `.log`로 아카이브한다.
- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다.
- [ ] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `agent-contract/outer/openai-compatible-api.md`가 source of truth이고 docs pointer가 계약 본문을 복제하지 않는지 확인한다.
- `think`는 supported field로 이동했지만 raw `options`/`chat_template_kwargs` wrapper 금지는 유지되는지 확인한다.
- dev guide에 secret/token 원문이 추가되지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### API-1 중간 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md
(output)
```
### API-2 중간 검증
```bash
$ rg --sort path -n "think=false|include_reasoning|reasoning_effort|thinking_token_budget" docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
(output)
```
### 최종 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
(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?**

View file

@ -235,6 +235,43 @@ Node host OS 재부팅은 필요하지 않다. Edge process 재시작이나 일
Qwen 계열 모델은 thinking/reasoning 텍스트를 포함해 응답할 수 있다. 이 dev smoke에서는 thinking 출력을 실패로 보지 않고, HTTP 성공, final marker 포함, provider log/run count 증가를 기준으로 판정한다.
### 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 공통으로 통과하는 조합을 우선한다.
- 안정 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` 오류를 반환한다.
예시 (thinking 비활성화):
```bash
curl -fsS http://toki-labs.com:18083/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":false}'
```
예시 (thinking 생성은 허용하고 response reasoning_content만 숨김):
```bash
curl -fsS http://toki-labs.com:18083/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"include_reasoning":false}'
```
예시 (budget-only):
```bash
curl -fsS http://toki-labs.com:18083/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-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로 라우팅될 때 실패할 수 있다.
현재 공개 API는 개별 request의 최종 `node_id`를 응답에 노출하지 않는다. 요청별 배정을 확정해야 할 때는 Edge dispatch trace/log를 추가한 뒤 판정한다.
## 7. 기본 Smoke