contexa-core

Advisor 시스템

Advisor 시스템은 Spring AI의 CallAdvisorStreamAdvisor 인터페이스와 통합되어 모든 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
register(Advisor advisor) void
Advisor를 등록합니다. Advisor가 BaseAdvisor를 확장하는 경우 검증되고 도메인별로 인덱싱됩니다. 활성화 캐시를 무효화합니다.
registerAll(Collection<? extends Advisor> advisors) void
여러 Advisor를 일괄 등록합니다.
unregister(String advisorName) void
이름으로 Advisor를 제거하고 도메인 매핑을 정리합니다.
get(String advisorName) Optional<Advisor>
전체 이름 (형식: domain.name)으로 특정 Advisor를 검색합니다.
getEnabled() List<Advisor>
순서대로 정렬된 모든 활성화된 Advisor를 반환합니다. 레지스트리가 수정될 때까지 결과가 캐시됩니다.
getByDomain(String domain) List<Advisor>
주어진 도메인의 모든 Advisor를 순서대로 정렬하여 반환합니다.
enableDomain(String domain) / disableDomain(String domain) void
도메인의 모든 Advisor를 일괄 활성화 또는 비활성화합니다.
enableAll() / disableAll() void
등록된 모든 Advisor를 활성화 또는 비활성화합니다.
getStats() RegistryStats
총 Advisor 수, 총 도메인 수, 도메인별 Advisor 수를 포함한 통계를 반환합니다.

BaseAdvisor

CallAdvisorStreamAdvisor를 모두 구현하는 추상 기본 클래스입니다. 수명 주기 훅, 오류 처리, 컨텍스트 보강과 함께 템플릿 메서드 패턴을 제공합니다.

public abstract class BaseAdvisor implements CallAdvisor, StreamAdvisor

생성자

BaseAdvisor(String domain, String name, int order)
주어진 도메인, 이름, 실행 순서로 Advisor를 생성합니다. 전체 Advisor 이름은 domain.name 형식입니다.

추상 메서드 (구현 필수)

beforeCall(ChatClientRequest request) ChatClientRequest
LLM 요청 전에 호출됩니다. 컨텍스트를 보강하기 위해 요청을 수정하고 반환합니다.
afterCall(ChatClientResponse response, ChatClientRequest request) ChatClientResponse
LLM 응답 후에 호출됩니다. 후처리, 감사, 검증을 위해 응답을 수정하고 반환합니다.

오버라이드 가능한 훅

  • 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-authenticationtrue일 때, 인증되지 않은 요청은 LLM 호출을 방지하는 차단 AdvisorException을 던집니다.

코드 예제

커스텀 Advisor 구현

Java
@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 관리

Java
// 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를 다룹니다.