contexa-core

커스텀 AI 만들기

Contexa AI 진단 프로세스를 사용해 자체 AI 기반 기능을 구축합니다. 이 가이드는 Strategy, Lab, Pipeline 아키텍처로 도메인 타입 정의부터 REST 엔드포인트 노출까지 완전한 커스텀 AI 기능 생성 과정을 안내합니다.

무엇을 만들게 됩니까

이 가이드를 따라 다음 기능을 모두 갖춘 AI 기능을 완성합니다:

  • REST API를 통해 도메인별 입력을 수신
  • AI 진단 프로세스로 요청 라우팅
  • JSON 파싱이 적용된 구조화된 LLM 응답 생성
  • 표준(동기) 모드와 스트리밍(SSE) 모드 모두 지원
  • 벡터 스토어에서 RAG 컨텍스트를 선택적으로 조회

전체 실행 흐름

Contexa의 모든 AI 요청은 다음 실행 경로를 따릅니다:

Controller
AINativeProcessor
AIStrategyRegistry
Strategy
Lab
Pipeline (6 단계)

강조된 컴포넌트가 사용자가 구현할 부분입니다. 프레임워크는 나머지 모든 것 — 요청 라우팅, 분산 잠금, 감사 로깅, LLM 실행, 응답 파싱 — 을 처리합니다.

예제 도메인
이 가이드는 상품 추천 기능을 예제로 사용합니다. 동일한 패턴이 감정 분석, 코드 리뷰, 문서 요약 또는 모든 커스텀 AI 작업에 적용됩니다.

아키텍처

AI 진단 프로세스는 3계층 아키텍처를 사용합니다. 각 계층은 명확한 책임을 가집니다:

계층클래스책임
Strategy AbstractAIStrategy DiagnosisType으로 요청을 라우팅하고, 입력을 검증하며, 적절한 Lab에 위임합니다.
Lab AbstractAILab 도메인 데이터를 수집·보강한 후 LLM 처리를 위해 Pipeline에 위임합니다.
Pipeline PipelineOrchestrator 6개 처리 단계 실행: 컨텍스트 검색, 전처리, 프롬프트 생성, LLM 실행, 응답 파싱, 후처리.

사용자 구현 vs 프레임워크 제공

컴포넌트사용자 구현용도
DomainContext필수도메인 입력 데이터 모델
AIResponse필수도메인 출력 데이터 모델
PromptTemplate필수LLM용 시스템·사용자 프롬프트와 JSON 스키마
Lab필수데이터 보강 및 파이프라인 오케스트레이션
Strategy필수요청 검증 및 Lab 위임
ContextRetriever선택벡터 스토어로부터 RAG 컨텍스트
DomainResponseProcessor선택커스텀 응답 후처리
Controller필수표준·스트리밍용 REST 엔드포인트

Step 1: 도메인 타입 정의

먼저 입력 컨텍스트와 출력 응답을 정의합니다. 이는 AI 기능이 다루는 데이터 모델입니다.

DomainContext — 입력

도메인별 입력 데이터를 담기 위해 DomainContext를 확장합니다. 필수 메서드는 도메인을 식별하는 문자열을 반환하는 getDomainType() 하나뿐입니다.

Java
@Getter
public class ProductContext extends DomainContext {

    private final String productId;
    private final String category;
    private final List<String> userPreferences;

    public ProductContext(String productId, String category,
                          List<String> userPreferences) {
        this.productId = productId;
        this.category = category;
        this.userPreferences = userPreferences;
    }

    @Override
    public String getDomainType() {
        return "PRODUCT_RECOMMENDATION";
    }
}

DomainContext는 다음 내장 필드를 제공합니다: contextId(자동 생성 UUID), createdAt(타임스탬프), userId, sessionId, organizationId, metadata 맵. 추가 키-값 쌍을 첨부하려면 addMetadata(key, value)를 사용하고, 타입 지정 값을 조회하려면 getMetadata(key, Class<T>)를 사용합니다.

AIResponse — 출력

LLM이 생성할 구조화된 출력을 정의하기 위해 AIResponse를 확장합니다. 이 클래스는 LLM 응답을 역직렬화하는 JSON 파서가 사용합니다.

Java
@Getter @Setter
@NoArgsConstructor
public class ProductRecommendationResponse extends AIResponse {

    private List<Recommendation> recommendations;
    private double confidence;
    private String reasoning;

