# SDD: OpenAI-compatible Think 제어 MVP

## 위치

- Milestone: `agent-roadmap/archive/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/archive/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: 없음
