Skip to content

[Spring AI] Request/Usage Adapter와 Advisor E2E 연결 - #69

Closed
Rigu1 wants to merge 14 commits into
tokenpliot:mainfrom
Rigu1:feat/spring-ai-advisor-e2e
Closed

[Spring AI] Request/Usage Adapter와 Advisor E2E 연결#69
Rigu1 wants to merge 14 commits into
tokenpliot:mainfrom
Rigu1:feat/spring-ai-advisor-e2e

Conversation

@Rigu1

@Rigu1 Rigu1 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

요약

Spring AI 요청과 provider usage를 Token Pilot의 preflight, reservation, accounting lifecycle에 연결했습니다.

Spring AI 타입은 adapter 모듈 안에 유지하고, 정책·가격·정산은 기존 Core와 ReservationAccounting 계약을 사용했습니다. 실제 과금 여부를 확인할 수 없는 호출은 비용이 사라지지 않도록 pending liability로 보존합니다.

구현 내용

  • Spring AI request 변환과 model/output resolution을 작은 컴포넌트로 분리해 Spring AI 타입이 Core 계약으로 유입되지 않도록 했습니다.
  • 지원 범위는 non-streaming TEXT_ONLY이며 media, tool과 structured output 요청은 provider 호출 전에 차단합니다.
  • adviseCall()이 preflight부터 reservation, provider 호출과 actual reconciliation까지 하나의 흐름으로 조율합니다.
  • 예약 후 dispatch 전 내부 오류는 reservation을 해제하고, markInFlight() 이후 오류는 RECONCILIATION_REQUIRED로 남깁니다.
  • 유효한 provider usage만 actual로 commit하며 usage 미확정과 정산 과정의 추출·비용 계산·통화 검증 오류는 0원으로 처리하지 않습니다.
  • 중복 accounting 명령은 ReservationAccounting의 기존 결과를 재사용하여 비용, 상태와 event를 다시 변경하지 않습니다.
  • listener 실패가 완료된 정산이나 이미 받은 provider 응답을 뒤집지 않도록 했습니다.
  • requestId, attemptId, idempotency key와 reservation ID는 namespaced request context로 전달하며 ThreadLocal을 사용하지 않습니다.
  • provider 전용 output option을 지원할 수 있도록 ReservedOutputTokensResolver 확장 지점을 추가했습니다.
  • autoconfigure는 budget과 accounting 빈이 모두 준비된 경우에만 새 lifecycle을 구성하고, 그렇지 않으면 불완전한 lifecycle 대신 기존 ledger-only 경로를 유지합니다.

설계

  • Token Pilot Advisor는 일반 user Advisor의 request 변경을 반영하면서 terminal model 호출 직전에 preflight를 수행하도록 LOWEST_PRECEDENCE - 1에 배치했습니다. 사용자가 순서를 직접 지정하면 이 배치가 달라질 수 있습니다.
  • framing은 message role과 경계를 보존하고 headroom을 적용하지만 실제 provider payload의 정확한 token count는 아닙니다.
  • Spring AI가 provider 내부 dispatch 직전을 완전히 노출하지 않으므로 markInFlight() 이후 downstream 오류는 보수적으로 pending liability로 보존합니다.
  • 생성한 idempotency key는 현재 accounting command의 중복 처리를 위한 값이며, 새로운 context로 만들어진 upstream retry까지 중복 제거한다고 보장하지 않습니다.
  • enforcement streaming은 provider 호출 전에 차단합니다. budget 비활성 ledger-only streaming은 정식 accounting 지원이 아니라 기존 동작의 회귀 방지 경로입니다.

검증

  • ./gradlew test
  • ./gradlew verifyCompatibilityMatrix verifyCoreConsumer
  • git diff --check
  • Java 25, Spring Boot 4.1.0, Spring AI 2.0.0 호환성 검증 성공
  • Spring AI 없는 Core 단독 소비 검증 성공
  • API key 없는 fake provider E2E 성공