    @Getter @Setter
    @NoArgsConstructor
    public static class Recommendation {
        private String productName;
        private String reason;
        private double score;
    }
}
중요
AIResponse 하위 클래스는 무인자 생성자와 setter 메서드를 가져야 합니다 (또는 @NoArgsConstructor@Setter 사용). JSON 파서가 이를 사용해 LLM 출력을 역직렬화합니다.

Lab Request DTO

Lab 입력용 단순 DTO를 정의합니다. Strategy가 AIRequest를 변환해 전달하는 객체입니다.

Java
@Getter @AllArgsConstructor
public class ProductRecommendationRequest {
    private final String productId;
    private final String category;
    private final List<String> userPreferences;
}

타입 식별자

TemplateType은 사용할 프롬프트 템플릿을 식별합니다. DiagnosisType은 요청을 처리할 strategy를 식별합니다. 둘 다 단순 래퍼 클래스입니다.

Java
// AIRequest에서 사용해 프롬프트 템플릿을 선택
new TemplateType("PRODUCT_RECOMMENDATION")

// AIRequest에서 사용해 올바른 strategy로 라우팅
new DiagnosisType("PRODUCT_RECOMMENDATION")

Step 2: 프롬프트 템플릿 생성

프롬프트 템플릿은 LLM이 보는 내용을 정의합니다: 시스템 프롬프트(역할, 규칙, 출력 형식)와 사용자 프롬프트(데이터를 포함한 실제 질의). 표준 모드와 스트리밍 모드용으로 두 개의 템플릿이 필요합니다.

표준 프롬프트 템플릿

AbstractStandardPromptTemplate<T>를 확장합니다 (T는 응답 타입). 프레임워크는 BeanOutputConverter를 사용해 응답 클래스로부터 JSON 스키마 지시문을 자동 생성합니다.

Java
@Component
public class ProductPromptTemplate
        extends AbstractStandardPromptTemplate<ProductRecommendationResponse> {

    public ProductPromptTemplate() {
        super(ProductRecommendationResponse.class);
    }

    @Override
    public TemplateType getSupportedType() {
        return new TemplateType("PRODUCT_RECOMMENDATION");
    }

    @Override
    protected String generateDomainSystemPrompt(
            AIRequest<? extends DomainContext> request,
            String systemMetadata) {
        return """
            You are a product recommendation AI assistant.
            Analyze the user's preferences and product category
            to generate personalized recommendations.

            Rules:
            - Provide 3-5 recommendations ranked by relevance
            - Each recommendation must include a reason and score (0.0-1.0)
            - Set overall confidence based on preference match quality
            - Be specific about why each product fits the user's needs
            """;
    }

    @Override
    public String generateUserPrompt(
            AIRequest<? extends DomainContext> request,
            String contextInfo) {
        ProductContext ctx = (ProductContext) request.getContext();
        StringBuilder prompt = new StringBuilder();
        prompt.append("[Query]\n");
        prompt.append("Product ID: ").append(ctx.getProductId()).append("\n");
        prompt.append("Category: ").append(ctx.getCategory()).append("\n");
        prompt.append("User Preferences: ")
              .append(String.join(", ", ctx.getUserPreferences()))
              .append("\n");

        if (contextInfo != null && !contextInfo.isEmpty()) {
            prompt.append("\n[Context]\n").append(contextInfo);
        }
        return prompt.toString();
    }
}

스트리밍 프롬프트 템플릿

스트리밍 모드에서는 AbstractStreamingPromptTemplate를 확장합니다. 핵심 차이는 getJsonSchemaExample()입니다 — LLM이 구조화된 출력의 템플릿으로 사용할 구체적인 JSON 예제를 제공하며, 스트리밍 프로토콜 마커로 감쌉니다.

Java
@Component
public class ProductStreamingPromptTemplate
        extends AbstractStreamingPromptTemplate {

    @Override
    public TemplateType getSupportedType() {
        return new TemplateType("PRODUCT_RECOMMENDATION_STREAMING");
    }

    @Override
    protected String generateDomainSystemPrompt(
            AIRequest<? extends DomainContext> request,
            String systemMetadata) {
        return """
            You are a product recommendation AI assistant.
            First, provide a natural language analysis of the user's
            preferences, then generate structured recommendations.
            """;
    }

    @Override
    protected String getJsonSchemaExample() {
        return """
            {
              "recommendations": [
                {
                  "productName": "Example Product",
                  "reason": "Matches preference for...",
                  "score": 0.95
                }
              ],
              "confidence": 0.88,
              "reasoning": "Based on the user's preferences..."
            }
            """;
    }

    @Override
    public String generateUserPrompt(
            AIRequest<? extends DomainContext> request,
            String contextInfo) {
        ProductContext ctx = (ProductContext) request.getContext();
        return String.format(
            "Recommend products for category '%s' based on " +
            "preferences: %s",
            ctx.getCategory(),
            String.join(", ", ctx.getUserPreferences()));
    }
}
스트리밍 프로토콜 동작 방식
스트리밍 모드에서 LLM은 먼저 ###STREAMING### 접두사가 붙은 자연어 텍스트를 출력한 뒤, ===JSON_START======JSON_END=== 마커 사이에 구조화된 JSON을 출력합니다. 프레임워크가 모든 마커 감지와 JSON 추출을 자동 처리합니다. 자세한 내용은 스트리밍을 참조하세요.

Step 3: Lab 구현

Lab은 요청이 Pipeline에 도달하기 전 도메인 데이터 수집과 보강이 일어나는 곳입니다. AbstractAILab<Req, Res>를 확장하고 처리 메서드를 오버라이드합니다.

Lab 실행 패턴

Lab은 세 가지 실행 모드를 가진 템플릿 메서드 패턴을 따릅니다:

  • doProcess(request) — 동기 실행, 응답을 직접 반환
  • doProcessAsync(request) — 비동기 실행, Mono<Res> 반환
  • doProcessStream(request) — 스트리밍 실행, Flux<String> 반환

일반적 패턴: AIRequest 빌드, Pipeline 구성, 위임.

Java
@Component
public class ProductRecommendationLab
        extends AbstractAILab<ProductRecommendationRequest,
                               ProductRecommendationResponse> {

    private final PipelineOrchestrator orchestrator;

    public ProductRecommendationLab(PipelineOrchestrator orchestrator) {
        super("ProductRecommendation");
        this.orchestrator = orchestrator;
    }

    @Override
    protected ProductRecommendationResponse doProcess(
            ProductRecommendationRequest request) throws Exception {
        return doProcessAsync(request).block();
    }

    @Override
    protected Mono<ProductRecommendationResponse> doProcessAsync(
            ProductRecommendationRequest request) {
        AIRequest<ProductContext> aiRequest =
            buildAIRequest(request, "PRODUCT_RECOMMENDATION");

        PipelineConfiguration config = PipelineConfiguration.builder()
            .addStep(PipelineConfiguration.PipelineStep.CONTEXT_RETRIEVAL)
            .addStep(PipelineConfiguration.PipelineStep.PREPROCESSING)
            .addStep(PipelineConfiguration.PipelineStep.PROMPT_GENERATION)
            .addStep(PipelineConfiguration.PipelineStep.LLM_EXECUTION)
            .addStep(PipelineConfiguration.PipelineStep.RESPONSE_PARSING)
            .addStep(PipelineConfiguration.PipelineStep.POSTPROCESSING)
            .timeoutSeconds(120)
            .build();

        return orchestrator.execute(
            aiRequest, config, ProductRecommendationResponse.class);
    }

    @Override
    protected Flux<String> doProcessStream(
            ProductRecommendationRequest request) {
        AIRequest<ProductContext> aiRequest =
            buildAIRequest(request,
                           "PRODUCT_RECOMMENDATION_STREAMING");

        PipelineConfiguration config = PipelineConfiguration.builder()
            .addStep(PipelineConfiguration.PipelineStep.CONTEXT_RETRIEVAL)
            .addStep(PipelineConfiguration.PipelineStep.PREPROCESSING)
            .addStep(PipelineConfiguration.PipelineStep.PROMPT_GENERATION)
            .addStep(PipelineConfiguration.PipelineStep.LLM_EXECUTION)
            .enableStreaming(true)
            .timeoutSeconds(120)
            .build();

        return orchestrator.executeStream(aiRequest, config);
    }

    private AIRequest<ProductContext> buildAIRequest(
            ProductRecommendationRequest request,
            String templateName) {
        ProductContext context = new ProductContext(
            request.getProductId(),
            request.getCategory(),
            request.getUserPreferences());

        return new AIRequest<>(
            context,
            new TemplateType(templateName),
            new DiagnosisType("PRODUCT_RECOMMENDATION"));
    }
}

데이터 보강 패턴

더 복잡한 기능을 위해, Pipeline에 보내기 전 추가 데이터로 요청을 보강할 수 있습니다. 이 패턴은 도메인 데이터(데이터베이스·외부 API 등)를 수집해 AIRequest의 파라미터로 추가합니다.

Java
// Inside your Lab, before calling orchestrator:
private AIRequest<ProductContext> enrichRequest(
        AIRequest<ProductContext> request) {
    // Collect domain data from your services
    String productData = productService
        .getProductDetails(request.getContext().getProductId());
    String userHistory = userService
        .getPurchaseHistory(request.getContext().getUserId());

    // Add as parameters - these become available in the prompt
    request.withParameter("productData", productData);
    request.withParameter("userHistory", userHistory);
    request.withParameter("systemMetadata",
        "Product catalog size: " + productService.getCatalogSize());

    return request;
}
표준 vs 스트리밍 Pipeline 구성
표준 모드에서는 6단계 모두 포함합니다 (CONTEXT_RETRIEVAL부터 POSTPROCESSING까지). 스트리밍 모드에서는 처음 4단계(LLM_EXECUTION까지)만 사용하고 enableStreaming(true)를 설정합니다. 스트리밍 실행기가 응답을 Flux<String>으로 직접 전달합니다.

Step 4: Strategy 구현

Strategy는 DiagnosisType을 기준으로 AI 요청을 올바른 Lab으로 라우팅합니다. 입력을 검증하고, Lab을 해결하고, 요청 형식을 변환하며, 실행을 위임합니다.

AbstractAIStrategy<T, R>를 확장하고 다섯 개의 추상 메서드 validateRequest, getLabType, convertLabRequest, processLabExecution, processLabExecutionAsync를 구현합니다. 스트리밍이 필요하면 processLabExecutionStreamsupportsStreaming도 오버라이드합니다.

Java
@Component
public class ProductRecommendationStrategy
        extends AbstractAIStrategy<ProductContext,
                                    ProductRecommendationResponse> {

    public ProductRecommendationStrategy(AILabFactory labFactory) {
        super(labFactory);
    }

    @Override
    public DiagnosisType getSupportedType() {
        return new DiagnosisType("PRODUCT_RECOMMENDATION");
    }

    @Override
    public int getPriority() {
        return 100;
    }

    @Override
    protected Class<?> getLabType() {
        return ProductRecommendationLab.class;
    }

    @Override
    protected void validateRequest(
            AIRequest<ProductContext> request)
            throws DiagnosisException {
        if (request.getContext() == null) {
            throw new DiagnosisException(
                getSupportedType().name(),
                "INVALID_REQUEST",
                "ProductContext is required");
        }
        if (request.getContext().getCategory() == null) {
            throw new DiagnosisException(
                getSupportedType().name(),
                "INVALID_REQUEST",
                "Product category is required");
        }
    }

    @Override
    protected Object convertLabRequest(
            AIRequest<ProductContext> request)
            throws DiagnosisException {
        ProductContext ctx = request.getContext();
        return new ProductRecommendationRequest(
            ctx.getProductId(),
            ctx.getCategory(),
            ctx.getUserPreferences());
    }

    @Override
    protected ProductRecommendationResponse processLabExecution(
            Object lab, Object labRequest,
            AIRequest<ProductContext> request) throws Exception {
        ProductRecommendationLab productLab =
            (ProductRecommendationLab) lab;
        return productLab.process(
            (ProductRecommendationRequest) labRequest);
    }

    @Override
    protected Mono<ProductRecommendationResponse>
            processLabExecutionAsync(
                Object lab, Object labRequest,
                AIRequest<ProductContext> request) {
        ProductRecommendationLab productLab =
            (ProductRecommendationLab) lab;
        return productLab.processAsync(
            (ProductRecommendationRequest) labRequest);
    }

    @Override
    protected Flux<String> processLabExecutionStream(
            Object lab, Object labRequest,
            AIRequest<ProductContext> request) {
        ProductRecommendationLab productLab =
            (ProductRecommendationLab) lab;
        return productLab.processStream(
            (ProductRecommendationRequest) labRequest);
    }

    @Override
    public boolean supportsStreaming() {
        return true;
    }
}

템플릿 메서드 실행 순서

AbstractAIStrategy는 템플릿 메서드 패턴을 사용합니다. 각 공개 진입점은 검증·Lab 해석·요청 변환을 공통으로 수행한 뒤 매칭되는 processLabExecution* 메서드로 분기합니다:

  1. validateRequest(request) — 들어오는 AIRequest를 검증합니다.
  2. getRequiredLab()getLabType()으로 AILabFactory를 통해 Lab을 해석합니다.
  3. convertLabRequest(request)AIRequest를 Lab의 요청 형식으로 변환합니다.
  4. 공개 진입점별로 분기합니다: execute(...)processLabExecution(...), executeAsync(...)processLabExecutionAsync(...), executeStream(...)supportsStreaming() 확인 후 processLabExecutionStream(...)을 호출합니다.

Step 5: RAG 컨텍스트 추가 (선택)

기능이 LLM 프롬프트에 벡터 스토어의 관련 컨텍스트를 보강해야 한다면 ContextRetriever 클래스를 확장합니다. Pipeline의 CONTEXT_RETRIEVAL 단계가 등록된 retriever를 자동으로 호출합니다.

Java
@Component
public class ProductContextRetriever extends ContextRetriever {

    public ProductContextRetriever(
            VectorStore vectorStore,
            ContexaRagProperties ragProperties) {
        super(vectorStore, ragProperties);
    }

    @Override
    public ContextRetrievalResult retrieveContext(
            AIRequest<? extends DomainContext> request) {
        if (!(request.getContext() instanceof ProductContext ctx)) {
            return new ContextRetrievalResult("", List.of(), Map.of());
        }

        String query = ctx.getCategory() + " " +
            String.join(" ", ctx.getUserPreferences());

        List<Document> docs = vectorOperations
            .searchSimilar(query);

        String contextInfo = docs.stream()
            .map(Document::getText)
            .collect(Collectors.joining("\n\n"));

        return new ContextRetrievalResult(
            contextInfo,
            docs,
            Map.of("retrieverType", "product",
                    "timestamp", Instant.now().toString()));
    }
}

조회된 contextInfo는 PROMPT_GENERATION 단계에서 프롬프트 템플릿의 generateUserPrompt() 메서드에 contextInfo 파라미터로 자동 전달됩니다.

질의 소스
기본 ContextRetrieverrequest.getParameter("naturalLanguageQuery", String.class)를 읽습니다. setNaturalLanguageQuery(...)만 호출했다면 extractQueryFromRequest()를 오버라이드하거나 동일한 값을 요청 파라미터로 추가해야 합니다.
벡터 스토어 설정
RAG를 사용하려면 벡터 스토어가 구성되어 있어야 합니다. Contexa는 pgvector를 기본 지원합니다. contexa.rag.defaults.top-k·contexa.rag.defaults.similarity-threshold 등 RAG 속성은 AI 설정을 참조하세요.

Step 6: 모든 것 연결

AIRequest 객체를 빌드하고 streaming 서비스에 위임하는 REST 컨트롤러를 만듭니다. 이것이 AI 기능의 진입점입니다.

두 가지 실행 경로

다음 두 경로 중 하나로 AI 기능을 호출할 수 있습니다:

  • Strategy/Lab 경로AINativeProcessor(또는 StandardStreamingService)를 사용해 AIStrategyRegistry를 거쳐 Strategy → Lab → Pipeline으로 라우팅. 분산 잠금과 감사 로깅 제공.
  • Direct Lab 경로 — Lab을 직접 주입해 processAsync() 또는 processStream() 호출. 더 단순하지만 strategy 라우팅과 감사를 우회.

Strategy/Lab 경로 컨트롤러 (권장)

Java
@RestController
@RequestMapping("/api/recommend")
public class ProductRecommendationController {

    private final StandardStreamingService streamingService;
    private final AINativeProcessor<ProductContext> aiProcessor;

    public ProductRecommendationController(
            StandardStreamingService streamingService,
            AINativeProcessor<ProductContext> aiProcessor) {
        this.streamingService = streamingService;
        this.aiProcessor = aiProcessor;
    }

    @PostMapping
    public Mono<ResponseEntity<ProductRecommendationResponse>>
            recommend(@RequestBody RecommendRequest body) {
        AIRequest<ProductContext> request = buildRequest(body,
            "PRODUCT_RECOMMENDATION");
        return streamingService.process(
                request, aiProcessor,
                ProductRecommendationResponse.class)
            .map(ResponseEntity::ok);
    }

