From eab8bad73df684211ccf9fb2c67a2936ef1518f9 Mon Sep 17 00:00:00 2001 From: toki Date: Tue, 14 Jul 2026 14:43:57 +0900 Subject: [PATCH] feat: update dev-corp test configs and add openai reasoning usage estimation --- .../01_metric_core/CODE_REVIEW-local-G05.md | 147 ++++++++++ .../01_metric_core/PLAN-local-G05.md | 260 ++++++++++++++++++ .../CODE_REVIEW-cloud-G07.md | 162 +++++++++++ .../02+01_dev_corp_rollout/PLAN-cloud-G07.md | 255 +++++++++++++++++ agent-test/dev-corp/control-plane-smoke.md | 6 +- agent-test/dev-corp/edge-smoke.md | 4 +- agent-test/dev-corp/inventory.yaml | 4 +- agent-test/dev-corp/rules.md | 8 +- configs/prometheus/prometheus.dev-corp.yml | 2 +- docs/openai-usage-grafana.md | 2 +- 10 files changed, 837 insertions(+), 13 deletions(-) create mode 100644 agent-task/openai_reasoning_estimated_usage/01_metric_core/CODE_REVIEW-local-G05.md create mode 100644 agent-task/openai_reasoning_estimated_usage/01_metric_core/PLAN-local-G05.md create mode 100644 agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/CODE_REVIEW-cloud-G07.md create mode 100644 agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/PLAN-cloud-G07.md diff --git a/agent-task/openai_reasoning_estimated_usage/01_metric_core/CODE_REVIEW-local-G05.md b/agent-task/openai_reasoning_estimated_usage/01_metric_core/CODE_REVIEW-local-G05.md new file mode 100644 index 0000000..882991f --- /dev/null +++ b/agent-task/openai_reasoning_estimated_usage/01_metric_core/CODE_REVIEW-local-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 selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close 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 only Milestone lock decisions 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-07-14 +task=openai_reasoning_estimated_usage/01_metric_core, plan=0, tag=API + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G05.md` → `code_review_local_G05_0.log`, `PLAN-local-G05.md` → `plan_local_G05_0.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/openai_reasoning_estimated_usage/01_metric_core/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Reasoning estimated token metric 추가 | [ ] | +| [API-2] Usage metric regression tests 정리 | [ ] | +| [API-3] Grafana/spec 문서 갱신 | [ ] | + +## 구현 체크리스트 + +- [ ] `apps/edge/internal/openai/usage_metrics.go`에 provider 미보고 reasoning text를 별도 estimated-token metric으로 emit하는 로직을 추가한다. +- [ ] `apps/edge/internal/openai/usage_metrics_test.go`에 estimated metric 증가, provider-reported reasoning 우선, label allowlist regression을 추가/갱신한다. +- [ ] `docs/openai-usage-grafana.md`와 `agent-spec/input/openai-compatible-surface.md`에 reported reasoning token과 estimated reasoning token의 차이를 문서화한다. +- [ ] 최종 검증 명령을 모두 실행하고 예상 결과와 실제 출력 차이를 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G05_0.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G05_0.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/openai_reasoning_estimated_usage/01_metric_core/`를 `agent-task/archive/YYYY/MM/openai_reasoning_estimated_usage/01_metric_core/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/openai_reasoning_estimated_usage/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G05.md`와 `CODE_REVIEW-local-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`를 남기지 않는다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `iop_openai_usage_tokens_total{token_type="reasoning"}`가 provider-reported 전용으로 남아 있는지 확인한다. +- `iop_openai_reasoning_estimated_tokens_total`이 reasoning text 관측 + provider token 미보고 조건에서만 증가하는지 확인한다. +- estimated metric label에 raw token, prompt, request id, session id가 들어가지 않는지 확인한다. +- docs/spec가 estimated 값을 billing-grade 확정 토큰으로 표현하지 않는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. + +### API-1 중간 검증 +```bash +$ GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai -run 'UsageMetrics|Reasoning' +(output) +``` + +### API-2 중간 검증 +```bash +$ GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai -run 'OpenAI.*Usage|OpenAI.*Reasoning|ProviderTunnelPassthrough.*Usage|ResponsesProviderTunnelPassthrough.*Usage' +(output) +``` + +### API-3 중간 검증 +```bash +$ rg --sort path --fixed-strings "iop_openai_reasoning_estimated_tokens_total" apps/edge/internal/openai docs/openai-usage-grafana.md agent-spec/input/openai-compatible-surface.md +(output) +``` + +### 최종 검증 +```bash +$ GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai +(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. + +## Section Ownership + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute finalization | +| 구현 항목별 완료 여부 | Implementing agent | Check only after implementation evidence exists | +| 구현 체크리스트 | Implementing agent | Check only; do not change item text | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders with actual notes | +| 사용자 리뷰 요청 | Implementing agent | Keep 없음 unless selected Milestone lock blocks implementation | +| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus | +| 검증 결과 | Implementing agent | Paste actual stdout/stderr | diff --git a/agent-task/openai_reasoning_estimated_usage/01_metric_core/PLAN-local-G05.md b/agent-task/openai_reasoning_estimated_usage/01_metric_core/PLAN-local-G05.md new file mode 100644 index 0000000..5139309 --- /dev/null +++ b/agent-task/openai_reasoning_estimated_usage/01_metric_core/PLAN-local-G05.md @@ -0,0 +1,260 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션 작성까지가 구현의 마지막 단계다. 구현 후 검증 명령을 실행하고 실제 출력과 변경 내용을 review stub에 채운 뒤 active 파일을 그대로 둔 채 review ready로 보고한다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막는 경우에만 review stub의 `사용자 리뷰 요청` 섹션을 근거와 함께 채우고 멈춘다. 직접 사용자에게 질문하거나, 선택지를 제시하거나, `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다. 환경/secret/service blocker, 일반 범위 변경, 검증 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과나 후속 plan 후보로 기록한다. + +## 배경 + +dev-corp Ornith 경로는 reasoning text는 관측되지만 provider가 `reasoning_tokens`를 보고하지 않는 경우가 있다. 현재 Edge는 이 경우 `iop_openai_reasoning_observed_total`과 `iop_openai_reasoning_chars_total`만 emit하고 token 추정치는 제공하지 않는다. 목표는 provider-reported `token_type="reasoning"`의 의미를 보존하면서, provider 미보고 케이스도 운영자가 추정 토큰으로 조회할 수 있게 하는 것이다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자에게 묻지 않는다. 선택된 Milestone lock 결정이 실제 구현을 차단하는 경우에만 `CODE_REVIEW-local-G05.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록하고 멈춘다. 이 plan은 non-roadmap 독립 task이므로 일반 구현 판단은 plan 범위 안에서 처리한다. + +## 분석 결과 + +### 읽은 파일 + +- `agent-roadmap/current.md` +- `agent-test/dev-corp/rules.md` +- `agent-test/dev-corp/edge-smoke.md` +- `agent-test/dev-corp/platform-common-smoke.md` +- `agent-contract/outer/openai-compatible-api.md` +- `agent-spec/input/openai-compatible-surface.md` +- `docs/openai-usage-grafana.md` +- `apps/edge/internal/openai/usage_metrics.go` +- `apps/edge/internal/openai/usage_metrics_test.go` + +### SDD 기준 + +not applicable. 이 plan은 현재 Milestone 완료 체크를 주장하지 않는 non-roadmap 독립 task다. + +### 테스트 환경 규칙 + +- test_env: `dev-corp` +- `agent-test/dev-corp/rules.md`: present/read. dev-corp Edge는 public `iop.ai.kr`, OpenAI-compatible 기본 URL은 `https://digitalplatform.iop.ai.kr/v1`, Edge metrics는 `19105`를 사용한다. +- matched profiles: + - `agent-test/dev-corp/edge-smoke.md`: present/read. `apps/edge/internal/openai/**` 변경 시 `go test ./apps/edge/...` 또는 변경 패키지 테스트와 OpenAI-compatible 경계 확인이 필요하다. + - `agent-test/dev-corp/platform-common-smoke.md`: present/read. 이 plan은 proto/config를 변경하지 않으므로 platform-common 필수 검증은 적용하지 않는다. +- 이 core plan의 필수 검증은 local checkout 안에서 끝난다. dev-corp 원격 preflight, 배포, Prometheus live 확인은 sibling plan `openai_reasoning_estimated_usage/02+01_dev_corp_rollout`에서 수행한다. +- fallback verification source: `agent-spec/input/openai-compatible-surface.md`의 OpenAI usage 검증 명령과 기존 Go package tests. + +### 테스트 커버리지 공백 + +- 현재 `TestOpenAIReasoningObservedDoesNotEstimateTokens`는 reasoning text 미보고 케이스에서 token counter가 증가하지 않음을 검증한다. 신규 동작은 같은 케이스에서 별도 estimated metric이 증가해야 하므로 regression update가 필요하다. +- provider가 `reasoning_tokens`를 보고하는 경우 `token_type="reasoning"`만 증가하고 estimated metric이 증가하지 않는 guard가 없다. 새 test가 필요하다. +- metric label allowlist에 estimated metric의 `estimation_method`가 추가되면 secret/high-cardinality label 배제 검증을 확장해야 한다. + +### 심볼 참조 + +renamed/removed symbol: none. + +관련 참조: + +- `apps/edge/internal/openai/usage_metrics.go`: metric label/counter 정의, `usageObservation`, `emitUsageMetrics` +- `apps/edge/internal/openai/usage_metrics_test.go`: usage metric label, token type, reasoning observation tests +- `docs/openai-usage-grafana.md`: reasoning 보조 metric 문서와 PromQL +- `agent-spec/input/openai-compatible-surface.md`: usage metric surface 설명 + +### 분할 판단 + +split decision policy를 plan 파일 선택 전에 평가했다. 공유 task group은 `openai_reasoning_estimated_usage`다. + +- `01_metric_core`: source/test/docs/spec 변경. dependency 없음. +- `02+01_dev_corp_rollout`: dev-corp 배포/Prometheus/concurrency smoke. predecessor `01_metric_core`의 `complete.log`가 필요하다. + +코드 변경과 dev-corp rollout은 검증 환경과 실패 원인이 다르므로 분리한다. core plan은 local-G05로 안전하게 완료하고, runtime 반영은 cloud-G07 rollout plan에서 독립 검증한다. + +### 범위 결정 근거 + +- `iop_openai_usage_tokens_total{token_type="reasoning"}`는 provider-reported token 전용으로 유지한다. 추정값을 이 counter에 섞지 않는다. +- provider adapter, proto `Usage.reasoning_tokens`, Node tunnel parsing은 이미 provider-reported 값을 전달하므로 변경하지 않는다. +- Control Plane dashboard, billing/chargeback, request-level ledger, live cloud price sync는 제외한다. +- 현재 checkout에는 dev-corp metrics port 관련 dirty docs/test 변경이 있으므로 구현자는 같은 파일 수정 시 기존 변경을 보존한다. + +### 빌드 등급 + +lane=`local`, grade=`G05`. 변경은 Edge OpenAI usage metric 한 파일과 해당 unit tests/docs/spec에 한정되며 local Go tests로 대부분 검증된다. + +## 구현 체크리스트 + +- [ ] `apps/edge/internal/openai/usage_metrics.go`에 provider 미보고 reasoning text를 별도 estimated-token metric으로 emit하는 로직을 추가한다. +- [ ] `apps/edge/internal/openai/usage_metrics_test.go`에 estimated metric 증가, provider-reported reasoning 우선, label allowlist regression을 추가/갱신한다. +- [ ] `docs/openai-usage-grafana.md`와 `agent-spec/input/openai-compatible-surface.md`에 reported reasoning token과 estimated reasoning token의 차이를 문서화한다. +- [ ] 최종 검증 명령을 모두 실행하고 예상 결과와 실제 출력 차이를 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [API-1] Reasoning estimated token metric 추가 + +#### 문제 + +`apps/edge/internal/openai/usage_metrics.go:79-82`의 chars metric help는 "Never converted to a token estimate"라고 고정되어 있다. `apps/edge/internal/openai/usage_metrics.go:85-96`도 `reasoningChars`를 보조 metric에만 쓰고, `apps/edge/internal/openai/usage_metrics.go:166-169`는 observed/chars만 증가시킨다. 따라서 provider가 token count를 주지 않는 think text는 token 단위로 조회할 수 없다. + +#### 해결 방법 + +`iop_openai_usage_tokens_total`은 provider-reported 전용으로 유지하고, 별도 counter를 추가한다. + +Before: + +```go +// apps/edge/internal/openai/usage_metrics.go:79 +openAIReasoningCharsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "iop_openai_reasoning_chars_total", + Help: "Observed reasoning text characters for requests without provider-reported reasoning tokens. Never converted to a token estimate.", +}, usageReasoningLabelNames) +``` + +After: + +```go +usageReasoningEstimateLabelNames = []string{ + "edge_id", "principal_ref", "principal_alias", "token_ref", + "model_group", "endpoint", "response_mode", "estimation_method", +} + +openAIReasoningEstimatedTokensTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "iop_openai_reasoning_estimated_tokens_total", + Help: "Estimated reasoning tokens for requests with observed reasoning text but without provider-reported reasoning token usage.", +}, usageReasoningEstimateLabelNames) +``` + +`emitUsageMetrics`에서 `obs.reasoningChars > 0 && obs.reasoningTokens == 0`일 때만 `ceil(chars / 4)`를 `estimation_method="chars_div_4"`로 emit한다. `math` 표준 패키지를 쓰거나 integer helper `(chars + 3) / 4`를 사용한다. provider가 `reasoningTokens > 0`이면 estimated metric은 증가하지 않는다. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/edge/internal/openai/usage_metrics.go`: estimated metric label set과 counter 추가 +- [ ] `apps/edge/internal/openai/usage_metrics.go`: `estimatedReasoningTokens(chars int) int` helper 추가 +- [ ] `apps/edge/internal/openai/usage_metrics.go`: observed/chars emit path에서 estimated counter도 emit +- [ ] `apps/edge/internal/openai/usage_metrics.go`: comments/help text를 "estimated는 별도 metric" 기준으로 갱신 + +#### 테스트 작성 + +write tests. + +- `TestOpenAIReasoningObservedEstimatesTokens`: 5 chars reasoning text가 provider reasoning token 없이 오면 estimated delta가 `2`, observed delta가 `1`, chars delta가 `5`, `token_type="reasoning"` delta가 `0`. +- `TestOpenAIReasoningProviderReportedDoesNotEstimate`: provider가 `ReasoningTokens: 3`을 주고 reasoning text도 있으면 estimated delta가 `0`, `token_type="reasoning"` delta가 `3`. +- `TestOpenAIUsageMetricsLabelsExcludeSecretsAndHighCardinality`: `usageReasoningEstimateLabelNames`를 forbidden-label loop에 포함하고 `estimation_method`만 추가 허용. + +#### 중간 검증 + +```bash +GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai -run 'UsageMetrics|Reasoning' +``` + +Expected: PASS. Go test cache output is not acceptable here; use `-count=1`. + +### [API-2] Usage metric regression tests 정리 + +#### 문제 + +`apps/edge/internal/openai/usage_metrics_test.go:144-189`는 "DoesNotEstimateTokens"라는 기존 정책을 직접 고정한다. 새 정책은 provider-reported token counter에는 추정치를 섞지 않는 대신 별도 estimated counter를 제공해야 하므로 테스트 이름과 assertion이 현재 목표와 어긋난다. + +#### 해결 방법 + +기존 테스트를 rename/update하고, provider-reported 우선 테스트를 추가한다. 테스트 helper는 `requestTokenValue`처럼 estimated counter 전용 helper를 하나 둔다. + +Before: + +```go +// apps/edge/internal/openai/usage_metrics_test.go:180 +if got := requestTokenValue(t, labels, tokenTypeReasoning) - reasoningBefore; got != 0 { + t.Fatalf("reasoning token counter must not move when provider did not report reasoning tokens: got delta %v", got) +} +``` + +After: + +```go +if got := requestTokenValue(t, labels, tokenTypeReasoning) - reasoningBefore; got != 0 { + t.Fatalf("provider-reported reasoning token counter must not move for estimated reasoning: got delta %v", got) +} +if got := requestReasoningEstimateValue(t, labels, "chars_div_4") - estimatedBefore; got != 2 { + t.Fatalf("reasoning estimated tokens: got delta %v, want 2", got) +} +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/edge/internal/openai/usage_metrics_test.go`: estimated counter helper 추가 +- [ ] `apps/edge/internal/openai/usage_metrics_test.go`: reasoning observed test rename/update +- [ ] `apps/edge/internal/openai/usage_metrics_test.go`: provider-reported reasoning + text no-estimate test 추가 +- [ ] `apps/edge/internal/openai/usage_metrics_test.go`: label allowlist test에 estimated labels 포함 + +#### 테스트 작성 + +write tests as listed above. 기존 passthrough usage tests는 provider-reported `reasoning_tokens`가 기존 counter로 유지되는 regression으로 그대로 둔다. + +#### 중간 검증 + +```bash +GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai -run 'OpenAI.*Usage|OpenAI.*Reasoning|ProviderTunnelPassthrough.*Usage|ResponsesProviderTunnelPassthrough.*Usage' +``` + +Expected: PASS. Go test cache output is not acceptable here; use `-count=1`. + +### [API-3] Grafana/spec 문서 갱신 + +#### 문제 + +`docs/openai-usage-grafana.md:40-48`은 observed/chars metric만 설명하고, `docs/openai-usage-grafana.md:185`는 token 추정을 권유하지 않는다고 적는다. `agent-spec/input/openai-compatible-surface.md:66-68`, `agent-spec/input/openai-compatible-surface.md:124-128`, `agent-spec/input/openai-compatible-surface.md:155-156`도 reasoning text를 token으로 추정하지 않는 현 상태를 고정한다. + +#### 해결 방법 + +문서에서 provider-reported token과 estimated token을 분리해 설명한다. `iop_openai_reasoning_estimated_tokens_total`은 운영 관측용 추정값이며 billing-grade/chargeback 확정값이 아니라고 명시한다. + +Before: + +```markdown +Observed reasoning text characters for requests without provider-reported reasoning tokens. Never converted to a token estimate. +``` + +After: + +```markdown +Observed reasoning text characters for requests without provider-reported reasoning tokens. The Edge also emits `iop_openai_reasoning_estimated_tokens_total` as an explicit estimate; provider-reported `token_type="reasoning"` remains authoritative. +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `docs/openai-usage-grafana.md`: Metric 목록에 `iop_openai_reasoning_estimated_tokens_total` 추가 +- [ ] `docs/openai-usage-grafana.md`: PromQL 예시와 dashboard 후보에 estimated reasoning token query 추가 +- [ ] `docs/openai-usage-grafana.md`: cloud-equivalent cost 섹션에서 estimated reasoning은 확정 정산값이 아니라고 명시 +- [ ] `agent-spec/input/openai-compatible-surface.md`: 기능 목록, 설정/데이터/이벤트, 한계 문구를 updated metric 기준으로 갱신 + +#### 테스트 작성 + +skip new executable tests. 문서/spec 변경은 final `rg --sort path --fixed-strings`로 신규 metric 명칭이 source/docs/spec에 모두 반영됐는지 확인한다. + +#### 중간 검증 + +```bash +rg --sort path --fixed-strings "iop_openai_reasoning_estimated_tokens_total" apps/edge/internal/openai docs/openai-usage-grafana.md agent-spec/input/openai-compatible-surface.md +``` + +Expected: source, test, docs, spec references are all present. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/edge/internal/openai/usage_metrics.go` | API-1 | +| `apps/edge/internal/openai/usage_metrics_test.go` | API-2 | +| `docs/openai-usage-grafana.md` | API-3 | +| `agent-spec/input/openai-compatible-surface.md` | API-3 | + +## 최종 검증 + +```bash +GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai -run 'UsageMetrics|Reasoning' +GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai -run 'OpenAI.*Usage|OpenAI.*Reasoning|ProviderTunnelPassthrough.*Usage|ResponsesProviderTunnelPassthrough.*Usage' +GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/openai +rg --sort path --fixed-strings "iop_openai_reasoning_estimated_tokens_total" apps/edge/internal/openai docs/openai-usage-grafana.md agent-spec/input/openai-compatible-surface.md +git diff --check +``` + +Expected: all commands PASS. Go test cache output is not acceptable for the Go commands; `-count=1` is required. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/CODE_REVIEW-cloud-G07.md b/agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..2fe5174 --- /dev/null +++ b/agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,162 @@ + + +# 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 selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close 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 only Milestone lock decisions 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-07-14 +task=openai_reasoning_estimated_usage/02+01_dev_corp_rollout, plan=0, tag=API + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 runtime evidence와 대조하고, `검증 결과` 섹션의 출력이 plan의 expected outcome과 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_0.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] dev-corp Edge binary rebuild and rollout | [ ] | +| [API-2] single-call reasoning estimated metric smoke | [ ] | +| [API-3] concurrent smoke and Prometheus accounting | [ ] | + +## 구현 체크리스트 + +- [ ] `openai_reasoning_estimated_usage/01_metric_core/complete.log` 존재를 확인하고 없으면 배포하지 않는다. +- [ ] dev-corp runner, public Edge, Prometheus, ports, binary/config path preflight를 실행하고 blocker를 기록한다. +- [ ] 구현 완료 source state를 mac-mini runner에 sync하고 `iop-edge` Linux ARM64 binary를 rebuild한다. +- [ ] public Edge host에 새 binary를 배포하고 Edge process를 restart한 뒤 `/healthz`, `/v1/models`, `/metrics`를 확인한다. +- [ ] smoke token으로 `ornith:35b` single-call을 실행하고 Prometheus에서 estimated reasoning metric 증가를 확인한다. +- [ ] 같은 token으로 6개 이상 concurrent `/v1/chat/completions`를 실행하고 request/estimated metric 증가를 확인한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_0.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_0.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/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/`를 `agent-task/archive/YYYY/MM/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/openai_reasoning_estimated_usage/`를 제거하거나, 남은 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`를 남기지 않는다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- predecessor `01_metric_core/complete.log` 없이 배포가 진행되지 않았는지 확인한다. +- public Edge에 새 metric이 실제 `/metrics`로 노출되는지 확인한다. +- smoke token 원문이 review output에 남지 않았는지 확인한다. +- single-call과 6개 이상 concurrent call 모두 Prometheus evidence로 연결되는지 확인한다. +- provider-reported `reasoning` counter와 estimated counter를 혼동하지 않았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- token/secret 원문은 붙여 넣지 않는다. + +### API-1 중간 검증 +```bash +$ ssh toki@iop.ai.kr 'curl -fsS http://127.0.0.1:18086/healthz && curl -fsS http://127.0.0.1:19105/metrics | grep -F "iop_openai_reasoning_estimated_tokens_total"' +(output) +``` + +### API-2 중간 검증 +```bash +$ ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"' +(output) +``` + +### API-3 중간 검증 +```bash +$ test "$(ls /tmp/devcorp-reasoning-concurrent-*.json | wc -l | tr -d ' ')" -ge 6 +(output) +``` + +### 최종 검증 +```bash +$ test -f agent-task/openai_reasoning_estimated_usage/01_metric_core/complete.log +(output) + +$ ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && git status --short --branch && git rev-parse HEAD && (/opt/homebrew/bin/go version || go version)' +(output) + +$ ssh fe@172.24.63.178 'curl -fsS http://127.0.0.1:19112/-/ready' +(output) + +$ ssh toki@iop.ai.kr 'cd /Users/toki/agent-work/iop-dev-corp && test -f build/dev-corp-runtime/edge.yaml && pgrep -af "iop-edge.*serve" && lsof -nP -iTCP:18086 -sTCP:LISTEN && lsof -nP -iTCP:19105 -sTCP:LISTEN' +(output) + +$ ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,status) (iop_openai_requests_total{token_ref=\"devcorp-smoke-usage\"})"' +(output) + +$ git diff --check -- agent-task/openai_reasoning_estimated_usage +(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. + +## Section Ownership + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute finalization | +| 구현 항목별 완료 여부 | Implementing agent | Check only after runtime evidence exists | +| 구현 체크리스트 | Implementing agent | Check only; do not change item text | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders with actual notes | +| 사용자 리뷰 요청 | Implementing agent | Keep 없음 unless selected Milestone lock blocks implementation | +| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus | +| 검증 결과 | Implementing agent | Paste actual stdout/stderr with secrets masked | diff --git a/agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/PLAN-cloud-G07.md b/agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/PLAN-cloud-G07.md new file mode 100644 index 0000000..c8673f0 --- /dev/null +++ b/agent-task/openai_reasoning_estimated_usage/02+01_dev_corp_rollout/PLAN-cloud-G07.md @@ -0,0 +1,255 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +이 rollout plan은 `openai_reasoning_estimated_usage/01_metric_core`가 PASS되어 `complete.log`를 만든 뒤에만 구현한다. `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션 작성까지가 구현의 마지막 단계다. 검증 출력에는 token/secret 원문을 남기지 않는다. 구현 후 active 파일을 그대로 둔 채 review ready로 보고하고, `USER_REVIEW.md`, `complete.log`, archive log는 만들지 않는다. + +## 배경 + +core plan이 estimated reasoning token metric을 추가해도 dev-corp public Edge에 배포되지 않으면 사용량 측정 상태가 운영에서 확인되지 않는다. 이 plan은 public `iop.ai.kr` Edge와 mac-mini Prometheus를 기준으로 새 metric이 실제 OpenAI-compatible 호출과 동시 호출에서 증가하는지 검증한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자에게 묻지 않는다. 선택된 Milestone lock 결정이 실구현을 차단하는 경우에만 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 이 plan은 non-roadmap 독립 rollout task이므로 SSH, secret, host readiness 문제는 사용자 리뷰가 아니라 검증 blocker 또는 follow-up으로 기록한다. + +## 분석 결과 + +### 읽은 파일 + +- `agent-roadmap/current.md` +- `agent-test/dev-corp/rules.md` +- `agent-test/dev-corp/edge-smoke.md` +- `agent-test/dev-corp/platform-common-smoke.md` +- `agent-contract/outer/openai-compatible-api.md` +- `agent-spec/input/openai-compatible-surface.md` +- `docs/openai-usage-grafana.md` +- `apps/edge/internal/openai/usage_metrics.go` +- `apps/edge/internal/openai/usage_metrics_test.go` + +### SDD 기준 + +not applicable. 이 plan은 현재 Milestone 기능 Task 완료 체크를 하지 않는 non-roadmap rollout task다. + +### 테스트 환경 규칙 + +- test_env: `dev-corp` +- `agent-test/dev-corp/rules.md`: present/read. public Edge runtime은 `iop.ai.kr`, OpenAI-compatible 기본 URL은 `https://digitalplatform.iop.ai.kr/v1`, Edge metrics는 `19105`, Prometheus는 mac-mini runner `19112`다. +- matched profiles: + - `agent-test/dev-corp/edge-smoke.md`: present/read. OpenAI-compatible 경계 변경은 `/healthz`, `/v1/models`, `/v1/chat/completions` 동등 smoke와 Edge service/node adapter 수렴 확인이 필요하다. + - `agent-test/dev-corp/platform-common-smoke.md`: present/read. 이 rollout은 proto/config 계약을 바꾸지 않으므로 platform-common command는 적용하지 않는다. + +#### 테스트 환경 프리플라이트 + +- runner: `ssh fe@172.24.63.178` +- runner repo root: `/Users/fe/agent-work/iop-dev-corp` +- public Edge host: `iop.ai.kr` +- public Edge runtime root: `/Users/toki/agent-work/iop-dev-corp` +- Edge config path: `/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/edge.yaml` +- Edge id: `dev-corp-edge` +- expected ports: OpenAI-compatible `18086`, Edge-Node `18087`, admin `19094`, metrics `19105`, Prometheus API `19112` on mac-mini +- source sync rule: if runner checkout is dirty/divergent/stale, sync the exact implemented source state before building. +- setup blocker: if `01_metric_core/complete.log` is missing, do not deploy. + +Preflight commands: + +```bash +test -f agent-task/openai_reasoning_estimated_usage/01_metric_core/complete.log +ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && git status --short --branch && git rev-parse HEAD && (/opt/homebrew/bin/go version || go version)' +ssh fe@172.24.63.178 'curl -fsS http://127.0.0.1:19112/-/ready' +ssh toki@iop.ai.kr 'cd /Users/toki/agent-work/iop-dev-corp && test -f build/dev-corp-runtime/edge.yaml && pgrep -af "iop-edge.*serve" && lsof -nP -iTCP:18086 -sTCP:LISTEN && lsof -nP -iTCP:19105 -sTCP:LISTEN' +``` + +### 테스트 커버리지 공백 + +- Local unit tests prove metric emission in-process, but not that the public Edge binary was rebuilt and Prometheus scraped the new metric. +- Single-call smoke proves metric availability but not concurrent request accounting. +- Prometheus query can lag scrape interval; rollout verification must either wait for scrape or inspect direct Edge metrics plus Prometheus. + +### 심볼 참조 + +renamed/removed symbol: none. + +### 분할 판단 + +split decision policy를 평가했다. 이 plan은 `openai_reasoning_estimated_usage/02+01_dev_corp_rollout`이며 predecessor index `01`은 `openai_reasoning_estimated_usage/01_metric_core`다. 현재 plan 작성 시점에는 predecessor `complete.log`가 없으므로 구현 시작 전 `agent-task/openai_reasoning_estimated_usage/01_metric_core/complete.log` 존재를 확인해야 한다. + +### 범위 결정 근거 + +- source code 변경은 이 plan에서 하지 않는다. source 변경이 필요하면 `01_metric_core`로 되돌려 follow-up을 만든다. +- provider runtime tuning, capacity policy, Control Plane dashboard, Grafana dashboard JSON import는 제외한다. +- raw smoke token은 remote secret path에서 읽되 stdout/stderr, docs, task artifact에 기록하지 않는다. +- 기존 dev-corp metrics port 관련 dirty docs/test 변경은 보존한다. + +### 빌드 등급 + +lane=`cloud`, grade=`G07`. public Edge 배포, remote runner build, Prometheus scrape, 동시 호출 검증처럼 외부 runtime 상태가 핵심이며 local-only 검증으로 완료를 판단할 수 없다. + +## 의존 관계 및 구현 순서 + +1. `openai_reasoning_estimated_usage/01_metric_core/complete.log` 확인 +2. runner/public Edge preflight +3. source sync와 Edge binary rebuild +4. public Edge binary 교체 및 restart +5. single-call metric smoke +6. 6개 이상 concurrent smoke +7. Prometheus query evidence 기록 + +## 구현 체크리스트 + +- [ ] `openai_reasoning_estimated_usage/01_metric_core/complete.log` 존재를 확인하고 없으면 배포하지 않는다. +- [ ] dev-corp runner, public Edge, Prometheus, ports, binary/config path preflight를 실행하고 blocker를 기록한다. +- [ ] 구현 완료 source state를 mac-mini runner에 sync하고 `iop-edge` Linux ARM64 binary를 rebuild한다. +- [ ] public Edge host에 새 binary를 배포하고 Edge process를 restart한 뒤 `/healthz`, `/v1/models`, `/metrics`를 확인한다. +- [ ] smoke token으로 `ornith:35b` single-call을 실행하고 Prometheus에서 estimated reasoning metric 증가를 확인한다. +- [ ] 같은 token으로 6개 이상 concurrent `/v1/chat/completions`를 실행하고 request/estimated metric 증가를 확인한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [API-1] dev-corp Edge binary rebuild and rollout + +#### 문제 + +core plan의 local tests가 PASS해도 public `iop.ai.kr` Edge binary가 갱신되지 않으면 dev-corp에서는 새 metric이 보이지 않는다. `agent-test/dev-corp/edge-smoke.md:36-44`와 `agent-test/dev-corp/edge-smoke.md:130-135`는 OpenAI-compatible 경계 변경 시 public Edge 기준 검증을 요구한다. + +#### 해결 방법 + +preflight로 runner/public Edge 상태를 확인하고, 구현 완료 source state를 runner에 sync한 뒤 Linux ARM64 Edge binary를 rebuild해 public Edge runtime에 배포한다. restart 후 direct health와 metrics endpoint에서 새 metric name이 노출되는지 확인한다. + +Source sync/build command 예시: + +```bash +rsync -a --delete --exclude .git --exclude build/dev-corp-runtime --exclude agent-task/archive ./ fe@172.24.63.178:/Users/fe/agent-work/iop-dev-corp/ +ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && GOCACHE=/tmp/iop-go-cache /opt/homebrew/bin/go test -count=1 ./apps/edge/internal/openai && GOOS=linux GOARCH=arm64 CGO_ENABLED=0 GOCACHE=/tmp/iop-go-cache /opt/homebrew/bin/go build -o build/dev-corp-runtime/bin/iop-edge ./apps/edge/cmd/edge' +scp fe@172.24.63.178:/Users/fe/agent-work/iop-dev-corp/build/dev-corp-runtime/bin/iop-edge /tmp/iop-edge.dev-corp +scp /tmp/iop-edge.dev-corp toki@iop.ai.kr:/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/bin/iop-edge.new +ssh toki@iop.ai.kr 'cd /Users/toki/agent-work/iop-dev-corp && mv build/dev-corp-runtime/bin/iop-edge.new build/dev-corp-runtime/bin/iop-edge && pkill -f "iop-edge.*serve" && nohup build/dev-corp-runtime/bin/iop-edge serve --config build/dev-corp-runtime/edge.yaml > build/dev-corp-runtime/logs/iop-edge-rollout-$(date +%Y%m%d%H%M%S).log 2>&1 &' +``` + +If the existing process manager differs, record the replacement restart command in `계획 대비 변경 사항` with `pgrep -af` evidence. + +#### 수정 파일 및 체크리스트 + +- [ ] no source file edits in this plan +- [ ] runner source sync performed from completed core source state +- [ ] runner `iop-edge` rebuilt for `GOOS=linux GOARCH=arm64` +- [ ] public Edge binary replaced atomically with `.new` then `mv` +- [ ] public Edge restarted and listening ports verified + +#### 테스트 작성 + +skip new source tests. This is a rollout-only plan; local source tests are inherited from `01_metric_core` and rerun before build. + +#### 중간 검증 + +```bash +ssh toki@iop.ai.kr 'curl -fsS http://127.0.0.1:18086/healthz && curl -fsS http://127.0.0.1:19105/metrics | grep -F "iop_openai_reasoning_estimated_tokens_total"' +``` + +Expected: healthz succeeds and metrics output contains the new metric name. + +### [API-2] single-call reasoning estimated metric smoke + +#### 문제 + +Metric name exposure alone does not prove runtime request accounting. A real OpenAI-compatible request must produce reasoning text without provider-reported reasoning token count and increase the estimated metric for the smoke principal/token. + +#### 해결 방법 + +Read the smoke token from the existing remote secret path without echoing it. Call `ornith:35b` through public OpenAI-compatible URL and query Prometheus on the mac-mini runner for the smoke `token_ref`. + +Commands: + +```bash +SMOKE_TOKEN="$(ssh toki@iop.ai.kr 'cat /Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/.secrets/openai_usage_metering_smoke_token')" +curl -fsS https://digitalplatform.iop.ai.kr/v1/models -H "Authorization: Bearer ${SMOKE_TOKEN}" >/tmp/devcorp-models.json +curl -fsS https://digitalplatform.iop.ai.kr/v1/chat/completions \ + -H "Authorization: Bearer ${SMOKE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"model":"ornith:35b","messages":[{"role":"user","content":"짧게 생각 과정을 내부적으로 사용해서 2+2 답만 말해."}],"max_tokens":64,"include_reasoning":true}' \ + >/tmp/devcorp-reasoning-single.json +ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"' +``` + +Do not paste `${SMOKE_TOKEN}` value into review output. + +#### 수정 파일 및 체크리스트 + +- [ ] `/v1/models` with smoke token returns 200 +- [ ] single `/v1/chat/completions` returns 200 +- [ ] Prometheus query shows `token_ref="devcorp-smoke-usage"` and positive estimated token value +- [ ] `iop_openai_usage_tokens_total{token_type="reasoning"}` remains absent or unchanged when provider did not report reasoning tokens + +#### 테스트 작성 + +skip source tests. This item is live smoke verification. + +#### 중간 검증 + +```bash +ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"' +``` + +Expected: query succeeds and returns a positive value after scrape. + +### [API-3] concurrent smoke and Prometheus accounting + +#### 문제 + +Single-call smoke does not prove concurrent accounting. The original user flow requires usage measurement under real runtime calls, and earlier dev-corp smokes used concurrent requests to catch routing/accounting misses. + +#### 해결 방법 + +Run at least 6 concurrent requests with the same smoke token. Then query requests and estimated reasoning metric deltas by `token_ref`. If Prometheus scrape lags, wait one scrape interval and rerun only the query. + +Commands: + +```bash +SMOKE_TOKEN="$(ssh toki@iop.ai.kr 'cat /Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/.secrets/openai_usage_metering_smoke_token')" +rm -f /tmp/devcorp-reasoning-concurrent-*.json +seq 1 6 | xargs -P6 -I{} sh -c 'curl -fsS https://digitalplatform.iop.ai.kr/v1/chat/completions -H "Authorization: Bearer '"${SMOKE_TOKEN}"'" -H "Content-Type: application/json" -d "{\"model\":\"ornith:35b\",\"messages\":[{\"role\":\"user\",\"content\":\"요청 {}: 한 문장으로 답해.\"}],\"max_tokens\":64,\"include_reasoning\":true}" > /tmp/devcorp-reasoning-concurrent-{}.json' +ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,status) (iop_openai_requests_total{token_ref=\"devcorp-smoke-usage\"})"' +ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"' +``` + +#### 수정 파일 및 체크리스트 + +- [ ] 6 concurrent requests all return HTTP 200 +- [ ] `iop_openai_requests_total{token_ref="devcorp-smoke-usage",status="success"}` increases by at least 6 from the pre-concurrency snapshot +- [ ] `iop_openai_reasoning_estimated_tokens_total{token_ref="devcorp-smoke-usage"}` increases from the pre-concurrency snapshot +- [ ] no raw token appears in terminal output copied into review stub + +#### 테스트 작성 + +skip source tests. This item is live concurrency verification. + +#### 중간 검증 + +```bash +test "$(ls /tmp/devcorp-reasoning-concurrent-*.json | wc -l | tr -d ' ')" -ge 6 +``` + +Expected: at least 6 response files exist and each curl command succeeded. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| none | API-1, API-2, API-3 | + +## 최종 검증 + +```bash +test -f agent-task/openai_reasoning_estimated_usage/01_metric_core/complete.log +ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && git status --short --branch && git rev-parse HEAD && (/opt/homebrew/bin/go version || go version)' +ssh fe@172.24.63.178 'curl -fsS http://127.0.0.1:19112/-/ready' +ssh toki@iop.ai.kr 'cd /Users/toki/agent-work/iop-dev-corp && test -f build/dev-corp-runtime/edge.yaml && pgrep -af "iop-edge.*serve" && lsof -nP -iTCP:18086 -sTCP:LISTEN && lsof -nP -iTCP:19105 -sTCP:LISTEN' +ssh toki@iop.ai.kr 'curl -fsS http://127.0.0.1:18086/healthz && curl -fsS http://127.0.0.1:19105/metrics | grep -F "iop_openai_reasoning_estimated_tokens_total"' +ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"' +ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,status) (iop_openai_requests_total{token_ref=\"devcorp-smoke-usage\"})"' +git diff --check -- agent-task/openai_reasoning_estimated_usage +``` + +Expected: predecessor complete.log exists, preflight succeeds, new metric is exposed, Prometheus returns positive estimated reasoning token values for smoke token, request success count includes concurrent calls, and task artifact diff has no whitespace errors. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-test/dev-corp/control-plane-smoke.md b/agent-test/dev-corp/control-plane-smoke.md index e20edd0..fc08f3c 100644 --- a/agent-test/dev-corp/control-plane-smoke.md +++ b/agent-test/dev-corp/control-plane-smoke.md @@ -3,7 +3,7 @@ test_env: dev-corp test_profile: control-plane-smoke domain: control-plane verification_type: smoke -last_rule_updated_at: 2026-07-02 +last_rule_updated_at: 2026-07-14 --- # control-plane-smoke dev-corp 테스트 @@ -75,7 +75,7 @@ last_rule_updated_at: 2026-07-02 - mac-mini runner에서 compose project `iop-dev-corp-agent`로 `postgres`, `redis`, `control-plane`, `prometheus`, `grafana`가 기동됐다. - Control Plane은 host `18002/19004/19005`를 container `18000/19080/19081`로 publish하고, `/healthz`, `/readyz`, Client WebSocket upgrade가 통과했다. -- Prometheus는 `configs/prometheus/prometheus.dev-corp.yml`을 사용하며 `control-plane:9093`, `host.docker.internal:19102`, `prometheus:9090` target이 `up`이다. +- Prometheus는 `configs/prometheus/prometheus.dev-corp.yml`을 사용하며 `control-plane:9093`, `iop.ai.kr:19105`, `prometheus:9090` target이 `up`이다. - Grafana health는 runner-local `127.0.0.1:19122/api/health`에서 통과했다. - `web` service는 runner Flutter가 Dart `3.10.0`이고 앱 요구 조건이 `^3.11.3`인 점, `agent-shell`/`nexo` sibling path dependency가 없는 점 때문에 별도 차단 상태다. - 외부 public `13002`, `18002`, `19004`는 연결 거부 상태로 유지한다. @@ -83,7 +83,7 @@ last_rule_updated_at: 2026-07-02 ## 판정 기준 - control-plane service가 dev-corp stack으로 기동되고 health endpoint가 성공 응답을 반환한다. -- Prometheus와 Grafana가 같은 dev-corp compose project에서 올라오며, Prometheus는 dev-corp 설정에서 `control-plane:9093`과 native Edge metrics `host.docker.internal:19102`를 scrape한다. +- Prometheus와 Grafana가 같은 dev-corp compose project에서 올라오며, Prometheus는 dev-corp 설정에서 `control-plane:9093`과 native Edge metrics `iop.ai.kr:19105`를 scrape한다. - wire endpoint는 Control Plane 브라우저/앱 경계로 유지되고 Node 직접 연결/스케줄링 경로로 확장되지 않는다. - dev-corp stack은 local/test/dev stack의 host publish 포트를 점유하지 않는다. diff --git a/agent-test/dev-corp/edge-smoke.md b/agent-test/dev-corp/edge-smoke.md index 740f40e..967856b 100644 --- a/agent-test/dev-corp/edge-smoke.md +++ b/agent-test/dev-corp/edge-smoke.md @@ -3,7 +3,7 @@ test_env: dev-corp test_profile: edge-smoke domain: edge verification_type: smoke -last_rule_updated_at: 2026-07-13 +last_rule_updated_at: 2026-07-14 --- # edge-smoke dev-corp 테스트 @@ -35,7 +35,7 @@ last_rule_updated_at: 2026-07-13 - host: local checkout. provider pool과 source/build evidence가 필요하면 mac-mini `ssh fe@172.24.63.178`의 `/Users/fe/agent-work/iop-dev-corp` checkout을 사용한다. - Edge runtime evidence: public `iop.ai.kr` host를 기준으로 한다. -- port: compose Edge-Node TCP transport `19006`; native provider-pool Edge-Node TCP 후보 `18087`; artifact/bootstrap HTTP 후보 `18085`; Edge OpenAI-compatible HTTP 후보 `18086`; Edge metrics 후보 `19102`; admin 후보 `19094`. +- port: compose Edge-Node TCP transport `19006`; native provider-pool Edge-Node TCP 후보 `18087`; artifact/bootstrap HTTP 후보 `18085`; Edge OpenAI-compatible HTTP 후보 `18086`; Edge metrics 후보 `19105`; admin 후보 `19094`. - runtime: Go `1.24` - package manager: Go modules / Makefile - docker: unit/smoke quick check는 Docker를 요구하지 않는다. compose dev-corp 검증은 mac-mini에서 `.env.dev-corp.example` 기준으로 수행한다. diff --git a/agent-test/dev-corp/inventory.yaml b/agent-test/dev-corp/inventory.yaml index d5da82c..5c65323 100644 --- a/agent-test/dev-corp/inventory.yaml +++ b/agent-test/dev-corp/inventory.yaml @@ -1,6 +1,6 @@ test_env: dev-corp profile: dev-corp-provider-pool -last_updated_at: "2026-07-13" +last_updated_at: "2026-07-14" source: remote_runner: @@ -40,7 +40,7 @@ compose: edge_node_tcp_compose: 19006 postgres: 15402 redis: 16302 - edge_metrics: 19102 + edge_metrics: 19105 control_plane_metrics: 19104 prometheus: 19112 grafana: 19122 diff --git a/agent-test/dev-corp/rules.md b/agent-test/dev-corp/rules.md index 5e35cd2..6be2e8c 100644 --- a/agent-test/dev-corp/rules.md +++ b/agent-test/dev-corp/rules.md @@ -1,6 +1,6 @@ --- test_env: dev-corp -last_rule_updated_at: 2026-07-13 +last_rule_updated_at: 2026-07-14 --- # dev-corp 테스트 규칙 @@ -36,7 +36,7 @@ last_rule_updated_at: 2026-07-13 - compose identity: `COMPOSE_PROJECT_NAME=iop-dev-corp-agent`, `IOP_COMPOSE_NETWORK=iop-dev-corp-agent-net`, `IOP_COMPOSE_SUBNET=10.89.2.0/24`. - port: web/dev preview `13002`, Control Plane HTTP `18002`, Client WS `19004`, CP-Edge wire `19005`, compose Edge-Node TCP `19006`, native Edge-Node TCP 후보 `18087`, Edge admin 후보 `19094`, Control Plane metrics `19104`, Prometheus `19112`, Grafana `19122`. - operator notice: 기존 dev-corp 사용자-facing 접속점(`13002`, `18002`, Edge OpenAI-compatible 후보 `18086`, native Edge 후보 `18087`)은 변경하지 않는다. 2026-07-13 기준 mac-mini runner에는 compose Control Plane/observability 그룹이 배포되어 있으나 public `13002`/`18002`/`19004`는 외부에서 닫힌 상태로 유지한다. -- optional field ports: artifact/bootstrap HTTP `18085`, Edge OpenAI-compatible HTTP `18086`, Edge metrics `19102`. +- optional field ports: artifact/bootstrap HTTP `18085`, Edge OpenAI-compatible HTTP `18086`, Edge metrics `19105`. - runtime: Go quick check는 local toolchain을 우선한다. Docker/Flutter/client/field/bootstrap/provider-pool evidence는 mac-mini와 내부 provider nodes를 사용한다. - package manager: Go modules / Makefile / Flutter pub - docker: 현재 작업 컨테이너에서는 Docker-in-Docker를 사용하지 않는다. Docker compose 검증은 mac-mini checkout에서 수행한다. @@ -55,7 +55,7 @@ last_rule_updated_at: 2026-07-13 | Edge-Node TCP | `19090` | `19003` | `19006` | | Edge artifact/bootstrap | `18080` | `18082` | `18085` | | Edge OpenAI-compatible | `18081` | `18083` | `18086` | -| Edge metrics | `19092` | `19101` | `19102` | +| Edge metrics | `19092` | `19101` | `19105` | | Control Plane metrics | `19100` | `19103` | `19104` | | Prometheus UI/API | `19110` | `19111` | `19112` | | Grafana UI | `19120` | `19121` | `19122` | @@ -71,7 +71,7 @@ dev-corp native/provider-pool profile은 compose Edge-Node TCP `19006`이 아니 - public Edge evidence: Edge runtime evidence와 OpenAI-compatible smoke는 public `iop.ai.kr` 경로로 판정한다. - compose dev-corp stack: `.env.dev-corp.example`, `COMPOSE_PROJECT_NAME=iop-dev-corp-agent`, `IOP_COMPOSE_NETWORK=iop-dev-corp-agent-net`, `IOP_COMPOSE_SUBNET=10.89.2.0/24`, Edge-Node TCP `19006`, Control Plane metrics `19104`, Prometheus `19112`, Grafana `19122`를 사용한다. - compose observability: dev-corp Control Plane 관측 그룹은 mac-mini runner의 compose project `iop-dev-corp-agent` 기준으로 검증한다. Prometheus는 dev-corp 전용 설정 `configs/prometheus/prometheus.dev-corp.yml`로 compose Control Plane metrics와 native Edge metrics를 함께 scrape한다. -- Edge direct dev-corp profile: artifact/bootstrap `18085`, OpenAI-compatible `18086`, metrics `19102`, admin `19094`를 사용한다. +- Edge direct dev-corp profile: artifact/bootstrap `18085`, OpenAI-compatible `18086`, metrics `19105`, admin `19094`를 사용한다. - dev-corp provider pool: 기본 public Edge-Node TCP `iop.ai.kr:18087`을 기준으로 한다. `/Users/fe/agent-work/iop-dev-corp/build/dev-corp-runtime/edge.yaml`은 mac-mini runner의 보조 산출물일 뿐 dev-corp Edge source of truth로 판정하지 않는다. retired reverse tunnel route는 Edge addr로 사용하지 않는다. 3-node/provider 세부는 `agent-test/dev-corp/edge-smoke.md`와 `agent-test/dev-corp/node-smoke.md`를 따른다. - current native Control Plane: 2026-07-02 기준 provider-pool runtime에서 `build/dev-corp-runtime/bin/control-plane`이 `18002/19004/19005`를 listen하고, Edge id `dev-corp-edge`가 `127.0.0.1:19005`로 연결된다. provider snapshot status 기준은 `http://127.0.0.1:18002/edges/dev-corp-edge/status`다. - external provider field: DGX Spark 01/02와 Mac Studio는 mac-mini에서 내부 SSH로 접속한다. 직접 접속 경로와 provider endpoint는 `agent-test/dev-corp/inventory.yaml`을 따른다. diff --git a/configs/prometheus/prometheus.dev-corp.yml b/configs/prometheus/prometheus.dev-corp.yml index 8925a54..36d012f 100644 --- a/configs/prometheus/prometheus.dev-corp.yml +++ b/configs/prometheus/prometheus.dev-corp.yml @@ -20,7 +20,7 @@ scrape_configs: metrics_path: /metrics static_configs: - targets: - - host.docker.internal:19102 + - iop.ai.kr:19105 labels: component: edge runtime: native-dev-corp diff --git a/docs/openai-usage-grafana.md b/docs/openai-usage-grafana.md index 47161ec..0362b6f 100644 --- a/docs/openai-usage-grafana.md +++ b/docs/openai-usage-grafana.md @@ -393,6 +393,6 @@ Host publish 기본 포트: - local/test: Prometheus `19110`, Grafana `19120`, Control Plane metrics `19100`, Edge metrics `19092` - dev: Prometheus `19111`, Grafana `19121`, Control Plane metrics `19103`, Edge metrics `19101` -- dev-corp compose: Prometheus `19112`, Grafana `19122`, Control Plane metrics `19104`, Edge metrics `19102` +- dev-corp compose: Prometheus `19112`, Grafana `19122`, Control Plane metrics `19104`, Edge metrics `19105` 이 관측 스택은 기존 테스트 사용자-facing endpoint를 변경하지 않는다. dev 기준 기존 UI/Control Plane/Edge OpenAI-compatible 접속점은 유지하며, Grafana/Prometheus/metrics 포트만 운영자용으로 추가한다. dev Grafana/Prometheus는 기본적으로 원격 runner 내부 또는 SSH 터널 접근 기준이다. dev-corp는 현재 접근 가능한 runner가 없을 때 문서 정의만 갱신하고 실제 compose 세팅은 별도 세션에서 수행한다.