diff --git a/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md b/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md index fa78c30..0349d60 100644 --- a/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md +++ b/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md @@ -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 후보로 스케치한다. diff --git a/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md new file mode 100644 index 0000000..505978d --- /dev/null +++ b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md @@ -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 기반 +- 확인 필요: 없음 diff --git a/agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md b/agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md new file mode 100644 index 0000000..237a17a --- /dev/null +++ b/agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md @@ -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: 없음 diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/code_review_local_G04_0.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/code_review_local_G04_0.log new file mode 100644 index 0000000..0cb29f9 --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/code_review_local_G04_0.log @@ -0,0 +1,134 @@ + + +# 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-`이면 완료 이벤트 메타데이터를 보고하고 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. diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/complete.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/complete.log new file mode 100644 index 0000000..1bdecd5 --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/complete.log @@ -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 + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/plan_local_G04_0.log b/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/plan_local_G04_0.log new file mode 100644 index 0000000..a09b644 --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-think-control/01_edge_request_contract/plan_local_G04_0.log @@ -0,0 +1,180 @@ + + +# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-openai-compatible-think-control/02+01_node_provider_mapping/CODE_REVIEW-local-G05.md b/agent-task/m-openai-compatible-think-control/02+01_node_provider_mapping/CODE_REVIEW-local-G05.md new file mode 100644 index 0000000..342d06d --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/02+01_node_provider_mapping/CODE_REVIEW-local-G05.md @@ -0,0 +1,105 @@ + + +# 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-`이면 완료 이벤트 메타데이터를 보고하고 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?** diff --git a/agent-task/m-openai-compatible-think-control/02+01_node_provider_mapping/PLAN-local-G05.md b/agent-task/m-openai-compatible-think-control/02+01_node_provider_mapping/PLAN-local-G05.md new file mode 100644 index 0000000..ba1cf8a --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/02+01_node_provider_mapping/PLAN-local-G05.md @@ -0,0 +1,217 @@ + + +# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/CODE_REVIEW-local-G04.md b/agent-task/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/CODE_REVIEW-local-G04.md new file mode 100644 index 0000000..557511f --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/CODE_REVIEW-local-G04.md @@ -0,0 +1,104 @@ + + +# 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-`이면 완료 이벤트 메타데이터를 보고하고 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?** diff --git a/agent-task/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/PLAN-local-G04.md b/agent-task/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/PLAN-local-G04.md new file mode 100644 index 0000000..87fcebb --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/03+01_edge_reasoning_visibility/PLAN-local-G04.md @@ -0,0 +1,219 @@ + + +# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/CODE_REVIEW-local-G03.md b/agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/CODE_REVIEW-local-G03.md new file mode 100644 index 0000000..52a737d --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/CODE_REVIEW-local-G03.md @@ -0,0 +1,104 @@ + + +# 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-`이면 완료 이벤트 메타데이터를 보고하고 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?** diff --git a/agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/PLAN-local-G03.md b/agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/PLAN-local-G03.md new file mode 100644 index 0000000..5667e8b --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/04+01,02,03_contract_docs/PLAN-local-G03.md @@ -0,0 +1,193 @@ + + +# 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 + +현재 지원하는 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md b/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..4e81a86 --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,160 @@ + + +# Code Review Reference - TEST + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> Start only after predecessors `01`, `02`, `03`, and `04` have `complete.log`. +> Fill implementation-owned sections and verification output, then stop with active files in place. Finalization is review-agent-only. + +## 개요 + +date=2026-07-03 +task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke, plan=0, tag=TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md` +- Task ids: + - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다. + +이 review는 외부 dev-runtime 증거의 신뢰성을 확인한다. source ref와 binary freshness, full test 결과, provider pool 연결 상태, capacity observation, 그리고 think-control raw trace/SSE 비교가 모두 있어야 PASS다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [TEST-1] Remote Runner Preflight | [ ] | +| [TEST-2] Clean Sync, Full Test, Rebuild, Restart | [ ] | +| [TEST-3] Think-Control SSE And Provider Trace | [ ] | +| [TEST-4] Capacity Health Evidence | [ ] | + +## 구현 체크리스트 + +- [ ] Predecessor `complete.log` for `01`, `02`, `03`, and `04`를 확인한 뒤 시작한다. +- [ ] Remote runner inventory를 재확인하고 source ref, dirty state, branch, HEAD를 기록한다. +- [ ] Clean sync 기준 ref를 확정한다. Milestone 구현 commit이 `origin/main`에 없으면 같은 source ref를 원격에 명시적으로 반영한 뒤 진행하고, 임의로 stale `origin/main`을 검증하지 않는다. +- [ ] Remote runner에서 `go test ./...`를 빌드 전 실행한다. 실패하면 배포를 중단한다. +- [ ] dev-runtime Edge/mac/Linux ARM64/Windows AMD64 Node binary를 같은 source ref에서 rebuild한다. +- [ ] 빌드 후 `config check`, `config refresh --help`, `config refresh --mode dry-run`, `go test ./...`를 실행한다. +- [ ] Edge, mac-codex-node, gx10-vllm-node, onexplayer-lemonade-node를 dev-runtime 절차대로 재시작하고 port/process/status를 확인한다. +- [ ] `/v1/models`가 `qwen3.6:35b`를 노출하는지 확인한다. +- [ ] `/v1/responses`와 `/v1/chat/completions` capacity smoke를 capacity+1 기준으로 실행하고 provider `in_flight/queued` snapshot을 남긴다. +- [ ] `/v1/chat/completions`에서 omitted default, `think=false`, `include_reasoning=false` 세 케이스를 stream으로 실행하고 Edge SSE와 provider raw trace를 비교한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] active plan/review 파일을 `.log`로 아카이브한다. +- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. +- [ ] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 실행한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 실행/검증 결정 사항을 기록한다. Source ref, provider trace source, masking policy, and any skipped deployment step must be explicit._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Source ref가 predecessor 구현을 포함하고, remote binary가 같은 ref에서 rebuild되었는지 확인한다. +- Full `go test ./...` pre/post 결과가 있거나, 누락 사유와 위험이 명확한지 확인한다. +- Raw trace/SSE evidence가 세 케이스 모두에 대해 있고, token/API key/private path가 마스킹되었는지 확인한다. +- `include_reasoning=false`는 generation disable이 아니라 exposure disable로 판정했는지 확인한다. +- Capacity smoke는 request success만이 아니라 provider snapshot의 `in_flight/queued` 관측을 포함하는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다. Secrets and tokens must be masked._ + +### TEST-1 중간 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1 && test -f build/dev-runtime/edge.yaml && test -x build/dev-runtime/bin/edge && test -x build/dev-runtime/bin/iop-node' +(output) +``` + +### TEST-2 중간 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && go test ./...' +(output) +``` + +```bash +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq ".edge_id, .nodes"' +(output) +``` + +```bash +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18083/v1/models | jq ".data[].id"' +(output) +``` + +### TEST-3 중간 검증 +```bash +$ # omitted default SSE and raw trace summary +(output) +``` + +```bash +$ # think=false SSE and raw trace summary +(output) +``` + +```bash +$ # include_reasoning=false SSE and raw trace summary +(output) +``` + +### TEST-4 중간 검증 +```bash +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq ".. | objects | select(has(\"capacity\") and has(\"in_flight\") and has(\"queued\")) | {id, capacity, in_flight, queued, health, served_models}"' +(output) +``` + +### 최종 검증 +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1' +(output) +``` + +```bash +$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && go test ./...' +(output) +``` + +```bash +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18083/v1/models | jq ".data[].id"' +(output) +``` + +```bash +$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "."' +(output) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/PLAN-cloud-G07.md b/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/PLAN-cloud-G07.md new file mode 100644 index 0000000..213e727 --- /dev/null +++ b/agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/PLAN-cloud-G07.md @@ -0,0 +1,265 @@ + + +# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/apps/edge/internal/openai/chat_handler.go b/apps/edge/internal/openai/chat_handler.go index 1e35d46..800550f 100644 --- a/apps/edge/internal/openai/chat_handler.go +++ b/apps/edge/internal/openai/chat_handler.go @@ -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 } diff --git a/apps/edge/internal/openai/server_test.go b/apps/edge/internal/openai/server_test.go index 4469885..7137cd5 100644 --- a/apps/edge/internal/openai/server_test.go +++ b/apps/edge/internal/openai/server_test.go @@ -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(`{ diff --git a/apps/edge/internal/openai/types.go b/apps/edge/internal/openai/types.go index 72ada81..d429ad7 100644 --- a/apps/edge/internal/openai/types.go +++ b/apps/edge/internal/openai/types.go @@ -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