    @PostMapping(value = "/stream",
        produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> recommendStream(
            @RequestBody RecommendRequest body) {
        AIRequest<ProductContext> request = buildRequest(body,
            "PRODUCT_RECOMMENDATION_STREAMING");
        return streamingService.stream(request, aiProcessor);
    }

    private AIRequest<ProductContext> buildRequest(
            RecommendRequest body, String templateName) {
        ProductContext context = new ProductContext(
            body.getProductId(),
            body.getCategory(),
            body.getPreferences());
        return new AIRequest<>(
            context,
            new TemplateType(templateName),
            new DiagnosisType("PRODUCT_RECOMMENDATION"));
    }
}

애플리케이션 설정

Java
@SpringBootApplication
@EnableAISecurity  // from io.contexa.contexacommon.annotation
public class MyAiApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyAiApplication.class, args);
    }
}

최소 구성

YAML
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        model: qwen2.5:7b

spring:
  ai:
    security:
      layer1:
        model: qwen2.5:7b
      layer2:
        model: gpt-4o-mini
      tiered:
        layer1:
          timeout-ms: 30000
        layer2:
          timeout-ms: 60000
contexa:
  streaming:
    timeout: PT5M

RAG, Advisor, 모델 계층 설정을 포함한 전체 속성 레퍼런스는 AI 설정을 참조하세요.

설정

커스텀 AI 기능에 영향을 주는 주요 속성:

속성설명기본값
spring.ai.security.layer1.modelLayer 1 분석에 사용되는 기본 모델qwen2.5:7b
spring.ai.security.layer2.modelLayer 2 분석에 사용되는 기본 모델gpt-4o-mini
spring.ai.security.tiered.layer1.timeout-msLayer 1 모델 실행에 적용되는 타임아웃30000
spring.ai.security.tiered.layer2.timeout-msLayer 2 모델 실행에 적용되는 타임아웃60000
contexa.streaming.timeout선택된 strategy가 청크 출력을 방출할 때 사용되는 스트리밍 타임아웃PT5M
전체 설정 레퍼런스
AI 속성 전체 목록은 AI 설정을 참조하세요.

API 레퍼런스

AIStrategy<T, R> 인터페이스
public interface AIStrategy<T extends DomainContext, R extends AIResponse>
getSupportedType() DiagnosisType
이 strategy가 처리하는 DiagnosisType을 반환합니다.
getPriority() int
동일 타입 strategy 간 우선순위. 낮을수록 우선.
execute(AIRequest<T> request, Class<R> responseType) R
동기 실행.
executeAsync(AIRequest<T> request, Class<R> responseType) Mono<R>
비동기 실행.
executeStream(AIRequest<T> request, Class<R> responseType) Flux<String>
스트리밍 실행.
supportsStreaming() boolean
이 strategy가 스트리밍을 지원하는지 여부. 기본값: false.
AbstractAIStrategy<T, R> 추상 메서드
public abstract class AbstractAIStrategy<T extends DomainContext, R extends AIResponse> implements AIStrategy<T, R>

생성자: protected AbstractAIStrategy(AILabFactory labFactory)

getLabType() Class<?>
이 strategy가 위임할 Lab 클래스를 반환합니다.
validateRequest(AIRequest<T> request) void
요청을 검증합니다. 잘못된 입력은 DiagnosisException을 발생시킵니다.
convertLabRequest(AIRequest<T> request) Object
AIRequest를 Lab의 요청 타입으로 변환합니다.
processLabExecution(Object lab, Object labRequest, AIRequest<T> request) R
동기 lab 실행.
processLabExecutionAsync(Object lab, Object labRequest, AIRequest<T> request) Mono<R>
비동기 lab 실행. 추상 메서드로 선언되어 있어 모든 서브클래스가 반드시 구현해야 합니다.
processLabExecutionStream(Object lab, Object labRequest, AIRequest<T> request) Flux<String>
스트리밍 lab 실행. 추상이 아니며 기본 구현은 Flux.error(new UnsupportedOperationException(...))을 반환합니다. 스트리밍을 활성화하려면 supportsStreaming()과 함께 오버라이드합니다.
AbstractAILab<Req, Res>
public abstract class AbstractAILab<Req, Res> implements AILab<Req, Res>

