Advisor 시스템
Advisor 시스템은 Spring AI의 CallAdvisor 및 StreamAdvisor 인터페이스와 통합되어 모든 LLM 요청과 응답을 가로채고 보강합니다. AdvisorRegistry는 Advisor 수명 주기, 도메인 조직, 활성화/비활성화 상태를 관리합니다. UnifiedLLMOrchestrator는 생성하는 모든 ChatClient에 활성화된 모든 Advisor를 자동으로 적용합니다.
개요
Advisor는 모든 LLM 호출 주위에 책임 체인을 형성합니다. 각 Advisor는 LLM에 도달하기 전 요청을 수정하고, 이후 응답을 검사하거나 변환하며, 하위 Advisor를 위한 컨텍스트 메타데이터를 추가할 수 있습니다. Advisor는 도메인별로 구성되며 (예: "SECURITY", "AUDIT") 우선순위에 따라 정렬됩니다.
시스템이 제공하는 기능:
- 도메인 기반 조직 — Advisor가 도메인별로 그룹화되어 일괄 활성화/비활성화 작업이 가능합니다.
- 우선순위 정렬 — Advisor는
getOrder()값 순서대로 실행됩니다 (낮을수록 먼저 실행). - 핫 리로드 — Advisor를 런타임에 등록, 해제, 활성화, 비활성화할 수 있습니다.
- 오류 격리 — 비차단 Advisor 오류는 해당 Advisor를 건너뛰고 체인을 계속합니다; 차단 오류는 실행을 중단합니다.
AdvisorRegistry
Advisor 수명 주기 관리를 위한 중앙 레지스트리입니다. 활성화된 Advisor 목록의 내부 캐싱을 통해 스레드 안전합니다.
public class AdvisorRegistry
BaseAdvisor를 확장하는 경우 검증되고 도메인별로 인덱싱됩니다. 활성화 캐시를 무효화합니다.domain.name)으로 특정 Advisor를 검색합니다.BaseAdvisor
CallAdvisor와 StreamAdvisor를 모두 구현하는 추상 기본 클래스입니다. 수명 주기 훅, 오류 처리, 컨텍스트 보강과 함께 템플릿 메서드 패턴을 제공합니다.
public abstract class BaseAdvisor implements CallAdvisor, StreamAdvisor
생성자
domain.name 형식입니다.추상 메서드 (구현 필수)
오버라이드 가능한 훅
beforeStream()— 기본적으로beforeCall()을 호출합니다. Stream 전용 전처리를 위해 오버라이드하세요.afterStream()— 기본적으로 아무 작업도 수행하지 않습니다. Stream 전용 후처리를 위해 오버라이드하세요.enrichContext(Map<String, Object> context)— 컨텍스트에advisor.domain,advisor.name,advisor.timestamp를 추가합니다.handleBlockingError(AdvisorException e, ChatClientRequest request)— 차단 오류를 처리하여 오류 컨텍스트를 설정하고 다시 던집니다.
오류 처리
AdvisorException은 두 가지 모드를 지원합니다:
- 차단 — Advisor 체인을 중단하고 LLM 호출을 방지합니다. 보안 적용에 사용됩니다.
- 비차단 — 오류를 로깅하고 체인을 계속합니다. 선택적 보강에 사용됩니다.
SecurityContextAdvisor
보안 컨텍스트 (사용자 ID, 세션, 인증 상태, 권한)를 모든 LLM 요청에 주입합니다. 비HTTP 스레드에서는 AsyncSecurityContextProvider 구현체를 통해 비동기 보안 컨텍스트를 복구하며, 분산 환경에서는 Redis 기반 구현이 사용될 수 있습니다.
public class SecurityContextAdvisor extends BaseAdvisor
주입되는 컨텍스트 키
| 키 | 설명 |
|---|---|
user.id | 인증된 사용자 식별자. |
session.id | 세션 식별자. |
authenticated | 불리언 인증 상태. |
authorities | 부여된 권한의 문자열 표현. |
principal.type | 인증 주체의 타입. |
async.context | 항상 true, 비동기 컨텍스트 모드를 나타냄. |
timestamp | 밀리초 단위의 요청 타임스탬프. |
인증 강제: contexa.advisor.security.require-authentication이 true일 때, 인증되지 않은 요청은 LLM 호출을 방지하는 차단 AdvisorException을 던집니다.
코드 예제
커스텀 Advisor 구현
@Component
public class RateLimitAdvisor extends BaseAdvisor {
private final RateLimiter rateLimiter;
public RateLimitAdvisor(RateLimiter rateLimiter) {
super("INFRASTRUCTURE", "rate-limit", 10);
this.rateLimiter = rateLimiter;
}
@Override
protected ChatClientRequest beforeCall(
ChatClientRequest request) {
String userId = (String) request.context()
.get("user.id");
if (!rateLimiter.tryAcquire(userId)) {
throw AdvisorException.blocking(
getDomain(), getName(),
"Rate limit exceeded for user: " + userId);
}
return request;
}
@Override
protected ChatClientResponse afterCall(
ChatClientResponse response,
ChatClientRequest request) {
return response;
}
}
런타임 Advisor 관리
// Disable all security advisors during maintenance
advisorRegistry.disableDomain("SECURITY");
// Re-enable after maintenance
advisorRegistry.enableDomain("SECURITY");
// Check registry health
AdvisorRegistry.RegistryStats stats =
advisorRegistry.getStats();
// stats.totalAdvisors, stats.totalDomains,
// stats.advisorsPerDomain
관련 문서
Advisor
application.yml 속성 (활성화된 Advisor, 순서, 보안 컨텍스트 전파)은 AI 설정을 참조하세요 — ContexaAdvisorProperties를 다룹니다.