범위 외

  • 실제 provider API를 사용한 호환성 검증은 이번 범위에 포함하지 않습니다.
  • provider 내부 dispatch 직전의 정확한 관찰은 Spring AI Advisor API 제한으로 보장하지 않습니다.
  • chunk별 exact token count, partial usage, cancel 이후 과금 판정과 streaming reconciliation은 post-MVP입니다.
  • budget 비활성 ledger-only streaming은 정식 streaming accounting 지원으로 간주하지 않습니다.
  • provider별 ReservedOutputTokensResolver 구현은 각 provider adapter 또는 사용자 애플리케이션이 담당합니다.
  • 생성된 idempotency key는 별도 upstream retry 요청의 중복 제거를 보장하지 않습니다.
  • metric 연결은 [Observability] TokenPilot 고유 Micrometer 지표 구현 #40에서 다룹니다.
  • notification 연결은 [Budget/Notification] atomic accounting lifecycle에 threshold 알림 연결 #48에서 다룹니다.
  • durable store, outbox와 exactly-once event delivery는 이번 범위에 포함하지 않습니다.

체크리스트

필수 E2E 테스트

  • 정상 non-streaming call은 reserve → in-flight → actual commit으로 종료되며 provider를 한 번 호출한다.
  • context, budget과 missing-pricing BLOCK은 provider, reservation, ledger와 비용 기록을 만들지 않는다.
  • request model과 configured default가 없으면 MODEL_UNRESOLVED로 provider 호출 전에 차단한다.
  • request maxTokens와 configured default가 없으면 OUTPUT_RESERVATION_UNRESOLVED로 provider 호출 전에 차단한다.
  • configured model/output fallback은 versioned model과 safe bound로 예약한다.
  • reservation 이후 markInFlight() 이전 오류는 reservation을 한 번 release한다.
  • markInFlight() 이후 downstream 오류는 pending liability를 한 번 반영하며 0원 commit/release하지 않는다.
  • usage unavailable과 extraction 오류는 provider 응답을 보존하고 pending liability로 남긴다.
  • request model과 response model을 같은 reservation 정산 명령에 보존한다.
  • 기존 cache-read, cache-create와 reasoning usage 정규화를 재사용한다.
  • tool schema, media와 structured output 요청은 bounded reason을 보존하며 fail-closed한다.
  • enforcement streaming은 provider와 reservation을 만들지 않는다.
  • Core public/runtime dependency에 Spring AI 타입이 유입되지 않는다.
  • Spring AI 2.0.0 fake provider E2E가 API key 없이 반복 실행된다.

Acceptance Criteria

  • non-streaming adviseCall()이 reserve부터 reconciliation까지 around lifecycle을 소유한다.
  • request와 provider usage가 동일 reservation과 ReservationAccounting lifecycle로 연결된다.
  • context, budget, pricing과 model/output resolution BLOCK을 provider 호출 전에 집행한다.
  • nullable model/maxTokens의 fallback과 fail-closed 계약을 검증한다.
  • TEXT_ONLY, framing headroom과 unsupported request scope의 지원 범위와 제한을 기록한다.
  • reservation 이후 지원하는 성공·오류 경로가 COMMITTED, RELEASED 또는 RECONCILIATION_REQUIRED로 끝난다.
  • enforcement streaming은 provider 호출 전에 명시적으로 차단한다.
  • 정책, 가격, reservation과 회계 로직을 Advisor에 복제하지 않는다.
  • post-call accounting/listener 실패가 provider 응답을 뒤집지 않는다.
  • Core는 Spring AI 없이 동일하게 동작한다.
  • 지원 버전, Advisor 순서와 streaming/request-scope 제한을 AGENTS.md에 기록한다.
  • fake provider로 API key 없이 E2E를 반복 실행한다.

Closes #39

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: abce27bd-53cf-42a9-bb19-440705481aa2


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@HuitaePark

Copy link
Copy Markdown
Member

#69의 변경은 통합 PR #71에 포함되어 CI 검증을 완료했습니다. 이후 수정과 검토는 #71에서 이어갑니다.

@HuitaePark HuitaePark closed this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Spring AI] Request/Usage Adapter와 Advisor E2E 연결

2 participants