refactor: clean up long-context admission smoke test files and input estimator
This commit is contained in:
parent
e1dcbc105c
commit
698f03c0ea
5 changed files with 38 additions and 43 deletions
|
|
@ -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 대상으로 확정했다.
|
||||
|
||||
## 범위 제외
|
||||
|
||||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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" {
|
||||
|
|
|
|||
Loading…
Reference in a new issue