diff --git a/agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md b/agent-roadmap/archive/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md similarity index 96% rename from agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md rename to agent-roadmap/archive/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md index 2ee66d9..64ddd68 100644 --- a/agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md +++ b/agent-roadmap/archive/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md @@ -13,7 +13,7 @@ Ollama, Lemonade, vLLM, SGLang 같은 provider별 상태 확인 방식 차이를 ## 상태 -[검토중] +[완료] ## 승격 조건 @@ -54,13 +54,13 @@ Provider별 동시 처리 한도를 IOP가 소유하고, 한도를 넘는 요청 ## 완료 리뷰 -- 상태: 요청됨 +- 상태: 승인됨 - 요청일: 2026-06-15 - 완료 근거: 모든 기능 Task가 PASS 완료되었고, 마지막 `queue-observe`는 `agent-task/archive/2026/06/m-provider-availability-capacity-queue-foundation/05+04_queue_observe_snapshot/complete.log`에서 완료 근거와 검증을 남겼다. - 리뷰 필요: - - [ ] 사용자가 완료 결과를 확인했다 - - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 사용자 최종 확인과 archive 승인 대기 + - [x] 사용자가 완료 결과를 확인했다 + - [x] archive 이동을 승인했다 +- 리뷰 코멘트: 2026-06-15 코드 레벨 종료 검토와 smoke 검증에서 차단 이슈가 없어 archive 이동을 승인받고 완료 처리했다. ## 범위 제외 diff --git a/agent-roadmap/phase/inference-provider-extension/PHASE.md b/agent-roadmap/phase/inference-provider-extension/PHASE.md index 3ef316f..fb3862e 100644 --- a/agent-roadmap/phase/inference-provider-extension/PHASE.md +++ b/agent-roadmap/phase/inference-provider-extension/PHASE.md @@ -20,8 +20,8 @@ Ollama 경로가 안정화된 뒤, 그 결과를 기준선으로 삼아 Lemonade - 경로: `agent-roadmap/archive/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` - 요약: 하나의 Node 연결이 여러 CLI profile, terminal gateway, Ollama/vLLM/SGLang 같은 추론 엔진 연결, 여러 model target을 동시에 제공할 수 있도록 config/proto/routing/runtime 기준선을 정리했고, 최종 코드 리뷰와 검증까지 통과해 archive했다. -- [검토중] Node provider 상태와 Capacity Queue 기반 - - 경로: `agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md` +- [완료] Node provider 상태와 Capacity Queue 기반 + - 경로: `agent-roadmap/archive/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md` - 요약: NomadCode workspace 실행 계약이 닫힌 뒤 provider별 health/model probe 차이를 Node adapter 내부로 숨기고, Edge가 공통으로 볼 수 있는 최소 상태와 capacity/in-flight/queued snapshot, FIFO admission queue 기준선을 만든다. - [계획] Lemonade provider 서빙 경로 추가 diff --git a/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md b/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md index e1f11e6..f5ed9c6 100644 --- a/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md +++ b/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md @@ -23,7 +23,7 @@ Edge OpenAI-compatible 입력 표면에서 Lemonade target/model의 모델 조 - 상태: 잠금 - 결정 필요: 아래 체크리스트 - - [ ] Lemonade provider를 독립 `lemonade` adapter로 둘지, OpenAI-compatible inference server 공통 adapter 계열로 둘지 결정한다. + - [x] Lemonade provider를 독립 `lemonade` adapter로 둘지, OpenAI-compatible inference server 공통 adapter 계열로 둘지 결정한다. - [ ] 실테스트에 사용할 Lemonade endpoint, served model name 또는 기준 model, 인증/헤더 필요 여부, streaming 지원 기대 동작을 확인한다. ## 범위 @@ -39,7 +39,7 @@ Edge OpenAI-compatible 입력 표면에서 Lemonade target/model의 모델 조 ### Epic: [lemonade-provider] Lemonade Provider Serving Path -- [ ] [provider-boundary] Lemonade provider를 독립 adapter로 둘지 OpenAI-compatible inference server 공통 adapter로 둘지 결정 근거가 정리되어 있다. +- [x] [provider-boundary] Lemonade provider를 독립 adapter로 둘지 OpenAI-compatible inference server 공통 adapter로 둘지 결정 근거가 정리되어 있다. - [ ] [config-contract] Lemonade endpoint, served model name 또는 model, auth/header, timeout, option passthrough 설정 계약이 정리되어 있다. - [ ] [availability] Lemonade provider의 endpoint/model probe 결과가 Node 공통 상태 모델 `unknown`, `available`, `unavailable`로 노출되는 기준이 검증되어 있다. - [ ] [models-chat] Edge OpenAI-compatible `/v1/models`와 non-streaming `/v1/chat/completions`가 Lemonade provider로 수렴하는 기준이 검증되어 있다. @@ -72,6 +72,7 @@ Edge OpenAI-compatible 입력 표면에서 Lemonade target/model의 모델 조 - 표준선(선택): 내부 실행 계약은 `adapter + target`을 유지하고, OpenAI-compatible API는 외부 호환 입력 표면으로 둔다. - 표준선(선택): Node provider 상태와 capacity queue 기반을 Lemonade provider에도 동일하게 적용한다. - 표준선(선택): 실제 provider가 보장하지 않는 확장 상태는 추가하지 않고, 초기 상태는 `unknown`, `available`, `unavailable`만 사용한다. +- 표준선(확정): Lemonade는 독립 `lemonade` adapter를 새로 만들기보다 OpenAI-compatible inference server 공통 adapter 계열로 둔다. 공식 Lemonade Server 문서가 `/v1/models`, `/v1/chat/completions`와 streaming SSE를 OpenAI-compatible 표면으로 제공하고, 현재 코드에도 `vllm` adapter가 같은 `/v1/models`/`/v1/chat/completions` 기준선을 이미 구현하고 있기 때문이다. - 선행 작업: OpenAI Workspace Agent Execution Contract, Node provider 상태와 Capacity Queue 기반 - 후속 작업: Lemonade provider 실테스트에서 확인된 serving 경로 안정화 보완 -- 확인 필요: Lemonade adapter 경계, 실테스트 endpoint/served model name 또는 model, 인증/헤더 필요 여부, streaming 기대 동작 +- 확인 필요: 실테스트 endpoint/served model name 또는 model, 인증/헤더 필요 여부, streaming 기대 동작 diff --git a/agent-task/m-lemonade-provider-serving-validation/01_config_contract/CODE_REVIEW-cloud-G05.md b/agent-task/m-lemonade-provider-serving-validation/01_config_contract/CODE_REVIEW-cloud-G05.md new file mode 100644 index 0000000..a8022f6 --- /dev/null +++ b/agent-task/m-lemonade-provider-serving-validation/01_config_contract/CODE_REVIEW-cloud-G05.md @@ -0,0 +1,147 @@ + + +# 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 user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-15 +task=m-lemonade-provider-serving-validation/01_config_contract, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `config-contract`: Lemonade endpoint, served model name 또는 model, auth/header, timeout, option passthrough 설정 계약이 정리되어 있다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G05.md` -> `code_review_cloud_G05_N.log`, `PLAN-cloud-G05.md` -> `plan_cloud_G05_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/01_config_contract/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] OpenAI-compatible config contract | [ ] | +| [API-2] Typed proto payload and mapper | [ ] | +| [API-3] Edge validation and example config | [ ] | + +## 구현 체크리스트 + +- [ ] `packages/go/config`에 OpenAI-compatible inference server 설정 계약을 추가하고 normalization/validation 테스트를 작성한다. +- [ ] `proto/iop/runtime.proto`에 typed `OpenAICompatAdapterConfig`를 추가하고 `make proto`로 Go 생성물을 갱신한다. +- [ ] Edge config payload mapper와 config validation, `configs/edge.yaml` 예시를 새 계약에 맞춘다. +- [ ] 계획의 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 붙인다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G05_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G05_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-lemonade-provider-serving-validation/01_config_contract/`를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/01_config_contract/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-lemonade-provider-serving-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G05.md`와 `CODE_REVIEW-cloud-G05.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- 새 `openai_compat` config가 legacy single config와 instance slice 모두에서 idempotent normalize를 보장하는지 확인한다. +- proto generated 파일이 `make proto` 결과인지, 직접 편집 흔적이 없는지 확인한다. +- Edge payload가 provider, endpoint, headers, queue fields를 typed oneof로 모두 전달하는지 확인한다. +- `configs/edge.yaml`에 secret 값이나 개인 endpoint 원문이 들어가지 않았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### API-1 중간 검증 +```bash +$ go test -count=1 ./packages/go/config +(output) +``` + +### API-2 중간 검증 +```bash +$ make proto +(output) +$ go test -count=1 ./apps/edge/internal/node ./proto/gen/... +(output) +``` + +### API-3 중간 검증 +```bash +$ go test -count=1 ./apps/edge/internal/edgecmd +(output) +``` + +### 최종 검증 +```bash +$ make proto +(output) +$ go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/edgecmd ./proto/gen/... +(output) +$ git diff --check +(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?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. diff --git a/agent-task/m-lemonade-provider-serving-validation/01_config_contract/PLAN-cloud-G05.md b/agent-task/m-lemonade-provider-serving-validation/01_config_contract/PLAN-cloud-G05.md new file mode 100644 index 0000000..b7b59ac --- /dev/null +++ b/agent-task/m-lemonade-provider-serving-validation/01_config_contract/PLAN-cloud-G05.md @@ -0,0 +1,303 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +구현 완료 전 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 설계 결정, 검증 출력으로 반드시 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, secret, 서비스 준비, 또는 범위 충돌 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 증거를 남기고 멈춘다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다. 최종 판정, archive, `complete.log` 작성은 code-review-skill 전용이다. + +## 배경 + +현 마일스톤의 Lemonade provider는 OpenAI-compatible inference server 공통 adapter 계열로 두기로 정리됐다. 현재 config/proto 계약은 `ollama`와 `vllm` 중심이라 Lemonade endpoint, served model, header/auth, timeout, option passthrough의 설정 표면을 안정적으로 표현할 수 없다. 이 plan은 런타임 실행 구현 전에 Edge config에서 Node typed payload까지 이어지는 계약만 확정한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단이 생기면 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트가 채팅으로 직접 사용자에게 묻는 것은 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `config-contract`: Lemonade endpoint, served model name 또는 model, auth/header, timeout, option passthrough 설정 계약이 정리되어 있다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/update-roadmap/SKILL.md` +- `agent-ops/rules/project/domain/node/rules.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/node-smoke.md` +- `agent-test/local/edge-smoke.md` +- `agent-test/local/platform-common-smoke.md` +- `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- `proto/iop/node.proto` +- `proto/iop/runtime.proto` +- `packages/go/config/config.go` +- `packages/go/config/config_test.go` +- `apps/edge/internal/node/mapper.go` +- `apps/edge/internal/node/mapper_test.go` +- `apps/edge/internal/node/store.go` +- `apps/edge/internal/node/store_test.go` +- `apps/edge/internal/edgecmd/config.go` +- `apps/edge/internal/edgecmd/edgecmd_test.go` +- `configs/edge.yaml` +- `configs/node.yaml` +- `Makefile` +- `apps/node/internal/adapters/factory.go` +- `apps/node/internal/adapters/factory_internal_test.go` +- `apps/node/internal/adapters/adapters_blackbox_test.go` +- `apps/node/internal/adapters/vllm/vllm.go` +- `apps/node/internal/adapters/vllm/vllm_test.go` + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`를 읽었고, 도메인 매핑에 따라 `node-smoke`, `edge-smoke`, `platform-common-smoke`를 읽었다. +- 적용 명령: protobuf 변경은 `make proto`, 공통 설정/proto는 `go test ./packages/go/... ./proto/gen/...`, Edge config/mapper는 변경 패키지 `go test ./apps/edge/...` 중 관련 패키지로 축소한다. +- `go`, `make`, `protoc`, `protoc-gen-go`는 현재 checkout에서 발견됐다. +- 신선한 실행이 필요하므로 Go test cache 출력은 허용하지 않고 `-count=1`을 사용한다. +- secret, token, 개인 endpoint 원문은 tracked 파일에 기록하지 않는다. + +### 테스트 커버리지 공백 + +- `openai_compat` 설정 구조체, normalization, 중복 이름, queue validation은 기존 테스트가 없다. `packages/go/config/config_test.go`에 추가한다. +- `OpenAICompatAdapterConfig` typed proto payload와 Edge mapper는 기존 `vllm` oneof 테스트만 있다. `apps/edge/internal/node/mapper_test.go`와 필요 시 `store_test.go`에 추가한다. +- Edge config validation은 `vllm_instances` endpoint 검사만 있다. `apps/edge/internal/edgecmd/edgecmd_test.go`에 enabled `openai_compat_instances` empty endpoint rejection을 추가한다. +- 실제 Lemonade execution, streaming, field smoke는 이 plan 범위 밖이다. + +### 심볼 참조 + +- rename/remove 없음. +- 추가되는 새 심볼의 call site는 `packages/go/config/config.go`, `apps/edge/internal/node/mapper.go`, `apps/edge/internal/edgecmd/config.go`, generated `proto/gen/iop/runtime.pb.go`, 관련 테스트로 제한한다. + +### 분할 판단 + +- split decision policy를 plan 작성 전에 평가했다. +- 공유 task group: `agent-task/m-lemonade-provider-serving-validation`. +- `01_config_contract`: 독립 선행 작업. 설정/proto/Edge payload 계약만 다룬다. +- `02+01_openai_compatible_adapter`: `01`의 typed config와 generated proto가 PASS된 뒤 Node adapter를 구현한다. +- `03+02_field_smoke`: `02`의 adapter가 PASS된 뒤 fake/real Lemonade smoke를 추가하고 실행한다. +- 이 subtask는 predecessor가 없다. + +### 범위 결정 근거 + +- Node adapter 실행, SSE parsing, real Lemonade endpoint smoke는 제외한다. 계약이 먼저 고정되어야 후속 구현이 안정적이다. +- `apps/node/internal/adapters/vllm`의 behavior 변경은 제외한다. 기존 vLLM 경로 회귀를 피하고 새 common adapter는 후속 subtask에서 구현한다. +- host setup template, tracked `docs/`, archive roadmap/task 경로는 수정하지 않는다. +- `configs/edge.yaml`에는 secret 값 없이 주석 예시만 추가한다. + +### 빌드 등급 + +- `cloud-G05`: protobuf/config schema 변경이고 Edge->Node payload 계약까지 닿지만, 범위와 테스트가 명확하게 제한된다. + +## 구현 체크리스트 + +- [ ] `packages/go/config`에 OpenAI-compatible inference server 설정 계약을 추가하고 normalization/validation 테스트를 작성한다. +- [ ] `proto/iop/runtime.proto`에 typed `OpenAICompatAdapterConfig`를 추가하고 `make proto`로 Go 생성물을 갱신한다. +- [ ] Edge config payload mapper와 config validation, `configs/edge.yaml` 예시를 새 계약에 맞춘다. +- [ ] 계획의 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 붙인다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## API-1 OpenAI-compatible config contract + +### 문제 + +`packages/go/config/config.go:182-195`는 provider instance slice가 `ollama_instances`와 `vllm_instances`뿐이다. Lemonade를 common OpenAI-compatible adapter 계열로 두려면 endpoint, provider label, auth/header, timeout, queue 설정을 표현할 새 계약이 필요하다. + +```go +182 type AdaptersConf struct { +183 // Legacy single-instance fields. When non-empty they are normalised into +184 // OllamaInstances / VllmInstances during config load so that all downstream +185 // code only needs to inspect the slice fields. +186 Ollama OllamaConf `mapstructure:"ollama" yaml:"ollama"` +187 Vllm VllmConf `mapstructure:"vllm" yaml:"vllm"` +188 CLI CLIConf `mapstructure:"cli" yaml:"cli"` +189 +190 // Multi-instance collections. Each entry carries a unique Name that acts as +191 // the stable adapter instance identity within the node. Names must be unique +192 // within each type collection; duplicate names are rejected at load time. +193 OllamaInstances []OllamaInstanceConf `mapstructure:"ollama_instances" yaml:"ollama_instances,omitempty"` +194 VllmInstances []VllmInstanceConf `mapstructure:"vllm_instances" yaml:"vllm_instances,omitempty"` +195 } +``` + +### 해결 방법 + +- `AdaptersConf`에 legacy single config `OpenAICompat OpenAICompatConf`와 multi config `OpenAICompatInstances []OpenAICompatInstanceConf`를 추가한다. +- YAML key는 `openai_compat`와 `openai_compat_instances`, adapter type string은 `openai_compat`로 고정한다. +- `OpenAICompatConf`와 `OpenAICompatInstanceConf` 필드는 `enabled`, `provider`, `endpoint`, `headers map[string]string`, `capacity`, `max_queue`, `queue_timeout_ms`, `request_timeout_ms`로 둔다. +- `provider`는 비어 있으면 후속 adapter에서 `"openai_compat"`로 취급할 수 있게 보존하고, Lemonade 예시는 `"lemonade"`를 사용한다. +- legacy `openai_compat`가 enabled면 name `"openai_compat"` instance로 normalize한다. 명시 instance와 충돌하면 `provider/endpoint/headers/capacity/max_queue/queue_timeout_ms/request_timeout_ms` 불일치 에러를 낸다. +- enabled `openai_compat_instances`도 `validateProviderQueueConfig`를 통과해야 한다. + +### 수정 파일 및 체크리스트 + +- [ ] `packages/go/config/config.go`: struct, normalize, `sameOpenAICompatInstance`, `findOpenAICompatInstance`, queue validation을 추가한다. +- [ ] `packages/go/config/config_test.go`: load/normalize/idempotent/conflict/duplicate/negative queue 테스트를 추가한다. + +### 테스트 작성 + +- `TestLoadEdge_OpenAICompatInstances` +- `TestNormalizeAdapters_OpenAICompatIdempotent` +- `TestNormalizeAdapters_OpenAICompatConflictErrors` +- `TestNormalizeAdapters_OpenAICompatDuplicateNameRejects` +- `TestNormalizeAdapters_OpenAICompatQueueValidation` + +### 중간 검증 + +```bash +go test -count=1 ./packages/go/config +``` + +기대 결과: 새 config 계약 테스트와 기존 config 테스트가 모두 PASS. + +## API-2 Typed proto payload and mapper + +### 문제 + +`proto/iop/runtime.proto:166-178`의 typed oneof는 `cli`, `ollama`, `vllm`, `mock`뿐이다. `apps/edge/internal/node/mapper.go:58-81`도 vLLM instance만 typed `VllmAdapterConfig`로 보낸다. + +```proto +166 message AdapterConfig { +167 string type = 1; // "mock" | "ollama" | "vllm" | "cli" +168 bool enabled = 2; +169 google.protobuf.Struct settings = 3; // legacy/compat path; new adapters use oneof +170 oneof config { +171 CLIAdapterConfig cli = 4; +172 OllamaAdapterConfig ollama = 5; +173 VllmAdapterConfig vllm = 6; +174 MockAdapterConfig mock = 7; +175 } +176 string name = 8; // stable instance identity; empty = legacy single-instance +177 string target = 9; // optional default target/route hint for this instance +178 } +``` + +```go +58 for _, inst := range rec.Adapters.VllmInstances { +59 if !inst.Enabled { +60 continue +61 } +62 key := "vllm:" + inst.Name +63 if _, dup := seen[key]; dup { +64 return nil, fmt.Errorf("duplicate vllm instance name %q", inst.Name) +65 } +66 seen[key] = struct{}{} +67 payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ +68 Type: "vllm", +69 Enabled: true, +70 Name: inst.Name, +71 Config: &iop.AdapterConfig_Vllm{ +``` + +### 해결 방법 + +- `runtime.proto`에 `OpenAICompatAdapterConfig openai_compat = 10;`을 oneof에 추가한다. 기존 tag는 재사용하지 않는다. +- `OpenAICompatAdapterConfig` message를 추가하고 fields는 `provider=1`, `endpoint=2`, `map headers=3`, `capacity=4`, `max_queue=5`, `queue_timeout_ms=6`, `request_timeout_ms=7`로 둔다. +- comment의 type 목록에 `"openai_compat"`를 추가한다. +- `make proto`로 `proto/gen/iop/runtime.pb.go`를 갱신한다. generated 파일은 직접 수정하지 않는다. +- `BuildConfigPayload`가 enabled `OpenAICompatInstances`를 `Type: "openai_compat"`, `Name: inst.Name`, typed oneof로 보낸다. +- duplicate key prefix는 `"openai_compat:"`를 사용한다. + +### 수정 파일 및 체크리스트 + +- [ ] `proto/iop/runtime.proto`: oneof와 message 추가. +- [ ] `proto/gen/iop/runtime.pb.go`: `make proto` 결과로만 갱신. +- [ ] `apps/edge/internal/node/mapper.go`: OpenAI-compatible instance mapping 추가. +- [ ] `apps/edge/internal/node/mapper_test.go`: payload type/name/provider/endpoint/headers/queue 필드 검증 추가. +- [ ] `apps/edge/internal/node/store_test.go`: `LoadFromConfig` normalize 결과가 payload까지 유지되는지 필요 시 추가. + +### 테스트 작성 + +- `TestBuildConfigPayload_OpenAICompatInstances` +- `TestLoadFromConfig_OpenAICompatInPayload` 또는 mapper test로 충분하면 store test는 skip 사유를 review에 기록한다. + +### 중간 검증 + +```bash +make proto +go test -count=1 ./apps/edge/internal/node ./proto/gen/... +``` + +기대 결과: generated proto compile과 Edge payload tests PASS. + +## API-3 Edge validation and example config + +### 문제 + +`apps/edge/internal/edgecmd/config.go:167-187`은 enabled adapter 판정과 endpoint validation에서 Ollama, CLI, vLLM만 본다. `configs/edge.yaml:49-76`, `configs/edge.yaml:84-189`에도 Lemonade/OpenAI-compatible route와 node adapter 예시가 없다. + +```go +167 hasOllama := anyOllamaEnabled(n.Adapters) +168 hasVllm := anyVllmEnabled(n.Adapters) +169 if !hasOllama && !n.Adapters.CLI.Enabled && !hasVllm { +170 return fmt.Errorf("node %q: at least one adapter must be enabled", name) +171 } +``` + +```yaml +68 # - model: "qwen3" +69 # adapter: "vllm" +70 # target: "qwen3-72b" +71 # node: "node-gpu-01" +``` + +### 해결 방법 + +- `anyOpenAICompatEnabled` helper를 추가하고 enabled adapter validation에 포함한다. +- enabled `openai_compat` 또는 `openai_compat_instances`의 empty endpoint를 reject한다. +- `configs/edge.yaml`에 commented route 예시를 추가한다: `adapter: "openai_compat"`, `target: ""`, `node: "node-lemonade-01"`. +- node adapter 예시에도 commented `openai_compat_instances`를 추가한다. endpoint 예시는 Lemonade 기본 local API인 `http://127.0.0.1:13305`를 comment로 두고, headers는 secret 값 없이 placeholder comment만 둔다. + +### 수정 파일 및 체크리스트 + +- [ ] `apps/edge/internal/edgecmd/config.go`: helper와 validation 추가. +- [ ] `apps/edge/internal/edgecmd/edgecmd_test.go`: enabled OpenAI-compatible instance, empty endpoint rejection, adapter-only node pass 테스트 추가. +- [ ] `configs/edge.yaml`: commented example 추가. secret 값 금지. + +### 테스트 작성 + +- `TestValidateEdgeConfig_OpenAICompatInstanceEnabled` +- `TestValidateEdgeConfig_OpenAICompatInstanceEmptyEndpointRejected` +- legacy single config를 추가했다면 `TestValidateEdgeConfig_OpenAICompatLegacyEmptyEndpointRejected` + +### 중간 검증 + +```bash +go test -count=1 ./apps/edge/internal/edgecmd +``` + +기대 결과: config validation tests PASS. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `packages/go/config/config.go` | API-1 | +| `packages/go/config/config_test.go` | API-1 | +| `proto/iop/runtime.proto` | API-2 | +| `proto/gen/iop/runtime.pb.go` | API-2 | +| `apps/edge/internal/node/mapper.go` | API-2 | +| `apps/edge/internal/node/mapper_test.go` | API-2 | +| `apps/edge/internal/node/store_test.go` | API-2 | +| `apps/edge/internal/edgecmd/config.go` | API-3 | +| `apps/edge/internal/edgecmd/edgecmd_test.go` | API-3 | +| `configs/edge.yaml` | API-3 | + +## 최종 검증 + +```bash +make proto +go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/edgecmd ./proto/gen/... +git diff --check +``` + +기대 결과: 모든 명령이 exit 0. `git diff --check`는 whitespace error 없음. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/CODE_REVIEW-local-G08.md b/agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/CODE_REVIEW-local-G08.md new file mode 100644 index 0000000..24c91c5 --- /dev/null +++ b/agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/CODE_REVIEW-local-G08.md @@ -0,0 +1,145 @@ + + +# 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 user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-15 +task=m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `availability`: Lemonade provider의 endpoint/model probe 결과가 Node 공통 상태 모델 `unknown`, `available`, `unavailable`로 노출되는 기준이 검증되어 있다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G08.md` -> `code_review_local_G08_N.log`, `PLAN-local-G08.md` -> `plan_local_G08_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Common OpenAI-compatible adapter | [ ] | +| [API-2] Factory registration | [ ] | +| [API-3] Node execution regression coverage | [ ] | + +## 구현 체크리스트 + +- [ ] 구현 시작 전 `01_config_contract`의 `complete.log`를 확인하고, 선행 계약의 type/field 이름을 이 plan에 맞춰 반영한다. +- [ ] `openai_compat` Node adapter를 추가하고 `/v1/models`, `/v1/chat/completions` streaming, headers, option passthrough를 구현한다. +- [ ] factory가 typed/legacy `openai_compat` payload를 registry에 등록하도록 연결한다. +- [ ] Node adapter/factory/blackbox 테스트를 작성하고 기존 vLLM 테스트가 그대로 PASS하는지 확인한다. +- [ ] 계획의 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 붙인다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G08_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G08_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/`를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-lemonade-provider-serving-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G08.md`와 `CODE_REVIEW-local-G08.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `01_config_contract` predecessor completion이 실제 `complete.log`로 충족됐는지 확인한다. +- `openai_compat` adapter가 `vllm` adapter name/behavior를 깨뜨리지 않았는지 확인한다. +- headers, options passthrough, `[DONE]`, finish_reason, usage fallback이 tests와 구현에서 일치하는지 확인한다. +- endpoint가 root 또는 trailing `/v1` base URL이어도 provider request path가 중복되지 않는지 확인한다. +- provider status가 `unknown`, `available`, `unavailable` baseline 밖으로 확장되지 않았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### API-1 중간 검증 +```bash +$ go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm +(output) +``` + +### API-2 중간 검증 +```bash +$ go test -count=1 ./apps/node/internal/adapters +(output) +``` + +### API-3 중간 검증 +```bash +$ go test -count=1 ./apps/node/internal/adapters +(output) +``` + +### 최종 검증 +```bash +$ go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm ./apps/node/internal/adapters ./apps/node/... +(output) +$ git diff --check +(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?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. diff --git a/agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/PLAN-local-G08.md b/agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/PLAN-local-G08.md new file mode 100644 index 0000000..c34b087 --- /dev/null +++ b/agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/PLAN-local-G08.md @@ -0,0 +1,284 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +구현 완료 전 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 설계 결정, 검증 출력으로 반드시 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, secret, 서비스 준비, 또는 범위 충돌 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 증거를 남기고 멈춘다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다. 최종 판정, archive, `complete.log` 작성은 code-review-skill 전용이다. + +## 배경 + +Lemonade Server는 OpenAI-compatible `/v1/models`와 `/v1/chat/completions` 표면을 제공한다. 현재 Node에는 vLLM 전용 이름의 adapter가 같은 HTTP/SSE 흐름을 일부 구현하지만, Lemonade를 vLLM adapter로 노출하면 provider 경계와 설정 계약이 흐려진다. 이 plan은 선행 config/proto 계약을 사용해 `openai_compat` Node adapter를 구현하고 provider availability 기준을 검증한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단이 생기면 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트가 채팅으로 직접 사용자에게 묻는 것은 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `availability`: Lemonade provider의 endpoint/model probe 결과가 Node 공통 상태 모델 `unknown`, `available`, `unavailable`로 노출되는 기준이 검증되어 있다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/rules/project/domain/node/rules.md` +- `agent-ops/rules/project/domain/edge/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/node-smoke.md` +- `agent-test/local/edge-smoke.md` +- `agent-test/local/platform-common-smoke.md` +- `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- `proto/iop/runtime.proto` +- `packages/go/config/config.go` +- `apps/node/internal/runtime/types.go` +- `apps/node/internal/adapters/factory.go` +- `apps/node/internal/adapters/factory_internal_test.go` +- `apps/node/internal/adapters/adapters_blackbox_test.go` +- `apps/node/internal/adapters/vllm/vllm.go` +- `apps/node/internal/adapters/vllm/vllm_test.go` +- `apps/edge/internal/openai/types.go` +- `apps/edge/internal/openai/chat_handler.go` +- `apps/edge/internal/openai/server_test.go` + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`, `node-smoke`, `edge-smoke`, `platform-common-smoke`를 읽었다. +- 적용 명령: Node adapter 변경은 변경 패키지 또는 `go test ./apps/node/...`; OpenAI-compatible 경계는 후속 smoke에서 `iop-edge smoke openai` 또는 동등한 HTTP 확인을 적용한다. +- 이 subtask는 httptest 기반 deterministic Node adapter 검증까지 담당한다. Edge HTTP field convergence는 `03+02_field_smoke`에서 담당한다. +- 신선한 실행이 필요하므로 Go test cache 출력은 허용하지 않고 `-count=1`을 사용한다. + +### 테스트 커버리지 공백 + +- `apps/node/internal/adapters/vllm/vllm_test.go`는 `/v1/models`, SSE delta, target miss를 검증하지만 adapter name, provider label, headers, option passthrough가 없다. +- `apps/node/internal/adapters/factory_internal_test.go`는 vLLM typed/legacy config만 검증한다. `openai_compat` typed config와 legacy settings fallback 테스트가 필요하다. +- `apps/node/internal/adapters/adapters_blackbox_test.go`는 vLLM execution만 있다. `openai_compat` registry key, headers, options, complete metadata를 검증하는 blackbox test가 필요하다. +- real Lemonade endpoint 검증은 사용자 소유 환경에 의존하므로 이 subtask 범위가 아니다. + +### 심볼 참조 + +- rename/remove 없음. +- 새 adapter type string `openai_compat` call site: `apps/node/internal/adapters/factory.go`, `apps/node/internal/adapters/adapters_blackbox_test.go`, 후속 `apps/edge/internal/node/mapper.go` payload 출력. + +### 분할 판단 + +- split decision policy를 plan 작성 전에 평가했다. +- 공유 task group: `agent-task/m-lemonade-provider-serving-validation`. +- `01_config_contract`: 선행 config/proto 계약. 현재 active/archive `complete.log` 없음. +- `02+01_openai_compatible_adapter`: 이 plan. predecessor `01`은 아직 만족되지 않았다. 구현 시작 전 같은 task group의 `01_*` active/archive `complete.log`가 있어야 한다. +- `03+02_field_smoke`: 후속 field smoke. 이 plan의 PASS 후 시작한다. + +### 범위 결정 근거 + +- 선행 `01_config_contract`의 type/field 이름을 사용한다. 선행 결과와 이름이 다르면 이 plan의 `계획 대비 변경 사항`에 맞춘다. +- `apps/node/internal/adapters/vllm`의 public behavior를 깨뜨리지 않는다. 공통 로직 추출은 허용하지만 기존 `vllm` adapter name, default capacity, tests는 유지한다. +- Edge OpenAI HTTP handler의 request parsing/response formatting은 수정하지 않는다. Edge route convergence는 후속 smoke에서 검증한다. +- real Lemonade installation, credentials, external endpoint 준비는 제외한다. + +### 빌드 등급 + +- `local-G08`: streaming/probe/error handling을 다루지만 httptest로 deterministic하게 검증 가능하고 Node adapter 경계로 범위를 제한한다. + +## 구현 체크리스트 + +- [ ] 구현 시작 전 `01_config_contract`의 `complete.log`를 확인하고, 선행 계약의 type/field 이름을 이 plan에 맞춰 반영한다. +- [ ] `openai_compat` Node adapter를 추가하고 `/v1/models`, `/v1/chat/completions` streaming, headers, option passthrough를 구현한다. +- [ ] factory가 typed/legacy `openai_compat` payload를 registry에 등록하도록 연결한다. +- [ ] Node adapter/factory/blackbox 테스트를 작성하고 기존 vLLM 테스트가 그대로 PASS하는지 확인한다. +- [ ] 계획의 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 review stub에 붙인다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- 디렉터리명 `02+01_openai_compatible_adapter`의 predecessor는 `01`뿐이다. +- 구현 시작 전 `agent-task/m-lemonade-provider-serving-validation/01_config_contract/complete.log` 또는 archive의 matching `01_*` `complete.log`를 확인한다. +- 이 plan 본문은 추가 runtime dependency를 만들지 않는다. + +## API-1 Common OpenAI-compatible adapter + +### 문제 + +`apps/node/internal/adapters/vllm/vllm.go:1-3`은 이미 OpenAI-compatible inference engine이라고 설명하지만 package/type/name은 vLLM 전용이다. `apps/node/internal/adapters/vllm/vllm.go:85-123`은 request body가 `model/messages/stream`뿐이라 Lemonade의 option passthrough와 auth/header 계약을 사용할 수 없다. + +```go +1 // Package vllm provides an Adapter for OpenAI-compatible inference engines +2 // such as vLLM and SGLang via the /v1/chat/completions SSE endpoint. +3 package vllm +``` + +```go +109 chatReq := vllmChatRequest{ +110 Model: model, +111 Messages: messages, +112 Stream: true, +113 } +114 body, err := json.Marshal(chatReq) +115 if err != nil { +116 return fmt.Errorf("vllm adapter: marshal request: %w", err) +117 } +118 req, err := http.NewRequestWithContext(ctx, http.MethodPost, joinURL(v.endpoint, "/v1/chat/completions"), bytes.NewReader(body)) +119 if err != nil { +120 return fmt.Errorf("vllm adapter: build request: %w", err) +121 } +122 req.Header.Set("Content-Type", "application/json") +``` + +### 해결 방법 + +- 새 package `apps/node/internal/adapters/openai_compat`를 만든다. +- `const Name = "openai_compat"`와 adapter type을 정의한다. +- config는 선행 task의 `config.OpenAICompatConf`를 사용한다. +- endpoint는 `strings.TrimRight(endpoint, "/")`; empty endpoint는 execution/probe에서 error. `joinOpenAIPath` helper는 endpoint path가 이미 `/v1`로 끝나도 `/v1/v1/...`을 만들지 않아야 한다. +- HTTP headers는 `/v1/models`와 `/v1/chat/completions` 양쪽에 적용한다. `Content-Type: application/json`은 POST에서 adapter가 항상 설정하고, user header가 같은 key를 제공하면 secret/auth header만 허용하되 content type override는 명시적으로 막거나 review에 근거를 기록한다. +- request body는 `map[string]any`로 만들고 adapter-owned `model`, `messages`, `stream=true`를 우선한다. `spec.Input["options"].(map[string]any)`는 top-level OpenAI-compatible request field로 복사하되 `model`, `messages`, `stream`은 덮어쓰지 못하게 한다. `tools`, `format`, `think`, `keep_alive`는 현재 Edge input 그대로 필요 시 복사한다. +- SSE parser는 `data: [DONE]`에서 complete를 emit한다. chunk `choices[0].delta.content`는 `EventTypeDelta`, `choices[0].delta.reasoning_content`가 있으면 `EventTypeReasoningDelta`로 emit한다. +- chunk `choices[0].finish_reason`은 complete event `Metadata["finish_reason"]`로 보존한다. usage가 있으면 `RuntimeEvent.Usage`에 반영하고, 없으면 현재 vLLM처럼 output token fallback을 사용한다. +- `Capabilities`와 `ProbeProvider`는 `/v1/models`의 `data[].id`를 사용하고 status는 `runtime.NormalizeProviderStatus`로 `available`/`unavailable`만 반환한다. + +### 수정 파일 및 체크리스트 + +- [ ] `apps/node/internal/adapters/openai_compat/openai_compat.go`: adapter 구현 추가. +- [ ] `apps/node/internal/adapters/openai_compat/openai_compat_test.go`: capabilities/probe/execute/header/options/stream tests 추가. +- [ ] 필요 시 `apps/node/internal/adapters/vllm/vllm.go`: 공통 helper reuse를 위한 최소 수정. vLLM behavior/name 회귀 금지. +- [ ] 필요 시 `apps/node/internal/adapters/vllm/vllm_test.go`: 기존 expectations 유지 또는 공통 helper 이동에 따른 import 조정. + +### 테스트 작성 + +- `TestOpenAICompatCapabilitiesQueryModels` +- `TestOpenAICompatProbeProviderAvailability` +- `TestOpenAICompatExecuteStreamsDeltasAndFinishReason` +- `TestOpenAICompatExecuteSendsHeaders` +- `TestOpenAICompatExecutePassesOptionsAsTopLevelFields` +- `TestOpenAICompatExecuteRejectsEmptyEndpointOrModel` + +### 중간 검증 + +```bash +go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm +``` + +기대 결과: 새 adapter tests PASS, 기존 vLLM tests PASS. + +## API-2 Factory registration + +### 문제 + +`apps/node/internal/adapters/factory.go:27-56`는 `mock`, `ollama`, `vllm`, `cli`만 등록한다. 선행 task가 `openai_compat` typed payload를 만들더라도 Node가 이를 모르면 등록 실패가 난다. + +```go +27 switch typeName { +28 case "mock": +29 reg.RegisterKeyed(instanceKey, typeName, mock.New(logger)) +30 case "ollama": +31 var cfg config.OllamaConf +32 if m := ac.GetOllama(); m != nil { +33 cfg = ollamaConfFromProto(m) +34 } else { +35 cfg = ollamaConfFromStruct(ac.GetSettings()) +36 } +37 reg.RegisterKeyed(instanceKey, typeName, ollama.New(cfg, logger, instanceKey)) +38 case "vllm": +``` + +### 해결 방법 + +- factory imports에 `iop/apps/node/internal/adapters/openai_compat`를 추가한다. +- switch에 `case "openai_compat"`를 추가한다. +- typed payload는 `ac.GetOpenaiCompat()` 또는 generated accessor 이름을 사용한다. accessor 이름은 `make proto` 결과를 확인해 정확히 맞춘다. +- `openAICompatConfFromProto`와 `openAICompatConfFromStruct` helper를 추가한다. +- legacy settings fallback은 `provider`, `endpoint`, `headers`, queue fields를 읽는다. +- `reg.RegisterKeyed(instanceKey, typeName, openai_compat.New(cfg, logger, instanceKey))` 형태로 등록한다. + +### 수정 파일 및 체크리스트 + +- [ ] `apps/node/internal/adapters/factory.go`: import, switch case, proto/struct config helpers 추가. +- [ ] `apps/node/internal/adapters/factory_internal_test.go`: typed config와 legacy settings fallback 테스트 추가. +- [ ] `apps/node/internal/adapters/adapters_blackbox_test.go`: payload에서 registry key와 execution까지 blackbox 테스트 추가. + +### 테스트 작성 + +- `TestOpenAICompatConfFromProto` +- `TestOpenAICompatConfFromStruct_LegacyPayload` +- `TestBuildFromPayload_OpenAICompatTypedRegistered` +- `TestBuildFromPayload_OpenAICompatExecution` + +### 중간 검증 + +```bash +go test -count=1 ./apps/node/internal/adapters +``` + +기대 결과: factory/internal blackbox tests PASS. + +## API-3 Node execution regression coverage + +### 문제 + +`apps/node/internal/adapters/adapters_blackbox_test.go:520-585`는 vLLM execution만 검증한다. `openai_compat`의 registry key, headers, options passthrough, model fallback이 route-level로 깨져도 현재 blackbox coverage가 없다. + +```go +520 func TestBuildFromPayload_VllmExecution(t *testing.T) { +521 var gotModel string +522 var gotStream bool +... +549 reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{ +550 Adapters: []*iop.AdapterConfig{ +551 { +552 Type: "vllm", +553 Name: "vllm@gpu", +554 Enabled: true, +555 Config: &iop.AdapterConfig_Vllm{Vllm: &iop.VllmAdapterConfig{Endpoint: server.URL}}, +``` + +### 해결 방법 + +- blackbox test server가 `/v1/models`와 `/v1/chat/completions`를 모두 제공하게 한다. +- typed `OpenAICompatAdapterConfig` payload로 registry를 만들고 `reg.Get("lemonade-local")` lookup을 검증한다. +- `ExecutionSpec{Target:"lemonade-model", Input:{"messages":..., "options":{"temperature":0.2,"max_tokens":8}}}` 실행 후 server가 header와 top-level options를 받았는지 확인한다. +- stream은 `data: {"choices":[{"delta":{"content":"LEMONADE_"}}]}`와 `[DONE]`을 사용한다. + +### 수정 파일 및 체크리스트 + +- [ ] `apps/node/internal/adapters/adapters_blackbox_test.go`: integration-style blackbox test 추가. +- [ ] 필요 시 shared test sink/helper를 중복 최소화한다. 과도한 test abstraction은 만들지 않는다. + +### 테스트 작성 + +- `TestBuildFromPayload_OpenAICompatExecution` + +### 중간 검증 + +```bash +go test -count=1 ./apps/node/internal/adapters +``` + +기대 결과: registry/factory/execution blackbox tests PASS. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/node/internal/adapters/openai_compat/openai_compat.go` | API-1 | +| `apps/node/internal/adapters/openai_compat/openai_compat_test.go` | API-1 | +| `apps/node/internal/adapters/vllm/vllm.go` | API-1 | +| `apps/node/internal/adapters/vllm/vllm_test.go` | API-1 | +| `apps/node/internal/adapters/factory.go` | API-2 | +| `apps/node/internal/adapters/factory_internal_test.go` | API-2 | +| `apps/node/internal/adapters/adapters_blackbox_test.go` | API-2, API-3 | + +## 최종 검증 + +```bash +go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm ./apps/node/internal/adapters ./apps/node/... +git diff --check +``` + +기대 결과: 모든 명령이 exit 0. `git diff --check`는 whitespace error 없음. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/CODE_REVIEW-cloud-G07.md b/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..fb11a6c --- /dev/null +++ b/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,157 @@ + + +# Code Review Reference - TEST + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-15 +task=m-lemonade-provider-serving-validation/03+02_field_smoke, plan=0, tag=TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `models-chat`: Edge OpenAI-compatible `/v1/models`와 non-streaming `/v1/chat/completions`가 Lemonade provider로 수렴하는 기준이 검증되어 있다. + - `streaming`: streaming `/v1/chat/completions`에서 SSE chunk, finish reason, 종료 신호가 Lemonade provider 경로로 안정적으로 전달되는지 검증되어 있다. + - `field-smoke`: split-host field smoke에서 Lemonade target/model을 선택해 모델 조회와 chat 왕복이 검증되어 있다. + - `follow-up-scope`: 코드 수정이 필요한 항목은 이 Milestone의 추가 Task 또는 같은 Milestone task group의 후속 plan으로 정리되어 있다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/03+02_field_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [TEST-1] Lemonade smoke script | [ ] | +| [TEST-2] Makefile target and command contract | [ ] | +| [TEST-3] Real Lemonade field gate and follow-up scope | [ ] | + +## 구현 체크리스트 + +- [ ] 구현 시작 전 `02+01_openai_compatible_adapter`의 `complete.log`를 확인하고, 선행 adapter type/config 이름을 smoke config에 맞춘다. +- [ ] `scripts/e2e-openai-lemonade.sh`를 추가해 fake mode와 real mode에서 Edge/Node/OpenAI-compatible route를 검증한다. +- [ ] `Makefile`에 명시 실행 target `test-openai-lemonade`를 추가하되 `test-e2e` 기본 dependency에는 넣지 않는다. +- [ ] fake mode smoke, real mode smoke, 관련 Go package tests를 실행한다. real mode 환경값이 없으면 review stub의 `사용자 리뷰 요청`에 기록하고 PASS를 주장하지 않는다. +- [ ] smoke 결과 큰 코드 이슈가 발견되면 이 plan 범위를 넘겨 수정하지 말고 증거를 남겨 후속 plan으로 넘긴다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/`를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/03+02_field_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-lemonade-provider-serving-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `02+01_openai_compatible_adapter` predecessor completion이 실제 `complete.log`로 충족됐는지 확인한다. +- fake mode가 Edge/Node/OpenAI-compatible route를 실제로 통과하고, real mode 없이는 PASS를 주장하지 않았는지 확인한다. +- script가 auth header value나 개인 endpoint를 tracked 파일 또는 review 출력에 노출하지 않았는지 확인한다. +- real smoke 실패가 큰 코드 이슈라면 후속 plan으로 넘어가고 이 task가 PASS되지 않았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### TEST-1 중간 검증 +```bash +$ bash -n scripts/e2e-openai-lemonade.sh +(output) +$ ./scripts/e2e-openai-lemonade.sh +(output) +``` + +### TEST-2 중간 검증 +```bash +$ make test-openai-lemonade +(output) +``` + +### TEST-3 중간 검증 +```bash +$ IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +(output) +``` + +### 최종 검증 +```bash +$ bash -n scripts/e2e-openai-lemonade.sh +(output) +$ ./scripts/e2e-openai-lemonade.sh +(output) +$ make test-openai-lemonade +(output) +$ IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +(output) +$ go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/edgecmd ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters +(output) +$ git diff --check +(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?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. diff --git a/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/PLAN-cloud-G07.md b/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/PLAN-cloud-G07.md new file mode 100644 index 0000000..e4ea4be --- /dev/null +++ b/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/PLAN-cloud-G07.md @@ -0,0 +1,273 @@ + + +# Plan - TEST + +## 이 파일을 읽는 구현 에이전트에게 + +구현 완료 전 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 설계 결정, 검증 출력으로 반드시 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, secret, 서비스 준비, 또는 범위 충돌 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 증거를 남기고 멈춘다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다. 최종 판정, archive, `complete.log` 작성은 code-review-skill 전용이다. + +## 배경 + +Lemonade provider의 최종 기준은 Edge OpenAI-compatible surface에서 모델 조회, non-streaming chat, streaming chat이 provider 경로로 수렴하는지 확인하는 것이다. 현재 repo에는 Ollama 전용 OpenAI e2e script와 generic `iop-edge smoke openai`만 있고, Lemonade/OpenAI-compatible provider 경로를 선택해 검증하는 field smoke가 없다. 이 plan은 선행 adapter 구현을 전제로 fake mode와 real mode를 모두 지원하는 smoke를 추가한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단이 생기면 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트가 채팅으로 직접 사용자에게 묻는 것은 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. 실제 Lemonade endpoint/model/auth 값이 준비되지 않아 real mode smoke를 실행할 수 없으면 이 섹션에 기록하고 PASS를 주장하지 않는다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `models-chat`: Edge OpenAI-compatible `/v1/models`와 non-streaming `/v1/chat/completions`가 Lemonade provider로 수렴하는 기준이 검증되어 있다. + - `streaming`: streaming `/v1/chat/completions`에서 SSE chunk, finish reason, 종료 신호가 Lemonade provider 경로로 안정적으로 전달되는지 검증되어 있다. + - `field-smoke`: split-host field smoke에서 Lemonade target/model을 선택해 모델 조회와 chat 왕복이 검증되어 있다. + - `follow-up-scope`: 코드 수정이 필요한 항목은 이 Milestone의 추가 Task 또는 같은 Milestone task group의 후속 plan으로 정리되어 있다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/rules/project/domain/node/rules.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/node-smoke.md` +- `agent-test/local/edge-smoke.md` +- `agent-test/local/platform-common-smoke.md` +- `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- `scripts/e2e-openai-ollama.sh` +- `apps/edge/internal/edgecmd/smoke_openai.go` +- `Makefile` +- `configs/edge.yaml` +- `configs/node.yaml` +- `apps/edge/internal/openai/types.go` +- `apps/edge/internal/openai/chat_handler.go` +- `apps/edge/internal/openai/server_test.go` +- `apps/node/internal/adapters/vllm/vllm.go` +- `apps/node/internal/adapters/vllm/vllm_test.go` +- `apps/node/internal/adapters/adapters_blackbox_test.go` + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`, `edge-smoke`, `node-smoke`, `platform-common-smoke`를 읽었다. +- 적용 명령: Edge OpenAI-compatible 경계 변경은 `iop-edge smoke openai` 또는 동등한 `/healthz`, `/v1/models`, `/v1/responses` 확인이 필요하다. 이 plan은 추가로 `/v1/chat/completions` non-streaming/streaming curl 검증을 포함한다. +- field runtime이나 외부 provider evidence는 원격 runner 또는 사용자 소유 host가 필요할 수 있다. 이 plan의 fake mode는 local deterministic check이고, Roadmap `field-smoke` PASS에는 real mode evidence가 필요하다. +- secret, token, 개인 endpoint 원문은 tracked 파일과 최종 보고에 기록하지 않는다. + +### 테스트 커버리지 공백 + +- `scripts/e2e-openai-ollama.sh`는 Ollama fake server와 Ollama adapter만 검증한다. Lemonade/OpenAI-compatible adapter route는 없다. +- `apps/edge/internal/edgecmd/smoke_openai.go`는 `/healthz`, `/v1/models`, `/v1/responses`만 확인한다. `/v1/chat/completions` non-streaming/streaming 검증이 없다. +- real Lemonade endpoint/model/auth/streaming expectation은 아직 마일스톤 잠금에 남아 있으므로, 구현 시 environment가 없으면 사용자 리뷰 요청으로 멈춰야 한다. + +### 심볼 참조 + +- rename/remove 없음. +- 새 script target `test-openai-lemonade` call site는 `Makefile`에 한정한다. + +### 분할 판단 + +- split decision policy를 plan 작성 전에 평가했다. +- 공유 task group: `agent-task/m-lemonade-provider-serving-validation`. +- `01_config_contract`: config/proto contract. +- `02+01_openai_compatible_adapter`: Node adapter. 현재 active/archive `complete.log` 없음. +- `03+02_field_smoke`: 이 plan. predecessor `02`는 아직 만족되지 않았다. 구현 시작 전 같은 task group의 `02_*` 또는 `02+*` active/archive `complete.log`가 있어야 한다. + +### 범위 결정 근거 + +- Lemonade 서버 설치, 모델 다운로드, 운영 제품화는 제외한다. +- `make test-e2e` 기본 흐름에는 real Lemonade dependency를 추가하지 않는다. 새 Makefile target은 명시 실행용으로 둔다. +- secret/header 값은 tracked script, config, docs, roadmap에 쓰지 않는다. +- Smoke 중 발견된 큰 코드 이슈는 이 plan에서 임의 확장하지 않는다. review stub에 증거를 남기고 code-review가 후속 plan을 만들게 한다. + +### 빌드 등급 + +- `cloud-G07`: shell smoke orchestration, process control, real external endpoint evidence와 PASS gate가 핵심이며 terminal workflow 실패 분석이 필요할 수 있다. + +## 구현 체크리스트 + +- [ ] 구현 시작 전 `02+01_openai_compatible_adapter`의 `complete.log`를 확인하고, 선행 adapter type/config 이름을 smoke config에 맞춘다. +- [ ] `scripts/e2e-openai-lemonade.sh`를 추가해 fake mode와 real mode에서 Edge/Node/OpenAI-compatible route를 검증한다. +- [ ] `Makefile`에 명시 실행 target `test-openai-lemonade`를 추가하되 `test-e2e` 기본 dependency에는 넣지 않는다. +- [ ] fake mode smoke, real mode smoke, 관련 Go package tests를 실행한다. real mode 환경값이 없으면 review stub의 `사용자 리뷰 요청`에 기록하고 PASS를 주장하지 않는다. +- [ ] smoke 결과 큰 코드 이슈가 발견되면 이 plan 범위를 넘겨 수정하지 말고 증거를 남겨 후속 plan으로 넘긴다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- 디렉터리명 `03+02_field_smoke`의 predecessor는 `02`뿐이다. +- 구현 시작 전 `agent-task/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/complete.log` 또는 archive의 matching `02_*`/`02+*` `complete.log`를 확인한다. +- 이 plan 본문은 추가 runtime dependency를 만들지 않는다. + +## TEST-1 Lemonade smoke script + +### 문제 + +`scripts/e2e-openai-ollama.sh:31-64`는 fake Ollama model/server만 만들고, Edge config도 `adapter: "ollama"`에 고정되어 있다. Lemonade/OpenAI-compatible provider를 선택해 `/v1/models`와 `/v1/chat/completions`를 검증하는 script가 없다. + +```bash +31 MODEL="fake-ollama-model" +32 +33 FAKE_OLLAMA="$TMP_DIR/fake_ollama.go" +34 cat > "$FAKE_OLLAMA" <<'EOF' +... +60 mux.HandleFunc("/api/tags", func(w http.ResponseWriter, _ *http.Request) { +61 w.Header().Set("Content-Type", "application/json") +62 _, _ = w.Write([]byte(`{"models":[{"name":"fake-ollama-model"}]}`)) +63 }) +64 mux.HandleFunc("/api/chat", func(w http.ResponseWriter, r *http.Request) { +``` + +```bash +123 openai: +124 enabled: true +125 listen: "127.0.0.1:$OPENAI_PORT" +126 adapter: "ollama" +127 session_id: "cline" +128 timeout_sec: 30 +129 strict_output: false +130 model_routes: +131 - model: "client-request-model" +132 adapter: "ollama" +133 target: "$MODEL" +``` + +### 해결 방법 + +- 새 `scripts/e2e-openai-lemonade.sh`를 추가한다. +- 기본 `IOP_LEMONADE_MODE`는 `fake`다. fake mode는 temp Go server가 `/v1/models`와 `/v1/chat/completions` SSE를 제공한다. +- real mode는 `IOP_LEMONADE_MODE=real`일 때 외부 Lemonade server를 사용한다. required env: `IOP_LEMONADE_ENDPOINT`, `IOP_LEMONADE_MODEL`. endpoint는 root 또는 trailing `/v1` 둘 다 허용하되 Node adapter config에는 root 형태로 넣는다. +- optional auth: `IOP_LEMONADE_AUTH_HEADER_NAME` + `IOP_LEMONADE_AUTH_HEADER_VALUE`가 모두 있을 때 generated temp YAML `headers`에만 쓴다. script stdout/stderr에는 header value를 출력하지 않는다. +- generated Edge config는 `openai.model_routes`에 client-facing model `"client-request-model"`을 두고 `adapter: "openai_compat"`, `target: "$MODEL"`, `node: "test-node"`로 보낸다. +- generated node adapter는 선행 contract의 `openai_compat_instances` name `"lemonade-local"`, provider `"lemonade"`, endpoint, capacity/queue timeout을 사용한다. +- 검증 단계: + - Edge health `/healthz`. + - Edge `/v1/models`에 client-facing model 노출. + - Edge `/v1/chat/completions` non-streaming 응답에 sentinel 포함. + - Edge `/v1/chat/completions` streaming 응답에 SSE content chunk와 `data: [DONE]`. + - `go run ./apps/edge/cmd/edge smoke openai --model client-request-model ...`로 `/v1/responses` 경로도 확인. +- fake mode sentinel은 `IOP_OPENAI_LEMONADE_OK`로 고정한다. real mode는 empty response가 아니고 model route가 성공하면 통과하되, streaming `[DONE]`은 반드시 확인한다. + +### 수정 파일 및 체크리스트 + +- [ ] `scripts/e2e-openai-lemonade.sh`: 새 smoke script 추가. +- [ ] script 내부 temp config에는 secret value를 출력하지 않는다. +- [ ] script cleanup은 Edge/Node/fake server process와 temp dir를 정리한다. + +### 테스트 작성 + +- shell script 자체는 Go unit test 대신 `bash -n`과 fake mode 실행으로 검증한다. + +### 중간 검증 + +```bash +bash -n scripts/e2e-openai-lemonade.sh +./scripts/e2e-openai-lemonade.sh +``` + +기대 결과: syntax check PASS, fake mode smoke PASS. + +## TEST-2 Makefile target and command contract + +### 문제 + +`Makefile:79-90`은 OpenAI-compatible e2e target으로 Ollama만 제공한다. Lemonade smoke는 명시적으로 실행할 target이 없다. + +```makefile +79 test-e2e: +80 @echo "NOTE: test-e2e runs auxiliary smoke (Edge-Node + OpenAI) plus Control Plane-Edge wire smoke; completion still requires user-flow verification when changing runtime paths." +81 ./scripts/e2e-smoke.sh +82 ./scripts/e2e-openai-ollama.sh +83 ./scripts/e2e-control-plane-edge-wire.sh +... +88 test-openai-ollama: +89 ./scripts/e2e-openai-ollama.sh +``` + +### 해결 방법 + +- `.PHONY`에 `test-openai-lemonade`를 추가한다. +- `test-openai-lemonade` target은 `./scripts/e2e-openai-lemonade.sh`를 실행한다. +- `test-e2e`에는 추가하지 않는다. fake mode는 local check로 충분하지만 real Lemonade dependency를 기본 e2e에 숨기면 field smoke PASS 기준이 흐려진다. + +### 수정 파일 및 체크리스트 + +- [ ] `Makefile`: `.PHONY`와 `test-openai-lemonade` target 추가. + +### 테스트 작성 + +- Makefile target은 script fake mode 실행으로 검증한다. + +### 중간 검증 + +```bash +make test-openai-lemonade +``` + +기대 결과: fake mode smoke PASS. + +## TEST-3 Real Lemonade field gate and follow-up scope + +### 문제 + +`apps/edge/internal/edgecmd/smoke_openai.go:118-124`는 generic OpenAI smoke 목적을 설명하지만 chat completions streaming을 직접 확인하지 않는다. 마일스톤은 실제 Lemonade target/model을 선택한 split-host field smoke와 streaming 안정성 확인을 요구한다. + +```go +118 Long: `Validate the IOP Edge OpenAI-compatible HTTP API surface. +119 It runs a three-step diagnostics check: +120 1. Checks GET /healthz (server status). +121 2. Checks GET /v1/models (advertised model compatibility). +122 3. Checks POST /v1/responses (non-streaming Responses API request processing). +123 +124 If --base-url is not provided, it auto-discovers the OpenAI URL from the effective config's listen address.`, +``` + +### 해결 방법 + +- `scripts/e2e-openai-lemonade.sh` real mode를 최종 PASS gate로 둔다. +- 구현 에이전트는 real mode를 아래 명령으로 실행한다. 환경값이 준비되어 있지 않으면 실행한 명령과 실패 출력을 `사용자 리뷰 요청`에 남기고 멈춘다. +- real mode가 provider-specific code issue를 찾았고 이 plan의 script/config 소규모 수정으로 해결할 수 없으면 PASS를 주장하지 않는다. review stub의 `계획 대비 변경 사항`과 `검증 결과`에 증거를 남겨 code-review가 후속 plan을 만들게 한다. + +### 수정 파일 및 체크리스트 + +- [ ] `scripts/e2e-openai-lemonade.sh`: real mode validation branch 추가. +- [ ] `CODE_REVIEW-cloud-G07.md`: real mode 실행 결과 또는 사용자 리뷰 요청 근거 기록. + +### 테스트 작성 + +- real Lemonade 환경은 unit test로 대체하지 않는다. Roadmap `field-smoke` PASS에는 real mode command output이 필요하다. + +### 중간 검증 + +```bash +IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +``` + +기대 결과: env가 준비된 경우 real Lemonade smoke PASS. env가 없으면 사용자 리뷰 요청에 blocker evidence를 남기고 PASS 금지. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `scripts/e2e-openai-lemonade.sh` | TEST-1, TEST-3 | +| `Makefile` | TEST-2 | +| `CODE_REVIEW-cloud-G07.md` | TEST-3 | + +## 최종 검증 + +```bash +bash -n scripts/e2e-openai-lemonade.sh +./scripts/e2e-openai-lemonade.sh +make test-openai-lemonade +IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/edgecmd ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters +git diff --check +``` + +기대 결과: 모든 명령이 exit 0. real mode 환경값이 없거나 endpoint/model/auth 준비가 안 되어 있으면 PASS 금지, `사용자 리뷰 요청` 작성. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.