생성자: protected AbstractAILab(String labName)

doProcess(Req request) Res
동기 처리 로직 오버라이드.
doProcessAsync(Req request) Mono<Res>
비동기 처리 오버라이드. 기본 구현은 doProcess()를 Mono로 감쌉니다.
doProcessStream(Req request) Flux<String>
스트리밍 처리 오버라이드.
validateRequest(Req request) void
처리 전에 호출됩니다. lab 수준 검증 추가용 오버라이드.
preProcess(Req request) void
doProcess() 전에 호출됩니다. 전처리 훅용 오버라이드.
postProcess(Req request, Res result) void
doProcess() 후에 호출됩니다. 후처리 훅용 오버라이드.
AIRequest<T>
public class AIRequest<T extends DomainContext>

생성자: public AIRequest(T context, TemplateType templateType, DiagnosisType diagnosisType)

withParameter(String key, Object value) AIRequest<T>
요청에 파라미터를 추가합니다. 체이닝을 위해 this를 반환합니다.
getParameter(String key, Class<P> type) P
키로 타입이 지정된 파라미터를 조회합니다.
getContext() T
도메인 컨텍스트를 반환합니다.
getPromptTemplate() TemplateType
프롬프트 선택용 템플릿 타입을 반환합니다.
setNaturalLanguageQuery(String query) void
자체 템플릿 또는 strategy에서 사용할 요청 필드를 설정합니다. 기본 ContextRetrievergetParameter(...)를 통해 "naturalLanguageQuery" 요청 파라미터를 찾고, 파라미터가 없으면 context.toString()으로 폴백합니다.
AIStrategyRegistry
public class AIStrategyRegistry

Spring이 모든 AIStrategy 빈을 레지스트리에 주입합니다. 동일한 DiagnosisType을 선언한 전략이 여러 개 있을 때는 getPriority() 값이 가장 낮은 전략이 유지됩니다.

getStrategy(DiagnosisType diagnosisType) AIStrategy<T, R>
주어진 DiagnosisType으로 등록된 전략을 반환합니다. 등록된 전략이 없으면 코드 STRATEGY_NOT_FOUNDDiagnosisException을 던집니다.
executeStrategy(AIRequest<T> request, Class<R> responseType) R
동기 실행. 내부적으로 executeStrategyAsync(...)를 호출한 뒤 최대 5초간 block합니다.
executeStrategyAsync(AIRequest<T> request, Class<R> responseType) Mono<R>
전략을 조회해 비동기로 실행합니다. 요청에 DiagnosisType이 없으면 코드 MISSING_DIAGNOSIS_TYPEDiagnosisException을 방출합니다.
executeStrategyStream(AIRequest<T> request, Class<R> responseType) Flux<String>
전략을 조회해 스트리밍 모드로 실행합니다. 요청에 DiagnosisType이 없으면 코드 MISSING_DIAGNOSIS_TYPEDiagnosisException을 방출합니다.
AINativeProcessor<T>
public final class AINativeProcessor<T extends DomainContext> implements AICoreOperations<T>

AI 처리의 기본 진입점입니다. strategy 실행을 분산 잠금과 감사 로깅으로 감쌉니다.

process(AIRequest<T> request, Class<R> responseType) Mono<R>
분산 잠금과 감사 추적과 함께 strategy 계층을 통해 요청을 처리합니다.
processStream(AIRequest<T> request) Flux<String>
strategy 계층을 통한 스트리밍 처리.
PromptTemplate 인터페이스

AbstractStandardPromptTemplate<T>

생성자: protected AbstractStandardPromptTemplate(Class<T> responseType)

getSupportedType() TemplateType
이 템플릿이 처리하는 템플릿 타입을 반환합니다.
generateDomainSystemPrompt(AIRequest<?> request, String systemMetadata) String
도메인 특화 시스템 프롬프트를 반환합니다. 프레임워크가 JSON 형식 지시문을 자동으로 추가합니다.
generateUserPrompt(AIRequest<?> request, String contextInfo) String
사용자 프롬프트를 반환합니다. contextInfo는 사용 가능한 RAG 조회 컨텍스트를 담습니다.

AbstractStreamingPromptTemplate

위와 동일한 메서드와 함께:

getJsonSchemaExample() String
스트리밍 모드에서 LLM의 구조화된 출력 템플릿으로 사용할 JSON 예제를 반환합니다.