feat: model route queue policy alignment phase added and edge service updates
- Add model-route-queue-policy-alignment milestone to inference provider extension phase - Add SDD documentation for inference provider extension - Update edge chat/responses handlers for OpenAI compatible API - Update edge config with new queue policy settings - Add config tests for queue policy support - Update task tracking for model route queue policy alignment
This commit is contained in:
parent
d276c74700
commit
957700c2d6
15 changed files with 1140 additions and 34 deletions
|
|
@ -32,6 +32,10 @@ Ollama 경로가 안정화된 뒤, 그 결과를 기준선으로 삼아 Lemonade
|
|||
- 경로: `agent-roadmap/archive/phase/inference-provider-extension/milestones/edge-model-group-queue-scheduling.md`
|
||||
- 요약: OpenAI-compatible 요청의 `model` 값을 queue group key로 사용하고, Node-local admission queue를 Edge-owned model-group FIFO와 node dispatch scheduler로 전환한다.
|
||||
|
||||
- [진행중] Model route Queue 정책 정합화
|
||||
- 경로: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- 요약: Edge-owned model group queue의 정책 원천을 `openai.model_routes[]`의 model alias로 정리하고, Node/provider instance에는 per-node capacity 기준만 남긴다.
|
||||
|
||||
- [진행중] vLLM provider 서빙 경로 추가
|
||||
- 경로: `agent-roadmap/phase/inference-provider-extension/milestones/vllm-provider-serving-validation.md`
|
||||
- 요약: Ollama 경로와 Node 단일 통로 멀티 타겟 기준선을 바탕으로 vLLM OpenAI-compatible provider를 붙이고, 모델 조회와 non-streaming/streaming chat을 검증한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
# Milestone: Model route Queue 정책 정합화
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/inference-provider-extension/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
Edge-owned model group queue의 정책 원천을 OpenAI-compatible `model_routes`의 model alias로 정리한다.
|
||||
`max_queue`와 `queue_timeout_ms`는 model alias queue 정책으로 두고, Node/provider instance의 `capacity`는 per-node 동시 실행 slot 기준으로 남긴다.
|
||||
기존 adapter instance queue 설정은 호환 fallback으로만 유지해, 다중 Node/다중 provider pool에서 queue owner와 설정 위치가 어긋나지 않게 만든다.
|
||||
|
||||
## 상태
|
||||
|
||||
[진행중]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- 없음
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- SDD: 필요
|
||||
- SDD 문서: `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- SDD 사유: OpenAI route config schema와 Edge queue dispatch 계약을 바꾸는 작업이다.
|
||||
- 잠금 해제 조건:
|
||||
- [x] SDD 잠금이 해제되어 있다
|
||||
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다
|
||||
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다
|
||||
- [x] Evidence Map이 plan의 `Spec Targets`와 완료 시 `Spec Completion`으로 검증 가능하게 연결되어 있다
|
||||
- 결정 필요: 없음
|
||||
|
||||
## 범위
|
||||
|
||||
- `openai.model_routes[]`에 Edge-owned model group queue 정책 필드를 추가한다.
|
||||
- route-level `max_queue`와 `queue_timeout_ms`가 있으면 Edge queue group policy에서 우선 사용한다.
|
||||
- route-level queue policy가 없으면 기존 Node/provider adapter instance의 queue 설정을 호환 fallback으로 유지한다.
|
||||
- Node/provider instance의 `capacity`는 각 Node가 해당 adapter instance로 동시에 받을 수 있는 요청 slot으로 유지한다.
|
||||
- `/v1/chat/completions`와 `/v1/responses` 모두 같은 route-level queue policy 전달 경로를 사용한다.
|
||||
- 설정 예시와 테스트에서 model alias queue policy와 per-node capacity의 책임 경계를 명확히 한다.
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [route-policy] Model Route Queue Policy
|
||||
|
||||
OpenAI-compatible model alias가 Edge queue group의 정책 원천이 되도록 config, dispatch DTO, queue admission 경로를 정리한다.
|
||||
|
||||
- [ ] [config-schema] `openai.model_routes[]`가 `max_queue`, `queue_timeout_ms`를 받을 수 있고 validation/default/fallback 문서가 정리되어 있다. 검증: `go test -count=1 ./packages/go/config ./apps/edge/internal/openai`
|
||||
- [ ] [dispatch-contract] OpenAI Chat Completions와 Responses route resolution이 route-level queue policy를 `SubmitRunRequest`로 전달한다. 검증: `go test -count=1 ./apps/edge/internal/openai`
|
||||
- [ ] [queue-policy] Edge queue admission이 route-level queue policy를 adapter instance fallback보다 우선하고, route policy가 없을 때 기존 fallback을 유지한다. 검증: `go test -count=1 ./apps/edge/internal/service`
|
||||
- [ ] [docs-example] `configs/edge.yaml` 예시가 model alias queue policy와 Node/provider `capacity`의 책임 경계를 보여준다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 모든 기능 Task가 아직 충족되지 않았다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- GPU/CPU/VRAM 실측 기반 자동 capacity 조정
|
||||
- cross-Edge 부하 라우팅, cloud fallback, 품질/비용 기반 모델 선택
|
||||
- `model_routes` 하나에서 provider별로 서로 다른 target을 선언하는 route 후보 목록
|
||||
- vLLM/Lemonade/Ollama provider 자체의 실서비스 smoke 완료 판정
|
||||
- Node bootstrap, Control Plane, Client UI 변경
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `packages/go/config`, `configs/edge.yaml`, `apps/edge/internal/openai`, `apps/edge/internal/service`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`
|
||||
- 표준선(선택): queue owner는 Edge이며, OpenAI-compatible `model` 값은 model group key다.
|
||||
- 표준선(선택): `model_routes`는 외부 model alias와 Edge queue policy를 소유하고, Node/provider instance는 adapter endpoint와 per-node `capacity`를 소유한다.
|
||||
- 표준선(선택): 기존 adapter instance `max_queue`/`queue_timeout_ms`는 route-level queue policy가 없는 설정의 호환 fallback으로 유지한다.
|
||||
- 선행 작업: Edge 모델 그룹 Queue 스케줄링 전환
|
||||
- 후속 작업: vLLM provider 서빙 경로 추가, SGLang provider 서빙 경로 추가, provider catalog/device status에서 queue policy 표시 확장
|
||||
- 확인 필요: 없음
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# SDD: Model route Queue 정책 정합화
|
||||
|
||||
## 위치
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- Phase: `agent-roadmap/phase/inference-provider-extension/PHASE.md`
|
||||
|
||||
## 상태
|
||||
|
||||
[승인됨]
|
||||
|
||||
## SDD 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- 사용자 리뷰: 없음
|
||||
- 잠금 항목:
|
||||
- 없음
|
||||
|
||||
## 문제 / 비목표
|
||||
|
||||
- 문제: Edge-owned model group queue는 OpenAI `model` alias를 group key로 쓰지만, 현재 queue policy의 설정 원천은 Node/provider adapter instance에 남아 있어 다중 Node/다중 provider pool에서 어느 값이 alias queue 정책인지 불명확하다.
|
||||
- 비목표:
|
||||
- GPU/CPU/VRAM metric 기반 자동 capacity 산정
|
||||
- cross-Edge 라우팅, cloud fallback, 품질/비용 기반 모델 선택
|
||||
- provider별 target 후보 목록을 `model_routes` 하나에 직접 여러 개 선언하는 schema
|
||||
|
||||
## Source of Truth
|
||||
|
||||
| 영역 | 기준 | 메모 |
|
||||
|------|------|------|
|
||||
| Roadmap | `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md` | Milestone 기능 Task와 완료 반영 기준 |
|
||||
| Code | `packages/go/config/config.go`, `apps/edge/internal/openai`, `apps/edge/internal/service` | route schema, OpenAI dispatch, Edge queue policy 구현 원천 |
|
||||
| External Provider | 없음 | provider endpoint 자체의 동작 변경은 범위 밖 |
|
||||
| User Decision | 없음 | 사용자가 queue policy는 model alias, capacity는 Node/provider instance에 둔다는 방향을 확정했다 |
|
||||
|
||||
## State Machine
|
||||
|
||||
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|
||||
|------|-----------|-----------|------|
|
||||
| route-resolved | OpenAI request `model`이 `model_routes[]` 또는 fallback route로 해석된다 | queue-admission | `resolveRouteDispatch` 결과와 `SubmitRunRequest.ModelGroupKey` |
|
||||
| queue-admission | `ModelGroupKey`가 있고 Edge queue manager가 후보 Node와 policy를 받는다 | dispatched 또는 queued 또는 rejected | `modelQueueManager.admit` |
|
||||
| queued | 모든 후보 Node의 `in_flight >= capacity`이고 group queue 여유가 있다 | dispatched 또는 timeout/cancelled | terminal run event, queue timeout, request context |
|
||||
| dispatched | 후보 Node의 `in_flight < capacity` slot이 예약되어 Node로 run request를 보냈다 | released | run `complete`, `error`, `cancelled`, node disconnect |
|
||||
| released | terminal event 또는 disconnect로 slot이 해제됐다 | queue-admission | 다음 queued item dispatch 시도 |
|
||||
|
||||
## Interface Contract
|
||||
|
||||
- 계약 원문: 없음
|
||||
- 입력:
|
||||
- `openai.model_routes[].model`: OpenAI-compatible 외부 model alias이며 Edge model group queue key다.
|
||||
- `openai.model_routes[].adapter`: 내부 adapter 또는 adapter instance key다.
|
||||
- `openai.model_routes[].target`: 내부 provider/model target이다.
|
||||
- `openai.model_routes[].max_queue`: 해당 model alias queue의 최대 대기 요청 수다. 0이면 fallback/default를 쓴다.
|
||||
- `openai.model_routes[].queue_timeout_ms`: 해당 model alias queue의 대기 timeout이다. 0이면 fallback/default를 쓴다.
|
||||
- `nodes[].adapters.*_instances[].capacity`: 해당 Node/provider instance가 동시에 받을 수 있는 실행 slot 수다.
|
||||
- 출력:
|
||||
- Edge queue admission은 route-level policy를 우선 적용하고, 없으면 기존 adapter instance queue policy 또는 기본값을 사용한다.
|
||||
- provider snapshot의 `capacity`, `in_flight`, `queued`는 Edge-owned queue 상태와 per-node capacity 기준으로 표현된다.
|
||||
- 금지:
|
||||
- GPU utilization만으로 Edge queue admission 가능 여부를 판단하지 않는다.
|
||||
- route-level queue policy가 있는데 adapter instance `max_queue`가 이를 덮어쓰지 않는다.
|
||||
- `model_routes`의 중복 `model` alias를 허용하지 않는다.
|
||||
|
||||
## Acceptance Scenarios
|
||||
|
||||
| ID | Milestone Task | Given | When | Then |
|
||||
|----|----------------|-------|------|------|
|
||||
| S01 | `config-schema` | `openai.model_routes[]`에 `max_queue`, `queue_timeout_ms`가 있다 | Edge config를 load/validate한다 | route entry에 값이 보존되고 중복/빈 model 검증은 유지된다 |
|
||||
| S02 | `dispatch-contract` | Chat/Responses 요청이 route catalog model alias로 들어온다 | OpenAI handler가 service request를 만든다 | `ModelGroupKey`와 route queue policy가 함께 전달된다 |
|
||||
| S03 | `queue-policy` | route-level queue policy와 adapter fallback policy가 모두 있다 | Edge queue group을 생성한다 | route-level `max_queue`, `queue_timeout_ms`가 우선 적용된다 |
|
||||
| S04 | `docs-example` | 사용자가 다중 Node/provider pool 예시를 본다 | `configs/edge.yaml`을 읽는다 | queue policy는 `model_routes`, capacity는 Node/provider instance에 둔다는 책임 경계가 드러난다 |
|
||||
|
||||
## Evidence Map
|
||||
|
||||
| Scenario | Required Evidence | `agent-task` 연결 | `Spec Completion` 기대 |
|
||||
|----------|-------------------|------------------|---------------------------|
|
||||
| S01 | `go test -count=1 ./packages/go/config ./apps/edge/internal/openai` | `agent-task/m-model-route-queue-policy-alignment/01_config_route_policy` | `config-schema` PASS와 S01 evidence |
|
||||
| S02 | `go test -count=1 ./apps/edge/internal/openai` | `agent-task/m-model-route-queue-policy-alignment/01_config_route_policy` | `dispatch-contract` PASS와 S02 evidence |
|
||||
| S03 | `go test -count=1 ./apps/edge/internal/service` | `agent-task/m-model-route-queue-policy-alignment/02+01_service_queue_policy` | `queue-policy` PASS와 S03 evidence |
|
||||
| S04 | `git diff --check`와 `configs/edge.yaml` 예시 변경 리뷰 | `agent-task/m-model-route-queue-policy-alignment/01_config_route_policy` | `docs-example` PASS와 S04 evidence |
|
||||
|
||||
## Cross-repo Dependencies
|
||||
|
||||
- 없음
|
||||
|
||||
## Drift Check
|
||||
|
||||
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
|
||||
- [x] Evidence Map이 plan/code-review/complete.log에서 검증 가능하다.
|
||||
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
|
||||
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
|
||||
|
||||
## 사용자 리뷰 이력
|
||||
|
||||
- 없음
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 표준선: Edge queue owner 원칙, OpenAI `model` 값 기반 group key, 내부 `adapter + target` 실행 계약을 유지한다.
|
||||
- 후속 SDD: 없음
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
<!-- task=m-model-route-queue-policy-alignment/01_config_route_policy plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a 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-16
|
||||
task=m-model-route-queue-policy-alignment/01_config_route_policy, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- Task ids:
|
||||
- `config-schema`: `openai.model_routes[]`가 `max_queue`, `queue_timeout_ms`를 받을 수 있고 validation/default/fallback 문서가 정리되어 있다.
|
||||
- `dispatch-contract`: OpenAI Chat Completions와 Responses route resolution이 route-level queue policy를 `SubmitRunRequest`로 전달한다.
|
||||
- `docs-example`: `configs/edge.yaml` 예시가 model alias queue policy와 Node/provider `capacity`의 책임 경계를 보여준다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Spec Targets
|
||||
|
||||
- SDD: `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- Acceptance scenarios:
|
||||
- `S01`: task=`config-schema`; evidence=`go test -count=1 ./packages/go/config ./apps/edge/internal/openai`
|
||||
- `S02`: task=`dispatch-contract`; evidence=`go test -count=1 ./apps/edge/internal/openai`
|
||||
- `S04`: task=`docs-example`; evidence=`git diff --check`와 `configs/edge.yaml` 예시 변경 리뷰
|
||||
- Completion mode: spec-check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G07.md` -> `code_review_local_G07_N.log`, `PLAN-local-G07.md` -> `plan_local_G07_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-model-route-queue-policy-alignment/01_config_route_policy/`로 이동한다. 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-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Route config schema에 queue policy 필드 추가 | [x] |
|
||||
| [API-2] OpenAI route dispatch가 queue policy를 전달 | [x] |
|
||||
| [API-3] edge.yaml 예시에 책임 경계 반영 | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `OpenAIRouteEntry`에 `max_queue`, `queue_timeout_ms` 필드를 추가하고 non-negative validation을 붙인다.
|
||||
- [x] OpenAI route dispatch DTO와 Chat/Responses submit path가 route-level queue policy를 `SubmitRunRequest`로 전달한다.
|
||||
- [x] `configs/edge.yaml` 예시가 route-level queue policy와 Node/provider `capacity` 책임 경계를 보여준다.
|
||||
- [x] Focused config/OpenAI tests와 `git diff --check`를 실행한다.
|
||||
- [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_local_G07_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_local_G07_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 active task 디렉터리 `agent-task/m-model-route-queue-policy-alignment/01_config_route_policy/`를 `agent-task/archive/YYYY/MM/m-model-route-queue-policy-alignment/01_config_route_policy/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-model-route-queue-policy-alignment/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G07.md`와 `CODE_REVIEW-local-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로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- 없음. 계획된 모든 기능 요구 사항과 검증이 차질 없이 구현 및 통과되었습니다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `config.OpenAIRouteEntry`에 `max_queue`, `queue_timeout_ms` 필드를 노출하고, `validateOpenAIRoutes`에서 음수 값 여부를 검증하게 하였습니다.
|
||||
- OpenAI Chat Completions와 Responses에서 routeCatalog 매칭 시 `MaxQueue`와 `QueueTimeoutMS`를 `SubmitRunRequest`로 정상 전달하도록 handlers 및 service DTO를 수정하였습니다.
|
||||
- `configs/edge.yaml`에서 route-level queue policy와 node capacity의 책임 경계에 대한 설명 주석을 추가하고 형식을 보강하였습니다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- Route-level queue policy fields are loaded, validated non-negative, and do not break duplicate/empty model validation.
|
||||
- Chat and Responses both pass route-level queue policy to `SubmitRunRequest`.
|
||||
- `configs/edge.yaml` presents queue policy on model aliases and capacity on provider instances without implying GPU metric scheduling.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
|
||||
### API-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./packages/go/config
|
||||
ok iop/packages/go/config 0.016s
|
||||
```
|
||||
|
||||
### API-2 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./apps/edge/internal/openai
|
||||
ok iop/apps/edge/internal/openai 1.508s
|
||||
```
|
||||
|
||||
### API-3 중간 검증
|
||||
```text
|
||||
$ git diff --check
|
||||
(출력 없음 - 성공)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ go test -count=1 ./packages/go/config ./apps/edge/internal/openai
|
||||
ok iop/packages/go/config 0.016s
|
||||
ok iop/apps/edge/internal/openai 1.508s
|
||||
|
||||
$ git diff --check
|
||||
(출력 없음 - 성공)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[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.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify |
|
||||
| Spec Targets | Fixed at stub creation from plan | Implementing agent must not modify |
|
||||
| 구현 항목별 완료 여부 | Implementing agent checks boxes only | Item names fixed |
|
||||
| 구현 체크리스트 | Implementing agent checks boxes only | Text/order fixed |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text |
|
||||
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless blocked |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
|
||||
| 검증 결과 | Implementing agent fills command output | Command changes require `계획 대비 변경 사항` |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- correctness: Pass
|
||||
- completeness: Pass
|
||||
- test coverage: Pass
|
||||
- API contract: Pass
|
||||
- code quality: Pass
|
||||
- plan deviation: Pass
|
||||
- verification trust: Pass
|
||||
- spec conformance: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 다음 단계: PASS이므로 active plan/review를 로그로 아카이브하고 `complete.log` 작성 후 task directory를 `agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/`로 이동한다. `m-model-route-queue-policy-alignment` 완료 이벤트 메타데이터를 보고하고 roadmap 수정은 런타임에 맡긴다.
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# Complete - m-model-route-queue-policy-alignment/01_config_route_policy
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-16
|
||||
|
||||
## 요약
|
||||
|
||||
Route-level OpenAI queue policy config/dispatch/docs contract review completed in loop 0 with final verdict PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_local_G07_0.log` | `code_review_local_G07_0.log` | PASS | `OpenAIRouteEntry` queue policy schema, OpenAI Chat/Responses `SubmitRunRequest` propagation, and `configs/edge.yaml` responsibility-boundary example passed review. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- Added `max_queue` and `queue_timeout_ms` to OpenAI route config with non-negative validation and focused config tests.
|
||||
- Propagated route-level queue policy through OpenAI Chat/Responses dispatch into `SubmitRunRequest`.
|
||||
- Updated `configs/edge.yaml` comments/examples to show model-alias queue policy and per-node capacity responsibilities.
|
||||
- Review cleanup: ran `gofmt` on `apps/edge/internal/service/run_dispatch.go` for formatting only.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `go test -count=1 ./packages/go/config ./apps/edge/internal/openai` - PASS; `ok iop/packages/go/config 0.021s`, `ok iop/apps/edge/internal/openai 1.508s`.
|
||||
- `git diff --check` - PASS; no output.
|
||||
- `go test -count=1 ./apps/edge/...` - PASS; all edge packages passed, including `apps/edge/internal/openai` and `apps/edge/internal/service`.
|
||||
- `go test -count=1 ./packages/go/...` - PASS; all packages with tests passed and no-test packages reported normally.
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- Completed task ids:
|
||||
- `config-schema`: PASS; evidence=`agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/plan_local_G07_0.log`, `agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/code_review_local_G07_0.log`; verification=`go test -count=1 ./packages/go/config ./apps/edge/internal/openai`
|
||||
- `dispatch-contract`: PASS; evidence=`agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/plan_local_G07_0.log`, `agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/code_review_local_G07_0.log`; verification=`go test -count=1 ./apps/edge/internal/openai`
|
||||
- `docs-example`: PASS; evidence=`agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/plan_local_G07_0.log`, `agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/code_review_local_G07_0.log`; verification=`git diff --check` and reviewed `configs/edge.yaml`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## Spec Completion
|
||||
|
||||
- SDD: `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- Completed scenario ids:
|
||||
- `S01`: PASS; task=`config-schema`; evidence=`agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/plan_local_G07_0.log`, `agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/code_review_local_G07_0.log`; verification=`go test -count=1 ./packages/go/config ./apps/edge/internal/openai`
|
||||
- `S02`: PASS; task=`dispatch-contract`; evidence=`agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/plan_local_G07_0.log`, `agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/code_review_local_G07_0.log`; verification=`go test -count=1 ./apps/edge/internal/openai`
|
||||
- `S04`: PASS; task=`docs-example`; evidence=`agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/plan_local_G07_0.log`, `agent-task/archive/2026/06/m-model-route-queue-policy-alignment/01_config_route_policy/code_review_local_G07_0.log`; verification=`git diff --check` and reviewed `configs/edge.yaml`
|
||||
- Not completed scenario ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
<!-- task=m-model-route-queue-policy-alignment/01_config_route_policy plan=0 tag=API -->
|
||||
|
||||
# Plan - API route queue policy contract
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 필수 최종 단계입니다. 아래 체크리스트와 검증 명령을 실제로 실행하고, 구현 내용과 stdout/stderr를 review stub에 붙이세요. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청`에 정확한 근거를 남기고 멈춥니다. 구현 에이전트는 사용자에게 직접 묻거나 선택지를 제시하거나 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않습니다. 명령 재실행이나 산출물 수집으로 메울 수 있는 증거 공백은 사용자 리뷰 요청이 아닙니다.
|
||||
|
||||
## 배경
|
||||
|
||||
Edge queue owner 원칙은 이미 있으나, group queue policy는 Node/provider adapter instance에서 파생되고 있다. model alias queue 정책은 `openai.model_routes[]`가 소유하고, Node/provider instance는 per-node `capacity`만 소유해야 다중 Node/provider pool에서 설정 위치가 명확하다. 이 선행 작업은 route config schema와 OpenAI request dispatch DTO를 먼저 확장한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 USER_REVIEW 작성 여부를 판단한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- Task ids:
|
||||
- `config-schema`: `openai.model_routes[]`가 `max_queue`, `queue_timeout_ms`를 받을 수 있고 validation/default/fallback 문서가 정리되어 있다.
|
||||
- `dispatch-contract`: OpenAI Chat Completions와 Responses route resolution이 route-level queue policy를 `SubmitRunRequest`로 전달한다.
|
||||
- `docs-example`: `configs/edge.yaml` 예시가 model alias queue policy와 Node/provider `capacity`의 책임 경계를 보여준다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Spec Targets
|
||||
|
||||
- SDD: `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- Acceptance scenarios:
|
||||
- `S01`: task=`config-schema`; evidence=`go test -count=1 ./packages/go/config ./apps/edge/internal/openai`
|
||||
- `S02`: task=`dispatch-contract`; evidence=`go test -count=1 ./apps/edge/internal/openai`
|
||||
- `S04`: task=`docs-example`; evidence=`git diff --check`와 `configs/edge.yaml` 예시 변경 리뷰
|
||||
- Completion mode: spec-check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/inference-provider-extension/PHASE.md`
|
||||
- `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/edge-smoke.md`
|
||||
- `agent-test/local/platform-common-smoke.md`
|
||||
- `agent-ops/rules/project/domain/edge/rules.md`
|
||||
- `agent-ops/rules/project/domain/platform-common/rules.md`
|
||||
- `packages/go/config/config.go`
|
||||
- `packages/go/config/config_test.go`
|
||||
- `configs/edge.yaml`
|
||||
- `apps/edge/internal/openai/chat_handler.go`
|
||||
- `apps/edge/internal/openai/responses_handler.go`
|
||||
- `apps/edge/internal/openai/server_test.go`
|
||||
- `apps/edge/internal/service/run_dispatch.go`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- test_env: local.
|
||||
- `agent-test/local/rules.md` present/read.
|
||||
- Matched profiles read: `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`.
|
||||
- 적용 명령: `go test -count=1 ./packages/go/config ./apps/edge/internal/openai`, `git diff --check`.
|
||||
- config 계약 변경이므로 `packages/go/config` struct/default와 `configs/edge.yaml` 예시 일치를 확인한다. OpenAI-compatible 입력 표면 변경은 focused handler tests로 검증한다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `OpenAIRouteEntry`는 `model`, `node`, `adapter`, `target`, `session_id`, `timeout_sec`, `workspace_required`만 테스트한다. `max_queue`, `queue_timeout_ms` load/negative validation test가 없다.
|
||||
- Chat/Responses route catalog tests는 adapter/target/model group key만 확인한다. route queue policy가 `SubmitRunRequest`로 전달되는 assertion이 없다.
|
||||
- `configs/edge.yaml` 예시는 route queue policy와 provider capacity 책임 경계를 보여주지 않는다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- 제거/rename 없음.
|
||||
- 추가 예정: `config.OpenAIRouteEntry.MaxQueue`, `config.OpenAIRouteEntry.QueueTimeoutMS`; `edgeservice.SubmitRunRequest.MaxQueue`, `edgeservice.SubmitRunRequest.QueueTimeoutMS`; `openai.routeDispatch.MaxQueue`, `openai.routeDispatch.QueueTimeoutMS`.
|
||||
- call site: `apps/edge/internal/openai/chat_handler.go`, `apps/edge/internal/openai/responses_handler.go`, `apps/edge/internal/service/run_dispatch.go`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- split decision policy evaluated. Multi-plan 선택.
|
||||
- shared task group: `agent-task/m-model-route-queue-policy-alignment`.
|
||||
- `01_config_route_policy`: config schema, route DTO, OpenAI handler propagation, docs example. 선행 없음.
|
||||
- `02+01_service_queue_policy`: Edge service queue policy application. Depends on `01_config_route_policy` because service request fields must exist before behavior can consume them.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 포함: `packages/go/config`, `configs/edge.yaml`, `apps/edge/internal/openai`, service request DTO field addition only.
|
||||
- 제외: `apps/edge/internal/service` queue behavior 변경은 sibling `02+01_service_queue_policy`가 담당한다.
|
||||
- 제외: Node/provider runtime, proto, Control Plane, Client UI. 이번 계약은 Edge config와 OpenAI handler-local DTO 확장으로 충분하다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- build lane/grade: `local-G07`. Config schema + OpenAI surface DTO change이며 focused tests로 검증 가능하지만 public config contract와 roadmap SDD target을 포함해 중간 이상 review가 필요하다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `OpenAIRouteEntry`에 `max_queue`, `queue_timeout_ms` 필드를 추가하고 non-negative validation을 붙인다.
|
||||
- [ ] OpenAI route dispatch DTO와 Chat/Responses submit path가 route-level queue policy를 `SubmitRunRequest`로 전달한다.
|
||||
- [ ] `configs/edge.yaml` 예시가 route-level queue policy와 Node/provider `capacity` 책임 경계를 보여준다.
|
||||
- [ ] Focused config/OpenAI tests와 `git diff --check`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [API-1] Route config schema에 queue policy 필드 추가
|
||||
|
||||
#### 문제
|
||||
|
||||
`packages/go/config/config.go:90`의 `OpenAIRouteEntry`에는 route-level queue policy 필드가 없다.
|
||||
|
||||
```go
|
||||
type OpenAIRouteEntry struct {
|
||||
Model string `mapstructure:"model" yaml:"model"`
|
||||
NodeRef string `mapstructure:"node" yaml:"node,omitempty"`
|
||||
Adapter string `mapstructure:"adapter" yaml:"adapter,omitempty"`
|
||||
Target string `mapstructure:"target" yaml:"target"`
|
||||
SessionID string `mapstructure:"session_id" yaml:"session_id,omitempty"`
|
||||
TimeoutSec int `mapstructure:"timeout_sec" yaml:"timeout_sec,omitempty"`
|
||||
WorkspaceRequired bool `mapstructure:"workspace_required" yaml:"workspace_required,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`OpenAIRouteEntry`에 `MaxQueue`, `QueueTimeoutMS`를 추가한다. `validateOpenAIRoutes`에서 음수 값을 거부한다.
|
||||
|
||||
```go
|
||||
type OpenAIRouteEntry struct {
|
||||
Model string `mapstructure:"model" yaml:"model"`
|
||||
NodeRef string `mapstructure:"node" yaml:"node,omitempty"`
|
||||
Adapter string `mapstructure:"adapter" yaml:"adapter,omitempty"`
|
||||
Target string `mapstructure:"target" yaml:"target"`
|
||||
SessionID string `mapstructure:"session_id" yaml:"session_id,omitempty"`
|
||||
TimeoutSec int `mapstructure:"timeout_sec" yaml:"timeout_sec,omitempty"`
|
||||
MaxQueue int `mapstructure:"max_queue" yaml:"max_queue,omitempty"`
|
||||
QueueTimeoutMS int `mapstructure:"queue_timeout_ms" yaml:"queue_timeout_ms,omitempty"`
|
||||
WorkspaceRequired bool `mapstructure:"workspace_required" yaml:"workspace_required,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
Validation은 `openai.model_routes[i].max_queue must be non-negative`, `openai.model_routes[i].queue_timeout_ms must be non-negative`처럼 필드명을 포함하게 한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/go/config/config.go`: struct field 추가, route validation 보강.
|
||||
- [ ] `packages/go/config/config_test.go`: 기존 route catalog load test에 값 보존 assertion 추가.
|
||||
- [ ] `packages/go/config/config_test.go`: negative max_queue/queue_timeout_ms reject tests 추가.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
작성.
|
||||
- `TestLoadEdge_OpenAIRouteCatalog`에 `max_queue`, `queue_timeout_ms` 값을 추가하고 보존 여부를 검증한다.
|
||||
- `TestLoadEdge_OpenAIRouteCatalogQueuePolicyRejectsNegative`를 추가해 두 음수 필드가 거부되는지 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/go/config
|
||||
```
|
||||
|
||||
기대 결과: config package tests PASS. Go test cache 출력은 허용하지 않는다.
|
||||
|
||||
### [API-2] OpenAI route dispatch가 queue policy를 전달
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/edge/internal/openai/chat_handler.go:163`의 `routeDispatch`는 queue policy를 담을 수 없고, `chat_handler.go:73`과 `responses_handler.go:81`의 `SubmitRunRequest`도 route policy를 넘기지 않는다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`routeDispatch`와 `edgeservice.SubmitRunRequest`에 같은 queue policy 필드를 추가하고, route catalog match 시 route 값을 복사한다. fallback route에서는 0 값을 유지해 기존 adapter fallback을 보존한다.
|
||||
|
||||
```go
|
||||
type routeDispatch struct {
|
||||
NodeRef string
|
||||
Adapter string
|
||||
Target string
|
||||
SessionID string
|
||||
TimeoutSec int
|
||||
MaxQueue int
|
||||
QueueTimeoutMS int
|
||||
WorkspaceRequired bool
|
||||
}
|
||||
```
|
||||
|
||||
`SubmitRunRequest`에는 exported field로 `MaxQueue int`, `QueueTimeoutMS int`를 추가한다. Chat/Responses submit path는 `dispatch.MaxQueue`, `dispatch.QueueTimeoutMS`를 전달한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/chat_handler.go`: `routeDispatch` 필드와 route resolution 복사 추가.
|
||||
- [ ] `apps/edge/internal/openai/chat_handler.go`: chat submit request에 queue policy 전달.
|
||||
- [ ] `apps/edge/internal/openai/responses_handler.go`: responses submit request에 queue policy 전달.
|
||||
- [ ] `apps/edge/internal/service/run_dispatch.go`: `SubmitRunRequest` 필드 추가만 수행. Behavior consumption은 sibling task에서 처리.
|
||||
- [ ] `apps/edge/internal/openai/server_test.go`: Chat/Responses route catalog tests에 `MaxQueue`, `QueueTimeoutMS` assertion 추가.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
작성.
|
||||
- `TestChatCompletionsRouteCatalogDispatchesModelB`: route에 `MaxQueue`, `QueueTimeoutMS`를 넣고 `fake.req`가 같은 값을 받는지 검증한다.
|
||||
- `TestResponsesRouteCatalogTakesPriorityOverMetadataTarget`: route queue policy도 함께 전달되는지 검증한다.
|
||||
- fallback route test는 값이 0인지 확인해 adapter fallback 여지를 보존한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
기대 결과: OpenAI handler tests PASS. Go test cache 출력은 허용하지 않는다.
|
||||
|
||||
### [API-3] edge.yaml 예시에 책임 경계 반영
|
||||
|
||||
#### 문제
|
||||
|
||||
`configs/edge.yaml:56`의 `model_routes` 예시는 adapter/target/node만 보여주고, queue policy가 model alias에 붙는다는 점을 설명하지 않는다. `configs/edge.yaml:165` 이후 Node adapter examples는 `capacity`, `max_queue`, `queue_timeout_ms`가 provider instance에 있어 queue owner가 Node처럼 보인다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`model_routes` 예시에 `max_queue`, `queue_timeout_ms`를 추가하고, provider instance 예시 주석은 `capacity`가 per-node slot이라는 점을 명시한다. 기존 adapter instance `max_queue`/`queue_timeout_ms`는 호환 fallback이라는 설명을 추가하거나 route 예시와 충돌하지 않게 줄인다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `configs/edge.yaml`: `model_routes` 주석과 예시 갱신.
|
||||
- [ ] `configs/edge.yaml`: adapter instance `capacity` 설명을 per-node slot으로 명시.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
별도 테스트 작성 없음. YAML example은 config load fixture가 아니며, schema 보존과 handler propagation은 API-1/API-2 tests가 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
```
|
||||
|
||||
기대 결과: whitespace error 없음.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `packages/go/config/config.go` | API-1 |
|
||||
| `packages/go/config/config_test.go` | API-1 |
|
||||
| `apps/edge/internal/service/run_dispatch.go` | API-2 |
|
||||
| `apps/edge/internal/openai/chat_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/responses_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/server_test.go` | API-2 |
|
||||
| `configs/edge.yaml` | API-3 |
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
1. API-1로 config schema와 validation을 먼저 고정한다.
|
||||
2. API-2로 OpenAI handler에서 service DTO까지 route policy를 전달한다.
|
||||
3. API-3으로 예시를 새 책임 경계에 맞춘다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/go/config ./apps/edge/internal/openai
|
||||
git diff --check
|
||||
```
|
||||
|
||||
기대 결과: 모든 명령 PASS. Go test cache 출력은 허용하지 않는다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<!-- task=m-model-route-queue-policy-alignment/02+01_service_queue_policy plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a 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-16
|
||||
task=m-model-route-queue-policy-alignment/02+01_service_queue_policy, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- Task ids:
|
||||
- `queue-policy`: Edge queue admission이 route-level queue policy를 adapter instance fallback보다 우선하고, route policy가 없을 때 기존 fallback을 유지한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Spec Targets
|
||||
|
||||
- SDD: `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- Acceptance scenarios:
|
||||
- `S03`: task=`queue-policy`; evidence=`go test -count=1 ./apps/edge/internal/service`
|
||||
- Completion mode: spec-check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G07.md` -> `code_review_local_G07_N.log`, `PLAN-local-G07.md` -> `plan_local_G07_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-model-route-queue-policy-alignment/02+01_service_queue_policy/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
|
||||
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Route policy 우선순위 적용 | [ ] |
|
||||
| [API-2] Queue timeout route policy 검증 | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `SubmitRunRequest`의 route-level queue policy가 있으면 Edge group policy에서 adapter fallback보다 우선 사용한다.
|
||||
- [ ] route policy가 0이면 기존 adapter instance `max_queue`/`queue_timeout_ms` fallback과 default policy가 유지된다.
|
||||
- [ ] route policy override와 fallback regression tests를 추가/갱신한다.
|
||||
- [ ] Focused service tests와 `git diff --check`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G07_N.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_local_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-model-route-queue-policy-alignment/02+01_service_queue_policy/`를 `agent-task/archive/YYYY/MM/m-model-route-queue-policy-alignment/02+01_service_queue_policy/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-model-route-queue-policy-alignment/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G07.md`와 `CODE_REVIEW-local-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`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- Route-level queue policy wins over adapter instance fallback.
|
||||
- Existing adapter instance fallback behavior is preserved when request policy fields are zero.
|
||||
- Queue timeout and max queue semantics still use Edge-owned model queue, not Node-local queue ownership.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
|
||||
### API-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./apps/edge/internal/service
|
||||
```
|
||||
|
||||
### API-2 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./apps/edge/internal/service -run 'TestSubmitRunModelQueue'
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ go test -count=1 ./apps/edge/internal/service
|
||||
$ git diff --check
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[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.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify |
|
||||
| Spec Targets | Fixed at stub creation from plan | Implementing agent must not modify |
|
||||
| 구현 항목별 완료 여부 | Implementing agent checks boxes only | Item names fixed |
|
||||
| 구현 체크리스트 | Implementing agent checks boxes only | Text/order fixed |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text |
|
||||
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless blocked |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
|
||||
| 검증 결과 | Implementing agent fills command output | Command changes require `계획 대비 변경 사항` |
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<!-- task=m-model-route-queue-policy-alignment/02+01_service_queue_policy plan=0 tag=API -->
|
||||
|
||||
# Plan - API service queue policy priority
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 필수 최종 단계입니다. 이 task는 `01_config_route_policy` 완료 후 진행해야 합니다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청`에 정확한 근거를 남기고 멈춥니다. 구현 에이전트는 사용자에게 직접 묻거나 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않습니다.
|
||||
|
||||
## 배경
|
||||
|
||||
선행 task가 route-level queue policy를 `SubmitRunRequest`에 실어 보낸다. 이 후속 작업은 Edge service가 그 값을 실제 model queue group policy로 우선 적용하게 만든다. route policy가 없으면 기존 Node/provider adapter instance fallback을 유지해야 기존 설정을 깨지 않는다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 USER_REVIEW 작성 여부를 판단한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- Task ids:
|
||||
- `queue-policy`: Edge queue admission이 route-level queue policy를 adapter instance fallback보다 우선하고, route policy가 없을 때 기존 fallback을 유지한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Spec Targets
|
||||
|
||||
- SDD: `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- Acceptance scenarios:
|
||||
- `S03`: task=`queue-policy`; evidence=`go test -count=1 ./apps/edge/internal/service`
|
||||
- Completion mode: spec-check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
|
||||
- `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/edge-smoke.md`
|
||||
- `agent-ops/rules/project/domain/edge/rules.md`
|
||||
- `apps/edge/internal/service/run_dispatch.go`
|
||||
- `apps/edge/internal/service/model_queue.go`
|
||||
- `apps/edge/internal/service/service_test.go`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- test_env: local.
|
||||
- `agent-test/local/rules.md` present/read.
|
||||
- Matched profile read: `agent-test/local/edge-smoke.md`.
|
||||
- 적용 명령: `go test -count=1 ./apps/edge/internal/service`, `git diff --check`.
|
||||
- Edge service dispatch 변경이므로 focused service tests가 필수다. 전체 edge smoke는 queue policy 자체보다 실행 경로 생존 확인 보조로 후속 review가 필요하다고 판단할 때만 확장한다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `resolveQueueCandidates`는 group policy가 adapter config에서 온다는 전제를 갖고 있다.
|
||||
- `TestSubmitRunModelQueueUsesProviderInstancePolicy`는 adapter instance `MaxQueue` fallback만 검증한다.
|
||||
- route-level policy가 adapter fallback보다 우선하는 regression test가 없다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- 제거/rename 없음.
|
||||
- 선행 task가 추가할 `SubmitRunRequest.MaxQueue`, `SubmitRunRequest.QueueTimeoutMS`를 소비한다.
|
||||
- call site: `submitRunQueued`, `resolveQueueCandidates`, `groupPolicyFromStore`, service tests.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- split decision policy evaluated. 이 plan은 `02+01_service_queue_policy`이며 predecessor `01_config_route_policy`의 `complete.log`가 필요하다.
|
||||
- 현재 작성 시점에는 predecessor active task가 생성되었지만 완료되지 않았다. 구현 runtime은 `01_config_route_policy` 완료 후 이 task를 실행해야 한다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 포함: Edge service queue policy derivation and tests.
|
||||
- 제외: config schema/OpenAI handler/docs 변경은 `01_config_route_policy` 담당.
|
||||
- 제외: Node/provider admission behavior, GPU metric detection, cross-Edge routing.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- build lane/grade: `local-G07`. Concurrency/queue policy path지만 focused service tests로 deterministic하게 검증 가능하다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `SubmitRunRequest`의 route-level queue policy가 있으면 Edge group policy에서 adapter fallback보다 우선 사용한다.
|
||||
- [ ] route policy가 0이면 기존 adapter instance `max_queue`/`queue_timeout_ms` fallback과 default policy가 유지된다.
|
||||
- [ ] route policy override와 fallback regression tests를 추가/갱신한다.
|
||||
- [ ] Focused service tests와 `git diff --check`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [API-1] Route policy 우선순위 적용
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/edge/internal/service/run_dispatch.go:187`의 comment와 구현은 group policy를 adapter config에서만 파생한다. `groupPolicyFromStore`는 `run_dispatch.go:397`에서 후보 Node adapter instance의 `maxQueue`, `queueTimeoutMS`를 먼저 만나면 반환한다.
|
||||
|
||||
```go
|
||||
policy := groupPolicyFromStore(s.nodeStore, entries, req.Adapter, req.Target)
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`SubmitRunRequest`의 route-level policy를 먼저 `groupPolicy`로 변환하는 helper를 추가한다.
|
||||
|
||||
```go
|
||||
func groupPolicyFromRequestOrStore(req SubmitRunRequest, store *edgenode.NodeStore, entries []*edgenode.NodeEntry) groupPolicy {
|
||||
if req.MaxQueue > 0 || req.QueueTimeoutMS > 0 {
|
||||
return groupPolicy{
|
||||
maxQueue: positiveOr(req.MaxQueue, defaultGroupMaxQueue),
|
||||
queueTimeout: queueTimeoutOrDefault(req.QueueTimeoutMS),
|
||||
}
|
||||
}
|
||||
return groupPolicyFromStore(store, entries, req.Adapter, req.Target)
|
||||
}
|
||||
```
|
||||
|
||||
`resolveQueueCandidates`의 explicit-node path와 all-candidates path 모두 이 helper를 사용한다. 음수는 config validation에서 막지만 service direct caller 방어를 위해 `<=0`은 fallback/default로만 해석한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/service/run_dispatch.go`: route policy 우선 helper 추가.
|
||||
- [ ] `apps/edge/internal/service/run_dispatch.go`: explicit NodeRef path와 all-candidates path 모두 helper 사용.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
작성.
|
||||
- `TestSubmitRunModelQueueUsesRoutePolicyBeforeProviderInstancePolicy`: provider instance `MaxQueue`가 큰 값이어도 request `MaxQueue: 1`이면 두 번째 queued request 후 세 번째 대기 request가 queue full로 거부되는지 검증한다.
|
||||
- `TestSubmitRunModelQueueFallsBackToProviderInstancePolicy`: request policy가 0이면 기존 `TestSubmitRunModelQueueUsesProviderInstancePolicy` 기대가 유지되는지 검증한다. 기존 test를 이름 유지한 채 보강하거나 새 test로 분리한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/service
|
||||
```
|
||||
|
||||
기대 결과: service package tests PASS. Go test cache 출력은 허용하지 않는다.
|
||||
|
||||
### [API-2] Queue timeout route policy 검증
|
||||
|
||||
#### 문제
|
||||
|
||||
`model_queue.go:183`은 `group.policy.queueTimeout`으로 queued item deadline을 만든다. route-level `queue_timeout_ms`가 적용되지 않으면 model alias queue의 timeout 정책을 독립적으로 설정할 수 없다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
API-1의 helper에서 `QueueTimeoutMS`가 있으면 `time.Duration(req.QueueTimeoutMS) * time.Millisecond`를 우선 사용한다. `MaxQueue`만 있고 timeout이 없으면 default timeout을 사용한다. `QueueTimeoutMS`만 있고 max queue가 없으면 default max queue를 사용한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/service/run_dispatch.go`: timeout conversion helper 작성.
|
||||
- [ ] `apps/edge/internal/service/service_test.go`: timeout override test 추가 또는 max_queue override test 안에 assertion 포함.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
작성.
|
||||
- `TestSubmitRunModelQueueUsesRouteQueueTimeout`: capacity를 채우고 queued request context보다 route timeout을 짧게 설정해 `errQueueTimeout` path가 route timeout 기준으로 발생하는지 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/service -run 'TestSubmitRunModelQueue'
|
||||
```
|
||||
|
||||
기대 결과: model queue 관련 service tests PASS. Go test cache 출력은 허용하지 않는다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `apps/edge/internal/service/run_dispatch.go` | API-1, API-2 |
|
||||
| `apps/edge/internal/service/service_test.go` | API-1, API-2 |
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
이 task directory name `02+01_service_queue_policy`가 runtime dependency source of truth다.
|
||||
Predecessor `01_config_route_policy` must produce `agent-task/m-model-route-queue-policy-alignment/01_config_route_policy/complete.log` or matching archived complete log before implementation starts.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/service
|
||||
git diff --check
|
||||
```
|
||||
|
||||
기대 결과: 모든 명령 PASS. Go test cache 출력은 허용하지 않는다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -71,16 +71,18 @@ func (s *Server) handleChatCompletions(w http.ResponseWriter, r *http.Request) {
|
|||
)
|
||||
|
||||
handle, err := s.service.SubmitRun(r.Context(), edgeservice.SubmitRunRequest{
|
||||
NodeRef: dispatch.NodeRef,
|
||||
ModelGroupKey: strings.TrimSpace(req.Model),
|
||||
Adapter: dispatch.Adapter,
|
||||
Target: dispatch.Target,
|
||||
SessionID: dispatch.SessionID,
|
||||
Workspace: workspace,
|
||||
Prompt: prompt,
|
||||
Input: input,
|
||||
TimeoutSec: dispatch.TimeoutSec,
|
||||
Metadata: chatRunMetadata(runMeta, req, outputPolicy),
|
||||
NodeRef: dispatch.NodeRef,
|
||||
ModelGroupKey: strings.TrimSpace(req.Model),
|
||||
Adapter: dispatch.Adapter,
|
||||
Target: dispatch.Target,
|
||||
SessionID: dispatch.SessionID,
|
||||
Workspace: workspace,
|
||||
Prompt: prompt,
|
||||
Input: input,
|
||||
TimeoutSec: dispatch.TimeoutSec,
|
||||
MaxQueue: dispatch.MaxQueue,
|
||||
QueueTimeoutMS: dispatch.QueueTimeoutMS,
|
||||
Metadata: chatRunMetadata(runMeta, req, outputPolicy),
|
||||
})
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadGateway, "node_dispatch_error", err.Error())
|
||||
|
|
@ -166,6 +168,8 @@ type routeDispatch struct {
|
|||
Target string
|
||||
SessionID string
|
||||
TimeoutSec int
|
||||
MaxQueue int
|
||||
QueueTimeoutMS int
|
||||
WorkspaceRequired bool
|
||||
}
|
||||
|
||||
|
|
@ -213,6 +217,8 @@ func (s *Server) resolveRouteDispatch(model, metadataTarget string) (routeDispat
|
|||
Target: route.Target,
|
||||
SessionID: sessionID,
|
||||
TimeoutSec: timeoutSec,
|
||||
MaxQueue: route.MaxQueue,
|
||||
QueueTimeoutMS: route.QueueTimeoutMS,
|
||||
WorkspaceRequired: route.WorkspaceRequired,
|
||||
}, true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,16 +79,18 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) {
|
|||
)
|
||||
|
||||
handle, err := s.service.SubmitRun(r.Context(), edgeservice.SubmitRunRequest{
|
||||
NodeRef: dispatch.NodeRef,
|
||||
ModelGroupKey: strings.TrimSpace(req.Model),
|
||||
Adapter: dispatch.Adapter,
|
||||
Target: dispatch.Target,
|
||||
SessionID: dispatch.SessionID,
|
||||
Workspace: workspace,
|
||||
Prompt: prompt,
|
||||
Input: input,
|
||||
TimeoutSec: dispatch.TimeoutSec,
|
||||
Metadata: runMeta,
|
||||
NodeRef: dispatch.NodeRef,
|
||||
ModelGroupKey: strings.TrimSpace(req.Model),
|
||||
Adapter: dispatch.Adapter,
|
||||
Target: dispatch.Target,
|
||||
SessionID: dispatch.SessionID,
|
||||
Workspace: workspace,
|
||||
Prompt: prompt,
|
||||
Input: input,
|
||||
TimeoutSec: dispatch.TimeoutSec,
|
||||
MaxQueue: dispatch.MaxQueue,
|
||||
QueueTimeoutMS: dispatch.QueueTimeoutMS,
|
||||
Metadata: runMeta,
|
||||
})
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadGateway, "node_dispatch_error", err.Error())
|
||||
|
|
|
|||
|
|
@ -1022,7 +1022,7 @@ func TestChatCompletionsRouteCatalogDispatchesModelB(t *testing.T) {
|
|||
srv := NewServer(config.EdgeOpenAIConf{
|
||||
ModelRoutes: []config.OpenAIRouteEntry{
|
||||
{Model: "model-a", Adapter: "ollama", Target: "llama3"},
|
||||
{Model: "model-b", Adapter: "vllm", Target: "qwen"},
|
||||
{Model: "model-b", Adapter: "vllm", Target: "qwen", MaxQueue: 5, QueueTimeoutMS: 2000},
|
||||
},
|
||||
}, fake, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
|
||||
|
|
@ -1041,6 +1041,9 @@ func TestChatCompletionsRouteCatalogDispatchesModelB(t *testing.T) {
|
|||
if fake.req.ModelGroupKey != "model-b" {
|
||||
t.Fatalf("model group key: got %q, want model-b", fake.req.ModelGroupKey)
|
||||
}
|
||||
if fake.req.MaxQueue != 5 || fake.req.QueueTimeoutMS != 2000 {
|
||||
t.Fatalf("queue policy mismatch: MaxQueue=%d, QueueTimeoutMS=%d", fake.req.MaxQueue, fake.req.QueueTimeoutMS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsCatalogMissFallsToLegacyTarget(t *testing.T) {
|
||||
|
|
@ -1118,7 +1121,7 @@ func TestResponsesRouteCatalogTakesPriorityOverMetadataTarget(t *testing.T) {
|
|||
|
||||
srv := NewServer(config.EdgeOpenAIConf{
|
||||
ModelRoutes: []config.OpenAIRouteEntry{
|
||||
{Model: "model-a", Adapter: "ollama", Target: "route-target"},
|
||||
{Model: "model-a", Adapter: "ollama", Target: "route-target", MaxQueue: 3, QueueTimeoutMS: 1500},
|
||||
},
|
||||
}, fake, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
|
||||
|
|
@ -1138,6 +1141,9 @@ func TestResponsesRouteCatalogTakesPriorityOverMetadataTarget(t *testing.T) {
|
|||
if fake.req.ModelGroupKey != "model-a" {
|
||||
t.Fatalf("model group key: got %q, want model-a", fake.req.ModelGroupKey)
|
||||
}
|
||||
if fake.req.MaxQueue != 3 || fake.req.QueueTimeoutMS != 1500 {
|
||||
t.Fatalf("queue policy mismatch: MaxQueue=%d, QueueTimeoutMS=%d", fake.req.MaxQueue, fake.req.QueueTimeoutMS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsEdgePassesNumCtxForAdapterEnforcement(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -14,18 +14,20 @@ import (
|
|||
)
|
||||
|
||||
type SubmitRunRequest struct {
|
||||
NodeRef string
|
||||
RunID string
|
||||
ModelGroupKey string
|
||||
Adapter string
|
||||
Target string
|
||||
SessionID string
|
||||
Workspace string
|
||||
Prompt string
|
||||
Input map[string]any
|
||||
Background bool
|
||||
TimeoutSec int
|
||||
Metadata map[string]string
|
||||
NodeRef string
|
||||
RunID string
|
||||
ModelGroupKey string
|
||||
Adapter string
|
||||
Target string
|
||||
SessionID string
|
||||
Workspace string
|
||||
Prompt string
|
||||
Input map[string]any
|
||||
Background bool
|
||||
TimeoutSec int
|
||||
MaxQueue int
|
||||
QueueTimeoutMS int
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// RunDispatch describes a dispatched run in surface-neutral terms. It is the
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ openai:
|
|||
# When set, /v1/models exposes these ids and /v1/chat/completions + /v1/responses
|
||||
# resolve adapter/target per-entry. Entries not matched fall back to target/adapter above.
|
||||
# workspace_required: true marks CLI agent routes that require a workspace path from the caller.
|
||||
# max_queue and queue_timeout_ms configure the Edge-owned queue policy for the model alias.
|
||||
# model_routes:
|
||||
# - model: "codex"
|
||||
# adapter: "cli"
|
||||
|
|
@ -65,6 +66,8 @@ openai:
|
|||
# - model: "llama3"
|
||||
# adapter: "ollama"
|
||||
# target: "llama3:8b"
|
||||
# max_queue: 10
|
||||
# queue_timeout_ms: 30000
|
||||
# - model: "qwen3"
|
||||
# adapter: "vllm"
|
||||
# target: "qwen3-72b"
|
||||
|
|
@ -100,7 +103,9 @@ nodes:
|
|||
enabled: false
|
||||
base_url: ""
|
||||
context_size: 0
|
||||
# capacity is the per-node concurrent execution slot limit.
|
||||
capacity: 0
|
||||
# max_queue and queue_timeout_ms are fallbacks used only if not specified in route-level queue policy.
|
||||
max_queue: 0
|
||||
queue_timeout_ms: 0
|
||||
request_timeout_ms: 0
|
||||
|
|
@ -162,7 +167,9 @@ nodes:
|
|||
# enabled: true
|
||||
# base_url: "http://127.0.0.1:11434"
|
||||
# context_size: 131072
|
||||
# # capacity is the per-node concurrent execution slot limit.
|
||||
# capacity: 4
|
||||
# # max_queue and queue_timeout_ms are fallbacks used only if not specified in route-level queue policy.
|
||||
# max_queue: 16
|
||||
# queue_timeout_ms: 30000
|
||||
# request_timeout_ms: 300000
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ type OpenAIRouteEntry struct {
|
|||
Target string `mapstructure:"target" yaml:"target"`
|
||||
SessionID string `mapstructure:"session_id" yaml:"session_id,omitempty"`
|
||||
TimeoutSec int `mapstructure:"timeout_sec" yaml:"timeout_sec,omitempty"`
|
||||
MaxQueue int `mapstructure:"max_queue" yaml:"max_queue,omitempty"`
|
||||
QueueTimeoutMS int `mapstructure:"queue_timeout_ms" yaml:"queue_timeout_ms,omitempty"`
|
||||
WorkspaceRequired bool `mapstructure:"workspace_required" yaml:"workspace_required,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -526,6 +528,12 @@ func validateOpenAIRoutes(routes []OpenAIRouteEntry) error {
|
|||
return fmt.Errorf("openai.model_routes: duplicate model %q", model)
|
||||
}
|
||||
seen[model] = struct{}{}
|
||||
if r.MaxQueue < 0 {
|
||||
return fmt.Errorf("openai.model_routes[%d].max_queue must be non-negative", i)
|
||||
}
|
||||
if r.QueueTimeoutMS < 0 {
|
||||
return fmt.Errorf("openai.model_routes[%d].queue_timeout_ms must be non-negative", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1146,6 +1146,8 @@ openai:
|
|||
node: "node-01"
|
||||
session_id: "sess-a"
|
||||
timeout_sec: 30
|
||||
max_queue: 10
|
||||
queue_timeout_ms: 5000
|
||||
- model: "model-b"
|
||||
adapter: "vllm"
|
||||
target: "qwen"
|
||||
|
|
@ -1167,6 +1169,9 @@ openai:
|
|||
if r0.NodeRef != "node-01" || r0.SessionID != "sess-a" || r0.TimeoutSec != 30 {
|
||||
t.Errorf("route[0] optional fields mismatch: %+v", r0)
|
||||
}
|
||||
if r0.MaxQueue != 10 || r0.QueueTimeoutMS != 5000 {
|
||||
t.Errorf("route[0] queue policy mismatch: max_queue=%d, queue_timeout_ms=%d", r0.MaxQueue, r0.QueueTimeoutMS)
|
||||
}
|
||||
if r0.WorkspaceRequired {
|
||||
t.Errorf("route[0] workspace_required should default to false: %+v", r0)
|
||||
}
|
||||
|
|
@ -1174,6 +1179,9 @@ openai:
|
|||
if r1.Model != "model-b" || r1.Adapter != "vllm" || r1.Target != "qwen" {
|
||||
t.Errorf("route[1] mismatch: %+v", r1)
|
||||
}
|
||||
if r1.MaxQueue != 0 || r1.QueueTimeoutMS != 0 {
|
||||
t.Errorf("route[1] queue policy should default to 0: max_queue=%d, queue_timeout_ms=%d", r1.MaxQueue, r1.QueueTimeoutMS)
|
||||
}
|
||||
if r1.WorkspaceRequired {
|
||||
t.Errorf("route[1] workspace_required should default to false: %+v", r1)
|
||||
}
|
||||
|
|
@ -1272,6 +1280,54 @@ openai:
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadEdge_OpenAIRouteCatalogQueuePolicyRejectsNegative(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Test negative max_queue
|
||||
f1 := filepath.Join(dir, "edge_neg_max.yaml")
|
||||
yaml1 := `
|
||||
server:
|
||||
listen: "0.0.0.0:9090"
|
||||
openai:
|
||||
model_routes:
|
||||
- model: "model-a"
|
||||
target: "llama3"
|
||||
max_queue: -1
|
||||
`
|
||||
if err := os.WriteFile(f1, []byte(yaml1), 0o600); err != nil {
|
||||
t.Fatalf("write yaml: %v", err)
|
||||
}
|
||||
_, err := config.LoadEdge(f1)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for negative max_queue")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "max_queue must be non-negative") {
|
||||
t.Fatalf("expected error mentioning max_queue must be non-negative, got %v", err)
|
||||
}
|
||||
|
||||
// Test negative queue_timeout_ms
|
||||
f2 := filepath.Join(dir, "edge_neg_timeout.yaml")
|
||||
yaml2 := `
|
||||
server:
|
||||
listen: "0.0.0.0:9090"
|
||||
openai:
|
||||
model_routes:
|
||||
- model: "model-a"
|
||||
target: "llama3"
|
||||
queue_timeout_ms: -500
|
||||
`
|
||||
if err := os.WriteFile(f2, []byte(yaml2), 0o600); err != nil {
|
||||
t.Fatalf("write yaml: %v", err)
|
||||
}
|
||||
_, err = config.LoadEdge(f2)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for negative queue_timeout_ms")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "queue_timeout_ms must be non-negative") {
|
||||
t.Fatalf("expected error mentioning queue_timeout_ms must be non-negative, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// NormalizeAdapters regression tests
|
||||
|
||||
func TestNormalizeAdapters_OllamaIdempotent(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue