fix(openai): usage 귀속 마일스톤을 종료한다

Stream Gate 준비 실패에서도 요청 terminal 관측 계약을 지키고, direct provider identity 마이그레이션 누락으로 기존 검증과 운영 설정이 깨지지 않게 한다.
This commit is contained in:
toki 2026-07-31 21:20:19 +09:00
parent 04879f2b43
commit f98d1b434b
20 changed files with 213 additions and 87 deletions

View file

@ -11,7 +11,7 @@ OpenAI-compatible hot path의 provider-reported token usage를 가상 요청 모
## 상태
[계획]
[완료]
## 승격 조건
@ -44,24 +44,26 @@ OpenAI-compatible hot path의 provider-reported token usage를 가상 요청 모
단일·하이브리드 실행에서 token 사용량을 실제 provider 호출에 정확히 귀속하는 hot-path capability를 묶는다.
- [ ] [group-policy] `models[].usage_attribution=model_group`으로 명시 승인된 동일 논리 모델 group만 가상 rollup query를 허용하고, 기본값 및 그 밖의 route에는 provider 기준 attribution을 강제한다. 검증: group·direct·hybrid config/route test에서 attribution basis가 기대값과 일치하고 별도 group counter가 중복 emit되지 않는다.
- [ ] [dispatch-binding] direct/legacy의 `openai.model_routes[].provider_id`와 top-level fallback `openai.provider_id`, provider-pool의 normalized/tunnel dispatch가 실제 `provider_id`, served model, node identity를 metric emitter까지 전달한다. 검증: adapter 이름만으로 provider를 대체하지 않고 각 dispatch binding을 검증한다.
- [ ] [attempt-usage] hybrid selection, retry, fallback에서 provider-reported token 및 reasoning observation usage를 실제 호출 시도별 provider binding으로 emit한다. 검증: provider가 바뀌는 deterministic test에서 token series가 각 provider에 분리되어 증가하고 request terminal counter는 한 번만 증가한다.
- [x] [group-policy] `models[].usage_attribution=model_group`으로 명시 승인된 동일 논리 모델 group만 가상 rollup query를 허용하고, 기본값 및 그 밖의 route에는 provider 기준 attribution을 강제한다. 검증: group·direct·hybrid config/route test에서 attribution basis가 기대값과 일치하고 별도 group counter가 중복 emit되지 않는다.
- [x] [dispatch-binding] direct/legacy의 `openai.model_routes[].provider_id`와 top-level fallback `openai.provider_id`, provider-pool의 normalized/tunnel dispatch가 실제 `provider_id`, served model, node identity를 metric emitter까지 전달한다. 검증: adapter 이름만으로 provider를 대체하지 않고 각 dispatch binding을 검증한다.
- [x] [attempt-usage] hybrid selection, retry, fallback에서 provider-reported token 및 reasoning observation usage를 실제 호출 시도별 provider binding으로 emit한다. 검증: provider가 바뀌는 deterministic test에서 token series가 각 provider에 분리되어 증가하고 request terminal counter는 한 번만 증가한다.
### Epic: [operations] Usage Metric Migration
provider 기준 운영 조회를 기존 OpenAI usage metric과 Grafana 가이드에 정착시킨다.
- [ ] [metric-contract] metric label allowlist와 OpenAI-compatible 관측 계약을 provider attribution 기준으로 갱신하고 기존 `model_group``route_model` trace와 승인된 rollup query로 migration한다. 검증: secret/high-cardinality label guard와 metric contract test가 통과한다.
- [ ] [grafana-migration] Grafana query와 usage 운영 가이드를 provider 기준 집계 및 승인된 model-group rollup 기준으로 갱신한다. 검증: 대표 provider·group query가 metric label schema와 일치한다.
- [x] [metric-contract] metric label allowlist와 OpenAI-compatible 관측 계약을 provider attribution 기준으로 갱신하고 기존 `model_group``route_model` trace와 승인된 rollup query로 migration한다. 검증: secret/high-cardinality label guard와 metric contract test가 통과한다.
- [x] [grafana-migration] Grafana query와 usage 운영 가이드를 provider 기준 집계 및 승인된 model-group rollup 기준으로 갱신한다. 검증: 대표 provider·group query가 metric label schema와 일치한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 계획 Milestone이며 기능 Task가 아직 충족되지 않았다.
- 상태: 통과
- 요청일: 2026-07-31
- 완료 근거: `group-policy`, `dispatch-binding`, `attempt-usage`, `metric-contract`의 현재 코드·계약·검증 evidence와 `grafana-migration`의 [Grafana query guide](../../../../docs/openai-usage-grafana.md) actual-provider/승인-group PromQL schema 검증이 충족되었다. 종료 감사에서 provider-pool Stream Gate 준비 실패의 request terminal 누락을 Chat/Responses 양쪽에서 보완하고, direct-route `provider_id` 필수 계약을 기존 성공 fixture·E2E 설정·운영 가이드에 동기화했다. `go test -count=1 ./packages/go/streamgate ./apps/edge/internal/openai`, `go test -count=1 ./packages/go/config ./apps/edge/internal/service`, `go test -count=1 ./apps/edge/cmd/edge ./apps/edge/internal/bootstrap ./apps/edge/internal/configrefresh`, `go test -race -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai`가 통과했다.
- Spec sync: [OpenAI-compatible 입력 표면](../../../../agent-spec/input/openai-compatible-surface.md)과 [Edge-Node 실행 경로](../../../../agent-spec/runtime/edge-node-execution.md)가 actual-provider dispatch binding, attempt usage, exactly-once terminal 및 Grafana migration 상태와 일치한다. 결과: Spec updated.
- 남은 차단 항목: 없음
- 검토 항목: 기능 Task의 deterministic attribution 검증, Grafana query migration, SDD Evidence Map 충족과 구현 잠금 해제를 함께 확인한다.
- 리뷰 코멘트: 없음
- 리뷰 코멘트: 전체 `go test ./...` 보조 실행의 잔여 실패는 다음 `IOP Agent CLI Runtime` 범위의 fake-owner Unix socket/권한 fixture와 taskloop 규칙 검사에 한정되며, 현재 마일스톤의 Edge·Node·config·OpenAI 범위에는 실패가 없다.
## 범위 제외

View file

@ -46,9 +46,9 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
- 경로: [provider-resource-admission-ownership-alignment](../../archive/phase/operational-observability-provider-management/milestones/provider-resource-admission-ownership-alignment.md)
- 요약: 공유 provider의 capacity·long-context lease, 공통 queue policy, Node reconnect/offline fencing과 Control Plane snapshot을 정렬하고 local two-alias capacity-1 smoke까지 검증했다.
- [계획] Provider 기준 Usage Attribution Hot Path
- 경로: [provider-usage-attribution-hot-path](milestones/provider-usage-attribution-hot-path.md)
- 요약: OpenAI-compatible token usage를 실제 호출 provider·served model·실행 시도에 귀속하고, 명시적으로 같은 논리 모델로 승인된 group에서만 가상 model group 집계를 허용한다.
- [완료] Provider 기준 Usage Attribution Hot Path
- 경로: [provider-usage-attribution-hot-path](../../archive/phase/operational-observability-provider-management/milestones/provider-usage-attribution-hot-path.md)
- 요약: OpenAI-compatible token usage를 actual provider·served model·실행 시도에 귀속하고, 승인된 model group의 query-time rollup과 Grafana 운영 조회를 정착시켰다.
- [계획] Provider 부하 메트릭과 Live Queue Dashboard
- 경로: [provider-load-metrics-queue-dashboard](milestones/provider-load-metrics-queue-dashboard.md)

View file

@ -4,89 +4,86 @@
## 실행 순서
1. [Provider 기준 Usage Attribution Hot Path](phase/operational-observability-provider-management/milestones/provider-usage-attribution-hot-path.md)
OpenAI-compatible token usage를 실제 provider·served model·실행 시도에 귀속하고, 명시적으로 같은 논리 모델로 승인된 group에서만 가상 model group 집계를 허용한다.
1. [IOP Agent CLI Runtime](phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
현재 Python 감시·dispatcher와 Node CLI runtime의 전체 동등성을 단일 Go CLI Provider·AgentTaskManager 및 독립 `iop-agent` binary로 이전한다.
2. [IOP Agent CLI Runtime](phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
현재 Python 감시·dispatcher와 Node CLI runtime의 전체 동등성을 단일 Go CLI Provider·AgentTaskManager 및 독립 `iop-agent` binary로 이전한다.
3. [OpenAI-compatible 출력 검증 필터](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
2. [OpenAI-compatible 출력 검증 필터](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
실제 의미 필터 전에 deterministic diagnostic mock으로 실제 Stream Evidence Gate의 pass·observe-only·blocking recovery를 관측하는 smoke를 통과시키고, OpenAI-compatible single-stream 반복과 incoming request history에 누적된 assistant 반복, JSON contract 검증/repair 경로를 안정화한다.
4. [OpenAI-compatible Incomplete Tool Call Syntax Gate](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-incomplete-tool-call-syntax-gate.md)
3. [OpenAI-compatible Incomplete Tool Call Syntax Gate](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-incomplete-tool-call-syntax-gate.md)
terminal provider 응답의 incomplete tool-call syntax를 deterministic하게 판정한다.
5. [OpenAI-compatible Runtime Output Integrity Filter](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-runtime-output-integrity-filter.md)
4. [OpenAI-compatible Runtime Output Integrity Filter](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-runtime-output-integrity-filter.md)
terminal output invariant와 공통 filter/retry pipeline을 정의한다.
6. [LLM 판별 기반 Missing Tool Call 재시도 Gate](phase/knowledge-tool-optimization-extension/milestones/llm-judged-missing-tool-call-retry-gate.md)
5. [LLM 판별 기반 Missing Tool Call 재시도 Gate](phase/knowledge-tool-optimization-extension/milestones/llm-judged-missing-tool-call-retry-gate.md)
tool 사용 의도 누락 케이스를 LLM judge와 buffered retry 후보로 검토한다.
7. [Tool Call 판정 모델 Gate 리뷰](phase/knowledge-tool-optimization-extension/milestones/tool-call-validator-model-gate-review.md)
6. [Tool Call 판정 모델 Gate 리뷰](phase/knowledge-tool-optimization-extension/milestones/tool-call-validator-model-gate-review.md)
schema만으로 어려운 tool-call 후보에 validator 모델을 쓸지 검토한다.
8. [Provider 부하 메트릭과 Live Queue Dashboard](phase/operational-observability-provider-management/milestones/provider-load-metrics-queue-dashboard.md)
7. [Provider 부하 메트릭과 Live Queue Dashboard](phase/operational-observability-provider-management/milestones/provider-load-metrics-queue-dashboard.md)
Edge provider-pool의 capacity, in-flight, queued와 queue wait를 Prometheus/Grafana로 관측해 provider별 live 부하와 적체·회복을 분석한다.
9. [Pi CLI Provider Integration](phase/automation-runtime-bridge/milestones/pi-cli-provider-integration.md)
8. [Pi CLI Provider Integration](phase/automation-runtime-bridge/milestones/pi-cli-provider-integration.md)
Pi를 Node CLI provider 실행 후보에 추가하고 OpenAI-compatible route smoke로 안정화한다.
10. [CLI Agent Group Grade Routing](phase/automation-runtime-bridge/milestones/cli-agent-group-grade-routing.md)
9. [CLI Agent Group Grade Routing](phase/automation-runtime-bridge/milestones/cli-agent-group-grade-routing.md)
lane/grade 파일명과 `metadata.agent_group.task_file` 기반 CLI agent group 라우팅 계약을 정리한다.
11. [에이전트 작업 루프 오케스트레이션 MVP](phase/automation-runtime-bridge/milestones/agent-workflow-loop-orchestration-mvp.md)
10. [에이전트 작업 루프 오케스트레이션 MVP](phase/automation-runtime-bridge/milestones/agent-workflow-loop-orchestration-mvp.md)
일반 사용자 요청을 direct/Plan/Milestone으로 분류하고, 사용자 agent의 tool call로 만든 작업 파일을 IOP가 읽어 다음 실행·리뷰·완료 단계까지 연결한다.
12. [Provider 사용량 알림과 운영 표면](phase/automation-runtime-bridge/milestones/provider-usage-notification-operations-surface.md)
11. [Provider 사용량 알림과 운영 표면](phase/automation-runtime-bridge/milestones/provider-usage-notification-operations-surface.md)
공통 runtime의 quota/status/failure event를 소비해 macOS·Desktop·후속 외부 채널에 전달하는 알림과 이력 표면을 스케치한다.
13. [단계 호출과 검증 최적화 MVP](phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
12. [단계 호출과 검증 최적화 MVP](phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
planner/generator/verifier 단계 호출과 runtime schema 검증 실행 모드를 스케치한다.
14. [원격 코딩/유지보수 작업 환경](phase/automation-runtime-bridge/milestones/remote-workspace-operations-environment.md)
13. [원격 코딩/유지보수 작업 환경](phase/automation-runtime-bridge/milestones/remote-workspace-operations-environment.md)
workspace-bound execution 기반 원격 코딩/유지보수 운영 경계를 스케치한다.
15. [Personal Local Edge 패키징과 배포 모드 프로파일](phase/personal-edge-packaging-deployment/milestones/personal-local-edge-deployment-profiles.md)
14. [Personal Local Edge 패키징과 배포 모드 프로파일](phase/personal-edge-packaging-deployment/milestones/personal-local-edge-deployment-profiles.md)
personal/server/fleet 배포 모드와 capability gate 경계를 스케치한다.
16. [요청 실행 로그와 Usage Ledger 기반](phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md)
15. [요청 실행 로그와 Usage Ledger 기반](phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md)
요청별 provider/model 선택, timing, token, status/error를 구조화된 ledger로 남기는 기반을 스케치한다.
17. [Update Plane 안정 프로토콜](phase/update-plane-self-update-foundation/milestones/update-plane-stable-protocol.md)
16. [Update Plane 안정 프로토콜](phase/update-plane-self-update-foundation/milestones/update-plane-stable-protocol.md)
hello/status, manifest, command, event, recovery 최소 계약을 스케치한다.
18. [Host-local Manager 기반 자체 업데이트](phase/update-plane-self-update-foundation/milestones/host-local-manager-self-update.md)
17. [Host-local Manager 기반 자체 업데이트](phase/update-plane-self-update-foundation/milestones/host-local-manager-self-update.md)
manager/updater의 release staging, 검증, restart, rollback 실행 모델을 정리한다.
19. [Edge/Node 롤아웃과 복구 정책](phase/update-plane-self-update-foundation/milestones/edge-node-rollout-recovery-policy.md)
18. [Edge/Node 롤아웃과 복구 정책](phase/update-plane-self-update-foundation/milestones/edge-node-rollout-recovery-policy.md)
Edge/Node rolling update, 실패/재연결/rollback 보고 정책을 스케치한다.
20. [Provider Runtime 설정과 모델 획득 오케스트레이션](phase/operational-observability-provider-management/milestones/provider-runtime-model-acquisition-orchestration.md)
19. [Provider Runtime 설정과 모델 획득 오케스트레이션](phase/operational-observability-provider-management/milestones/provider-runtime-model-acquisition-orchestration.md)
provider runtime launch/profile, model download/cache/verification 경계를 스케치한다.
21. [Provider-Device-Model Qualification 리포트와 Lifecycle 관리](phase/operational-observability-provider-management/milestones/provider-device-model-qualification-report.md)
20. [Provider-Device-Model Qualification 리포트와 Lifecycle 관리](phase/operational-observability-provider-management/milestones/provider-device-model-qualification-report.md)
provider/device/model별 compatibility, performance, quality, lifecycle 리포트 경계를 정리한다.
22. [Provider 입력 컨텍스트 선택과 축소](phase/knowledge-tool-optimization-extension/milestones/request-context-assembly-optimization.md)
21. [Provider 입력 컨텍스트 선택과 축소](phase/knowledge-tool-optimization-extension/milestones/request-context-assembly-optimization.md)
provider dispatch 전에 무관한 과거 요청-답변 단위를 제거하고, 유지한 답변·tool/search 결과 안에서도 필요한 문단·코드 블록·구간만 남기는 입력 context 최적화를 스케치한다.
23. [장기 기억과 RAG 업데이트 사이클 (2차)](phase/knowledge-tool-optimization-extension/milestones/long-term-memory-rag-second-wave.md)
22. [장기 기억과 RAG 업데이트 사이클 (2차)](phase/knowledge-tool-optimization-extension/milestones/long-term-memory-rag-second-wave.md)
repo 장기 기억, RAG 저장소, update cycle, MCP 기반 context 절약 후보를 스케치한다.
24. [Advisor와 Context Hook 확장 (2차)](phase/knowledge-tool-optimization-extension/milestones/advisor-context-hook-second-wave.md)
23. [Advisor와 Context Hook 확장 (2차)](phase/knowledge-tool-optimization-extension/milestones/advisor-context-hook-second-wave.md)
advisor 역할과 여러 기능을 실행 흐름에 연결하는 Context Hook 경계를 스케치한다.
25. [oto 자동화 스케줄러와 CI-CD 연동 (2차)](phase/automation-runtime-bridge/milestones/oto-automation-scheduler-second-wave.md)
24. [oto 자동화 스케줄러와 CI-CD 연동 (2차)](phase/automation-runtime-bridge/milestones/oto-automation-scheduler-second-wave.md)
oto 기반 자동화, scheduler, CI-CD 연동 후보를 스케치한다.
26. [Flutter Desktop Control UI](phase/automation-runtime-bridge/milestones/flutter-desktop-control-ui.md)
25. [Flutter Desktop Control UI](phase/automation-runtime-bridge/milestones/flutter-desktop-control-ui.md)
`iop-agent` local proto-socket을 소비해 YAML 전체 설정과 project·실행·오류·로그를 관리하는 macOS Flutter 설정·운영 UI를 제공한다.
27. [Unity 3D Desktop Character](phase/automation-runtime-bridge/milestones/unity-3d-desktop-character.md)
26. [Unity 3D Desktop Character](phase/automation-runtime-bridge/milestones/unity-3d-desktop-character.md)
같은 local proto-socket을 독립적으로 소비하고 작업 상태를 투명 배경 3D 캐릭터와 animation으로 표현하는 macOS Unity client를 제공한다.
28. [IOP Hot Path One-shot 실행 경로](phase/knowledge-tool-optimization-extension/milestones/iop-hot-path-one-shot-execution.md)
27. [IOP Hot Path One-shot 실행 경로](phase/knowledge-tool-optimization-extension/milestones/iop-hot-path-one-shot-execution.md)
외부 `model=iop` 요청을 Gemini 3.6 Flash와 RTX 5090 `ornith-fast`의 bounded one-shot 경로로 처리해 최대 속도와 실사용 품질의 균형을 맞춘다.
29. [Node Provider 실행 Liveness 관측과 안전 복구](phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md)
28. [Node Provider 실행 Liveness 관측과 안전 복구](phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md)
Node가 5분간 provider 진행이 없는 request를 health와 분리 판정하고 local attempt를 fence한 뒤 기존 recovery owner가 안전한 요청만 공통 budget 안에서 재실행한다.

View file

@ -104,7 +104,7 @@ Edge가 OpenAI-compatible HTTP 요청을 받아 내부 `adapter + target` 실행
| provider-native field 보존 | provider raw tunnel route는 `model` served target rewrite와 auth/header 처리 외에 selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field를 보존한다. |
| OpenAI usage metering | Edge emits one request terminal and one canonical token/reasoning series for each actual provider attempt that reports usage. Rejected, aborted, and replacement attempts remain attributable to their own actual provider and served model. |
| reasoning observation metric | provider가 reasoning token을 보고하지 않고 reasoning text만 관측되면 관측 횟수와 character count 보조 metric을 emit하고, 별도 estimated-token counter(`iop_openai_reasoning_estimated_tokens_total`)로 `estimation_method="chars_div_4"` 추정을 제공한다. |
| Grafana usage surface | 1차 조회 표면은 Prometheus/Grafana query guide이며 daily/monthly rollup, usage origin breakdown, operator-managed cloud price baseline, cloud-equivalent cost, avoided-cost ROI 기준을 문서로 제공한다. Control Plane/Client dashboard와 request-level ledger는 후속 범위다. |
| Grafana usage surface | 1차 조회 표면은 Prometheus/Grafana query guide이며 actual `provider_id`·`served_model` 기준 daily/monthly rollup과 `usage_attribution=model_group`으로 승인된 `route_model` query-time rollup, usage origin breakdown, operator-managed cloud price baseline, cloud-equivalent cost, avoided-cost ROI 기준을 문서로 제공한다. Control Plane/Client dashboard와 request-level ledger는 후속 범위다. |
| Responses API | normalized(non-provider) `/v1/responses`는 string input의 non-streaming 요청만 지원한다. provider model group route는 `/v1/responses`를 raw passthrough로 provider `POST /v1/responses`에 전달한다. |
| Responses provider passthrough | provider-pool model group route와 direct OpenAI-compatible provider route의 `/v1/responses`는 provider raw tunnel을 사용한다. Edge는 `model`만 served target으로 rewrite하고 unknown/Codex field와 `stream:true` raw SSE를 provider로 relay한다. Usage is recorded with endpoint=`responses`, response_mode=`passthrough`, route_model=request alias, and the selected actual provider/served model. |
| strict output | strict output이 켜져 있으면 XML completion contract 기반 instruction 또는 prompt prefix를 추가할 수 있다. |
@ -195,7 +195,7 @@ sequenceDiagram
- workspace는 prompt 본문에 섞지 않고 metadata에서 분리한다.
- pure `passthrough` body는 provider-original byte stream이며 IOP 확장 envelope나 normalized label을 포함하지 않는다.
- provider route와 non-provider normalized route의 차이는 selected provider capability에서 파생되며 caller metadata selector로 고르지 않는다.
- The implemented attribution binding changes the Prometheus label vector and terminal/attempt emission ownership described above. Grafana query migration, request ledgers, billing, and chargeback remain outside this implementation slice.
- Grafana guide는 actual provider 기준 canonical query와 승인된 model-group rollup을 분리한다. request ledger, billing, chargeback은 이 구현 범위 밖이다.
- text tool-call synthesis는 요청 `tools[]` schema를 기준으로만 수행한다. 자연어 추론으로 tool call을 만들지 않는다.
- private token이나 endpoint 원문은 tracked spec/docs에 남기지 않는다.
- `metadata.user`는 identity source가 아니며 사용되지 않는다.
@ -224,3 +224,4 @@ sequenceDiagram
- 2026-07-28: bounded ingress와 Stream Evidence Gate 활성 경로·한계·검증 포인터를 현재 구현 기준으로 반영.
- 2026-07-31: provider-default/model-group opt-in attribution policy, direct provider id precedence, actual Edge-local dispatch binding을 반영했다.
- 2026-07-31: Added request-local exactly-once terminal emission and actual-provider usage emission for every observed attempt, including recovery replacement and legacy tool-validation retry paths.
- 2026-07-31: Grafana query guide의 actual provider 집계와 승인된 model-group query-time rollup migration 완료 상태를 반영했다.

View file

@ -201,6 +201,7 @@ Edge 외부 입력은 OpenAI-compatible HTTP API와 A2A JSON-RPC HTTP API 두
openai:
enabled: true
listen: "0.0.0.0:18081"
provider_id: "ollama-dgx-provider"
adapter: "ollama"
target: "qwen3.6:35b-a3b-bf16"
models:
@ -225,6 +226,7 @@ nodes:
```yaml
openai:
provider_id: "legacy-direct-provider"
model_routes:
- model: "codex"
adapter: "cli"

View file

@ -449,6 +449,7 @@ func TestNodeRegisterPreservesDefaultedNonNodeConfig(t *testing.T) {
listen: "0.0.0.0:9090"
openai:
enabled: true
provider_id: "test-provider"
`
if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)

View file

@ -289,6 +289,7 @@ bootstrap:
openai:
enabled: true
listen: "0.0.0.0:8080"
provider_id: "test-provider"
nodes:
- id: "node-a"
alias: "a"

View file

@ -216,6 +216,7 @@ refresh:
openai:
enabled: true
listen: %q
provider_id: "test-provider"
adapter: "openai_compat"
target: ""
a2a:

View file

@ -87,6 +87,7 @@ refresh:
openai:
enabled: true
listen: %q
provider_id: "test-provider"
adapter: "openai_compat"
target: ""
a2a:

View file

@ -70,6 +70,7 @@ func TestClassifyStreamEvidenceGateEnabledRestartRequired(t *testing.T) {
openai:
enabled: true
listen: "127.0.0.1:8081"
provider_id: "test-provider"
stream_evidence_gate:
enabled: false
`
@ -77,6 +78,7 @@ openai:
openai:
enabled: true
listen: "127.0.0.1:8081"
provider_id: "test-provider"
stream_evidence_gate:
enabled: true
`

View file

@ -256,11 +256,13 @@ func (s *Server) handleChatCompletionsProviderPool(w http.ResponseWriter, dc *ch
if s.streamGateEnabled() {
fctx, err := s.openAIChatOutputFilterContext(dc)
if err != nil {
dc.finishUsageRequest(usageStatusError, responseModePassthrough)
writeError(w, http.StatusInternalServerError, "run_error", "stream gate runtime unavailable")
return
}
predicate, err := openAIStreamGateCandidatePredicate(s.streamGateConfig(), fctx)
if err != nil {
dc.finishUsageRequest(usageStatusError, responseModePassthrough)
writeError(w, http.StatusInternalServerError, "run_error", "stream gate runtime unavailable")
return
}

View file

@ -379,11 +379,13 @@ func (s *Server) handleResponsesProviderPool(w http.ResponseWriter, requestCtx *
if s.streamGateEnabled() {
fctx, err := s.openAIResponsesOutputFilterContext(requestCtx)
if err != nil {
requestCtx.finishUsageRequest(usageStatusError, responseModePassthrough)
writeError(w, http.StatusInternalServerError, "run_error", "stream gate runtime unavailable")
return
}
predicate, err := openAIStreamGateCandidatePredicate(s.streamGateConfig(), fctx)
if err != nil {
requestCtx.finishUsageRequest(usageStatusError, responseModePassthrough)
writeError(w, http.StatusInternalServerError, "run_error", "stream gate runtime unavailable")
return
}

View file

@ -666,6 +666,70 @@ func TestChatCompletionsDispatchFailureEmitsErrorMetric(t *testing.T) {
}
}
func TestProviderPoolStreamGateSetupFailureEmitsOneErrorMetric(t *testing.T) {
const (
rawToken = "sk-gate-setup-fail-token"
edgeID = "edge-gate-setup-fail"
model = "gate-pool-model"
)
for _, tc := range []struct {
name string
endpoint string
path string
body string
}{
{
name: "chat completions",
endpoint: usageEndpointChatCompletions,
path: "/v1/chat/completions",
body: `{"model":"gate-pool-model","messages":[{"role":"user","content":"hello"}]}`,
},
{
name: "responses",
endpoint: usageEndpointResponses,
path: "/v1/responses",
body: `{"model":"gate-pool-model","input":"hello"}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
fake := &providerFakeRunService{}
cfg := principalTokenCfg(rawToken, "ollama")
cfg.StreamEvidenceGate = config.StreamEvidenceGateConf{
Enabled: true,
Filters: []config.StreamGateFilterPolicyConf{{Filter: "unsupported_test_filter"}},
}
srv := NewServer(cfg, fake, nil)
srv.SetEdgeID(edgeID)
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: model, Providers: map[string]string{"prov-1": "served-model"}}})
before := testutil.ToFloat64(openAIRequestsTotal.WithLabelValues(
edgeID, "user:alice", "alice", "iop-tok-alice", model,
tc.endpoint, responseModePassthrough, usageStatusError, usageSourceUnavailable,
))
req := httptest.NewRequest(http.MethodPost, tc.path, strings.NewReader(tc.body))
req.Header.Set("Authorization", "Bearer "+rawToken)
w := httptest.NewRecorder()
srv.routes().ServeHTTP(w, req)
if w.Code != http.StatusInternalServerError {
t.Fatalf("expected 500, got %d body=%s", w.Code, w.Body.String())
}
if got := fake.poolSubmitCountSnapshot(); got != 0 {
t.Fatalf("provider-pool dispatch count = %d, want 0", got)
}
after := testutil.ToFloat64(openAIRequestsTotal.WithLabelValues(
edgeID, "user:alice", "alice", "iop-tok-alice", model,
tc.endpoint, responseModePassthrough, usageStatusError, usageSourceUnavailable,
))
if got := after - before; got != 1 {
t.Fatalf("requests_total error delta = %v, want 1", got)
}
})
}
}
// TestToolValidationRetryDispatchFailureEmitsErrorMetric verifies that a
// non-stream Chat tool-validation retry SubmitRun failure emits the error
// request metric exactly once and returns tool_validation_retry_error (REVIEW_USAGE_METRIC_RETRY-1).

View file

@ -54,6 +54,7 @@ refresh:
openai:
enabled: true
listen: "0.0.0.0:18081"
provider_id: "ollama-gemma"
adapter: "ollama"
target: "gemma4:26b"

View file

@ -16,7 +16,10 @@ Provider-reported OpenAI-compatible token usage by token type.
| `principal_ref` | `usr-abc123` | Principal foreign-key-like reference |
| `principal_alias` | `john@acme` | Human-readable alias |
| `token_ref` | `tok-xyz789` | Token identity (not a raw secret) |
| `model_group` | `gpt-4o` | Model group identifier |
| `route_model` | `gpt-4o` | Caller가 요청한 route alias; trace 및 승인된 model-group rollup key |
| `usage_attribution` | `provider`, `model_group` | 기본 provider 집계 또는 명시 승인된 model-group query-time rollup 정책 |
| `provider_id` | `provider-a` | 실제 호출된 provider resource identity |
| `served_model` | `gpt-4o-2024-08-06` | 해당 provider가 실제 호출한 model target |
| `endpoint` | `chat.completions`, `responses` | OpenAI-compatible route |
| `response_mode` | `passthrough`, `normalized` | Internal execution label; callers cannot set it via OpenAI metadata |
| `token_type` | `input`, `output`, `reasoning`, `cached_input` | Token type |
@ -31,7 +34,7 @@ OpenAI-compatible requests processed by terminal status and usage source.
| `principal_ref` | `usr-abc123` | Principal reference |
| `principal_alias` | `john@acme` | Human-readable alias |
| `token_ref` | `tok-xyz789` | Token identity |
| `model_group` | `gpt-4o` | Model group identifier |
| `route_model` | `gpt-4o` | Caller가 요청한 route alias; request terminal의 route dimension |
| `endpoint` | `chat.completions`, `responses` | OpenAI-compatible route |
| `response_mode` | `passthrough`, `normalized` | Internal execution label; callers cannot set it via OpenAI metadata |
| `status` | `success`, `error`, `cancel` | Terminal request status |
@ -40,7 +43,7 @@ OpenAI-compatible requests processed by terminal status and usage source.
### 3. `iop_openai_reasoning_observed_total` (Counter)
Requests where reasoning text was observed but the provider did not report reasoning token usage.
Same labels as `iop_openai_requests_total` excluding `status` and `usage_source`: `edge_id`, `principal_ref`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `response_mode`.
Token metric과 같은 실제 provider-attempt labels를 사용하되 `token_type`은 없다: `edge_id`, `principal_ref`, `principal_alias`, `token_ref`, `route_model`, `usage_attribution`, `provider_id`, `served_model`, `endpoint`, `response_mode`.
### 4. `iop_openai_reasoning_chars_total` (Counter)
@ -58,8 +61,9 @@ Estimated reasoning tokens for requests with observed reasoning text but without
```
edge_id, principal_ref, principal_alias, token_ref,
model_group, endpoint, response_mode, status,
token_type, usage_source, estimation_method
route_model, usage_attribution, provider_id, served_model,
endpoint, response_mode, status, token_type,
usage_source, estimation_method
```
`estimation_method``iop_openai_reasoning_estimated_tokens_total` 전용 보조 라벨이며, 그 외 counter에는 사용되지 않습니다.
@ -67,18 +71,22 @@ token_type, usage_source, estimation_method
### 금지 label
- `request_id`, `session_id` — request-level 상세는 후속 ledger/Loki 축에서 다룬다.
- `run_id`, `attempt_id`, `node_id` — 실행 시도 evidence는 request-local로 유지하고 public metric label로 노출하지 않는다.
- Bearer token, provider API key, raw payload, raw prompt/response text — secret 누출 방지 (SDD S08).
- 그 외 임의 label — cardinality 폭발을 막는다.
---
## Principal / Token Attribution 전제
## Principal / Token / Provider Attribution 전제
- `principal_ref`는 사용자/테넌트/외부 운영 시스템의 안정 참조값이다.
- `principal_alias`는 Grafana legend와 table에 보여줄 낮은 cardinality 별칭이다.
- `token_ref`는 raw bearer token이 아니라 앱/통합/용도별 token 참조값이다.
- 같은 `principal_ref` 아래 여러 `token_ref`를 둘 수 있다. 이 경우 `principal_ref` 기준 query는 사용자 합산, `token_ref` 기준 query는 앱/통합별 breakdown으로 본다.
- 운영 token 발급은 raw token을 tracked 파일에 남기지 않고 hash/reference만 기록하는 절차를 따른다.
- canonical token/reasoning series는 실제 호출 시도의 `provider_id``served_model`에 귀속한다. `route_model`은 요청 alias이며 기본 provider 집계 key가 아니다.
- `usage_attribution="model_group"`은 운영자가 동일 논리 모델임을 명시 승인한 route에서만 사용한다. 이 series만 `route_model`로 query-time rollup하며 별도 model-group counter는 없다.
- retry/fallback으로 provider가 바뀌면 각 실제 호출 시도가 별도 provider series로 남는다. `iop_openai_requests_total`은 최종 request terminal을 한 번만 기록하므로 provider 시도 횟수로 사용하지 않는다.
관련 기준:
@ -130,11 +138,24 @@ sum by (principal_ref, principal_alias, token_ref, token_type) (
)
```
### model_group별 usage
### 실제 provider별 usage
```promql
# model_group, token_type별 aggregation
sum by (model_group, token_type) (iop_openai_usage_tokens_total)
# 실제 provider와 served model, token_type별 aggregation
sum by (provider_id, served_model, token_type) (
iop_openai_usage_tokens_total
)
```
### 승인된 model-group rollup
`route_model` 집계는 `usage_attribution="model_group"`으로 명시 승인된 series에만 적용한다. 기본값인 `provider` series를 route alias로 합치지 않는다.
```promql
# 동일 논리 모델로 승인된 route만 query-time rollup
sum by (route_model, token_type) (
iop_openai_usage_tokens_total{usage_attribution="model_group"}
)
```
### endpoint별 요청 수
@ -157,7 +178,10 @@ sum by (response_mode, status) (iop_openai_requests_total{status="success"})
```promql
# "어디서 얼만큼 사용했는지"를 보는 table용 breakdown
sum by (principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (
sum by (
principal_alias, token_ref, provider_id, served_model,
route_model, usage_attribution, endpoint, response_mode, token_type
) (
iop_openai_usage_tokens_total
)
```
@ -182,13 +206,19 @@ provider가 reasoning token을 보고하지 않는 경우:
```promql
# reasoning text가 관찰된 요청 수 (provider token 미보고)
sum by (principal_alias) (iop_openai_reasoning_observed_total)
sum by (principal_alias, provider_id, served_model) (
iop_openai_reasoning_observed_total
)
# reasoning text character 합계
sum by (principal_alias) (iop_openai_reasoning_chars_total)
sum by (principal_alias, provider_id, served_model) (
iop_openai_reasoning_chars_total
)
# estimated reasoning tokens (provider가 token 수를 보고하지 않는 경우)
sum by (principal_alias, estimation_method) (iop_openai_reasoning_estimated_tokens_total)
sum by (principal_alias, provider_id, served_model, estimation_method) (
iop_openai_reasoning_estimated_tokens_total
)
```
> **주의**: `iop_openai_reasoning_observed_total` / `iop_openai_reasoning_chars_total` / `iop_openai_reasoning_estimated_tokens_total`는 provider-reported reasoning token이 있을 경우 emit되지 않는다. 즉, 이 metric들은 "reasoning text는 있으나 token count 보고를 안 하는 provider"만 커버한다. `iop_openai_reasoning_estimated_tokens_total``estimation_method="chars_div_4"`로 ceil(chars/4) 추정을 제공하지만 billing-grade 정산값이 아니다.
@ -200,14 +230,15 @@ sum by (principal_alias, estimation_method) (iop_openai_reasoning_estimated_toke
| 패널 | query | visualization |
|------|-------|---------------|
| 사용자별 token 사용량 (line) | `sum by (principal_alias, token_type) (irate(iop_openai_usage_tokens_total[5m]))` | Time series, legend=`{{principal_alias}} {{token_type}}` |
| 일별 usage origin (table) | `sum by (principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (increase(iop_openai_usage_tokens_total[1d]))` | Table |
| model_group별 token 비율 (pie) | `sum by (model_group, token_type) (iop_openai_usage_tokens_total)` | Stat or bar chart |
| 일별 usage origin (table) | `sum by (principal_alias, token_ref, provider_id, served_model, route_model, usage_attribution, endpoint, response_mode, token_type) (increase(iop_openai_usage_tokens_total[1d]))` | Table |
| 실제 provider별 token 비율 (pie) | `sum by (provider_id, served_model, token_type) (iop_openai_usage_tokens_total)` | Stat or bar chart |
| 승인된 model-group token 비율 (pie) | `sum by (route_model, token_type) (iop_openai_usage_tokens_total{usage_attribution="model_group"})` | Stat or bar chart |
| 요청 상태 분포 (table) | `sum by (endpoint, status, usage_source) (iop_openai_requests_total)` | Table |
| cloud-equivalent cost (stat/table) | token rollup query에 price scalar 또는 Grafana calculated field 적용 | Stat or Table |
| avoided-cost ROI summary (stat) | cloud-equivalent cost 합계, 필요 시 별도 infra cost field 차감 | Stat |
| reasoning 보조 지표 (singlestat) | `sum(iop_openai_reasoning_observed_total)` | Singlestat |
| estimated reasoning tokens (bar) | `sum by (estimation_method) (iop_openai_reasoning_estimated_tokens_total)` | Bar chart |
| estimated reasoning vs provider-reported (line) | `sum by (estimation_method) (iop_openai_reasoning_estimated_tokens_total)` vs `sum by (principal_alias) (iop_openai_usage_tokens_total{token_type="reasoning"})` | Time series |
| estimated reasoning vs provider-reported (line) | `sum by (provider_id, served_model, estimation_method) (iop_openai_reasoning_estimated_tokens_total)` vs `sum by (provider_id, served_model) (iop_openai_usage_tokens_total{token_type="reasoning"})` | Time series |
> Grafana JSON dashboard import file은 본 scope에 포함되지 않는다. 위 PromQL을 바탕으로 패널을 수동 구성한다.
@ -218,8 +249,11 @@ sum by (principal_alias, estimation_method) (iop_openai_reasoning_estimated_toke
### 일일 토큰 합계
```promql
# 일일 token rollup (principal/token/model/endpoint/response_mode/token_type별)
sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (
# 일일 canonical token rollup (principal/token/provider/served-model/route/endpoint/response-mode/token-type별)
sum by (
principal_ref, principal_alias, token_ref, provider_id, served_model,
route_model, usage_attribution, endpoint, response_mode, token_type
) (
increase(iop_openai_usage_tokens_total[1d])
)
```
@ -235,7 +269,10 @@ sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, respon
```promql
# 최근 30일 token rollup. 달력 월은 Grafana time range를 월 단위로 잡고 $__range를 사용한다.
sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (
sum by (
principal_ref, principal_alias, token_ref, provider_id, served_model,
route_model, usage_attribution, endpoint, response_mode, token_type
) (
increase(iop_openai_usage_tokens_total[30d])
)
```
@ -252,7 +289,8 @@ sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, respon
- `principal_alias` 또는 `principal_ref``by` clause에 추가해 사용자별 일일/월간 토큰 사용량을 산출한다.
- `token_ref`를 추가하면 같은 사용자 아래 앱/통합/용도별 사용량을 분해한다.
- `token_type`을 함께 group by하면 input/output/reasoning/cached_input을 구분한다.
- `model_group`, `endpoint`, `response_mode`를 추가하면 사용 위치와 응답 경로별 origin breakdown을 만든다.
- `provider_id`, `served_model`, `endpoint`, `response_mode`를 추가하면 실제 사용 위치와 응답 경로별 origin breakdown을 만든다.
- 동일 논리 모델로 승인된 합계가 필요할 때만 `usage_attribution="model_group"`을 선택하고 `route_model`로 group by한다.
---
@ -266,7 +304,9 @@ sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, respon
|-------|------|------|
| `cloud_provider` | `example-cloud` | 가격 기준 provider |
| `baseline_model` | `example-model` | 비교 기준 cloud model |
| `model_group` | `example-model` | IOP metric의 `model_group`과 매핑할 값 |
| `provider_id` | `example-provider` | IOP metric의 실제 provider와 매핑할 값 |
| `served_model` | `example-model` | IOP metric의 실제 served model과 매핑할 값 |
| `route_model` | `example-route` | `usage_attribution=model_group` 승인 시에만 사용하는 논리 model rollup key |
| `token_type` | `input`, `output`, `cached_input`, `reasoning` | 가격을 적용할 token type |
| `price_per_1m_tokens` | `1.00` | 1M tokens당 단가 |
| `currency` | `USD` | 통화 |
@ -275,11 +315,11 @@ sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, respon
baseline 예시. 아래 숫자는 문서용 예시이며 최신 cloud 가격이 아니다.
| cloud_provider | baseline_model | model_group | token_type | price_per_1m_tokens | currency | effective_date |
|----------------|----------------|-------------|------------|----------------------|----------|----------------|
| example-cloud | example-model | example-model | input | 1.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-model | output | 3.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-model | cached_input | 0.50 | USD | 2026-07-10 |
| cloud_provider | baseline_model | provider_id | served_model | route_model | token_type | price_per_1m_tokens | currency | effective_date |
|----------------|----------------|-------------|--------------|-------------|------------|----------------------|----------|----------------|
| example-cloud | example-model | example-provider | example-model | example-route | input | 1.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-provider | example-model | example-route | output | 3.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-provider | example-model | example-route | cached_input | 0.50 | USD | 2026-07-10 |
`reasoning` token은 provider가 별도 단가를 제공하면 별도 baseline을 둔다. 별도 단가가 없으면 운영자가 `output`과 같은 단가로 볼지, report에서 `reasoning`을 제외할지 baseline에 명시한다.
@ -306,22 +346,24 @@ Prometheus metric 자체에는 price table이 없으므로 1차 표면에서는
PromQL scalar 예시:
```promql
# example-model input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, model_group, endpoint) (
increase(iop_openai_usage_tokens_total{model_group="example-model", token_type="input"}[1d])
# example-provider / example-model input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, provider_id, served_model, endpoint) (
increase(iop_openai_usage_tokens_total{provider_id="example-provider", served_model="example-model", token_type="input"}[1d])
) / 1000000 * 1.00
# example-model output daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, model_group, endpoint) (
increase(iop_openai_usage_tokens_total{model_group="example-model", token_type="output"}[1d])
# example-provider / example-model output daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, provider_id, served_model, endpoint) (
increase(iop_openai_usage_tokens_total{provider_id="example-provider", served_model="example-model", token_type="output"}[1d])
) / 1000000 * 3.00
# example-model cached input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, model_group, endpoint) (
increase(iop_openai_usage_tokens_total{model_group="example-model", token_type="cached_input"}[1d])
# example-provider / example-model cached input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, provider_id, served_model, endpoint) (
increase(iop_openai_usage_tokens_total{provider_id="example-provider", served_model="example-model", token_type="cached_input"}[1d])
) / 1000000 * 0.50
```
동일 논리 모델로 승인된 route에 같은 baseline을 적용할 때만 `usage_attribution="model_group", route_model="example-route"` selector와 `route_model` group by를 사용할 수 있다. 승인되지 않은 provider series를 route alias 기준으로 비용 합산하지 않는다.
여러 token type을 합산할 때는 token type별 cost query를 Grafana transformation으로 더한다. 단일 query에서 서로 다른 가격을 자동 join하려면 별도 recording rule 또는 external price table이 필요하다.
---
@ -339,14 +381,14 @@ net_avoided_cost = cloud_equivalent_cost - separately_known_local_infra_cost
- local infra cost가 별도로 계상되지 않으면 `avoided_cost`만 표시한다.
- local infra cost를 운영자가 별도 산정해 Grafana에 넣을 수 있으면 `net_avoided_cost`를 보조 field로 둔다.
- 이 값은 실제 결제, chargeback, 조직별 비용 배부, full infra cost accounting 근거가 아니다.
- 최소 summary table column은 `date`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `tokens`, `currency`, `cloud_equivalent_cost`, `avoided_cost`다.
- 최소 summary table column은 `date`, `principal_alias`, `token_ref`, `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `tokens`, `currency`, `cloud_equivalent_cost`, `avoided_cost`다.
Grafana table 구성 예:
1. Query A: daily token rollup by `principal_alias`, `token_ref`, `model_group`, `endpoint`, `token_type`.
1. Query A: daily token rollup by `principal_alias`, `token_ref`, `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `token_type`.
2. Transformation: baseline price table 또는 panel-level scalar를 token type별로 적용한다.
3. Calculated field: `tokens / 1000000 * price_per_1m_tokens`.
4. Group by: `date`, `principal_alias`, `token_ref`, `model_group`, `endpoint`.
4. Group by: `date`, `principal_alias`, `token_ref`, `provider_id`, `served_model`, `endpoint`. 승인된 model-group view만 `route_model`을 대신 사용한다.
5. Reduce: `cloud_equivalent_cost` 합계와 token 합계를 보여준다.
---
@ -356,11 +398,12 @@ Grafana table 구성 예:
1. Grafana time range를 `Today`, `Yesterday`, 최근 7일, 또는 이번 달로 설정한다.
2. `daily / monthly Rollup` query로 token usage table을 만든다.
3. `principal_ref`/`principal_alias` 기준 panel과 `token_ref` 기준 panel을 나란히 둔다.
4. `model_group`, `endpoint`, `response_mode`, `token_type` column을 유지해 usage origin을 확인한다.
4. `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `response_mode`, `token_type` column을 유지해 실제 usage origin과 rollup 정책을 확인한다.
5. 운영 price baseline의 `effective_date`, `baseline_model`, `currency`를 dashboard text 또는 table에 함께 표시한다.
6. token type별 cost formula를 적용해 cloud-equivalent cost를 계산한다.
7. daily/monthly `avoided_cost` summary stat을 추가한다.
8. `usage_source="unavailable"` 요청 비율과 reasoning 보조 metric을 함께 확인해 cost report coverage 위험을 판단한다.
9. model-group view는 `usage_attribution="model_group"` selector가 있는 query만 사용하고, provider-default series가 섞이지 않았는지 확인한다.
---
@ -375,7 +418,8 @@ Grafana table 구성 예:
### 현재 문서가 제공하는 것
- `principal_ref`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `response_mode`, `token_type` 라벨 기반 daily/monthly rollup PromQL
- `principal_ref`, `principal_alias`, `token_ref`, `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `response_mode`, `token_type` 라벨 기반 daily/monthly rollup PromQL
- 실제 provider 기준 집계와 `usage_attribution="model_group"`으로 승인된 route만의 model-group rollup 분리 기준
- 같은 principal의 여러 token/app usage를 합산과 breakdown으로 함께 보는 기준
- 운영자가 관리하는 cloud price baseline 필드
- token type별 cloud-equivalent cost 산식과 query/report 예시
@ -395,6 +439,7 @@ Grafana table 구성 예:
| `usage_source` | `provider_reported`, `unavailable` |
| `token_type` | `input`, `output`, `reasoning`, `cached_input` |
| `response_mode` | `passthrough`, `normalized` |
| `usage_attribution` | `provider`, `model_group` |
| `estimation_method` | `chars_div_4` (특히 `iop_openai_reasoning_estimated_tokens_total` 전용) |
---

View file

@ -61,6 +61,7 @@ bootstrap:
openai:
enabled: true
listen: "127.0.0.1:$OPENAI_PORT"
provider_id: "cli-smoke-provider"
adapter: "cli"
session_id: "openai"
timeout_sec: 30

View file

@ -177,6 +177,7 @@ bootstrap:
openai:
enabled: true
listen: "127.0.0.1:$OPENAI_PORT"
provider_id: "lemonade-smoke-provider"
adapter: "openai_compat"
session_id: "cline"
timeout_sec: 30

View file

@ -123,6 +123,7 @@ bootstrap:
openai:
enabled: true
listen: "127.0.0.1:$OPENAI_PORT"
provider_id: "ollama-smoke-provider"
adapter: "ollama"
session_id: "cline"
timeout_sec: 30

View file

@ -190,6 +190,7 @@ bootstrap:
openai:
enabled: true
listen: "127.0.0.1:$OPENAI_PORT"
provider_id: "vllm-smoke-provider"
adapter: "openai_compat"
session_id: "smoke"
timeout_sec: 30