가드레일

가드레일 (Guardrails)

가드레일은 LLM의 입력과 출력을 검증해서 기대에 부합하는지 확인하는 메커니즘이에요. LLM을 호출하기 전에 사용자 입력이 범위를 벗어나지 않았는지, 프롬프트 인젝션 공격 같은 것에 대비해 입력이 기준을 충족하는지, 그리고 출력 형식이 올바른지(예: 올바른 스키마를 가진 JSON 문서인지), LLM 출력이 업무 규칙과 제약에 부합하는지(예: X 회사 챗봇이라면 경쟁사 Y에 대한 언급이 없어야 하는지), 그리고 환각(hallucination)을 탐지하는 등 수많은 일을 할 수 있어요.

출처: 공식문서

:::note 가드레일은 실험적 기능이에요. API와 동작이 향후 버전에서 바뀔 수 있어요. 다만 AI Services를 사용할 때만 쓸 수 있어요. ChatModel이나 StreamingChatModel에는 적용할 수 없는 상위 레벨 개념이에요. :::

이 구현은 원래 Quarkus LangChain4j 확장에서 만들어졌고 여기로 백포트 됐어요.

가드레일 구현하기

이상적으로 가드레일 구현은 단일 책임 원칙을 따라야 해요. 즉 각 가드레일 클래스는 한 가지를 검증해야 하죠. 그리고 여러 가지를 막기 위해 가드레일들을 체인으로 연결해요.

체인에서 가드레일들의 순서가 중요해요. 체인에서 첫 번째로 실패한 가드레일이 전체 실패를 유발해요. 가장 많이 실패를 잡아내는 가드레일을 앞쪽에 두고, 아주 드물게 실패하는 더 구체적인 가드레일은 체인 끝에 두는 게 좋아요.

또한 가드레일은 스스로 다른 서비스를 호출하거나 다른 LLM 상호작용을 유발할 수도 있어요. 이런 가드레일이 실행 비용이나 금전적 비용이 있다면 고려해야 해요. 더 비싼 가드레일은 체인의 끝에 두는 게 좋아요.

:::note 비싼(expensive) 이라는 말은 실행에 시간이 걸리거나 금전적 가치가 있다는 것을 뜻할 수 있어요. :::

입력 가드레일 (Input Guardrails)

입력 가드레일은 LLM을 호출하기 전에 실행되는 함수예요. 입력 가드레일이 실패하면 LLM은 호출되지 않아요. 입력 가드레일은 LLM 호출 직전의 마지막 단계예요. 어떤 RAG 작업이 끝난 후에 실행돼요.

입력 가드레일 구현하기

입력 가드레일은 InputGuardrail 인터페이스를 구현해서 만들어요. InputGuardrail 인터페이스에는 validate 메서드의 두 가지 변형이 있고, 적어도 하나는 구현해야 해요:

InputGuardrailResult validate(UserMessage userMessage);
InputGuardrailResult validate(InputGuardrailRequest params);

첫 번째 변형은 단순한 가드레일이거나 UserMessage에만 접근하면 되는 경우에 써요.

두 번째 변형은 채팅 메모리/히스토리, 사용자 메시지 템플릿, 증강(augmentation) 결과, 템플릿에 전달된 변수 같은 더 많은 정보가 필요한 복잡한 가드레일용이에요. 자세한 내용은 InputGuardrailRequest를 참고해요.

할 수 있는 일의 예:

  • 증강 결과에 문서가 충분히 있는지 확인
  • 사용자가 같은 질문을 여러 번 하지 않는지 확인
  • 잠재적 프롬프트 인젝션 공격 완화
  • 커뮤니티 Prompt Repetition 모듈로 적격한 단일 텍스트 입력 재작성

입력 가드레일은 동기/비동기(스트리밍) 여부와 무관하게 쓸 수 있어요.

입력 가드레일 결과 (Outcomes)

InputGuardrail 인터페이스에는 결과를 제공하는 헬퍼 메서드가 있어요:

