From 698f03c0eab93aa26130e982003241e2dba7c433 Mon Sep 17 00:00:00 2001 From: toki Date: Mon, 6 Jul 2026 09:33:08 +0900 Subject: [PATCH] refactor: clean up long-context admission smoke test files and input estimator --- .../model-group-long-context-admission.md | 34 ++++++++++--------- .../model-group-long-context-admission/SDD.md | 0 .../PHASE.md | 6 ++-- apps/edge/internal/openai/input_estimator.go | 26 ++------------ .../internal/openai/input_estimator_test.go | 15 ++++++++ 5 files changed, 38 insertions(+), 43 deletions(-) rename agent-roadmap/{ => archive}/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md (82%) rename agent-roadmap/{ => archive}/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md (100%) diff --git a/agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md b/agent-roadmap/archive/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md similarity index 82% rename from agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md rename to agent-roadmap/archive/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md index 931f9b6..36c1f58 100644 --- a/agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md +++ b/agent-roadmap/archive/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md @@ -13,7 +13,7 @@ long slot이 찬 provider도 일반 capacity가 남아 있으면 normal 요청 ## 상태 -[진행중] +[완료] ## 승격 조건 @@ -50,26 +50,28 @@ long-context 요청을 model group과 provider의 별도 slot 기준으로 admis - [x] [threshold-config] Edge root 설정에 `long_context_threshold_tokens`를 추가하고 dev 기준 `100000`으로 설정한다. 검증: 설정 로딩과 config check에서 threshold가 반영된다. - [x] [model-window] model group에 `context_window_tokens=262144` 계약을 명시하고 같은 model group provider가 provider별 단일 요청 최대 context를 낮춰 해석하지 않도록 한다. 검증: `qwen3.6:35b` catalog/config에서 context window가 단일 기준으로 조회된다. -- [ ] [provider-long-capacity] provider별 `total_context_tokens` 또는 runtime KV/context budget 기준, `long_context_capacity`, `long_in_flight` 상태를 추가하고, long 요청이 일반 capacity와 long capacity를 함께 점유하도록 한다. 검증: `total_context_tokens >= context_window_tokens * long_context_capacity` 검증과 long 요청 dispatch 중 `long_in_flight` 증가가 확인된다. -- [ ] [input-estimator] OpenAI-compatible chat/responses 요청의 system/developer/user messages, tool schema, metadata payload를 입력 기준으로 토큰 근사 추정해 long flag를 붙인다. 검증: 100k 이상 입력은 long, 작은 설명형 요청은 normal로 분류된다. -- [ ] [queue-skip] queue head의 long 요청이 long slot 부족으로 대기 중이어도 뒤의 normal 요청이 빈 일반 capacity로 dispatch될 수 있게 한다. 검증: long queue 대기 중 normal 요청이 head-of-line blocking 없이 완료된다. -- [ ] [provider-exclusion] long 요청 배정 시 이미 long slot이 찬 provider를 long 후보에서 제외하고, model group 전체 long slot이 모두 차면 long 요청을 대기시킨다. 검증: long 요청이 같은 provider에 과밀 배정되지 않고, normal 요청은 남은 일반 capacity로 계속 dispatch되며, long slot 회복 뒤 long 요청이 dispatch된다. -- [ ] [dev-runtime-policy] dev provider pool에서 GX10, OneXPlayer, mac-mlx-vllm의 `context_window_tokens`, `long_context_capacity`, 실제 runtime context/KV 설정을 인벤토리와 config에 맞춘다. 검증: OneXPlayer는 `ctx_size=524288`, `-np 3`, long slot 2 기준을 유지하며 `786432`로 변경하지 않는다. -- [ ] [status-logs] status/log에 `estimated_input_tokens`, `context_class`, `long_context_capacity`, `long_in_flight`, long queue reason을 남긴다. 검증: Control Plane status 또는 Edge log로 long admission 판단을 재구성할 수 있다. -- [ ] [capacity-smoke] dev 환경에서 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오를 수행하고 완료 후 `in_flight=0`, `queued=0`, `long_in_flight=0` 회복 근거를 남긴다. +- [x] [provider-long-capacity] provider별 `total_context_tokens` 또는 runtime KV/context budget 기준, `long_context_capacity`, `long_in_flight` 상태를 추가하고, long 요청이 일반 capacity와 long capacity를 함께 점유하도록 한다. 검증: `total_context_tokens >= context_window_tokens * long_context_capacity` 검증과 long 요청 dispatch 중 `long_in_flight` 증가가 확인된다. +- [x] [input-estimator] OpenAI-compatible chat/responses 요청의 system/developer/user messages, tool schema, metadata payload를 입력 기준으로 토큰 근사 추정해 long flag를 붙인다. 검증: 100k 이상 입력은 long, 작은 설명형 요청은 normal로 분류된다. +- [x] [queue-skip] queue head의 long 요청이 long slot 부족으로 대기 중이어도 뒤의 normal 요청이 빈 일반 capacity로 dispatch될 수 있게 한다. 검증: long queue 대기 중 normal 요청이 head-of-line blocking 없이 완료된다. +- [x] [provider-exclusion] long 요청 배정 시 이미 long slot이 찬 provider를 long 후보에서 제외하고, model group 전체 long slot이 모두 차면 long 요청을 대기시킨다. 검증: long 요청이 같은 provider에 과밀 배정되지 않고, normal 요청은 남은 일반 capacity로 계속 dispatch되며, long slot 회복 뒤 long 요청이 dispatch된다. +- [x] [dev-runtime-policy] dev provider pool에서 GX10, OneXPlayer, mac-mlx-vllm의 `context_window_tokens`, `long_context_capacity`, 실제 runtime context/KV 설정을 인벤토리와 config에 맞춘다. 검증: OneXPlayer는 `ctx_size=524288`, `-np 3`, long slot 2 기준을 유지하며 `786432`로 변경하지 않는다. +- [x] [status-logs] status/log에 `estimated_input_tokens`, `context_class`, `long_context_capacity`, `long_in_flight`, long queue reason을 남긴다. 검증: Control Plane status 또는 Edge log로 long admission 판단을 재구성할 수 있다. +- [x] [capacity-smoke] dev 환경에서 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오를 수행하고 완료 후 `in_flight=0`, `queued=0`, `long_in_flight=0` 회복 근거를 남긴다. ## 완료 리뷰 -- 상태: 없음 -- 요청일: 없음 -- 완료 근거: 진행중 Milestone이며 남은 기능 Task는 active `agent-task/m-model-group-long-context-admission/**` plan에서 처리한다. +- 상태: 통과 +- 요청일: 2026-07-06 +- 완료 근거: `agent-task/archive/2026/07/m-model-group-long-context-admission/**/complete.log` 6개가 `input-estimator`, `provider-long-capacity`, `provider-exclusion`, `queue-skip`, `status-logs`, `dev-runtime-policy`, `capacity-smoke` PASS와 SDD S03-S09 evidence를 남겼다. +- 완료 근거: `threshold-config`, `model-window`는 2026-07-05 직접 처리 근거와 현재 파일/git sanity pass로 SDD S01-S02 기준을 재확인했다. +- 완료 근거: 종료 전 코드 레벨 검토에서 `input_estimator` float schema 직렬화 panic 가능성을 작은 이슈로 수정했고, `go test -count=1 ./...`, `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`, `bash -n scripts/e2e-long-context-admission-smoke.sh`, `git diff --check`가 통과했다. - 검토 항목: - - [ ] long-context 분류가 입력 기준으로만 수행된다. - - [ ] 같은 model group provider는 `context_window_tokens=262144` 단일 요청 계약을 공유한다. - - [ ] long slot이 찬 provider는 long 요청 후보에서 제외되지만 일반 capacity가 남으면 normal 요청 후보로 유지된다. - - [ ] OneXPlayer는 `ctx_size=524288`, `-np 3`, long slot 2 기준을 유지하며 `786432`로 키우지 않는다. + - [x] long-context 분류가 입력 기준으로만 수행된다. + - [x] 같은 model group provider는 `context_window_tokens=262144` 단일 요청 계약을 공유한다. + - [x] long slot이 찬 provider는 long 요청 후보에서 제외되지만 일반 capacity가 남으면 normal 요청 후보로 유지된다. + - [x] OneXPlayer는 `ctx_size=524288`, `-np 3`, long slot 2 기준을 유지하며 `786432`로 키우지 않는다. - agent-ui 상태 반영: 해당 없음 -- 리뷰 코멘트: 없음 +- 리뷰 코멘트: 모든 기능 Task와 SDD Evidence Map 연결이 충족되고 종료 전 코드 레벨 검토의 작은 보완까지 반영되어 `[완료]` archive 대상으로 확정했다. ## 범위 제외 diff --git a/agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md b/agent-roadmap/archive/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md similarity index 100% rename from agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md rename to agent-roadmap/archive/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md diff --git a/agent-roadmap/phase/operational-observability-provider-management/PHASE.md b/agent-roadmap/phase/operational-observability-provider-management/PHASE.md index 0255139..04d191f 100644 --- a/agent-roadmap/phase/operational-observability-provider-management/PHASE.md +++ b/agent-roadmap/phase/operational-observability-provider-management/PHASE.md @@ -28,9 +28,9 @@ provider 확장 Phase에서 검증한 Ollama, vLLM, SGLang, Lemonade 같은 추 - 경로: `agent-roadmap/archive/phase/operational-observability-provider-management/milestones/node-provider-first-config-surface.md` - 요약: Node 설정 표면을 `providers[]` resource list 중심으로 재정렬하고, adapter 설정은 내부 실행 IR 또는 legacy compat로 낮춰 운영자가 한 Node의 CLI/provider 자원을 한 곳에서 이해하고 관리하게 만들었다. -- [계획] Model Group Long-Context Admission - - 경로: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md` - - 요약: model group의 단일 context window 계약을 유지하면서 입력 기준 long-context 요청을 별도 slot으로 admission하고, long 요청이 찬 provider와 queue 앞 long 요청이 normal 요청을 불필요하게 막지 않도록 라우팅 정책을 정리한다. +- [완료] Model Group Long-Context Admission + - 경로: `agent-roadmap/archive/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md` + - 요약: model group의 단일 context window 계약을 유지하면서 입력 기준 long-context 요청을 별도 slot으로 admission하고, long 요청이 찬 provider와 queue 앞 long 요청이 normal 요청을 불필요하게 막지 않도록 라우팅 정책을 완료했다. - [스케치] 사용량, 토큰, 로그 운영 추적 MVP - 경로: `agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md` diff --git a/apps/edge/internal/openai/input_estimator.go b/apps/edge/internal/openai/input_estimator.go index fa04262..1486781 100644 --- a/apps/edge/internal/openai/input_estimator.go +++ b/apps/edge/internal/openai/input_estimator.go @@ -1,6 +1,7 @@ package openai import ( + "strconv" "unicode/utf8" ) @@ -182,30 +183,7 @@ func encodeJSONArray(b *stringsBuilder, items []any) { } func ftoa(v float64) string { - // Simple float formatting: up to 6 decimal places, no trailing zeros. - if v == float64(int64(v)) { - return itoa64(int64(v)) - } - // Use a simple approach: multiply by power of 10 and truncate. - multiplier := 1.0 - for i := 0; i < 6; i++ { - if v*multiplier == float64(int64(v*multiplier)) { - s := itoa64(int64(v * multiplier)) - decimals := i + 1 - intPart := s[:len(s)-decimals] - fracPart := s[len(s)-decimals:] - return intPart + "." + fracPart - } - multiplier *= 10 - } - // Fallback: use standard approach. - s := itoa64(int64(v * 1000000)) - if len(s) <= 6 { - return "0." + zeroPadLeft(6-len(s), s) - } - intPart := s[:len(s)-6] - fracPart := s[len(s)-6:] - return intPart + "." + fracPart + return strconv.FormatFloat(v, 'f', -1, 64) } // lightweight integer/float formatters to avoid fmt import. diff --git a/apps/edge/internal/openai/input_estimator_test.go b/apps/edge/internal/openai/input_estimator_test.go index 0f06222..5067e09 100644 --- a/apps/edge/internal/openai/input_estimator_test.go +++ b/apps/edge/internal/openai/input_estimator_test.go @@ -1,6 +1,7 @@ package openai import ( + "strings" "testing" ) @@ -79,6 +80,20 @@ func TestEstimateInputTokens_WithTools(t *testing.T) { } } +func TestToolsSchemaToJSON_FloatLessThanOne(t *testing.T) { + got := toolsSchemaToJSON(map[string]any{ + "type": "number", + "minimum": 0.5, + "multiple": 0.25, + }) + if !strings.Contains(got, `"minimum":0.5`) { + t.Fatalf("expected minimum float to be encoded, got %q", got) + } + if !strings.Contains(got, `"multiple":0.25`) { + t.Fatalf("expected multiple float to be encoded, got %q", got) + } +} + func TestClassifyContext_Normal(t *testing.T) { threshold := 100000 if classifyContext(1000, threshold) != "normal" {