Aikit — AI + RAG 기반 민원 처리 PoC

민생지원금 신청/검증을 위한 AI 보조 백엔드. Spring Boot 3.5 + Spring AI로 Tool‑Calling 파이프라인을 구성하고, Qdrant 기반 RAG로 규정 버전별 검증을 일관 적용합니다.

Spring Boot 3.5.4 Spring AI 1.0.1 Qdrant(Vector) JPA Specification Java 17 · Gradle 8.14.3
기간
2025‑08‑20 ~ 2025‑08‑22
3일
역할
설계·구현·통합
백엔드/AI
스코프
REST 5 · 엔티티 4
RAG + Tools
핵심 요약
  • LLM Tool-Calling으로 조회·검증·일괄 업데이트 자동화
  • Qdrant 벡터 검색 + 프롬프트 버저닝으로 규정 버전 RAG
  • JPA Specification 빌더로 타입세이프 필터/업데이트
  • 3일/4커밋 PoC · Docker Compose로 재현

주요 기능 (핵심만 빠르게)

🔧 Tool‑Calling 파이프라인

ChatClient + ToolCallbacks로 내부 도메인 기능을 화이트리스트 실행.

  • fetch/validate/update 도구 스코프 최소화
  • 내부 실행 허용 플래그로 오남용 방지

📚 규정 RAG (Qdrant)

프롬프트/규정 문서를 벡터화 저장, 질문 시 컨텍스트 주입.

  • payload 필드 선별 추출로 토큰 절약
  • promptType 필터로 버전 제어

🧩 타입세이프 쿼리/일괄 업데이트

문자열 조립 없이 JPA Specification으로 where/order/limit 처리.

  • eq/in/like/gte/lte/between 지원
  • 요약 리포트 반환으로 감사 추적

케이스 스터디 — Problem → Solution → Impact

Problem

규정/버전별 검증 로직 상이, 운영 쿼리 스크립트 의존, 사람 오류.

  • 규정 변경 시 일관성 저하
  • 다양 조건 조회/일괄 변경의 반복

Solution

RAG로 규정 문맥 주입 + Tool‑Calling으로 안전한 내부 함수 실행 + JPA Spec.

  • 버전 태깅·벡터 검색
  • 화이트리스트 도구만 노출

Impact

검증 일관성 향상·온보딩 단축(정량 수치로 교체 권장: 예) 수작업 검토 30%↓).

  • 업데이트 요약으로 감사 용이
  • 토큰 절감으로 비용↓

아키텍처

Client (HTTP)
   │
   ▼
Spring Boot API (3.5.4, Java 17)
 ├─ Controller: /api/supportFund/*
 ├─ Services: Tool‑Calling, RAG, Versioning
 └─ Repos: JPA (MySQL)
   │
   ├─ MySQL 8.x (JPA/Hibernate)
   ├─ Qdrant (Vector DB) ← EmbeddingModel
   └─ OpenAI Chat (gpt‑4o‑mini)

Infra: Gradle 8.14.3, Docker Compose(Qdrant)

주요 버전: Spring AI 1.0.1(BOM), spring‑ai‑starter‑model‑openai, spring‑ai‑starter‑vector‑store‑qdrant, mysql‑connector‑j 8.3.0

관심사 분리

  • 도구 실행/검증/로그 일원화
  • RAG 컨텍스트 주입 경량화(payload 선별)
  • 스키마 상속(JOINED)로 버전 차이 영속화

기술 스택

Backend

  • Java 17, Spring Boot 3.5.4 (Web, Data JPA)
  • Spring AI 1.0.1: ChatClient, OpenAI, Qdrant Vector Store
  • JPA Specification, Lombok, JUnit5

Infra & DevOps

  • Gradle 8.14.3
  • MySQL(로컬) — jdbc:mysql://<host>:3306/aikit
  • Docker Compose: Qdrant latest
  • Git(4 커밋/3일), Swagger/GitHub Actions: N/A

코드 하이라이트 (5)

1) Qdrant 검색 + Payload 추출

src/main/java/com/foongdoll/aikit/comm/ai/service/QdrantService.java:133

SearchPoints req = SearchPoints.newBuilder()
  .setCollectionName(COLLECTION)
  .addAllVector(qVec)
  .setFilter(Filter.newBuilder().addMust(matchKeyword("promptType", promptType)).build())
  .setWithPayload(enable(true))  // payload 반환
  .setLimit(k)
  .build();

List<ScoredPoint> hits = qdrant.searchAsync(req).get();
return hits.stream()
  .map(sp -> sp.getPayload().getOrDefault("text", JsonWithInt.Value.newBuilder().setStringValue("").build()).getStringValue())
  .filter(s -> !s.isBlank())
  .toList();

payload 선별 추출로 LLM 입력 토큰 절약.

2) 동적 JPA Specification 빌더

comm/ai/toolcalling/FunctionUtils.java:83

switch (op) {
  case "eq" -> preds.add(cb.equal(path, castValue(value, type)));
  case "in" -> { /* ... */ }
  case "like" -> preds.add(cb.like(cb.lower(path.as(String.class)), "%"+s.toLowerCase()+"%"));
  case "gte" -> preds.add(gePredicate(cb, path, castValue(value, type)));
  case "lte" -> preds.add(lePredicate(cb, path, castValue(value, type)));
  case "between" -> { /* ... */ }
  default -> { /* skip */ }
}

문자열 JPQL 없이 안전한 where/between/like.

3) Tool‑Calling 옵션

comm/ai/utils/AiUtils.java:110

var opts = ToolCallingChatOptions.builder()
  .toolCallbacks(callbacks)
  .internalToolExecutionEnabled(true)
  .toolNames("FETCH_VERSION","FETCH_APPLICATION","VALIDATE_APPLICATION",
             "BUILD_REPORT","UPDATE_STATUS","UPDATE_RECORD")
  .build();
var resp = chatClient.prompt().system(system).user(userContent).options(opts).call();

화이트리스트로 도구 범위 최소화, 내부 실행 허용 제어.

4) 일괄 업데이트 가드

comm/ai/toolcalling/Functions.java:164

Specification<ExampleEntity> spec = buildSpec(args.getWhere());
List<ExampleEntity> targets = repo.findAll(spec);
if (targets.isEmpty()) return "조건에 맞는 항목이 없습니다.";
for (ExampleEntity e : targets) applySet(e, args.getSet());
repo.saveAll(targets);
return String.format("총 %d건 업데이트 완료", targets.size());

5) 버전 분기 저장 (JOINED)

example/service/impl/ExampleServiceImpl.java:54

switch (response.getVersion().replace(".", "").toLowerCase()){
  case "v001": repo.save(V001.builder().name(dto.getName()).build()); break;
  case "v002": v002Repo.save(V002.builder().name(dto.getName()).hasCar(v002.getHasCar()).build()); break;
}

API Quick Ref

REST

메서드 경로 설명
POST /api/supportFund/request 민생지원금 신청(LLM 검증 후 저장)
POST /api/supportFund/handle 의도별 조회/검증/리포트/업데이트
POST /api/supportFund/version/select 버전 목록 조회
POST /api/supportFund/version/update 버전 사용/등록 업데이트
POST /api/supportFund/vector/init Qdrant 컬렉션 초기화/재색인

Socket: N/A (게이트웨이 없음)

성과/하이라이트

RAG with Qdrant LLM Tool‑Calling Versioned Prompts JPA Specification Gradle Wrapper 8.14.3 Docker Compose(Qdrant)

Contact

협업과 코드 리뷰를 좋아합니다. 필요 시 실사용 데모·코드 워크스루 제공 가능합니다.