커스텀 AI 만들기
Contexa AI 진단 프로세스를 사용해 자체 AI 기반 기능을 구축합니다. 이 가이드는 Strategy, Lab, Pipeline 아키텍처로 도메인 타입 정의부터 REST 엔드포인트 노출까지 완전한 커스텀 AI 기능 생성 과정을 안내합니다.
무엇을 만들게 됩니까
이 가이드를 따라 다음 기능을 모두 갖춘 AI 기능을 완성합니다:
- REST API를 통해 도메인별 입력을 수신
- AI 진단 프로세스로 요청 라우팅
- JSON 파싱이 적용된 구조화된 LLM 응답 생성
- 표준(동기) 모드와 스트리밍(SSE) 모드 모두 지원
- 벡터 스토어에서 RAG 컨텍스트를 선택적으로 조회
전체 실행 흐름
Contexa의 모든 AI 요청은 다음 실행 경로를 따릅니다:
강조된 컴포넌트가 사용자가 구현할 부분입니다. 프레임워크는 나머지 모든 것 — 요청 라우팅, 분산 잠금, 감사 로깅, 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() 하나뿐입니다.
@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 파서가 사용합니다.
@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를 변환해 전달하는 객체입니다.
@Getter @AllArgsConstructor
public class ProductRecommendationRequest {
private final String productId;
private final String category;
private final List<String> userPreferences;
}
타입 식별자
TemplateType은 사용할 프롬프트 템플릿을 식별합니다. DiagnosisType은 요청을 처리할 strategy를 식별합니다. 둘 다 단순 래퍼 클래스입니다.
// AIRequest에서 사용해 프롬프트 템플릿을 선택
new TemplateType("PRODUCT_RECOMMENDATION")
// AIRequest에서 사용해 올바른 strategy로 라우팅
new DiagnosisType("PRODUCT_RECOMMENDATION")
Step 2: 프롬프트 템플릿 생성
프롬프트 템플릿은 LLM이 보는 내용을 정의합니다: 시스템 프롬프트(역할, 규칙, 출력 형식)와 사용자 프롬프트(데이터를 포함한 실제 질의). 표준 모드와 스트리밍 모드용으로 두 개의 템플릿이 필요합니다.
표준 프롬프트 템플릿
AbstractStandardPromptTemplate<T>를 확장합니다 (T는 응답 타입). 프레임워크는 BeanOutputConverter를 사용해 응답 클래스로부터 JSON 스키마 지시문을 자동 생성합니다.
@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 예제를 제공하며, 스트리밍 프로토콜 마커로 감쌉니다.
@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 구성, 위임.
@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의 파라미터로 추가합니다.
// 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;
}
표준 모드에서는 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를 구현합니다. 스트리밍이 필요하면 processLabExecutionStream과 supportsStreaming도 오버라이드합니다.
@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* 메서드로 분기합니다:
validateRequest(request)— 들어오는AIRequest를 검증합니다.getRequiredLab()—getLabType()으로AILabFactory를 통해 Lab을 해석합니다.convertLabRequest(request)—AIRequest를 Lab의 요청 형식으로 변환합니다.- 공개 진입점별로 분기합니다:
execute(...)는processLabExecution(...),executeAsync(...)는processLabExecutionAsync(...),executeStream(...)은supportsStreaming()확인 후processLabExecutionStream(...)을 호출합니다.
Step 5: RAG 컨텍스트 추가 (선택)
기능이 LLM 프롬프트에 벡터 스토어의 관련 컨텍스트를 보강해야 한다면 ContextRetriever 클래스를 확장합니다. Pipeline의 CONTEXT_RETRIEVAL 단계가 등록된 retriever를 자동으로 호출합니다.
@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 파라미터로 자동 전달됩니다.
기본
ContextRetriever는 request.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 경로 컨트롤러 (권장)
@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"));
}
}
애플리케이션 설정
@SpringBootApplication
@EnableAISecurity // from io.contexa.contexacommon.annotation
public class MyAiApplication {
public static void main(String[] args) {
SpringApplication.run(MyAiApplication.class, args);
}
}
최소 구성
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.model | Layer 1 분석에 사용되는 기본 모델 | qwen2.5:7b |
spring.ai.security.layer2.model | Layer 2 분석에 사용되는 기본 모델 | gpt-4o-mini |
spring.ai.security.tiered.layer1.timeout-ms | Layer 1 모델 실행에 적용되는 타임아웃 | 30000 |
spring.ai.security.tiered.layer2.timeout-ms | Layer 2 모델 실행에 적용되는 타임아웃 | 60000 |
contexa.streaming.timeout | 선택된 strategy가 청크 출력을 방출할 때 사용되는 스트리밍 타임아웃 | PT5M |
AI 속성 전체 목록은 AI 설정을 참조하세요.
API 레퍼런스
AIStrategy<T, R> 인터페이스
public interface AIStrategy<T extends DomainContext, R extends AIResponse>
DiagnosisType을 반환합니다.false.AbstractAIStrategy<T, R> 추상 메서드
public abstract class AbstractAIStrategy<T extends DomainContext, R extends AIResponse> implements AIStrategy<T, R>
생성자: protected AbstractAIStrategy(AILabFactory labFactory)
DiagnosisException을 발생시킵니다.AIRequest를 Lab의 요청 타입으로 변환합니다.Flux.error(new UnsupportedOperationException(...))을 반환합니다. 스트리밍을 활성화하려면 supportsStreaming()과 함께 오버라이드합니다.AbstractAILab<Req, Res>
public abstract class AbstractAILab<Req, Res> implements AILab<Req, Res>
생성자: protected AbstractAILab(String labName)
doProcess()를 Mono로 감쌉니다.doProcess() 전에 호출됩니다. 전처리 훅용 오버라이드.doProcess() 후에 호출됩니다. 후처리 훅용 오버라이드.AIRequest<T>
public class AIRequest<T extends DomainContext>
생성자: public AIRequest(T context, TemplateType templateType, DiagnosisType diagnosisType)
this를 반환합니다.ContextRetriever는 getParameter(...)를 통해 "naturalLanguageQuery" 요청 파라미터를 찾고, 파라미터가 없으면 context.toString()으로 폴백합니다.AIStrategyRegistry
public class AIStrategyRegistry
Spring이 모든 AIStrategy 빈을 레지스트리에 주입합니다. 동일한 DiagnosisType을 선언한 전략이 여러 개 있을 때는 getPriority() 값이 가장 낮은 전략이 유지됩니다.
DiagnosisType으로 등록된 전략을 반환합니다. 등록된 전략이 없으면 코드 STRATEGY_NOT_FOUND의 DiagnosisException을 던집니다.executeStrategyAsync(...)를 호출한 뒤 최대 5초간 block합니다.DiagnosisType이 없으면 코드 MISSING_DIAGNOSIS_TYPE의 DiagnosisException을 방출합니다.DiagnosisType이 없으면 코드 MISSING_DIAGNOSIS_TYPE의 DiagnosisException을 방출합니다.AINativeProcessor<T>
public final class AINativeProcessor<T extends DomainContext> implements AICoreOperations<T>
AI 처리의 기본 진입점입니다. strategy 실행을 분산 잠금과 감사 로깅으로 감쌉니다.
PromptTemplate 인터페이스
AbstractStandardPromptTemplate<T>
생성자: protected AbstractStandardPromptTemplate(Class<T> responseType)
contextInfo는 사용 가능한 RAG 조회 컨텍스트를 담습니다.AbstractStreamingPromptTemplate
위와 동일한 메서드와 함께: