chore: update knowledge-tool-optimization-extension phase, SDD, and openai changes

This commit is contained in:
toki 2026-07-03 14:29:51 +09:00
parent 8838e0aced
commit 3e1248d584
17 changed files with 2066 additions and 3 deletions

View file

@ -19,6 +19,10 @@ Ollama serving 경로와 운영 기반이 안정화된 뒤, 단계 호출, tool/
- 경로: `agent-roadmap/archive/phase/knowledge-tool-optimization-extension/milestones/tool-call-runtime-validation-retry.md`
- 요약: 명시적 `tools[]`/`tool_choice` 계약이 있는 OpenAI-compatible 요청에서 IOP 내부 provider 응답의 마지막 tool call을 검증하고, schema 불일치 시 같은 요청을 bounded exact replay로 재시도하는 runtime-only MVP를 구현한다.
- [계획] OpenAI-compatible Think 제어 MVP
- 경로: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- 요약: OpenAI-compatible Chat Completions 요청에서 thinking/reasoning 생성과 응답 노출을 요청별로 제어하고 provider별 option 매핑과 unsupported 정책을 구현한다.
- [스케치] 단계 호출과 검증 최적화 MVP
- 경로: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md`
- 요약: 요청 의도 분석, 실제 작업, 검증/schema 강제, 오류 시 회귀를 단계 호출 실행 모드의 MVP 후보로 스케치한다.

View file

@ -0,0 +1,81 @@
# Milestone: OpenAI-compatible Think 제어 MVP
## 위치
- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
## 목표
OpenAI-compatible Chat Completions 요청에서 thinking/reasoning 생성을 요청별로 켜고 끌 수 있게 한다.
provider runtime은 reasoning-capable 상태로 유지하되, Edge가 공개 요청 필드를 검증하고 Node provider adapter가 vLLM, vLLM-MLX, Lemonade의 지원 필드로 매핑한다.
호출자가 reasoning 생성을 끄거나 reasoning 응답 노출만 숨길 수 있어야 하며, 지원하지 않는 조합은 조용히 무시하지 않고 명확한 compatibility error 또는 정의된 fallback으로 처리한다.
## 상태
[계획]
## 승격 조건
- 없음
## 구현 잠금
- 상태: 해제
- SDD: 필요
- SDD 문서: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- SDD 사유: OpenAI-compatible request schema, provider option 매핑, streaming response 노출 정책이 바뀌는 API 계약 Milestone이다.
- 잠금 해제 조건:
- [x] SDD 잠금이 해제되어 있다.
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다.
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다.
- [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다.
- 결정 필요: 없음
## 범위
- `/v1/chat/completions` 요청의 thinking/reasoning 제어 field 수신, 검증, 내부 run input 전달
- `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`의 의미와 충돌 정책
- raw `chat_template_kwargs`는 공개 request field로 직접 열지 않고, Edge/Node가 provider별 내부 option으로 생성하는 매핑 정책
- vLLM Qwen3, vLLM-MLX Qwen3, Lemonade provider의 reasoning-capable runtime에 대한 요청별 option 매핑
- streaming/non-streaming 응답의 `reasoning_content` 노출/억제 정책
- OpenAI-compatible 계약 문서, edge/node 단위 테스트, dev-runtime smoke
## 기능
### Epic: [think-control] Request-Level Think Control
OpenAI-compatible 호출자가 요청 단위로 thinking/reasoning 생성과 노출을 제어하는 capability를 묶는다.
- [ ] [request-contract] `/v1/chat/completions``think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`을 허용하고 타입, 범위, 충돌 조합을 검증한다. 검증: 알 수 없는 provider 전용 wrapper는 계속 400으로 거부되고, 허용 필드는 Edge request decoder와 계약 테스트를 통과한다.
- [ ] [provider-mapping] Edge run input과 Node `openai_compat` adapter가 요청별 think 설정을 provider별 top-level field 또는 내부 `chat_template_kwargs.enable_thinking`으로 매핑한다. 검증: vLLM Qwen3, vLLM-MLX, Lemonade mock이 같은 요청 플래그에 대해 기대 request body를 받는다.
- [ ] [reasoning-visibility] `include_reasoning=false`가 provider reasoning 생성 여부와 별개로 OpenAI-compatible 응답의 `reasoning_content` 노출을 억제한다. 검증: stream과 non-stream 응답 모두 reasoning delta/message field가 빠지고 content/tool output은 유지된다.
- [ ] [default-compat] think 관련 필드가 생략된 기존 요청은 현재 provider 기본 thinking 동작을 유지한다. 검증: 기존 chat completion, tool calling, provider pool 테스트가 회귀 없이 통과한다.
- [ ] [dev-smoke] dev-runtime 세 provider(mac vLLM-MLX, GX10 vLLM, OneXPlayer Lemonade)에서 `think=false`, 기본값, `include_reasoning=false` smoke가 통과한다. 검증: provider raw trace와 Edge OpenAI-compatible SSE를 비교해 reasoning 생성/노출 상태가 기대와 일치한다.
- [ ] [contract-docs] OpenAI-compatible 계약 문서와 dev 운영 문서가 요청별 thinking/reasoning 제어 field, provider별 unsupported 정책, 기본값을 설명한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 계획 Milestone이며 기능 Task가 아직 충족되지 않았다.
- 검토 항목: 모든 기능 Task의 테스트와 dev-runtime smoke evidence가 필요하다.
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 없음
## 범위 제외
- `/v1/responses`의 streaming 지원 또는 reasoning output item 확장
- provider runtime 기동 옵션 자동 조정과 lifecycle 관리
- hidden reasoning token의 정확한 usage 산출
- 단계 호출 planner/generator/verifier 모드 전반의 제품 UX
- raw provider payload 장기 저장과 운영 ledger schema 구현
## 작업 컨텍스트
- 관련 경로: `apps/edge/internal/openai`, `apps/node/internal/adapters/openai_compat`, `agent-contract/outer/openai-compatible-api.md`, `docs/openai-compatible-api-contract.md`, `agent-test/dev`
- 표준선(선택): provider runtime은 reasoning-capable 상태로 띄우고, 요청별 제어는 OpenAI-compatible Edge request 계약에서 검증한 뒤 provider별 지원 option으로 매핑한다.
- 표준선(선택): 공개 request 표면은 provider raw wrapper를 그대로 열지 않고, IOP가 검증 가능한 OpenAI-compatible field와 내부 provider option 매핑으로 둔다.
- 선행 작업: OpenAI-compatible reasoning raw trace와 dev-runtime provider reasoning 활성화
- 후속 작업: 단계 호출과 검증 최적화 MVP, 요청 실행 로그와 Usage Ledger 기반
- 확인 필요: 없음

View file

@ -0,0 +1,106 @@
# SDD: OpenAI-compatible Think 제어 MVP
## 위치
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Phase: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
## 상태
[승인됨]
## SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
- 없음
## 문제 / 비목표
- 문제: dev-runtime provider는 reasoning-capable 상태로 기동되어 있지만, 현재 OpenAI-compatible 요청자는 요청 단위로 thinking 생성을 끄거나 reasoning 응답 노출을 숨길 수 없다. 공개 API가 provider별 raw option을 그대로 열면 호환성과 검증 경계가 흐려지므로, Edge가 검증 가능한 field를 받고 Node adapter가 provider별 option으로 매핑해야 한다.
- 비목표:
- `/v1/responses`의 streaming 또는 reasoning output item 확장
- provider runtime 기동/lifecycle 자동화
- usage ledger의 hidden reasoning token 정산
- 단계 호출 planner/generator/verifier 제품 UX 확정
## Source of Truth
| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` | 기능 Task와 완료 evidence 기준 |
| Code | `apps/edge/internal/openai`, `apps/node/internal/adapters/openai_compat` | request decode, run input, provider request body, response filtering 구현 기준 |
| Contract | `agent-contract/outer/openai-compatible-api.md` | 공개 OpenAI-compatible request/response 계약 |
| External Provider | vLLM Qwen3, vLLM-MLX Qwen3, Lemonade | provider별 reasoning option 지원 여부는 dev smoke와 mock 테스트로 확인 |
| User Decision | 없음 | 공개 field와 기본값은 기존 OpenAI-compatible 원칙과 provider 동작을 기준으로 확정 가능 |
## State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| default | 요청에 think 관련 field가 없다 | provider-default | 기존 요청 호환성 유지 |
| think-disabled | `think=false` 또는 `reasoning_effort=none`이 유효하다 | content-only | Edge request validation, provider request body |
| think-enabled | `think=true` 또는 thinking budget/effort가 유효하다 | reasoning-capable | provider runtime capability와 request body |
| reasoning-hidden | `include_reasoning=false`가 유효하다 | response-filtered | Edge stream/non-stream response writer |
| unsupported | provider가 요청 조합을 지원하지 않거나 field 타입/범위가 틀리다 | error | OpenAI-compatible 400 error 또는 명시 fallback 정책 |
## Interface Contract
- 계약 원문: `agent-contract/outer/openai-compatible-api.md`
- 입력:
- `think`: optional boolean. `false`는 reasoning/thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다.
- `reasoning_effort`: optional string. `none``think=false`와 같은 disable 의미로 처리하고, 그 외 값은 provider가 지원하는 경우에만 전달한다.
- `thinking_token_budget`: optional non-negative integer. provider가 지원하면 thinking budget으로 전달하고, 지원하지 않으면 명시 error 또는 정의된 fallback을 반환한다.
- `include_reasoning`: optional boolean. `false`이면 provider가 reasoning을 생성하더라도 OpenAI-compatible 응답의 `reasoning_content`를 노출하지 않는다.
- 출력:
- streaming: `include_reasoning`이 true 또는 생략이면 reasoning delta를 `delta.reasoning_content`로 반환하고, false이면 reasoning delta를 쓰지 않는다.
- non-streaming: `include_reasoning`이 true 또는 생략이면 `message.reasoning_content`를 반환할 수 있고, false이면 반환하지 않는다.
- content/tool output: reasoning 노출 정책과 별개로 기존 content/tool_calls 계약을 유지한다.
- 금지:
- 공개 request에 raw `options` wrapper 또는 provider raw `chat_template_kwargs` wrapper를 그대로 추가하지 않는다.
- provider가 지원하지 않는 think/budget 조합을 조용히 무시하지 않는다.
- `metadata`에 think 제어 field를 넣어 공개 API 의미를 우회하지 않는다.
- `include_reasoning=false`를 provider raw trace나 진단 로그 비활성화로 해석하지 않는다.
## Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `request-contract` | Chat Completions 요청에 `think=false`가 있다 | Edge가 요청을 decode한다 | 요청은 400으로 거부되지 않고 run input에 think disable 의미가 들어간다 |
| S02 | `provider-mapping` | provider pool 후보가 vLLM Qwen3, vLLM-MLX, Lemonade다 | `think=false` 요청을 dispatch한다 | 각 provider request body가 지원 field 또는 내부 `chat_template_kwargs.enable_thinking=false`로 매핑된다 |
| S03 | `reasoning-visibility` | provider가 reasoning delta를 반환한다 | 요청에 `include_reasoning=false`가 있다 | Edge OpenAI-compatible stream/non-stream 응답에는 `reasoning_content`가 없다 |
| S04 | `default-compat` | 기존 caller가 think field를 생략한다 | Chat Completions를 호출한다 | 현재 provider 기본 thinking 동작과 기존 content/tool behavior가 유지된다 |
| S05 | `dev-smoke` | dev-runtime 세 provider가 connected/healthy다 | 기본값, `think=false`, `include_reasoning=false` smoke를 실행한다 | provider raw trace와 Edge SSE의 reasoning 생성/노출 상태가 기대와 일치한다 |
| S06 | `contract-docs` | 구현이 완료되었다 | 계약 문서와 dev 문서를 확인한다 | 공개 field, 기본값, provider unsupported 정책이 문서화되어 있다 |
## Evidence Map
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | Edge OpenAI request decoder/unit test | `agent-task/m-openai-compatible-think-control/...` | `Roadmap Completion``request-contract`와 테스트 명령 기록 |
| S02 | Node `openai_compat` request body unit test와 provider별 mock | `agent-task/m-openai-compatible-think-control/...` | `Roadmap Completion``provider-mapping`과 mock 검증 기록 |
| S03 | stream/non-stream response filtering test | `agent-task/m-openai-compatible-think-control/...` | `Roadmap Completion``reasoning-visibility`와 테스트 명령 기록 |
| S04 | 기존 chat/tool/provider pool regression test | `agent-task/m-openai-compatible-think-control/...` | `Roadmap Completion``default-compat`와 회귀 테스트 기록 |
| S05 | dev-runtime smoke 결과와 raw trace/Edge SSE 비교 요약 | `agent-task/m-openai-compatible-think-control/...` | `Roadmap Completion``dev-smoke`와 provider별 관측 기록 |
| S06 | 계약 문서 diff와 문서 검증 | `agent-task/m-openai-compatible-think-control/...` | `Roadmap Completion``contract-docs`와 수정 문서 경로 기록 |
## Cross-repo Dependencies
- 없음
## Drift Check
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
## 사용자 리뷰 이력
- 없음
## 작업 컨텍스트
- 표준선: OpenAI-compatible 공개 표면은 provider raw wrapper가 아니라 검증 가능한 top-level field를 받으며, provider별 세부 option은 Edge/Node adapter 내부 매핑으로 처리한다.
- 후속 SDD: 없음

View file

@ -0,0 +1,134 @@
<!-- task=m-openai-compatible-think-control/01_edge_request_contract plan=0 tag=API -->
# Code Review Reference - API
> **[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 linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
## 개요
date=2026-07-03
task=m-openai-compatible-think-control/01_edge_request_contract, plan=0, tag=API
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `request-contract`: `/v1/chat/completions`가 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`을 허용하고 타입, 범위, 충돌 조합을 검증한다.
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
1. 판정을 append한다.
2. active `CODE_REVIEW-local-G04.md`와 `PLAN-local-G04.md`를 `.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
4. PASS이고 task group이 `m-openai-compatible-think-control`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정은 런타임 책임이다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] Edge Request Decode And Validation | [x] |
## 구현 체크리스트
- [x] `chatCompletionRequest`에 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 필드를 추가하고 `/v1/chat/completions` decoder allowlist와 validation을 갱신한다. 검증: unknown provider wrapper는 계속 400, 허용 필드는 Edge request decoder/unit test 통과.
- [x] `runInput`이 validated think-control 값을 내부 input map에 보존하되, omitted field는 기존 provider default 동작을 유지하도록 어떤 key도 추가하지 않는다.
- [x] `apps/edge/internal/openai/server_test.go`에 허용 필드, 범위/충돌 실패, unknown wrapper 회귀 테스트를 추가/갱신한다.
- [x] `go test -count=1 ./apps/edge/internal/openai`를 실행한다.
- [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] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다.
## 계획 대비 변경 사항
없음. 계획서에 기술된 모든 사항을 충족하여 정상 구현하였습니다.
## 주요 설계 결정
`chatCompletionRequest` 에 JSON 매핑과 decoding 시 raw 필드 validation(`validateThinkControl`)을 두어 strict-output 상황과 dynamic field validation을 결합시켰습니다. 생략된 필드가 internal map에 누출되지 않도록 구조체 포인터 멤버 변수로 널 체크를 적용하여 runInput 맵에 보존하도록 구현했습니다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `think`는 더 이상 unsupported가 아니지만 `options`, `format`, `keep_alive`는 계속 400인지 확인한다.
- omitted think-control field가 run input에 새 key를 만들지 않는지 확인한다.
- invalid effort/budget conflict가 400으로 테스트되는지 확인한다.
## 검증 결과
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
### API-1 중간 검증
```bash
$ go test -count=1 ./apps/edge/internal/openai
ok iop/apps/edge/internal/openai 1.568s
```
### 최종 검증
```bash
$ go test -count=1 ./apps/edge/internal/openai
ok iop/apps/edge/internal/openai 1.568s
```
---
> **[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
- 차원별 평가:
- correctness: Pass
- completeness: Pass
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- implementation deviation: Pass
- verification trust: Pass
- spec conformance: Pass
- 발견된 문제: 없음
- 다음 단계: PASS 종결 절차를 진행한다. `complete.log` 작성 후 task directory를 archive로 이동하고, `m-openai-compatible-think-control` 완료 이벤트 메타데이터를 보고한다.
### 리뷰 메모
- SDD S01(`request-contract`) 기준으로 `/v1/chat/completions` decoder allowlist, think-control validation, run input key 보존, omitted field 미삽입 테스트를 확인했다.
- 리뷰 중 `gofmt`로 테스트 파일의 공백 포맷만 정리했다.
- 최종 재검증: `go test -count=1 ./apps/edge/internal/openai` PASS (`ok iop/apps/edge/internal/openai 1.564s`), `git diff --check -- apps/edge/internal/openai/types.go apps/edge/internal/openai/chat_handler.go apps/edge/internal/openai/server_test.go` PASS.

View file

@ -0,0 +1,42 @@
# Complete - m-openai-compatible-think-control/01_edge_request_contract
## 완료 일시
2026-07-03T05:27:49Z
## 요약
Edge `/v1/chat/completions` think-control request contract 구현을 1회 리뷰 루프로 PASS 완료했다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_local_G04_0.log` | `code_review_local_G04_0.log` | PASS | SDD S01 `request-contract` 기준 decoder, validation, run input 보존, 단위 테스트 evidence 충족 |
## 구현/정리 내용
- `chatCompletionRequest`와 decoder allowlist에 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`을 추가하고 범위/충돌 검증을 연결했다.
- validated think-control 값만 `SubmitRunRequest.Input`에 보존하고, omitted field는 내부 input key를 만들지 않도록 했다.
- 허용 필드, invalid 조합, 기존 unsupported wrapper 회귀 테스트를 추가/갱신했다.
- 리뷰 중 `gofmt`로 테스트 파일 공백 포맷만 정리했다.
## 최종 검증
- `go test -count=1 ./apps/edge/internal/openai` - PASS; `ok iop/apps/edge/internal/openai 1.564s`
- `git diff --check -- apps/edge/internal/openai/types.go apps/edge/internal/openai/chat_handler.go apps/edge/internal/openai/server_test.go` - PASS; 출력 없음
## Roadmap Completion
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Completed task ids:
- `request-contract`: PASS; evidence=`agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/plan_local_G04_0.log`, `agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/code_review_local_G04_0.log`; verification=`go test -count=1 ./apps/edge/internal/openai`
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,180 @@
<!-- task=m-openai-compatible-think-control/01_edge_request_contract plan=0 tag=API -->
# Plan - API Edge Request Contract
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성까지가 구현 완료다. 검증을 실행하고 실제 출력/변경 내용을 채운 뒤 active 파일을 그대로 두고 review ready로 보고한다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 막으면 review stub의 `사용자 리뷰 요청`만 채우고 멈춘다. 직접 사용자에게 묻거나 `USER_REVIEW.md`, `complete.log`, archive 파일을 만들지 않는다.
## 배경
현재 Milestone은 승인된 SDD를 통해 Chat Completions의 요청별 thinking 제어 필드를 공개 API로 열기로 했다. 현재 Edge decoder는 `think`를 unsupported field로 거부하고, strict-output 내부 경로만 `input["think"]=false`를 만든다. 먼저 공개 request contract와 내부 run input key를 고정해야 Node provider mapping과 response filtering split이 같은 기준을 쓸 수 있다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 선택된 Milestone lock decision이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 중 직접 사용자 질문, 선택지 제시, `request_user_input` 호출은 금지이며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성을 소유한다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `request-contract`: `/v1/chat/completions`가 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`을 허용하고 타입, 범위, 충돌 조합을 검증한다.
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-roadmap/current.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-contract/index.md`
- `agent-contract/outer/openai-compatible-api.md`
- `apps/edge/internal/openai/types.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/server_test.go`
### SDD 기준
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- 상태: `[승인됨]`, SDD 잠금: `해제`, 사용자 리뷰: 없음
- Acceptance Scenario: S01 -> `request-contract`
- Evidence Map: S01 requires Edge OpenAI request decoder/unit test and `Roadmap Completion` evidence for `request-contract`.
- 이 plan은 S01만 완료 대상으로 삼는다. S02/S03/S05/S06은 sibling split에서 처리한다.
### 테스트 환경 규칙
- test_env: `local`
- 읽은 env/profile: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`
- 적용 명령: `go test -count=1 ./apps/edge/internal/openai`
- 확인한 baseline: `go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/adapters/openai_compat` PASS
- 외부 runner preflight: 이 split은 현재 checkout의 Edge unit test만 필요하므로 없음.
### 테스트 커버리지 공백
- 새 request field 허용: 현재 `TestChatCompletionsRejectsUnsupportedFields`가 `think`를 400으로 기대한다. 허용/거부 경계 테스트가 필요하다.
- 타입/범위/충돌: 현재 `thinking_token_budget`, `reasoning_effort`, `include_reasoning` 검증 테스트가 없다.
- 기존 unknown provider wrapper 거부: `options`, `format`, `keep_alive` 400 회귀를 유지해야 한다.
### 심볼 참조
- renamed/removed symbol: none.
### 분할 판단
- split decision policy를 먼저 적용했다.
- shared task group: `agent-task/m-openai-compatible-think-control/`
- siblings:
- `01_edge_request_contract`: independent foundation, this plan.
- `02+01_node_provider_mapping`: depends on 01 run input keys.
- `03+01_edge_reasoning_visibility`: depends on 01 `include_reasoning` request field.
- `04+01,02,03_contract_docs`: waits for implemented behavior before docs.
- `05+01,02,03,04_dev_smoke`: waits for code/docs and external runtime.
- predecessor completion: none required for `01_edge_request_contract`; active/archive complete logs for this task group were absent.
### 범위 결정 근거
- Node provider request body mapping is excluded; `02+01_node_provider_mapping` owns it.
- `reasoning_content` response filtering is excluded; `03+01_edge_reasoning_visibility` owns it.
- Contract/dev docs and external smoke are excluded to avoid documenting or smoking behavior before implementation.
### 빌드 등급
- `local-G04`: public API decode/validation change with bounded Edge files and deterministic unit tests.
## 구현 체크리스트
- [ ] `chatCompletionRequest`에 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 필드를 추가하고 `/v1/chat/completions` decoder allowlist와 validation을 갱신한다. 검증: unknown provider wrapper는 계속 400, 허용 필드는 Edge request decoder/unit test 통과.
- [ ] `runInput`이 validated think-control 값을 내부 input map에 보존하되, omitted field는 기존 provider default 동작을 유지하도록 어떤 key도 추가하지 않는다.
- [ ] `apps/edge/internal/openai/server_test.go`에 허용 필드, 범위/충돌 실패, unknown wrapper 회귀 테스트를 추가/갱신한다.
- [ ] `go test -count=1 ./apps/edge/internal/openai`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [API-1] Edge Request Decode And Validation
#### 문제
- [apps/edge/internal/openai/types.go](/config/workspace/iop/apps/edge/internal/openai/types.go:13)의 `chatCompletionRequest`에 think-control 필드가 없다.
- [apps/edge/internal/openai/chat_handler.go](/config/workspace/iop/apps/edge/internal/openai/chat_handler.go:92)의 decoder allowlist가 새 필드를 모두 거부한다.
- [apps/edge/internal/openai/server_test.go](/config/workspace/iop/apps/edge/internal/openai/server_test.go:2152)는 `think`를 unsupported로 기대한다.
Before:
```go
// apps/edge/internal/openai/chat_handler.go:97
for key := range raw {
switch key {
case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store":
default:
return fmt.Errorf("%s is not supported for /v1/chat/completions", key)
}
}
```
After:
```go
// apps/edge/internal/openai/chat_handler.go:97
case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store", "think", "reasoning_effort", "thinking_token_budget", "include_reasoning":
```
Validation baseline:
- `thinking_token_budget` must be `>= 0`.
- `reasoning_effort` allowed values: `none`, `low`, `medium`, `high`; empty string is invalid when present.
- `think=false` conflicts with `reasoning_effort` values other than `none`.
- `thinking_token_budget` conflicts with `think=false` or `reasoning_effort=none`.
- `include_reasoning` is exposure-only and does not conflict with generation controls.
#### 해결 방법
- Add pointer fields to distinguish omitted from zero values.
- Add `validateThinkControl(req)` from `decodeChatCompletionRequest` after existing sampling validation.
- Add helper methods on `chatCompletionRequest` to copy present fields into `input`: `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`.
- Keep strict-output `input["think"]=false` behavior unless request explicitly set `think=true`; explicit `think=true` should win only when validation allows it.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/types.go`: request fields, run input copy, helper functions.
- [ ] `apps/edge/internal/openai/chat_handler.go`: decoder allowlist and validation call.
- [ ] `apps/edge/internal/openai/server_test.go`: request contract tests and unsupported wrapper regression.
#### 테스트 작성
- Add `TestChatCompletionsAcceptsThinkControlFields` asserting `fake.req.Input`.
- Update `TestChatCompletionsRejectsUnsupportedFields` to remove `think` from unsupported cases while keeping `options`, `format`, `keep_alive`.
- Add `TestChatCompletionsRejectsInvalidThinkControlFields` table for negative budget, bad effort, `think=false` + `high`, and `reasoning_effort=none` + budget.
#### 중간 검증
```bash
go test -count=1 ./apps/edge/internal/openai
```
Expected: PASS.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/edge/internal/openai/types.go` | API-1 |
| `apps/edge/internal/openai/chat_handler.go` | API-1 |
| `apps/edge/internal/openai/server_test.go` | API-1 |
## 최종 검증
```bash
go test -count=1 ./apps/edge/internal/openai
```
Expected: PASS. Fresh execution is required; do not rely on Go test cache.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,105 @@
<!-- task=m-openai-compatible-think-control/02+01_node_provider_mapping plan=0 tag=API -->
# Code Review Reference - API
> **[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.
> Start only after predecessor `01_edge_request_contract` has `complete.log`.
> Fill implementation-owned sections, then stop with active files in place and report ready for review. Finalization is review-agent-only.
## 개요
date=2026-07-03
task=m-openai-compatible-think-control/02+01_node_provider_mapping, plan=0, tag=API
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `provider-mapping`: Edge run input과 Node `openai_compat` adapter가 요청별 think 설정을 provider별 top-level field 또는 내부 `chat_template_kwargs.enable_thinking`으로 매핑한다.
- `default-compat`: think 관련 필드가 생략된 기존 요청은 현재 provider 기본 thinking 동작을 유지한다.
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다.
실제 provider body mapping, omitted-field absence, predecessor dependency 준수, 검증 출력 일치를 확인한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] Provider-Specific Think Body Mapping | [ ] |
| [API-2] Default Compatibility Regression | [ ] |
## 구현 체크리스트
- [ ] `01_edge_request_contract` predecessor `complete.log`를 확인한 뒤 시작한다.
- [ ] `openai_compat` adapter request body builder가 provider label을 받아 think-control input을 provider별 body로 매핑하도록 바꾼다. 검증: vLLM Qwen3, vLLM-MLX, Lemonade mock이 같은 요청 플래그에 대해 기대 request body를 받는다.
- [ ] omitted think-control field가 `think`, `reasoning_effort`, `thinking_token_budget`, `chat_template_kwargs`를 만들지 않는 회귀 테스트를 추가한다. 검증: 기존 chat/tool/provider pool targeted tests가 통과한다.
- [ ] `go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai`를 실행한다.
- [ ] 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으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- vLLM/vLLM-MLX body가 raw Edge wrapper 없이 adapter 내부 `chat_template_kwargs`를 생성하는지 확인한다.
- Lemonade mapping과 omitted default absence가 별도 테스트되는지 확인한다.
- `tools`/`tool_choice` fallback behavior가 깨지지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### API-1 중간 검증
```bash
$ go test -count=1 ./apps/node/internal/adapters/openai_compat
(output)
```
### API-2 중간 검증
```bash
$ go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai
(output)
```
### 최종 검증
```bash
$ go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai
(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

@ -0,0 +1,217 @@
<!-- task=m-openai-compatible-think-control/02+01_node_provider_mapping plan=0 tag=API -->
# Plan - API Node Provider Mapping
## 이 파일을 읽는 구현 에이전트에게
`01_edge_request_contract``complete.log`가 있어야 이 plan을 구현한다. 구현 완료는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성까지다. Milestone lock decision이 막으면 review stub의 `사용자 리뷰 요청`에만 기록하고, 직접 사용자에게 질문하거나 archive/complete 파일을 만들지 않는다.
## 배경
Edge가 공개 think-control 필드를 내부 input map으로 전달해도 Node adapter가 provider별 request body로 매핑하지 않으면 실제 generation 제어가 동작하지 않는다. 현재 `openai_compat` adapter는 `think`를 단순 top-level passthrough하고, provider label별 `chat_template_kwargs.enable_thinking` 매핑이 없다. 이 split은 vLLM, vLLM-MLX, Lemonade mock request body를 고정하고 omitted field 회귀를 같이 닫는다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 선택된 Milestone lock decision이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 환경/secret/provider 접근 실패는 사용자 리뷰가 아니라 검증 blocker 또는 follow-up이다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `provider-mapping`: Edge run input과 Node `openai_compat` adapter가 요청별 think 설정을 provider별 top-level field 또는 내부 `chat_template_kwargs.enable_thinking`으로 매핑한다.
- `default-compat`: think 관련 필드가 생략된 기존 요청은 현재 provider 기본 thinking 동작을 유지한다.
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-roadmap/current.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/edge-smoke.md`
- `apps/node/internal/adapters/openai_compat/openai_compat.go`
- `apps/node/internal/adapters/openai_compat/openai_compat_test.go`
- `apps/edge/internal/openai/types.go`
- `apps/edge/internal/openai/server_test.go`
### SDD 기준
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- 상태: `[승인됨]`, SDD 잠금: `해제`, 사용자 리뷰: 없음
- Acceptance Scenario: S02 -> `provider-mapping`, S04 -> `default-compat`
- Evidence Map: S02 requires Node request body unit tests with provider mocks; S04 requires existing chat/tool/provider-pool regression evidence.
- 이 plan은 provider body mock tests를 completion evidence로 삼는다. External three-provider smoke is excluded to `05+01,02,03,04_dev_smoke`.
### 테스트 환경 규칙
- test_env: `local`
- 읽은 env/profile: `agent-test/local/rules.md`, `agent-test/local/node-smoke.md`, `agent-test/local/edge-smoke.md`
- 적용 명령: `go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai`
- baseline 확인: `go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/adapters/openai_compat` PASS
- 외부 provider preflight: 이 split은 mock HTTP server만 사용하므로 없음.
### 테스트 커버리지 공백
- `think=false` provider mapping: current tests do not assert `chat_template_kwargs.enable_thinking=false` for vLLM/vLLM-MLX or top-level Lemonade mapping.
- `think=true` and `reasoning_effort=none`: no provider body test.
- Omitted field default compatibility: existing tests check generic options/tool passthrough but not absence of `think`/`chat_template_kwargs`.
### 심볼 참조
- renamed/removed symbol: none. If `buildRequestBody` signature changes, update its call at `apps/node/internal/adapters/openai_compat/openai_compat.go:131` and tests in `apps/node/internal/adapters/openai_compat/openai_compat_test.go`.
### 분할 판단
- shared task group: `agent-task/m-openai-compatible-think-control/`
- current subtask: `02+01_node_provider_mapping`
- dependency from directory name: predecessor `01_edge_request_contract`
- predecessor completion: missing in active and archive lookup. Implementation must wait for `agent-task/m-openai-compatible-think-control/01_edge_request_contract/complete.log` or matching archive complete log.
- split reason: provider mapping touches Node adapter and mock provider semantics; Edge response visibility and docs have separate ownership/risk.
### 범위 결정 근거
- Edge request decoder is excluded except for running its targeted tests after dependency completion.
- Edge response `reasoning_content` filtering is excluded to `03+01_edge_reasoning_visibility`.
- Real dev-runtime provider validation is excluded to `05+01,02,03,04_dev_smoke`.
### 빌드 등급
- `local-G05`: bounded Node adapter change, but provider-specific compatibility matrix and default-preservation tests raise risk.
## 구현 체크리스트
- [ ] `01_edge_request_contract` predecessor `complete.log`를 확인한 뒤 시작한다.
- [ ] `openai_compat` adapter request body builder가 provider label을 받아 think-control input을 provider별 body로 매핑하도록 바꾼다. 검증: vLLM Qwen3, vLLM-MLX, Lemonade mock이 같은 요청 플래그에 대해 기대 request body를 받는다.
- [ ] omitted think-control field가 `think`, `reasoning_effort`, `thinking_token_budget`, `chat_template_kwargs`를 만들지 않는 회귀 테스트를 추가한다. 검증: 기존 chat/tool/provider pool targeted tests가 통과한다.
- [ ] `go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- `02+01_node_provider_mapping` decodes to `depends_on=[01]`.
- Do not implement until predecessor `01_edge_request_contract` has a `complete.log`.
### [API-1] Provider-Specific Think Body Mapping
#### 문제
- [apps/node/internal/adapters/openai_compat/openai_compat.go](/config/workspace/iop/apps/node/internal/adapters/openai_compat/openai_compat.go:36)의 adapter는 `provider` label을 갖고 있지만 body builder가 이를 사용하지 않는다.
- [apps/node/internal/adapters/openai_compat/openai_compat.go](/config/workspace/iop/apps/node/internal/adapters/openai_compat/openai_compat.go:440)의 `buildRequestBody``think`만 top-level passthrough한다.
Before:
```go
// apps/node/internal/adapters/openai_compat/openai_compat.go:440
func buildRequestBody(model string, messages []chatMessage, input map[string]any) map[string]any {
body := make(map[string]any)
...
for _, key := range []string{"tools", "tool_choice", "format", "think", "keep_alive"} {
if v, ok := input[key]; ok {
body[key] = v
}
}
body["model"] = model
body["messages"] = messages
body["stream"] = true
return body
}
```
After:
```go
// apps/node/internal/adapters/openai_compat/openai_compat.go
reqBody, err := a.buildRequestBody(model, messages, spec.Input)
if err != nil {
return err
}
```
Provider mapping baseline:
| Provider label | `think=false` / `reasoning_effort=none` | `think=true` | Budget/effort |
|---|---|---|---|
| `vllm`, `vllm-mlx` | `chat_template_kwargs.enable_thinking=false` | `chat_template_kwargs.enable_thinking=true` | place supported budget/effort under `chat_template_kwargs`; unsupported values return adapter error with `unsupported think control` |
| `lemonade` | top-level `think=false` | top-level `think=true` | top-level `reasoning_effort` / `thinking_token_budget` if present |
| empty/unknown | keep current top-level `think` passthrough for compatibility | same | top-level passthrough for explicit fields |
#### 해결 방법
- Convert `buildRequestBody` into an adapter method or pass provider label explicitly.
- Merge `chat_template_kwargs` only inside the adapter; do not expose raw wrapper at Edge.
- Preserve `tools`, `tool_choice`, `format`, `keep_alive` existing behavior unless tests show a regression.
- Return a clear adapter error before HTTP for explicitly unsupported provider/value combinations.
#### 수정 파일 및 체크리스트
- [ ] `apps/node/internal/adapters/openai_compat/openai_compat.go`: provider-aware body builder and helper functions.
- [ ] `apps/node/internal/adapters/openai_compat/openai_compat_test.go`: provider body assertions for vLLM, vLLM-MLX, Lemonade, omitted default.
#### 테스트 작성
- Add `TestOpenAICompatExecuteMapsThinkFalseForVLLMChatTemplateKwargs`.
- Add `TestOpenAICompatExecuteMapsThinkFalseForVLLMMLXChatTemplateKwargs`.
- Add `TestOpenAICompatExecuteMapsThinkFalseForLemonadeTopLevel`.
- Add `TestOpenAICompatExecuteOmitsThinkControlWhenRequestOmitsIt`.
- Add/extend tests for `reasoning_effort=none`, `think=true`, and explicit unsupported budget/effort behavior.
#### 중간 검증
```bash
go test -count=1 ./apps/node/internal/adapters/openai_compat
```
Expected: PASS.
### [API-2] Default Compatibility Regression
#### 문제
SDD S04 requires omitted think fields to preserve existing provider default thinking behavior. Current body tests do not assert that omitted requests avoid `think` and `chat_template_kwargs`.
#### 해결 방법
- In the omitted-field test, decode the provider request body and assert absence of:
- `think`
- `reasoning_effort`
- `thinking_token_budget`
- `chat_template_kwargs`
- Keep existing options/tool tests passing.
#### 수정 파일 및 체크리스트
- [ ] `apps/node/internal/adapters/openai_compat/openai_compat_test.go`: absence assertions.
#### 테스트 작성
- Covered by `TestOpenAICompatExecuteOmitsThinkControlWhenRequestOmitsIt`.
#### 중간 검증
```bash
go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai
```
Expected: PASS.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/node/internal/adapters/openai_compat/openai_compat.go` | API-1 |
| `apps/node/internal/adapters/openai_compat/openai_compat_test.go` | API-1, API-2 |
## 최종 검증
```bash
go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai
```
Expected: PASS. Fresh execution is required; do not rely on Go test cache.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,104 @@
<!-- task=m-openai-compatible-think-control/03+01_edge_reasoning_visibility 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 predecessor `01_edge_request_contract` has `complete.log`.
> Fill all 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/03+01_edge_reasoning_visibility, plan=0, tag=API
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `reasoning-visibility`: `include_reasoning=false`가 provider reasoning 생성 여부와 별개로 OpenAI-compatible 응답의 `reasoning_content` 노출을 억제한다.
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다.
stream/non-stream suppression, default exposure regression, tool/content preservation, 검증 출력 일치를 확인한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] Non-Streaming Reasoning Suppression | [ ] |
| [API-2] Streaming Reasoning Suppression | [ ] |
## 구현 체크리스트
- [ ] `01_edge_request_contract` predecessor `complete.log`를 확인한 뒤 시작한다.
- [ ] non-streaming completion에서 `include_reasoning=false`이면 `message.reasoning_content`를 비우되 `content`, `tool_calls`, `finish_reason`, usage는 유지한다. 검증: non-stream 응답에서 reasoning field가 빠지고 content/tool output은 유지된다.
- [ ] live SSE stream에서 `include_reasoning=false`이면 `delta.reasoning_content` chunk를 쓰지 않되 content/tool chunks와 `[DONE]`은 유지한다. 검증: stream 응답에서 reasoning delta가 빠지고 content/tool output은 유지된다.
- [ ] default/omitted `include_reasoning`은 기존 reasoning exposure tests를 유지한다.
- [ ] `go test -count=1 ./apps/edge/internal/openai`를 실행한다.
- [ ] 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으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `include_reasoning=false`가 response-only filter인지 확인한다.
- content/tool output과 `[DONE]`이 suppression 중에도 유지되는지 확인한다.
- omitted `include_reasoning`의 기존 reasoning exposure tests가 계속 통과하는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### API-1 중간 검증
```bash
$ go test -count=1 ./apps/edge/internal/openai
(output)
```
### API-2 중간 검증
```bash
$ go test -count=1 ./apps/edge/internal/openai
(output)
```
### 최종 검증
```bash
$ go test -count=1 ./apps/edge/internal/openai
(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

@ -0,0 +1,219 @@
<!-- task=m-openai-compatible-think-control/03+01_edge_reasoning_visibility plan=0 tag=API -->
# Plan - API Edge Reasoning Visibility
## 이 파일을 읽는 구현 에이전트에게
`01_edge_request_contract``complete.log`가 있어야 이 plan을 구현한다. 구현 완료는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채운 상태다. review finalization, archive, `complete.log` 작성은 code-review 전용이다.
## 배경
SDD는 `include_reasoning=false`를 generation control이 아니라 OpenAI-compatible response exposure control로 정의했다. 현재 Edge는 non-streaming `message.reasoning_content`와 live SSE `delta.reasoning_content`를 기본으로 노출한다. 요청자가 reasoning 생성을 유지하면서 노출만 숨길 수 있도록 response writer 단에서 필터를 추가해야 한다.
## 사용자 리뷰 요청 흐름
선택된 Milestone lock decision이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 외부 환경이나 검증 evidence 공백은 사용자 리뷰가 아니라 blocker/follow-up으로 기록한다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
- `reasoning-visibility`: `include_reasoning=false`가 provider reasoning 생성 여부와 별개로 OpenAI-compatible 응답의 `reasoning_content` 노출을 억제한다.
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-roadmap/current.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `apps/edge/internal/openai/types.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/stream.go`
- `apps/edge/internal/openai/server_test.go`
### SDD 기준
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- 상태: `[승인됨]`, SDD 잠금: `해제`, 사용자 리뷰: 없음
- Acceptance Scenario: S03 -> `reasoning-visibility`
- Evidence Map: S03 requires stream/non-stream response filtering tests and `Roadmap Completion` evidence for `reasoning-visibility`.
### 테스트 환경 규칙
- test_env: `local`
- 읽은 env/profile: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`
- 적용 명령: `go test -count=1 ./apps/edge/internal/openai`
- baseline 확인: `go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/adapters/openai_compat` PASS
- 외부 runner preflight: 이 split은 current checkout unit test만 필요하므로 없음.
### 테스트 커버리지 공백
- Current tests assert default reasoning exposure:
- non-streaming `TestChatCompletionsReturnsReasoningContentSeparately`
- streaming `TestChatCompletionsStreamsReasoningSSE`
- No tests assert `include_reasoning=false` suppression while preserving content/tool output.
- Strict-output tests suppress reasoning for a different policy and must not be reused as this feature's evidence.
### 심볼 참조
- renamed/removed symbol: none.
### 분할 판단
- current subtask: `03+01_edge_reasoning_visibility`
- dependency from directory name: predecessor `01_edge_request_contract`
- predecessor completion: missing in active and archive lookup. Implementation must wait for `01_edge_request_contract/complete.log`.
- split reason: response filtering is Edge-only but depends on the request field introduced by 01; Node provider body mapping remains separate.
### 범위 결정 근거
- Request decoder validation is excluded to predecessor 01.
- Provider-side generation control is excluded to sibling 02.
- Contract docs and dev smoke are excluded to later dependent subtasks.
### 빌드 등급
- `local-G04`: bounded Edge stream/non-stream filtering with deterministic tests, but SSE behavior and tool output preservation add moderate risk.
## 구현 체크리스트
- [ ] `01_edge_request_contract` predecessor `complete.log`를 확인한 뒤 시작한다.
- [ ] non-streaming completion에서 `include_reasoning=false`이면 `message.reasoning_content`를 비우되 `content`, `tool_calls`, `finish_reason`, usage는 유지한다. 검증: non-stream 응답에서 reasoning field가 빠지고 content/tool output은 유지된다.
- [ ] live SSE stream에서 `include_reasoning=false`이면 `delta.reasoning_content` chunk를 쓰지 않되 content/tool chunks와 `[DONE]`은 유지한다. 검증: stream 응답에서 reasoning delta가 빠지고 content/tool output은 유지된다.
- [ ] default/omitted `include_reasoning`은 기존 reasoning exposure tests를 유지한다.
- [ ] `go test -count=1 ./apps/edge/internal/openai`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- `03+01_edge_reasoning_visibility` decodes to `depends_on=[01]`.
- Do not implement until predecessor `01_edge_request_contract` has a `complete.log`.
### [API-1] Non-Streaming Reasoning Suppression
#### 문제
[apps/edge/internal/openai/chat_handler.go](/config/workspace/iop/apps/edge/internal/openai/chat_handler.go:249)의 collection path always builds `chatMessage{ReasoningContent: reasoning}` after normalization. No request-level visibility switch is applied.
Before:
```go
// apps/edge/internal/openai/chat_handler.go:249
text, reasoning, finishReason, nativeToolCalls, usage, textToolFallback, err := collectRunResult(ctx, handle.Stream(), handle.WaitTimeout())
...
message := chatMessage{Role: "assistant", Content: text, ReasoningContent: reasoning}
```
After:
```go
// apply after normalization, before response serialization
if !req.includeReasoning() {
message.ReasoningContent = ""
}
```
#### 해결 방법
- Add a small helper such as `req.includeReasoning()` that defaults to true.
- Filter only response exposure. Do not alter collected reasoning logs or provider generation input.
- Ensure tool call synthesis still sees content and does not depend on reasoning.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/types.go`: include helper if not already added by 01.
- [ ] `apps/edge/internal/openai/chat_handler.go`: non-streaming response filter.
- [ ] `apps/edge/internal/openai/server_test.go`: non-stream suppression tests.
#### 테스트 작성
- Add `TestChatCompletionsSuppressesReasoningContentWhenExcluded`.
- Add a tool-call preserving case if existing native/text tool tests make this cheap; otherwise include content preservation in the suppression test.
#### 중간 검증
```bash
go test -count=1 ./apps/edge/internal/openai
```
Expected: PASS.
### [API-2] Streaming Reasoning Suppression
#### 문제
- [apps/edge/internal/openai/stream.go](/config/workspace/iop/apps/edge/internal/openai/stream.go:65) writes every reasoning delta to SSE.
- [apps/edge/internal/openai/stream.go](/config/workspace/iop/apps/edge/internal/openai/stream.go:167) only suppresses reasoning for strict-output, not request visibility.
Before:
```go
// apps/edge/internal/openai/stream.go:167
case "reasoning_delta":
if event.GetDelta() == "" {
continue
}
reasoningBuilder.WriteString(event.GetDelta())
if outputPolicy.Strict {
continue
}
writeTracedReasoningDelta(event.GetDelta())
```
After:
```go
if outputPolicy.Strict || !req.includeReasoning() {
continue
}
writeTracedReasoningDelta(event.GetDelta())
```
#### 해결 방법
- Suppress live SSE reasoning chunks when `include_reasoning=false`.
- Keep `reasoningBuilder` for logs and diagnostics.
- Buffered strict stream currently emits only content/tool output; verify it still does not leak reasoning and add a non-strict live SSE test.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/stream.go`: live SSE reasoning filter.
- [ ] `apps/edge/internal/openai/server_test.go`: streaming suppression tests.
#### 테스트 작성
- Add `TestChatCompletionsStreamSuppressesReasoningWhenExcluded` asserting no `reasoning_content`, content chunk present, `[DONE]` present.
- Keep `TestChatCompletionsStreamsReasoningSSE` passing for omitted `include_reasoning`.
#### 중간 검증
```bash
go test -count=1 ./apps/edge/internal/openai
```
Expected: PASS.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/edge/internal/openai/types.go` | API-1 |
| `apps/edge/internal/openai/chat_handler.go` | API-1 |
| `apps/edge/internal/openai/stream.go` | API-2 |
| `apps/edge/internal/openai/server_test.go` | API-1, API-2 |
## 최종 검증
```bash
go test -count=1 ./apps/edge/internal/openai
```
Expected: PASS. Fresh execution is required; do not rely on Go test cache.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,104 @@
<!-- 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

@ -0,0 +1,193 @@
<!-- task=m-openai-compatible-think-control/04+01,02,03_contract_docs plan=0 tag=API -->
# Plan - API Contract Docs
## 이 파일을 읽는 구현 에이전트에게
이 plan은 `01_edge_request_contract`, `02+01_node_provider_mapping`, `03+01_edge_reasoning_visibility`가 PASS complete된 뒤 구현한다. 계약 문서를 실제 코드와 다르게 선반영하지 않는다. 구현 완료는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운 상태이며, finalization은 code-review 전용이다.
## 배경
현재 계약 원문은 Chat Completions 지원 필드 목록에서 think-control 필드를 빼고, 금지 목록에서는 `think`를 provider 전용 field로 거부한다고 설명한다. 앞선 split들이 공개 field와 provider mapping, response visibility를 구현한 뒤 계약 원문과 dev 운영 문서에 그 결과를 맞춰야 한다. `docs/openai-compatible-api-contract.md`는 계약 원문 포인터이므로 원문 복제 없이 유지한다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 선택된 Milestone lock decision이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 문서 문구 조정, evidence 공백, 외부 smoke 실패는 사용자 리뷰가 아니라 follow-up/review finding으로 처리한다.
## 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
## 분석 결과
### 읽은 파일
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- `agent-contract/index.md`
- `agent-contract/outer/openai-compatible-api.md`
- `docs/openai-compatible-api-contract.md`
- `docs/edge-local-dev-guide.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
### SDD 기준
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- 상태: `[승인됨]`, SDD 잠금: `해제`, 사용자 리뷰: 없음
- Acceptance Scenario: S06 -> `contract-docs`
- Evidence Map: S06 requires contract document diff and documentation verification.
### 테스트 환경 규칙
- test_env: `local`
- 읽은 env/profile: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`
- 적용 명령: deterministic docs search with `rg --sort path`, plus targeted Go tests from completed code splits if reviewer wants behavior/doc consistency.
- 외부 runner preflight: none. This docs split must not run external dev-runtime smoke.
### 테스트 커버리지 공백
- Docs currently do not describe `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`.
- Docs currently say `think` is forbidden for Chat Completions.
- Dev guide mentions provider runtime `enable_thinking=false` but not request-level overrides or response exposure policy.
### 심볼 참조
- renamed/removed symbol: none.
### 분할 판단
- current subtask: `04+01,02,03_contract_docs`
- dependency from directory name: predecessors `01`, `02`, `03`
- predecessor completion: all missing in active and archive lookup at plan creation.
- split reason: docs must mirror finalized code behavior and unsupported policy; writing them before code would create false public guidance.
### 범위 결정 근거
- Code changes are excluded. If implementation finds docs cannot match code, stop and record a normal review/follow-up issue rather than inventing contract behavior.
- `docs/openai-compatible-api-contract.md` should remain a pointer unless the project rule changes.
- External dev smoke evidence is excluded to `05+01,02,03,04_dev_smoke`.
### 빌드 등급
- `local-G03`: docs-only after code completion, with contract source-of-truth discipline.
## 구현 체크리스트
- [ ] 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의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- `04+01,02,03_contract_docs` decodes to `depends_on=[01,02,03]`.
- Do not implement until all three predecessors have `complete.log`.
- Contract update work should follow `agent-contract/index.md`; if the implementation turn triggers contract-update skill routing, read the matched skill before editing.
### [API-1] OpenAI-Compatible Contract Update
#### 문제
[agent-contract/outer/openai-compatible-api.md](/config/workspace/iop/agent-contract/outer/openai-compatible-api.md:148)의 supported field list lacks think-control fields, and [agent-contract/outer/openai-compatible-api.md](/config/workspace/iop/agent-contract/outer/openai-compatible-api.md:179) forbids `think`.
Before:
```markdown
<!-- agent-contract/outer/openai-compatible-api.md:148 -->
현재 지원하는 Chat Completions request field:
...
- `store`
...
- `options`, `think`, `format`, `keep_alive` 같은 provider/Ollama 전용 request field
```
After:
```markdown
현재 지원하는 Chat Completions request field:
...
- `think`
- `reasoning_effort`
- `thinking_token_budget`
- `include_reasoning`
Think 제어 field:
- `think`: ...
- `include_reasoning`: response exposure only ...
```
#### 해결 방법
- Add field semantics from approved SDD without copying the whole SDD.
- Describe default behavior: omitted fields preserve provider default.
- Describe unsupported policy from implemented code: invalid conflicts are 400 at Edge; provider-specific unsupported mapping returns clear compatibility/run error according to implementation.
- Keep raw `options` and raw `chat_template_kwargs` forbidden.
#### 수정 파일 및 체크리스트
- [ ] `agent-contract/outer/openai-compatible-api.md`: source-of-truth update.
#### 테스트 작성
- No code test. Use deterministic `rg` verification and review against completed code split behavior.
#### 중간 검증
```bash
rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md
```
Expected: supported fields and raw-wrapper prohibition are both visible.
### [API-2] Dev Operations Docs Update
#### 문제
[docs/edge-local-dev-guide.md](/config/workspace/iop/docs/edge-local-dev-guide.md:236) says Qwen models may include thinking/reasoning output but does not describe request-level control or visibility suppression.
#### 해결 방법
- Add a concise subsection near the dev-runtime provider pool smoke notes.
- Include sample intent, not secrets: `think=false`, omitted default, `include_reasoning=false`.
- Do not write bearer tokens or private endpoint secrets.
#### 수정 파일 및 체크리스트
- [ ] `docs/edge-local-dev-guide.md`: dev-runtime think-control note.
- [ ] `docs/openai-compatible-api-contract.md`: confirm pointer-only state; edit only if link text becomes stale.
#### 테스트 작성
- No code test. Use deterministic `rg` verification.
#### 중간 검증
```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
```
Expected: dev guide contains request-level guidance; pointer doc does not duplicate contract body unless intentionally updated.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `agent-contract/outer/openai-compatible-api.md` | API-1 |
| `docs/edge-local-dev-guide.md` | API-2 |
| `docs/openai-compatible-api-contract.md` | API-2 verification only |
## 최종 검증
```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
```
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.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,160 @@
<!-- task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke plan=0 tag=TEST -->
# 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-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 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?**

View file

@ -0,0 +1,265 @@
<!-- task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke plan=0 tag=TEST -->
# Plan - Dev Runtime Think-Control Smoke
## 이 파일을 읽는 구현 에이전트에게
이 plan은 `01_edge_request_contract`, `02+01_node_provider_mapping`, `03+01_edge_reasoning_visibility`, `04+01,02,03_contract_docs`가 PASS complete된 뒤 실행한다. 이 작업은 원격 dev-runtime과 실제 provider pool을 사용하는 smoke 검증이며, 구현 변경을 새로 만들지 않는다. 실패가 나오면 원인과 증거를 `CODE_REVIEW-*-G??.md`에 남기고 follow-up plan으로 넘긴다.
## 배경
Milestone의 `dev-smoke`는 로컬 단위 테스트만으로 닫을 수 없다. 요청별 `think=false`, omitted default, `include_reasoning=false`가 Edge SSE와 provider raw trace에서 일관되게 관측되어야 한다. dev-runtime 인벤토리는 runner, Edge, Node, provider capacity, model alias를 이미 정의하고 있으므로 이 split은 깨끗한 원격 source 기준으로 배포/재시작/관측 증거를 확보한다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 선택된 Milestone lock decision이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 원격 접속 실패, provider 미기동, secret 부재, trace 미관측, 용량 smoke 불충족은 사용자 결정이 아니라 환경 blocker 또는 follow-up evidence로 기록한다.
## 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
## 분석 결과
### 읽은 파일
- `agent-roadmap/current.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/dev/rules.md`
- `agent-test/dev/edge-smoke.md`
- `agent-test/dev/node-smoke.md`
- `agent-test/dev/platform-common-smoke.md`
- `agent-test/dev/inventory.yaml`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
### SDD 기준
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md`
- 상태: `[승인됨]`, SDD 잠금: `해제`, 사용자 리뷰: 없음
- Acceptance Scenario: S05 -> `dev-smoke`
- Evidence Map: S05 requires dev smoke logs that compare provider raw trace and Edge SSE behavior for `think=false`, omitted default, and `include_reasoning=false`.
### 테스트 환경 규칙
- test_env: `dev`
- profile: `dev-runtime-provider-pool`
- remote runner: `ssh toki@toki-labs.com`
- repo root: `/Users/toki/agent-work/iop-dev`
- Edge id: `edge-toki-labs-dev`
- Edge config: `build/dev-runtime/edge.yaml`
- OpenAI base URL on runner: `http://127.0.0.1:18083/v1`
- Control Plane status URL: `http://127.0.0.1:18001/edges/edge-toki-labs-dev/status`
- admin addr on runner: `127.0.0.1:19093`
- model alias: `qwen3.6:35b`
- provider capacity total: `10`
- dev-runtime-deploy skill applies. It requires clean sync, full test, full rebuild, restart, `/v1/models`, and OpenAI-compatible smoke evidence.
- Secrets, bearer tokens, bootstrap tokens, and private key paths must not be written to final report or review output.
### 테스트 커버리지 공백
- Local tests can prove request decoding, adapter body mapping, and response filtering, but cannot prove actual vLLM/vLLM-MLX/Lemonade runtime behavior.
- Provider request-body trace availability is environment-dependent. If raw request body cannot be observed, record the missing trace source and do not claim S05 PASS.
- Omitted default can be nondeterministic if the model produces no reasoning on a given prompt. Use a reasoning-friendly prompt and record the result as observed behavior rather than exact output matching.
### 심볼 참조
- renamed/removed symbol: none. This split should not edit source symbols.
### 분할 판단
- current subtask: `05+01,02,03,04_dev_smoke`
- dependency from directory name: predecessors `01`, `02`, `03`, `04`
- predecessor completion: all missing in active and archive lookup at plan creation.
- split reason: external dev-runtime deployment and provider evidence are slower, environment-sensitive, and should run only after code/docs splits are complete.
### 범위 결정 근거
- Code and docs changes are excluded. If smoke fails because implementation behavior is wrong, create a follow-up plan after review; do not hot-edit inside this smoke split.
- Capacity smoke from `dev-runtime-deploy` is included only as deployment health evidence. The primary milestone evidence is think-control trace/SSE comparison.
- OneXPlayer access must use `ssh r0bin@192.168.0.59` from the current host if it is needed. Do not use `toki` for OneXPlayer.
### 빌드 등급
- `cloud-G07`: external runner, clean sync/rebuild/restart, multiple provider runtimes, raw trace collection, and SSE comparison.
## 구현 체크리스트
- [ ] 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의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- `05+01,02,03,04_dev_smoke` decodes to `depends_on=[01,02,03,04]`.
- Do not execute remote smoke until all predecessors have `complete.log`.
- If predecessors are archived, read only the matching `complete.log` evidence needed to prove dependency completion.
### [TEST-1] Remote Runner Preflight
#### 문제
dev-runtime smoke can produce misleading results if the remote checkout or binaries are stale.
#### 해결 방법
- Confirm remote checkout source and clean state before deployment.
- Confirm expected binaries/config paths from inventory.
- Record versions/timestamps without printing secrets.
#### 수정 파일 및 체크리스트
- [ ] No source files.
- [ ] `CODE_REVIEW-*-G??.md`: record preflight output.
#### 테스트 작성
No automated test file. Use remote shell preflight.
#### 중간 검증
```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'
```
Expected: clean source for the intended ref, Edge config present, local Edge/mac Node binaries executable.
### [TEST-2] Clean Sync, Full Test, Rebuild, Restart
#### 문제
The smoke must validate the completed implementation, not an old binary or dirty remote tree.
#### 해결 방법
- Follow `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`.
- Clean sync only after confirming the target ref contains the milestone implementation.
- Run pre-build and post-build `go test ./...`.
- Rebuild all dev-runtime binaries from the same source ref.
- Restart Edge and all dev provider nodes according to inventory.
#### 수정 파일 및 체크리스트
- [ ] No source files.
- [ ] `CODE_REVIEW-*-G??.md`: record source ref, test result, build artifacts, restart status.
#### 테스트 작성
No repo test additions. This is deployment verification.
#### 중간 검증
```bash
ssh toki@toki-labs.com '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"'
ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18083/v1/models | jq ".data[].id"'
```
Expected: tests pass, Edge and three dev nodes are connected, `qwen3.6:35b` appears in models.
### [TEST-3] Think-Control SSE And Provider Trace
#### 문제
Milestone S05 requires real provider evidence for request-level think control and response exposure filtering.
#### 해결 방법
- Use `/v1/chat/completions` with `stream=true`, model `qwen3.6:35b`, and a reasoning-friendly prompt.
- Send these cases:
- omitted default: no think-control fields.
- generation disabled: `think=false`.
- visibility disabled: `include_reasoning=false`.
- Include trace metadata if supported by the implemented Edge/Node path. Do not print API keys.
- Save temporary SSE outputs and provider raw trace snippets under `/tmp` on the runner, then paste only relevant masked excerpts into `CODE_REVIEW-*-G??.md`.
#### 수정 파일 및 체크리스트
- [ ] No source files.
- [ ] `CODE_REVIEW-*-G??.md`: record each request body summary, Edge SSE observation, provider raw trace observation, and pass/fail judgment.
#### 테스트 작성
No committed test file. Use a temporary runner script or direct `curl` commands.
#### 중간 검증
```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 '\''for case_name in omitted_default think_false include_reasoning_false; do echo "case=$case_name"; done'\'''
```
Replace the placeholder loop with actual masked `curl -N` requests:
```json
{"model":"qwen3.6:35b","stream":true,"messages":[{"role":"user","content":"Solve a two-step logic puzzle and explain the answer briefly."}]}
{"model":"qwen3.6:35b","stream":true,"think":false,"messages":[{"role":"user","content":"Solve a two-step logic puzzle and explain the answer briefly."}]}
{"model":"qwen3.6:35b","stream":true,"include_reasoning":false,"messages":[{"role":"user","content":"Solve a two-step logic puzzle and explain the answer briefly."}]}
```
Expected:
- omitted default preserves provider default. If reasoning appears, Edge SSE may expose `reasoning_content` according to implemented default visibility.
- `think=false` provider raw request shows disabled thinking mapping for the selected provider path, and Edge SSE does not expose generated reasoning chunks for that request.
- `include_reasoning=false` may still allow provider-side reasoning generation, but Edge SSE must not include `reasoning_content` or equivalent reasoning deltas.
- All cases finish with valid content/tool/DONE stream behavior and no secret output.
### [TEST-4] Capacity Health Evidence
#### 문제
Think-control smoke should not pass while the dev provider pool is partially disconnected.
#### 해결 방법
- Run capacity+1 smoke for `/v1/responses` and `/v1/chat/completions`.
- Poll Control Plane status while requests are in flight.
- Record max aggregate `in_flight`, `queued`, and per-provider capacity observations.
#### 수정 파일 및 체크리스트
- [ ] No source files.
- [ ] `CODE_REVIEW-*-G??.md`: record max in-flight/queued and recovery snapshot.
#### 테스트 작성
No committed test file. Use temporary runner script if needed.
#### 중간 검증
```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}"'
```
Expected: total capacity reaches `10`, queued reaches at least `1` during capacity+1 smoke, and all providers recover to `in_flight=0`, `queued=0` afterward.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| none | external dev-runtime smoke 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 'cd /Users/toki/agent-work/iop-dev && go test ./...'
ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18083/v1/models | jq ".data[].id"'
ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "."'
```
Expected: intended source ref, full tests pass, model exposed, dev provider pool connected, capacity health evidence captured, and think-control raw trace/SSE comparison recorded for all three cases.
모든 검증 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -96,7 +96,7 @@ func decodeChatCompletionRequest(dec *json.Decoder, req *chatCompletionRequest)
}
for key := range raw {
switch key {
case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store":
case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store", "think", "reasoning_effort", "thinking_token_budget", "include_reasoning":
default:
return fmt.Errorf("%s is not supported for /v1/chat/completions", key)
}
@ -120,6 +120,38 @@ func decodeChatCompletionRequest(dec *json.Decoder, req *chatCompletionRequest)
if req.TopP != nil && (*req.TopP < 0 || *req.TopP > 1) {
return fmt.Errorf("top_p must be between 0 and 1")
}
if err := validateThinkControl(req); err != nil {
return err
}
return nil
}
func validateThinkControl(req *chatCompletionRequest) error {
if req.ThinkingTokenBudget != nil && *req.ThinkingTokenBudget < 0 {
return fmt.Errorf("thinking_token_budget must be non-negative")
}
if req.ReasoningEffort != nil {
eff := *req.ReasoningEffort
if eff == "" {
return fmt.Errorf("reasoning_effort cannot be empty when present")
}
if eff != "none" && eff != "low" && eff != "medium" && eff != "high" {
return fmt.Errorf("reasoning_effort must be one of none, low, medium, or high")
}
}
if req.Think != nil && !*req.Think {
if req.ReasoningEffort != nil && *req.ReasoningEffort != "none" {
return fmt.Errorf("think=false conflicts with reasoning_effort=%s", *req.ReasoningEffort)
}
}
if req.ThinkingTokenBudget != nil {
if req.Think != nil && !*req.Think {
return fmt.Errorf("thinking_token_budget cannot be set when think=false")
}
if req.ReasoningEffort != nil && *req.ReasoningEffort == "none" {
return fmt.Errorf("thinking_token_budget cannot be set when reasoning_effort=none")
}
}
return nil
}

View file

@ -2156,7 +2156,6 @@ func TestChatCompletionsRejectsUnsupportedFields(t *testing.T) {
body string
}{
{"options wrapper", `{"model":"m","messages":[{"role":"user","content":"hi"}],"options":{"num_ctx":8192}}`},
{"think", `{"model":"m","messages":[{"role":"user","content":"hi"}],"think":false}`},
{"format", `{"model":"m","messages":[{"role":"user","content":"hi"}],"format":"json"}`},
{"keep_alive", `{"model":"m","messages":[{"role":"user","content":"hi"}],"keep_alive":"10m"}`},
{"bad max_tokens", `{"model":"m","messages":[{"role":"user","content":"hi"}],"max_tokens":0}`},
@ -2174,6 +2173,105 @@ func TestChatCompletionsRejectsUnsupportedFields(t *testing.T) {
}
}
func TestChatCompletionsAcceptsThinkControlFields(t *testing.T) {
fake := &fakeRunService{
events: make(chan *iop.RunEvent, 1),
}
fake.events <- &iop.RunEvent{Type: "complete"}
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
// Case 1: All valid think-control fields provided
reqBody := `{
"model":"client-model",
"messages":[{"role":"user","content":"hi"}],
"think": true,
"reasoning_effort": "medium",
"thinking_token_budget": 1024,
"include_reasoning": false
}`
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(reqBody))
w := httptest.NewRecorder()
srv.routes().ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
}
if fake.req.Input["think"] != true {
t.Fatalf("think: got %v, want true", fake.req.Input["think"])
}
if fake.req.Input["reasoning_effort"] != "medium" {
t.Fatalf("reasoning_effort: got %v, want 'medium'", fake.req.Input["reasoning_effort"])
}
budgetVal := fake.req.Input["thinking_token_budget"]
switch v := budgetVal.(type) {
case float64:
if v != 1024 {
t.Fatalf("thinking_token_budget: got %f, want 1024", v)
}
case int:
if v != 1024 {
t.Fatalf("thinking_token_budget: got %d, want 1024", v)
}
default:
t.Fatalf("thinking_token_budget: got %T, want number type", budgetVal)
}
if fake.req.Input["include_reasoning"] != false {
t.Fatalf("include_reasoning: got %v, want false", fake.req.Input["include_reasoning"])
}
// Case 2: Omitted think-control fields should NOT add keys to runInput
fake2 := &fakeRunService{
events: make(chan *iop.RunEvent, 1),
}
fake2.events <- &iop.RunEvent{Type: "complete"}
srv2 := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake2, nil)
reqBody2 := `{
"model":"client-model",
"messages":[{"role":"user","content":"hi"}]
}`
req2 := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(reqBody2))
w2 := httptest.NewRecorder()
srv2.routes().ServeHTTP(w2, req2)
if w2.Code != http.StatusOK {
t.Fatalf("status: got %d body=%s", w2.Code, w2.Body.String())
}
for _, key := range []string{"think", "reasoning_effort", "thinking_token_budget", "include_reasoning"} {
if _, ok := fake2.req.Input[key]; ok {
t.Fatalf("expected key %q to be omitted from runInput, but it was present", key)
}
}
}
func TestChatCompletionsRejectsInvalidThinkControlFields(t *testing.T) {
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil)
cases := []struct {
name string
body string
}{
{"negative budget", `{"model":"m","messages":[{"role":"user","content":"hi"}],"thinking_token_budget":-1}`},
{"bad effort", `{"model":"m","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"super_high"}`},
{"empty effort", `{"model":"m","messages":[{"role":"user","content":"hi"}],"reasoning_effort":""}`},
{"think=false and reasoning_effort other than none", `{"model":"m","messages":[{"role":"user","content":"hi"}],"think":false,"reasoning_effort":"high"}`},
{"thinking_token_budget conflicts with think=false", `{"model":"m","messages":[{"role":"user","content":"hi"}],"think":false,"thinking_token_budget":100}`},
{"thinking_token_budget conflicts with reasoning_effort=none", `{"model":"m","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"none","thinking_token_budget":100}`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(tc.body))
w := httptest.NewRecorder()
srv.routes().ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("got %d want 400, body=%s", w.Code, w.Body.String())
}
})
}
}
func TestChatCompletionsRejectsSourceMetadata(t *testing.T) {
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, &fakeRunService{}, nil)
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{

View file

@ -29,6 +29,10 @@ type chatCompletionRequest struct {
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
StreamOptions any `json:"stream_options,omitempty"`
Store any `json:"store,omitempty"`
Think *bool `json:"think,omitempty"`
ReasoningEffort *string `json:"reasoning_effort,omitempty"`
ThinkingTokenBudget *int `json:"thinking_token_budget,omitempty"`
IncludeReasoning *bool `json:"include_reasoning,omitempty"`
}
type chatMessage struct {
@ -90,7 +94,22 @@ func (req chatCompletionRequest) runInput(prompt string, messages []chatMessage,
input["options"] = options
}
if strictOutput {
input["think"] = false
if req.Think != nil && *req.Think {
input["think"] = true
} else {
input["think"] = false
}
} else if req.Think != nil {
input["think"] = *req.Think
}
if req.ReasoningEffort != nil {
input["reasoning_effort"] = *req.ReasoningEffort
}
if req.ThinkingTokenBudget != nil {
input["thinking_token_budget"] = *req.ThinkingTokenBudget
}
if req.IncludeReasoning != nil {
input["include_reasoning"] = *req.IncludeReasoning
}
if len(req.Tools) > 0 {
input["tools"] = req.Tools