결과 InputGuardrail 헬퍼 메서드 설명
success success() - 입력이 유효함.
- 체인의 다음 가드레일이 실행됨.
- 마지막 가드레일이 통과하면 LLM이 호출됨.
success with alternate result successWith(String) **success**와 비슷하지만, 다음 단계(체인의 다음 가드레일 또는 LLM 호출)로 진행하기 전에 사용자 메시지가 변경됨.
failure failure(String) 또는 failure(String, Throwable) - 입력이 유효하지 않지만 가능한 모든 검증 문제를 모으기 위해 체인의 다음 가드레일들이 계속 실행됨.
- LLM은 호출되지 않음.
- Throwable이 전달되면 소비자는 InputGuardrailException을 잡아 cause를 확인할 수 있음. 여기 전달된 Throwable이 그 값임.
fatal fatal(String) 또는 fatal(String, Throwable) - 입력이 유효하지 않고 InputGuardrailException으로 실행이 중단됨.
- LLM은 호출되지 않음.
- Throwable이 전달되면 소비자는 InputGuardrailException을 잡아 cause를 확인할 수 있음.

입력 가드레일 선언하기

입력 가드레일을 선언하는 여러 방법이 있고, 우선순위 순서대로 나열하면:

  1. AiServices 빌더에 직접 설정한 InputGuardrail 구현 클래스 이름 또는 인스턴스
  2. 개별 AI Service 메서드에 단 @InputGuardrails 어노테이션
  3. AI Service 클래스에 단 @InputGuardrails 어노테이션

선언 방식과 무관하게 입력 가드레일은 항상 목록에 나타난 순서대로 실행돼요.

AiServices 빌더

AiServices 빌더에 직접 설정한 InputGuardrail 구현 클래스 이름이나 인스턴스는 가장 높은 우선순위를 가져요. 즉 다른 어떤 방식으로 선언돼도 빌더에 직접 선언한 것이 사용돼요.

public interface Assistant {
    String chat(String question);
    String doSomethingElse(String question);
}

var assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .inputGuardrailClasses(FirstInputGuardrail.class, SecondInputGuardrail.class)
    .build();

또는

public interface Assistant {
    String chat(String question);
    String doSomethingElse(String question);
}

var assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .inputGuardrails(new FirstInputGuardrail(), new SecondInputGuardrail())
    .build();

:::info 준비된 실험적 입력 가드레일이 필요하다면(프롬프트 반복으로 적격한 단일 텍스트 입력을 재작성), 커뮤니티 Prompt Repetition 모듈을 참고해요. :::

첫 번째 시나리오에서는 InputGuardrail을 구현하는 클래스들이 전달돼요. 이 클래스들의 새 인스턴스는 리플렉션을 사용해 동적으로 생성돼요.

:::info 클래스를 인스턴스로 변환하는 방식은 커스터마이즈될 수 있어요. 예를 들어 의존성 주입을 사용하는 프레임워크(QuarkusSpring 같은)는 확장 지점을 사용해서 매번 리플렉션으로 새 인스턴스를 만들지 않고 자신의 클래스 인스턴스 관리 방식에 따라 인스턴스를 제공할 수 있어요. :::

개별 AI Service 메서드에 단 어노테이션

개별 AI Service 메서드에 단 @InputGuardrails 어노테이션은 다음으로 높은 우선순위를 가져요.

public interface Assistant {
    @InputGuardrails({ FirstInputGuardrail.class, SecondInputGuardrail.class })
    String chat(String question);
    
    String doSomethingElse(String question);
}

var assistant = AiServices.create(Assistant.class, chatModel);

이 예시에서는 chat 메서드에만 가드레일이 있어요.

  • chat 메서드에서는 FirstInputGuardrail이 먼저 호출돼요.
  • 성공했을 때만 LLM이 호출돼요.
  • SecondInputGuardrailFirstInputGuardrailfatal 결과를 내지 않을 때만 호출돼요.
  • FirstInputGuardrail이나 SecondInputGuardrail은 사용자 메시지를 재작성할 수 있어요.
  • FirstInputGuardrail이 사용자 메시지를 재작성하면 SecondInputGuardrail은 새 사용자 메시지를 입력으로 받아요.

doSomethingElse 메서드에는 가드레일이 없어요.

AI Service 클래스에 단 어노테이션

