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를 포함한 도메인 이름 집합을 반환합니다.RegistryStats는 public 필드 totalAdvisors(int), totalDomains(int), advisorsPerDomain(Map<String, Integer>)을 노출하는 정적 nested class입니다.BaseAdvisor
CallAdvisor와 StreamAdvisor를 모두 구현하는 추상 기본 클래스입니다. 수명 주기 훅, 오류 처리, 컨텍스트 보강과 함께 템플릿 메서드 패턴을 제공합니다.
public abstract class BaseAdvisor implements CallAdvisor, StreamAdvisor
생성자
domain.name 형식입니다.추상 메서드 (구현 필수)
오버라이드 가능한 훅
beforeStream(ChatClientRequest request)— 기본적으로beforeCall(request)를 호출합니다. Stream 전용 전처리가 필요하면 오버라이드하세요.afterStream(ChatClientResponse response, ChatClientRequest request)— 기본적으로 아무 작업도 수행하지 않습니다. 스트리밍되는 각ChatClientResponse마다 호출됩니다. Stream 전용 후처리가 필요하면 오버라이드하세요.enrichContext(Map<String, Object> context)— 요청 컨텍스트에advisor.domain,advisor.name,advisor.timestamp를 추가합니다.handleBlockingError(AdvisorException e, ChatClientRequest request)— 요청 컨텍스트에advisor.error,advisor.error.message,advisor.error.domain,advisor.blocked.by를 설정한 뒤 예외를 다시 던집니다.
수명 주기 & 상태
getName()— 전체 Advisor 이름을domain.name형식으로 반환합니다.getOrder()— 생성자order값을 반환합니다. 낮을수록 먼저 실행됩니다.isEnabled()— Advisor의 현재 활성화 여부를 반환합니다. 외부 설정을 참조하려면 오버라이드하세요.setEnabled(boolean enabled)— 런타임에서 Advisor를 토글합니다.AdvisorRegistry.enableDomain(...),disableDomain(...),enableAll(),disableAll()이 호출합니다.validate()— 레지스트리가register(...)중 호출합니다.domain또는name이 비어 있으면false를 반환하여 등록을 롤백합니다.
오류 처리
AdvisorException은 두 가지 정적 팩토리 메서드로 생성되며 두 가지 모드를 지원합니다:
AdvisorException.blocking(domain, advisorName, message)— Advisor 체인을 중단하고 LLM 호출을 방지합니다. 보안 적용에 사용됩니다.AdvisorException.nonBlocking(domain, advisorName, message)— 오류를 로깅하고chain.nextCall(request)로 체인을 계속합니다. 선택적 보강에 사용됩니다.
beforeCall/afterCall에서 던져진 그 외의 Exception은 비차단으로 처리됩니다: 실패가 요청 컨텍스트의 {advisorName}.error로 기록되고 체인이 계속됩니다.
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를 다룹니다.