一键导入
scaffold-openai-service
OpenAI/Mock 이중 구현 서비스 스캐폴딩 (@ConditionalOnProperty 패턴). 새로운 LLM 연동 서비스 추가, OpenAI 서비스 생성, AI 기능 추가 요청 시 사용. Mock/Real 구현이 필요한 모든 AI 서비스에 사용.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
OpenAI/Mock 이중 구현 서비스 스캐폴딩 (@ConditionalOnProperty 패턴). 새로운 LLM 연동 서비스 추가, OpenAI 서비스 생성, AI 기능 추가 요청 시 사용. Mock/Real 구현이 필요한 모든 AI 서비스에 사용.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | scaffold-openai-service |
| description | OpenAI/Mock 이중 구현 서비스 스캐폴딩 (@ConditionalOnProperty 패턴). 새로운 LLM 연동 서비스 추가, OpenAI 서비스 생성, AI 기능 추가 요청 시 사용. Mock/Real 구현이 필요한 모든 AI 서비스에 사용. |
이 프로젝트의 핵심 패턴인 OpenAI/Mock 이중 구현을 자동으로 스캐폴딩합니다.
openai.enabled=true일 때 실제 OpenAI API를 호출하고, false일 때 Mock 구현으로 fallback합니다.
┌─────────────┐
│ Interface │ (서비스 계약)
│ XxxService │
└──────┬──────┘
│
┌────────────┴────────────┐
│ │
┌─────────┴──────────┐ ┌─────────┴──────────┐
│ MockXxxService │ │ OpenAiXxxService │
│ (항상 로드) │ │ (@Primary) │
│ (fallback 제공) │ │ (openai.enabled=true│
│ │ │ 일 때만 로드) │
└────────────────────┘ └──────────────────────┘
// 위치: interfaces/rest/{domain}/ 또는 application/{domain}/
public interface XxxService {
XxxResult execute(XxxInput input);
}
@Component
public class MockXxxService implements XxxService {
private static final Logger log = LoggerFactory.getLogger(MockXxxService.class);
@Override
public XxxResult execute(XxxInput input) {
log.info("[Mock] XxxService called with: {}", input);
return new XxxResult(/* 합리적인 기본값 */);
}
}
@Component
@Primary
@ConditionalOnProperty(prefix = "openai", name = "enabled", havingValue = "true")
public class OpenAiXxxService implements XxxService {
private static final Logger log = LoggerFactory.getLogger(OpenAiXxxService.class);
private final RestClient restClient;
private final String chatModel;
private final PromptRegistry promptRegistry;
private final MockXxxService fallback;
public OpenAiXxxService(
@Value("${openai.api-key}") String apiKey,
@Value("${openai.base-url:https://api.openai.com/v1}") String baseUrl,
@Value("${openai.model.chat-medium:gpt-4.1-mini}") String chatModel,
PromptRegistry promptRegistry,
MockXxxService fallback) {
this.restClient = RestClient.builder()
.baseUrl(baseUrl)
.defaultHeader("Authorization", "Bearer " + apiKey)
.build();
this.chatModel = chatModel;
this.promptRegistry = promptRegistry;
this.fallback = fallback;
}
@Override
public XxxResult execute(XxxInput input) {
try {
String systemPrompt = promptRegistry.get("xxx-system");
// ... OpenAI API 호출 로직
} catch (Exception e) {
log.warn("OpenAi Xxx failed, falling back to mock: {}", e.getMessage());
return fallback.execute(input);
}
}
}
프롬프트 파일: backend/app-api/src/main/resources/prompts/xxx-system.txt
당신은 Bio-Rad 기술지원 전문가입니다.
... 프롬프트 내용 ...
사용법:
// 기본
String prompt = promptRegistry.get("xxx-system");
// 변수 치환
String prompt = promptRegistry.get("xxx-system", Map.of("question", question));
| 티어 | 모델 | 용도 | @Value |
|---|---|---|---|
| Heavy | gpt-5-mini | 복잡한 추론 (답변 작성, 비평) | ${openai.model.chat-heavy:gpt-5-mini} |
| Medium | gpt-4.1-mini | 중간 복잡도 (검증, 검색, 리랭킹) | ${openai.model.chat-medium:gpt-4.1-mini} |
| Light | gpt-5-nano | 경량 작업 (변환, 보강, 번역) | ${openai.model.chat-light:gpt-5-nano} |
선택 기준: 출력 복잡도와 추론 깊이에 따라 결정. 단순 변환/분류는 Light, 판단/평가는 Medium, 긴 텍스트 생성은 Heavy.
@Primary + @ConditionalOnProperty 있음prompts/ 디렉토리에 외부화됨| File | Purpose |
|---|---|
backend/app-api/src/main/resources/application.yml | openai.enabled, 모델 티어 설정 |
backend/app-api/src/main/resources/prompts/ | 외부화된 프롬프트 디렉토리 |
backend/app-api/src/main/java/.../interfaces/rest/search/ | 기존 OpenAI 서비스 참고 예시 |
세션 변경사항을 분석하여 검증 스킬 누락을 탐지합니다. 기존 스킬을 동적으로 탐색하고, 새 스킬을 생성하거나 기존 스킬을 업데이트한 뒤 CLAUDE.md를 관리합니다.
REST API 응답 DTO 일관성 및 에러 코드 표준 검증. API 엔드포인트 추가/수정, DTO 변경, 에러 처리 수정 후 사용. API 계약 변경이 프론트엔드에 영향을 줄 수 있을 때 사용.
Flyway DB 마이그레이션 일관성 검증. 마이그레이션 추가/수정 후 사용.
프론트엔드 UI 컴포넌트 품질 검증 (shadcn/ui + Tailwind 기준). UI 컴포넌트 수정 후 사용.
KB 비동기 인덱싱 파이프라인 검증. 인덱싱 관련 코드 수정 후 사용.
RAG 파이프라인 (답변 작성 + 분석 + 다운로드) 검증. 답변 작성/분석/다운로드 관련 코드 수정 후 사용.