AI Service 클래스에 단 @InputGuardrails 어노테이션은 가장 낮은 우선순위를 가져요.

@InputGuardrails({ FirstInputGuardrail.class, SecondInputGuardrail.class })
public interface Assistant {
    String chat(String question);
    String doSomethingElse(String question);
}

var assistant = AiServices.create(Assistant.class, chatModel);

이 예시에서는 chatdoSomethingElse 메서드 둘 다 가드레일이 있어요.

  • 앞선 예시와 마찬가지로 FirstInputGuardrail이 먼저 호출돼요.
  • 성공했을 때만 LLM이 호출돼요.
  • SecondInputGuardrailFirstInputGuardrailfatal 결과를 내지 않을 때만 호출돼요.
  • FirstInputGuardrail이나 SecondInputGuardrail은 사용자 메시지를 재작성할 수 있어요.
  • FirstInputGuardrail이 사용자 메시지를 재작성하면 SecondInputGuardrail은 새 사용자 메시지를 입력으로 받아요.

입력 가드레일 단위 테스트

langchain4j-test 모듈에 AssertJ 기반의 단위 테스트 유틸리티가 있어요.

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-test</artifactId>
  <scope>test</scope>
</dependency>

의존성을 추가하면 이런 검증들을 할 수 있어요:

import static dev.langchain4j.test.guardrail.GuardrailAssertions.assertThat;

import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.guardrail.GuardrailResult.Result;

class Tests { 
    MyInputGuardrail inputGuardrail = new MyInputGuardrail();
    
    @Test 
    void test() {
        var userMessage = UserMessage.from("Some user message");
        var result = inputGuardrail.validate(userMessage);
        
        // These are just some examples of what you can do
        assertThat(result)
                .isSuccessful()
                .hasResult(Result.FATAL)
                .hasFailures()
                .hasSingleFailureWithMessage("Prompt injection detected")
                .assertSingleFailureSatisfied(failure -> assertThat(failure)...)
                .withFailures().....
    }
}

:::info 자세한 내용은 GuardrailAssertionsInputGuardrailResultAssert 클래스를 참고해요. :::

기본 제공 입력 가드레일 (Out-of-the-box Input Guardrails)

LangChain4j가 제공하는 자주 쓰이는 입력 가드레일 구현들이 있어요:

가드레일 클래스 설명
MessageModeratorInputGuardrail ModerationModel을 사용해 잠재적으로 유해하거나 부적절하거나 정책을 위반하는 콘텐츠를 탐지해 사용자 메시지를 검증하는 입력 가드레일.
- 증오 발언, 폭력, 자해, 성적 콘텐츠 또는 조정 모델이 정의한 다른 범주를 메시지에서 확인함.
- 메시지가 플래그되면 fatal 결과로 검증이 실패해, 그 메시지가 더 이상 처리되지 않게 막음.
- LLM에 보내기 전에 사용자 입력이 콘텐츠 정책을 준수하는지 보장할 때 유용함.
PatternBasedPromptInjectionGuardrail OWASP LLM01에서 파생된 정규 표현식을 사용해 프롬프트 인젝션 시도를 탐지하는 패턴 기반 입력 가드레일.
- 지시 오버라이드, 역할 탈취, 탈옥(jailbreak), 시스템 프롬프트 유출, 구분자 인젝션, 인코딩된 페이로드를 다룸.
- 외부 의존성이 없고 서브 밀리초 지연이라, LLM 기반 분류기 앞의 가드레일 체인에서 첫 번째(가장 저렴한) 관문으로 적합함.
- 하위 클래스는 도메인별 패턴을 추가하고 실패 메시지를 커스터마이즈할 수 있음.

출력 가드레일 (Output Guardrails)

출력 가드레일은 LLM이 출력을 만든 후에 실행되는 함수예요. 출력 가드레일이 실패하면 재시도재프롬프트 같은 더 고급 시나리오를 허용해 응답을 개선할 수 있어요. 함수/도구 호출을 포함한 모든 다른 작업이 끝난 후에 실행돼요.

출력 가드레일 구현하기

입력 가드레일과 비슷하게 출력 가드레일은 OutputGuardrail 인터페이스를 구현해서 만들어요. OutputGuardrail 인터페이스에는 validate 메서드의 두 가지 변형이 있고 적어도 하나는 구현해야 해요:

OutputGuardrailResult validate(AiMessage responseFromLLM);
OutputGuardrailResult validate(OutputGuardrailRequest params);

첫 번째 변형은 단순한 가드레일이거나 결과 AiMessage에만 접근하면 되는 경우에 써요.

두 번째 변형은 전체 채팅 응답, 채팅 메모리/히스토리, 사용자 메시지 템플릿, 템플릿에 전달된 변수 같은 더 많은 정보가 필요한 복잡한 가드레일용이에요. 자세한 내용은 OutputGuardrailRequest를 참고해요.

할 수 있는 일의 예:

  • 출력 형식이 올바른지 확인(예: 올바른 스키마를 가진 JSON 문서인지)
  • LLM 환각 탐지
  • LLM 응답이 특정 정보를 포함하는지 검증

출력 가드레일 결과

OutputGuardrail 인터페이스에는 결과를 제공하는 헬퍼 메서드가 있어요:

결과 OutputGuardrail 헬퍼 메서드 설명
success success() - 출력이 유효함.
- 체인의 다음 가드레일이 실행됨. 마지막 가드레일이 통과하면 출력이 호출자에게 반환됨.
success with rewrite successWith(String) 또는 successWith(String, Object) - **success**와 비슷하지만 출력이 원래 형태로는 유효하지 않아 유효하게 만들기 위해 재작성됨.
- 재작성된 출력에 대해 다음 가드레일이 실행됨. 마지막 가드레일이 통과하면 출력이 호출자에게 반환됨.
failure failure(String) 또는 failure(String, Throwable) - 출력이 유효하지 않지만 가능한 모든 검증 문제를 모으기 위해 체인의 다음 가드레일들이 계속 실행됨.
- 검증 실패는 OutputGuardrailException으로 사용자에게 반환됨.
fatal fatal(String) 또는 fatal(String, Throwable) - 출력이 유효하지 않고 OutputGuardrailException이 호출자에게 던져지며 실행이 중단됨.
fatal with retry retry(String) 또는 retry(String, Throwable) - **fatal**과 비슷하지만 원래 호출과 같은 프롬프트와 채팅 히스토리로 LLM이 다시 호출됨.
- 설정 가능한 재시도 횟수 이후에도 실패가 지속되면 OutputGuardrailException이 호출자에게 던져지며 실행이 중단됨.
- 재시도 후 가드레일이 통과하면 가드레일 체인 전체가 처음부터 다시 실행됨.
fatal with reprompt reprompt(String, String) 또는 reprompt(String, Throwable, String) - **fatal with retry**와 비슷하지만 가드레일이 제공한 새 프롬프트로 LLM이 다시 호출됨.
- 이 상황에서는 가드레일이 이전 사용자 메시지에 추가할 메시지를 제공한 뒤, 새 사용자 메시지와 원래 채팅 히스토리로 LLM에 새 요청을 보냄.
- 설정 가능한 재시도 횟수 이후에도 실패가 지속되면 OutputGuardrailException이 호출자에게 던져지며 실행이 중단됨.
- 재프롬프트 후 가드레일이 통과하면 가드레일 체인 전체가 처음부터 다시 실행됨.

출력 가드레일 선언하기

출력 가드레일을 선언하는 여러 방법이 있고, 우선순위 순서대로:

  1. AiServices 빌더에 직접 설정한 OutputGuardrail 구현 클래스 이름 또는 인스턴스
  2. 개별 AI Service 메서드에 단 @OutputGuardrails 어노테이션
  3. AI Service 클래스에 단 @OutputGuardrails 어노테이션

선언 방식과 무관하게 출력 가드레일은 항상 목록에 나타난 순서대로 실행돼요.

AiServices 빌더

AiServices 빌더에 직접 설정한 OutputGuardrail 구현 클래스 이름이나 인스턴스는 가장 높은 우선순위를 가져요.

public interface Assistant {
    String chat(String question);
    String doSomethingElse(String question);
}

var assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .outputGuardrailClasses(FirstOutputGuardrail.class, SecondOutputGuardrail.class)
    .build();

또는

public interface Assistant {
    String chat(String question);
    String doSomethingElse(String question);
}

var assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .outputGuardrails(new FirstOutputGuardrail(), new SecondOutputGuardrail())
    .build();

첫 번째 시나리오에서는 OutputGuardrail을 구현하는 클래스들이 전달돼요. 이 클래스들의 새 인스턴스는 리플렉션을 사용해 동적으로 생성돼요.

:::info 클래스를 인스턴스로 변환하는 방식은 커스터마이즈될 수 있어요. 의존성 주입을 사용하는 프레임워크(QuarkusSpring 같은)는 확장 지점을 사용해 매번 리플렉션으로 새 인스턴스를 만들지 않고 자신의 방식으로 인스턴스를 제공할 수 있어요. :::

개별 AI Service 메서드에 단 어노테이션

개별 AI Service 메서드에 단 @OutputGuardrails 어노테이션은 다음으로 높은 우선순위를 가져요.

public interface Assistant {
    @OutputGuardrails({ FirstOutputGuardrail.class, SecondOutputGuardrail.class })
    String chat(String question);
    
    String doSomethingElse(String question);
}

var assistant = AiServices.create(Assistant.class, chatModel);

이 예시에서는 chat 메서드에만 가드레일이 있어요.

  • chat 메서드에서는 FirstOutputGuardrail이 먼저 호출돼요.
  • 성공했을 때만 결과가 호출자에게 반환돼요. SecondOutputGuardrailFirstOutputGuardrailfatal, fatal with retry, fatal with reprompt 결과를 내지 않을 때만 호출돼요.
  • SecondOutputGuardrailFirstOutputGuardrail의 출력을 받아요.
  • SecondOutputGuardrail이 재시도나 재프롬프트 후 성공하면 FirstOutputGuardrailSecondOutputGuardrail 둘 다 다시 실행돼요.

doSomethingElse 메서드에는 가드레일이 없어요.

AI Service 클래스에 단 어노테이션

AI Service 클래스에 단 @OutputGuardrails 어노테이션은 가장 낮은 우선순위를 가져요.

@OutputGuardrails({ FirstOutputGuardrail.class, SecondOutputGuardrail.class })
public interface Assistant {
    String chat(String question);
    String doSomethingElse(String question);
}

var assistant = AiServices.create(Assistant.class, chatModel);

이 예시에서는 chatdoSomethingElse 메서드 둘 다 가드레일이 있어요.

  • 앞선 예시와 마찬가지로 FirstOutputGuardrail이 먼저 호출돼요.
  • 성공했을 때만 결과가 호출자에게 반환돼요. SecondOutputGuardrailFirstOutputGuardrailfatal, fatal with retry, fatal with reprompt 결과를 내지 않을 때만 호출돼요.
  • SecondOutputGuardrailFirstOutputGuardrail의 출력을 받아요.
  • SecondOutputGuardrail이 재시도나 재프롬프트 후 성공하면 둘 다 다시 실행돼요.

구성 (Configuration)

출력 가드레일에는 다음 추가 구성이 있어요:

구성 설명
maxRetries - 출력 가드레일이 재시도나 재프롬프트를 수행할 때 최대 재시도 횟수.
- 기본값은 2.
- 0으로 설정하면 재시도를 비활성화함.
개별 AI Service 메서드에 단 어노테이션
public interface MethodLevelAssistant {
    @OutputGuardrails(
            value = { FirstOutputGuardrail.class, SecondOutputGuardrail.class },
            maxRetries = 10
    )
    String chat(String question);
}

var assistant = AiServices.create(MethodLevelAssistant.class, chatModel);
AI Service 클래스에 단 어노테이션
@OutputGuardrails(
        value = { FirstOutputGuardrail.class, SecondOutputGuardrail.class },
        maxRetries = 10
)
public interface ClassLevelAssistant {
    String chat(String question);
}

var assistant = AiServices.create(ClassLevelAssistant.class, chatModel);
AiServices 빌더
public interface Assistant {
    String chat(String message);
}

var outputGuardrailsConfig = OutputGuardrailsConfig.builder()
        .maxRetries(10)
        .build();

var assistant = AiServices.builder(Assistant.class)
        .chatModel(chatModel)
        .outputGuardrailsConfig(outputGuardrailsConfig)
        .outputGuardrailClasss(FirstOutputGuardrail.class, SecondOutputGuardrail.class)
        .build();

스트리밍 응답에서의 출력 가드레일

스트리밍 응답이 있는 작업에서도 출력 가드레일은 동작해요:

public interface StreamingAssistant {
    @OutputGuardrails({ FirstOutputGuardrail.class, SecondOutputGuardrail.class })
    TokenStream streamingChat(String message);
}

이 시나리오에서는 전체 스트림이 완료되면, 더 정확히는 TokenStream.onCompleteResponse가 호출될 때 출력 가드레일이 실행돼요. onPartialResponse는 버퍼링됐다가 가드레일이 성공하면 재생돼요.

체인에서 retry 또는 **reprompt**가 결국 성공하는 상황에서는 전체 체인이 동기적으로 다시 실행돼요. 각 가드레일이 원래 순서대로 하나씩 다시 실행돼요. 체인이 완료되면 결과가 TokenStream.onCompleteResponse로 전달돼요.

기본 제공 출력 가드레일 (Out-of-the-box Output Guardrails)

가드레일 클래스 설명
JsonExtractorOutputGuardrail 응답을 JSON에서 특정 타입의 객체로 성공적으로 역직렬화할 수 있는지 확인하는 출력 가드레일.
- Jackson ObjectMapper를 사용해 객체 역직렬화를 시도함.
- 응답을 기대 객체 타입으로 역직렬화할 수 없으면 LLM이 재프롬프트됨.
- 있는 그대로 쓰거나 확장·커스터마이즈할 수 있음(동작을 커스터마이즈하도록 오버라이드할 수 있는 protected 메서드가 여럿 있음).

출력 가드레일 단위 테스트

langchain4j-test 모듈에 AssertJ 기반의 단위 테스트 유틸리티가 있어요.

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-test</artifactId>
  <scope>test</scope>
</dependency>

의존성을 추가하면 이런 검증들을 할 수 있어요:

import static dev.langchain4j.test.guardrail.GuardrailAssertions.assertThat;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.guardrail.GuardrailResult.Result;

class Tests { 
    MyOutputGuardrail outputGuardrail = new MyOutputGuardrail();
    
    @Test 
    void test() {
        var aiMessage = AiMessage.from("Some output");
        var result = outputGuardrail.validate(aiMessage);
        
        // These are just some examples of what you can do
        assertThat(result)
                .isSuccessful()
                .hasResult(Result.FATAL)
                .hasFailures()
                .hasSingleFailureWithMessage("Hallucination detected!")
                .hasSingleFailureWithMessageAndReprompt("Hallucination detected!", "Please LLM don't hallucinate!")
                .assertSingleFailureSatisfied(failure -> assertThat(failure)...)
                .withFailures().....
    }
}

:::info 자세한 내용은 GuardrailAssertionsOutputGuardrailResultAssert 클래스를 참고해요. :::

:::note I/O를 수행하는 가드레일은 validateAsync(...)를 구현해서 AI Service가 논블로킹 모드에서 사용될 때 스레드를 블로킹하지 않게 할 수 있어요. 입력·출력 가드레일(도구 인지 재프롬프트 포함)은 비동기·리액티브 모드에서 지원돼요. Non-blocking and Reactive를 참고해요. :::

섞어 쓰기 (Mixing and matching)

입력·출력 가드레일은 마음대로 섞어 쓸 수 있어요!

public class MyObjectJsonOutputGuardrail extends JsonExtractorOutputGuardrail<MyObject> {
    public MyObjectJsonOutputGuardrail() {
        super(MyObject.class);
    }
}

@InputGuardrails({ FirstInputGuardrail.class, SecondInputGuardrail.class })
@OutputGuardrails(value = SomeOutputGuardrail.class, maxRetries = 5)
public interface Assistant {
    String chat(String message);
    
    @InputGuardrails(PatternBasedPromptInjectionGuardrail.class)
    @OutputGuardrails(MyObjectJsonOutputGuardrail.class)
    MyObject chatAndReturnJson(String message);
}

var outputGuardrailsConfig = OutputGuardrailsConfig.builder()
        .maxRetries(10)
        .build();

var assistant = AiServices.builder(Assistant.class)
        .chatModel(chatModel)
        .inputGuardrails(new AnotherInputGuardrail())
        .outputGuardrailsConfig(outputGuardrailsConfig)
        .build();

이 예시에서 Assistant의 모든 메서드는 AiServices 빌더에 설정됐으므로 단일 입력 가드레일 AnotherInputGuardrail을 가져요. 추가로 모든 출력 가드레일의 maxRetries 값이 10인데, 그 구성도 AiServices 빌더에 설정됐기 때문이에요.

chat 메서드는 maxRetries 값이 10인 단일 출력 가드레일 SomeOutputGuardrail을 가져요.

chatAndReturnJson 메서드는 maxRetries 값이 10인 단일 출력 가드레일 MyObjectJsonOutputGuardrail을 가져요.

확장 지점 (Extension points)

가드레일 시스템은 다른 다운스트림 프레임워크(QuarkusSpring Boot 같은)에서 확장·재사용할 수 있도록 합성 가능한 방식으로 만들어졌어요. 이 절에서는 제공되는 확장 지점 또는 "훅"을 설명해요.

이 모든 확장 지점은 Java Service Provider Interface (Java SPI)를 활용해요.

확장 지점 인터페이스 목적
ClassInstanceFactory 클래스의 인스턴스를 제공함.
- 인스턴스 생성/조회를 다른 수단에 위임하기 위한 것.
- 제공되지 않으면 기본 생성자로 인스턴스를 만드는 리플렉션을 사용함.
- 다른 프레임워크(Quarkus나 Spring 같은)는 자신의 빈 컨테이너를 사용해 클래스 인스턴스를 제공할 수 있음. 그러한 프레임워크가 구현을 제공함.
- Quarkus 구현은 CDIClassInstanceFactory와 비슷할 수 있음.
- Spring 구현은 ApplicationContextClassInstanceFactory와 비슷할 수 있음.
ClassMetadataProviderFactory 클래스 메타데이터에 접근을 제공함.
- AiService 인터페이스의 메서드를 스캔하고 @InputGuardrails/@OutputGuardrails 어노테이션을 찾아 처리하는 데 사용됨.
- 다른 구현이 없으면 ReflectionBasedClassMetadataProviderFactory가 기본 구현으로 리플렉션으로 클래스 메타데이터를 제공함.
GuardrailServiceBuilderFactory GuardrailService 인스턴스를 만드는 빌더 인스턴스를 제공함. 애플리케이션이나 프레임워크가 GuardrailService 인스턴스를 만드는 방식을 커스터마이즈해야 한다면 이걸 구현함.
InputGuardrailsConfigBuilderFactory 기본 InputGuardrailsConfigBuilder를 덮어쓰거나 확장하는 SPI.
- 다른 프레임워크는 입력 가드레일용 추가 구성이 있는 자체 구현을 제공할 수 있음.
- 다른 프레임워크가 다른 메커니즘(예: properties 파일)으로 입력 가드레일 구성을 구동하게도 함.
OutputGuardrailsConfigBuilderFactory 기본 OutputGuardrailsConfigBuilder를 덮어쓰거나 확장하는 SPI.
- 다른 프레임워크는 출력 가드레일용 추가 구성이 있는 자체 구현을 제공할 수 있음.
- 다른 프레임워크가 다른 메커니즘(예: properties 파일)으로 출력 가드레일 구성을 구동하게도 함.
InputGuardrailExecutorBuilderFactory InputGuardrailExecutor 인스턴스를 만드는 기본 InputGuardrailExecutorBuilder를 덮어쓰거나 확장하는 SPI.
OutputGuardrailExecutorBuilderFactory OutputGuardrailExecutor 인스턴스를 만드는 기본 OutputGuardrailExecutorBuilder를 덮어쓰거나 확장하는 SPI.

더 알아보기