🤔 현재 문제점
- LLM이 항상 Chroma 벡터 검색 결과만 Context로 받는다. "출고작업 몇건" 같은 DB 조회가 필요한 질문도 문서에서 찾으려 한다.
- SQL 생성은 되는데 Chroma에서 엉뚱한 스키마를 가져와서 잘못된 SQL이 만들어지거나 실행 실패한다.
문서 기반 RAG는 나쁘지 않다.

그러나, DB 실시간 조회가 필요한 경우 바로 형편없어진다.

Phase 0. 현재 : 기본 QA
모델 → ollama CPU 온리 모델도 가능은 함 (llama3.2:3b~8b) → (인터넷, 무료 but 토큰 제한) groq / google(gemini) api key Phase I. 데모용 (단기) : QA+ DB조회
모델 → llama3.1:8b (8gb)~ llama3.3:70b ↑ (폐쇄망ok, but GPU 없이 검증불가) → GPT-4o , Claude Sonnet 4.5 , Claude Haiku (폐쇄망x, 토큰 비쌈-검증불가) GPU → 최소 rtx 3090~ 4090Phase II. 운영용 (*TBD) : 복합분석 + 의사결정
PhaseIII. 장기 목표 (TBD) : 자동화 지능화
모델 → 추후 fine-tuned 70b+ GPU → a10g or rtx 5090 오픈 API로 RAG 파이프라인 먼저 맛보기 ( groq 이나, google ai studio api 키 발급)
1. Groq (그록) API특징: Llama, Gemma 등 오픈소스 언어모델을 초고속(LPU 하드웨어 기반)으로 무료 제공하는 데 특화되어 있습니다.
속도가 압도적으로 빠릅니다.
현재 무료 등급(Free Tier)에서도 강력한 성능의 모델(예: Llama 3)을 제약 없이 빠르게 테스트할 수 있습니다.
2. Google Gemini (구글 제미나이) API특징: Google의 고성능 최신 AI 모델과 방대한 컨텍스트 창(긴 문서 처리 능력)을 제공합니다.
기본 제공되는 무료 플랜(Free Tier)이 있어 가볍게 테스트하거나 소규모 프로젝트를 개발하기 좋습니다.
무료 플랜 대비 더 높은 사용량과 기능을 원할 경우, Google Cloud 기반의 종량제(Pay-as-you-go)로 전환하여 유연하게 확장할 수 있습니다.
⚠️ 환경변수 영구 등록 후에는 반드시 새 PowerShell을 열어야 반영이 된다
# GROQ API 키 [System.Environment]::SetEnvironmentVariable("GROQ_API_KEY", "gsk_전체키", "User") # Google AI API 키 [System.Environment]::SetEnvironmentVariable("GOOGLE_AI_API_KEY", Alz_전체키", "User")
개발을 하면서 RAG에 프롬프트 가이드를 왜 작성해줘야할까 의문이 들었다.
e,g, 가령 다음과 같이 가이드를해주고 있다.
private String classifyQuestion(String question, ChatClient chatClient) {
String result = chatClient.prompt()
.system("""
질문을 분석해서 필요한 데이터 소스를 판단하세요.
DB: 현재 데이터 조회 (건수, 현황, 수량, 날짜별, 작업, 재고, 에러, 알람, 위치)
DOC: 사용법, 절차, 설명, 매뉴얼, 설계서 내용
BOTH: DB + 문서 둘 다 필요
LLM: 일반 지식으로 답변 가능 (기술 설명, 개념 등)
DB, DOC, BOTH, LLM 중 하나만 출력하세요.
""")
.user(question)
.call()
.content()
.trim()
.toUpperCase();
if (result.contains("BOTH")) return "BOTH";
if (result.contains("DB")) return "DB";
if (result.contains("DOC")) return "DOC";
return "LLM";
}
private String generateSql(String question, String schemaContext, ChatClient chatClient) {
String result = chatClient.prompt()
.system("""
당신은 Oracle DB 전문가입니다.
제공된 [스키마 정보]를 참고하여 질문에 맞는 Oracle SQL을 생성하세요.
규칙:
- SELECT 문만 생성
- SQL 코드만 출력 (설명, 마크다운 없이)
- FETCH FIRST 20 ROWS ONLY 로 결과 제한
- 스키마명 없이 테이블명만 사용
- TOP 문법 사용 금지 (Oracle 미지원)
- DB 조회가 필요 없는 질문이면 정확히 'NONE' 만 출력
""")
.user("""
[스키마 정보]
%s
[질문]
%s
SQL (또는 NONE):
""".formatted(schemaContext, question))
.call()
.content();
return result.replaceAll("```sql", "")
.replaceAll("```", "")
.trim();
}
private String generateAnswer(String question, String dbContext,
String vectorContext, ChatClient chatClient) {
StringBuilder context = new StringBuilder();
if (!dbContext.isEmpty()) {
context.append("[실시간 DB 데이터]\n").append(dbContext).append("\n\n");
}
if (!vectorContext.isEmpty()) {
context.append("[문서 데이터]\n").append(vectorContext);
}
if (context.isEmpty()) {
context.append("관련 데이터 없음");
}
return chatClient.prompt()
.options(org.springframework.ai.chat.prompt.ChatOptions.builder()
.model(modelConfig.getCurrentModel())
.build())
.system("""
당신은 자동화창고(WCS) 시스템 및 도메인의 전문가 어시스턴트입니다.
답변 우선순위:
1. [DB 데이터] 있으면 → DB 데이터 기반 답변
2. [문서 데이터] 있으면 → 문서 기반 답변
3. 둘 다 없으면 → 보유한 지식으로 자유롭게 답변
DB나 문서 데이터를 추측하거나 만들어내지 마세요.
답변은 한국어로 작성하세요.
""")
.user("""
%s
[질문]
%s
""".formatted(context.toString(), question))
.call()
.content();
}
컨텍스트(Context) 고립 및 환각 방지
LLM이 외부 문서보다 자신이 사전에 학습한 지식을 우선시하여 거짓 정보를 생성(할루시네이션)하는 것을 막아야 합니다.지시어 예시: "오직 제공된 내의 정보만 사용하여 답변하시오.", "외부 지식을 사용하지 마시오."
출처(Citation) 및 근거 명시
답변의 신뢰성을 높이려면 AI가 어느 문서에서 정보를 가져왔는지 역추적할 수 있어야 합니다.지시어 예시: "답변의 각 문장 끝에 [문서 제목]을 인용 표기하시오."
모호한 질문에 대한 대응 지침
검색된 문서에 질문과 일치하는 내용이 없을 때 LLM이 엉뚱한 말을 하지 않도록 대응 방식을 미리 지정합니다.지시어 예시: "문서에서 질문에 대한 답을 찾을 수 없는 경우, 문서를 유추하여 작성하지 말고 '제공된 문서에서 관련 정보를 찾을 수 없습니다'라고만 답하시오."
출력 형식(Format) 제어
사용자가 원하는 형태로 정보를 가공하여 출력하도록 포맷을 제한합니다.지시어 예시: "결과는 반드시 3개의 글머리 기호로 요약하시오.", "표 형태로 정리하시오."
결론은 고로 필요하다 !
💡 도커 내 ChromaDB의 매 실행(STOP-START)마다 RAG 데이터를 POST 해줘야할까?
- Chroma 데이터 유지 여부는 볼륨 마운트에 달려있다.
# 현재 설정 확인 docker inspect chromadb --format "{{json .Mounts}}" PS C:\Project\wms-rag-service> docker inspect chromadb --format "{{json .Mounts}}" [{"Type":"bind","Source":"C:\\Project\\wms-rag-service\\chroma-data","Destination":"/chroma/chroma","Mode":"","RW":true,"Propagation":"rprivate"}]위와 같다면, Source 경로(C:\Project\wms-rag-service\chroma-data) 내 SQLite 파일로 저장되어있다.
*# 파일 목록 Get-ChildItem C:\Project\wms-rag-service\chroma-data -Recurse *# 문서 수 확인 Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/v2/tenants/SpringAiTenant/databases/SpringAiDatabase/collections/82977eae-5e2e-47ae-bbdc-3735b506cf0a/count"이미 업로드 된 RAG 데이터 정보를 확인해볼 수 있다.
PS C:\Project\wms-rag-service> docker run -d -p 8000:8000 -v ./chroma-data:/data chromadb/chroma
cd84452fdecff908c12a89718b4f8af333076e874958b1f614b5932bef1b6486
창고 내 모든 사용자에게 챗봇 서비스가 제공된다고 치자.
사용자는 이와 같이 특정 USER의 PASSWORD를 질문해보았다.

-> NO.
- 프롬프트 가이드 -> 소프트 블로킹 (우회 가능)
- 코드 레벨 차단 -> 하드 블로킹 (우회 불가)